mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-24 19:18:44 +02:00
Compare commits
954 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16c8466841 | ||
|
|
764b0f8e31 | ||
|
|
7c7382f372 | ||
|
|
9f0e8dc49b | ||
|
|
b8d5c01382 | ||
|
|
d3db77c675 | ||
|
|
533e65fcd1 | ||
|
|
3fb0c768e6 | ||
|
|
3322074ce7 | ||
|
|
229f800266 | ||
|
|
75a0979eee | ||
|
|
d6a0a279a5 | ||
|
|
44f5d71e1b | ||
|
|
3de7aa1ada | ||
|
|
d4fec14123 | ||
|
|
e2c8237beb | ||
|
|
afb99c0f4b | ||
|
|
c5943c6c28 | ||
|
|
bc3b50ad23 | ||
|
|
2f15bbdaf3 | ||
|
|
cdba1e0d36 | ||
|
|
d0a766d424 | ||
|
|
23ec21e494 | ||
|
|
02617e8976 | ||
|
|
6b5cc7ca4b | ||
|
|
bf62b63173 | ||
|
|
b8fb1fa78a | ||
|
|
545504c0de | ||
|
|
32f1e97bcd | ||
|
|
9637e75f97 | ||
|
|
0aab80917a | ||
|
|
31a2b634cc | ||
|
|
dc7bafa41c | ||
|
|
4508b9d7d5 | ||
|
|
888aa5f958 | ||
|
|
4051524b5c | ||
|
|
c8c4b072b1 | ||
|
|
08543287e8 | ||
|
|
21e5eee31f | ||
|
|
ed3fd851f1 | ||
|
|
dfe81f6272 | ||
|
|
0bce9c78ea | ||
|
|
48c920f848 | ||
|
|
05bcfbe4c3 | ||
|
|
da76fc70bb | ||
|
|
4eb416d407 | ||
|
|
636a3940cd | ||
|
|
523e19528e | ||
|
|
778db225a8 | ||
|
|
ca48e8ff92 | ||
|
|
75d5630164 | ||
|
|
bc5643707e | ||
|
|
d31a4047f6 | ||
|
|
faba67b292 | ||
|
|
9828b905b2 | ||
|
|
cb3440c85d | ||
|
|
8400eb910f | ||
|
|
b38d33d7a6 | ||
|
|
23ee8d6a14 | ||
|
|
2154916848 | ||
|
|
d3fd81545a | ||
|
|
fb028710e0 | ||
|
|
59570f3a21 | ||
|
|
7d43ae40b8 | ||
|
|
2baa95ff6d | ||
|
|
6450d2339f | ||
|
|
0c8dd6fd1b | ||
|
|
0e0d254188 | ||
|
|
992421292c | ||
|
|
47cb4feeeb | ||
|
|
76acd8fe9e | ||
|
|
961b1570e8 | ||
|
|
88dea990e1 | ||
|
|
d2505d15fe | ||
|
|
d831549c7b | ||
|
|
0f20f9ca5d | ||
|
|
d5568afc68 | ||
|
|
44d7abac6e | ||
|
|
8530db347b | ||
|
|
3aed65c921 | ||
|
|
cf0ad59ec5 | ||
|
|
5a30a3dcb3 | ||
|
|
62bc6714e0 | ||
|
|
9c9ebeeceb | ||
|
|
9ceb8e9f0b | ||
|
|
5cedcb2389 | ||
|
|
7e4f235c59 | ||
|
|
c722d64850 | ||
|
|
6f79c16ba0 | ||
|
|
216d965d76 | ||
|
|
95fce0eefb | ||
|
|
841127c131 | ||
|
|
5f7e8c9229 | ||
|
|
78b626bbd2 | ||
|
|
823311dc86 | ||
|
|
588899db63 | ||
|
|
a37698a9de | ||
|
|
11ba459d1b | ||
|
|
215786d740 | ||
|
|
fe9ca2b9cc | ||
|
|
41a479b48d | ||
|
|
eb630efdf5 | ||
|
|
91b7130671 | ||
|
|
80be4b4371 | ||
|
|
21d7de7d8d | ||
|
|
4a431be0a9 | ||
|
|
b84ac80aaa | ||
|
|
b6d0843e55 | ||
|
|
396fc2cdcd | ||
|
|
11308dc76a | ||
|
|
7fddd6acdc | ||
|
|
10b7fa6014 | ||
|
|
cbe749af13 | ||
|
|
c5f3598f4e | ||
|
|
12e9e453d8 | ||
|
|
6e50a1c4be | ||
|
|
e3e416b467 | ||
|
|
fcb6a4069a | ||
|
|
02f83c4f52 | ||
|
|
452c6c3df6 | ||
|
|
2966efcfde | ||
|
|
1cf36a5d01 | ||
|
|
1973f7526e | ||
|
|
866dfe4531 | ||
|
|
f617cb556b | ||
|
|
a7000b2582 | ||
|
|
27332931d0 | ||
|
|
36e32b23e2 | ||
|
|
b9708c8d37 | ||
|
|
10683d943f | ||
|
|
9ad8c6a96d | ||
|
|
c74d9bbafb | ||
|
|
22df5d09fb | ||
|
|
931593a59e | ||
|
|
7092dc6f86 | ||
|
|
c3f80a5876 | ||
|
|
821eb4df8c | ||
|
|
e276587fdc | ||
|
|
a454a43111 | ||
|
|
045f58144e | ||
|
|
8ffea22f0e | ||
|
|
c630676792 | ||
|
|
0bbb586094 | ||
|
|
efa1f4ee43 | ||
|
|
e184eb6aae | ||
|
|
6e9fcb81f0 | ||
|
|
774ecb4003 | ||
|
|
c555d1274b | ||
|
|
261bc83811 | ||
|
|
2dbaf4dfa1 | ||
|
|
5df9f38391 | ||
|
|
93763c5932 | ||
|
|
242caff990 | ||
|
|
e97e9907c7 | ||
|
|
ac330b6665 | ||
|
|
a4a70a1287 | ||
|
|
dda8651ba2 | ||
|
|
83e2d48f4d | ||
|
|
a903711a7a | ||
|
|
9e17a611d2 | ||
|
|
1e11ed3041 | ||
|
|
0449470cdf | ||
|
|
21a5a2d4ef | ||
|
|
a848cb28f1 | ||
|
|
b7ae6b143e | ||
|
|
ba0c18eec1 | ||
|
|
61366b347d | ||
|
|
edab6643f6 | ||
|
|
ce36ef3aad | ||
|
|
fdb439f054 | ||
|
|
2229f3f015 | ||
|
|
18a5df1ce7 | ||
|
|
f5cb29fadd | ||
|
|
d929732fb6 | ||
|
|
6c36f3bc7c | ||
|
|
c6a59a5309 | ||
|
|
b02c30a525 | ||
|
|
46a647ae66 | ||
|
|
8943a67f85 | ||
|
|
f132d751f5 | ||
|
|
4ba8c9ff9e | ||
|
|
955ae6c392 | ||
|
|
ed33b327fb | ||
|
|
f8f7486be2 | ||
|
|
3215875e5f | ||
|
|
62da90418a | ||
|
|
7c620fae78 | ||
|
|
daef0c3a8f | ||
|
|
52b6d399a0 | ||
|
|
f210f63ec4 | ||
|
|
41694050ea | ||
|
|
3f612cfc90 | ||
|
|
272acdd8d3 | ||
|
|
5a4375cb71 | ||
|
|
5b3d7e2354 | ||
|
|
189cefac1b | ||
|
|
87d36914c4 | ||
|
|
72e14805b1 | ||
|
|
87e2f76793 | ||
|
|
8fbd53d72d | ||
|
|
586cc1f003 | ||
|
|
a77753cfef | ||
|
|
5e464ef3a2 | ||
|
|
0727c9774b | ||
|
|
ee43a365dc | ||
|
|
d5ba0d9ed5 | ||
|
|
7031a52a43 | ||
|
|
12af164dcc | ||
|
|
362cd72e87 | ||
|
|
cb19520b6b | ||
|
|
c74972488d | ||
|
|
b78e40153f | ||
|
|
f7212662b9 | ||
|
|
97c8e1f7a9 | ||
|
|
2afc6d1c62 | ||
|
|
708858da39 | ||
|
|
41dccb468e | ||
|
|
36cfe9e5c2 | ||
|
|
1c7fd57f2e | ||
|
|
f920851420 | ||
|
|
04b8fe3326 | ||
|
|
9fb4374efa | ||
|
|
885cabd6ef | ||
|
|
e00f9c2a83 | ||
|
|
fe24eda4b4 | ||
|
|
483d80b576 | ||
|
|
70a0a3c52e | ||
|
|
5b2f32c08a | ||
|
|
5bad1e1c88 | ||
|
|
2706ebf638 | ||
|
|
9fe3261424 | ||
|
|
9df087984f | ||
|
|
cbb9bcd93d | ||
|
|
ad8f7576e0 | ||
|
|
e205d85728 | ||
|
|
a01d5c2760 | ||
|
|
b57423386c | ||
|
|
22e525452a | ||
|
|
6c84074b02 | ||
|
|
6cc4a6e1be | ||
|
|
04e41ab676 | ||
|
|
3ad64d9823 | ||
|
|
8de7ff5470 | ||
|
|
0a44f34c2c | ||
|
|
4f900e36c1 | ||
|
|
571d90618e | ||
|
|
fe8436f2ad | ||
|
|
a4459901e8 | ||
|
|
bef8fd566f | ||
|
|
0f9994ac74 | ||
|
|
8691ccc013 | ||
|
|
bac7b50090 | ||
|
|
39ff1e318c | ||
|
|
1f8110573c | ||
|
|
7c128e0f6e | ||
|
|
11b50b4917 | ||
|
|
9d771be8b2 | ||
|
|
02315b8aa1 | ||
|
|
7fb3d133e3 | ||
|
|
65fb29a1d4 | ||
|
|
703a432f7b | ||
|
|
4d37942717 | ||
|
|
137067ea43 | ||
|
|
67e12dcc74 | ||
|
|
911c0d2c1b | ||
|
|
4b9648affa | ||
|
|
804afa65f2 | ||
|
|
c56dc6cade | ||
|
|
c4f7055e1a | ||
|
|
44b6dfab1d | ||
|
|
50e79b8c97 | ||
|
|
185825f83c | ||
|
|
c6be331f14 | ||
|
|
c0dd418992 | ||
|
|
c03d5167f6 | ||
|
|
56d625b7b9 | ||
|
|
e74b23f305 | ||
|
|
6e7d2abc9a | ||
|
|
3bebb9bf0f | ||
|
|
1063697e85 | ||
|
|
7bdad90564 | ||
|
|
1dccc54814 | ||
|
|
1c255213e1 | ||
|
|
cb2c172483 | ||
|
|
b43063a6d2 | ||
|
|
3974406f1b | ||
|
|
21aed2d2e1 | ||
|
|
820c257e96 | ||
|
|
56e5616080 | ||
|
|
6f7351ecc3 | ||
|
|
8f56032d49 | ||
|
|
1b6ca2ed14 | ||
|
|
d956682b9f | ||
|
|
76759f1847 | ||
|
|
d441595ee6 | ||
|
|
2be0250aee | ||
|
|
b2a3b10065 | ||
|
|
22b181a8f7 | ||
|
|
71d07be646 | ||
|
|
95e56e7148 | ||
|
|
ec471520f2 | ||
|
|
65b516d761 | ||
|
|
7d45d5e0ce | ||
|
|
d8e3966825 | ||
|
|
65409373eb | ||
|
|
50a1449a2a | ||
|
|
8150faaa40 | ||
|
|
019542ff10 | ||
|
|
20def0de02 | ||
|
|
4a30a875f0 | ||
|
|
61905f111b | ||
|
|
1832d72e51 | ||
|
|
4d63b9e463 | ||
|
|
7b54f51d75 | ||
|
|
eaf94bc10a | ||
|
|
9d6b5d347c | ||
|
|
e82a16146e | ||
|
|
9797021511 | ||
|
|
c27a9e8193 | ||
|
|
c5506dab5d | ||
|
|
d120109a78 | ||
|
|
b529f3d4cc | ||
|
|
ea11b76461 | ||
|
|
09c54d4fed | ||
|
|
c5f00c5363 | ||
|
|
256808c473 | ||
|
|
487d89d970 | ||
|
|
ed3665b8c5 | ||
|
|
ef7a9ff02e | ||
|
|
aa4416ac4e | ||
|
|
c734eec9e1 | ||
|
|
e1caf61a18 | ||
|
|
f7879256c1 | ||
|
|
1c86eba9d9 | ||
|
|
6a089aa1b7 | ||
|
|
9a3749c1ed | ||
|
|
d82b755557 | ||
|
|
5b83f2a554 | ||
|
|
1f2b01937b | ||
|
|
18bd0ae096 | ||
|
|
512f368cbf | ||
|
|
853d9ee87f | ||
|
|
e9440d0d4c | ||
|
|
381a988f43 | ||
|
|
5d6ec4ce56 | ||
|
|
182e644a33 | ||
|
|
c719fbf7fc | ||
|
|
9c3b053727 | ||
|
|
d32db977eb | ||
|
|
1eca74180c | ||
|
|
0210e090f2 | ||
|
|
baf413ee55 | ||
|
|
8e6c001bf3 | ||
|
|
127e940ed4 | ||
|
|
aa8072118d | ||
|
|
f07bbfa174 | ||
|
|
e3a2c5b05b | ||
|
|
3ba5e30a96 | ||
|
|
7bf49011a3 | ||
|
|
bd84dd9f2c | ||
|
|
c3fbdc907c | ||
|
|
f7817714a8 | ||
|
|
c485286114 | ||
|
|
bd8e44f835 | ||
|
|
eddf4226b7 | ||
|
|
804578e38d | ||
|
|
d464fe5d67 | ||
|
|
885428627f | ||
|
|
4c90a90131 | ||
|
|
ec2aadb7cf | ||
|
|
9d5ab75dbd | ||
|
|
d5b145e052 | ||
|
|
163f5dba8a | ||
|
|
e026ecf92f | ||
|
|
dcda5084d0 | ||
|
|
496441cae6 | ||
|
|
cf75937b1d | ||
|
|
d7fc003216 | ||
|
|
de54575e04 | ||
|
|
12093c311c | ||
|
|
5b9ca03fa6 | ||
|
|
bb820ab388 | ||
|
|
c68f56ecd4 | ||
|
|
910bae64e9 | ||
|
|
40258fb02a | ||
|
|
011ed65ea1 | ||
|
|
411d934e6a | ||
|
|
582de40960 | ||
|
|
79d7ac7c8e | ||
|
|
6d86bd516b | ||
|
|
f71bf1416c | ||
|
|
8a1a27ee19 | ||
|
|
dc30cb2e4a | ||
|
|
24e669c65b | ||
|
|
80e6ba2d96 | ||
|
|
beef4b2738 | ||
|
|
56f1369000 | ||
|
|
7bcde47081 | ||
|
|
35663281fa | ||
|
|
0b8f75f799 | ||
|
|
7309c046ae | ||
|
|
6dfd44b731 | ||
|
|
d6e7309c34 | ||
|
|
e15bad7d3b | ||
|
|
4450d6af2f | ||
|
|
efa7a4ee55 | ||
|
|
757130847f | ||
|
|
c562098ef7 | ||
|
|
42606873af | ||
|
|
df8b73999f | ||
|
|
234b0e6825 | ||
|
|
6ca9f8ad31 | ||
|
|
dbc0971b99 | ||
|
|
26127c8218 | ||
|
|
f4b8b4cae3 | ||
|
|
d641ff3ab7 | ||
|
|
a08904a936 | ||
|
|
c13158cdb5 | ||
|
|
70a8c50d47 | ||
|
|
991c87530f | ||
|
|
9d5156e0e0 | ||
|
|
ec3ac05a1f | ||
|
|
a20fe37e98 | ||
|
|
ee76eaedd6 | ||
|
|
b90b200cd1 | ||
|
|
873af8865c | ||
|
|
906ac14fa9 | ||
|
|
fcffe9d188 | ||
|
|
a84748a544 | ||
|
|
320c7646f0 | ||
|
|
f4f3c3bd37 | ||
|
|
2bb6acfa22 | ||
|
|
da5a8b0fd1 | ||
|
|
aa22956f87 | ||
|
|
8b300358e9 | ||
|
|
54c5edc5da | ||
|
|
5f08d98f66 | ||
|
|
4d45f8d012 | ||
|
|
b1c48929e4 | ||
|
|
612479b632 | ||
|
|
013dcdf93e | ||
|
|
d22d3945ee | ||
|
|
d4960080ea | ||
|
|
4bc3d0ce2d | ||
|
|
e1243532ba | ||
|
|
f41a80a309 | ||
|
|
b447418f07 | ||
|
|
95b523c2fa | ||
|
|
7dadd6e410 | ||
|
|
8ec75b9d45 | ||
|
|
e4b3086429 | ||
|
|
c56bda2f60 | ||
|
|
56566d83fd | ||
|
|
0e4dc43171 | ||
|
|
7154aa05a6 | ||
|
|
1e80d76383 | ||
|
|
0ca2e33e7c | ||
|
|
38b10b6c10 | ||
|
|
e9444d3055 | ||
|
|
7e884dc69f | ||
|
|
24c7ff4cfa | ||
|
|
456f8be6e5 | ||
|
|
dfab460478 | ||
|
|
04154fa40c | ||
|
|
6e9fab849c | ||
|
|
06555eb03e | ||
|
|
6b8d1b4b76 | ||
|
|
73d9ea42f0 | ||
|
|
06f648b714 | ||
|
|
155034092f | ||
|
|
11af11b3be | ||
|
|
3246c36984 | ||
|
|
c12a5cc98b | ||
|
|
18ee7b194d | ||
|
|
14c0733949 | ||
|
|
f3a2a24ee4 | ||
|
|
26ec1269a5 | ||
|
|
2811eb66c5 | ||
|
|
9b0ccb8943 | ||
|
|
e33bdab4e9 | ||
|
|
573b5fc879 | ||
|
|
678821d54d | ||
|
|
5772042dd3 | ||
|
|
7868a38137 | ||
|
|
7bccfef3bd | ||
|
|
736838474a | ||
|
|
d553fad58d | ||
|
|
9e66ef5460 | ||
|
|
9550ec6efd | ||
|
|
bc9e1b1d94 | ||
|
|
7672858d6b | ||
|
|
2a2a9ab8b7 | ||
|
|
d8354c6666 | ||
|
|
ba04725ee3 | ||
|
|
7664633f18 | ||
|
|
edcc211988 | ||
|
|
389e8f2de6 | ||
|
|
070ac49d1b | ||
|
|
40dbb2ce17 | ||
|
|
cd5dd04352 | ||
|
|
592792dd7a | ||
|
|
cfe892d35e | ||
|
|
e01f48303b | ||
|
|
f0c8b348c6 | ||
|
|
ac5d24a848 | ||
|
|
e8a37ff0af | ||
|
|
a60a8c0c4f | ||
|
|
e78f8c803e | ||
|
|
6ea0ba52d1 | ||
|
|
8b0d9670f9 | ||
|
|
440dd90316 | ||
|
|
5f86a60954 | ||
|
|
50e0ea5ec5 | ||
|
|
b566bead31 | ||
|
|
52731d7b0a | ||
|
|
465532014b | ||
|
|
7153ae9614 | ||
|
|
0feb0fe972 | ||
|
|
b3cdbfc71b | ||
|
|
3a32bd62ef | ||
|
|
c1adf880a4 | ||
|
|
b0332b6ef5 | ||
|
|
965e7b48df | ||
|
|
27f41baa9a | ||
|
|
43615450ad | ||
|
|
956b8958fb | ||
|
|
024459408a | ||
|
|
78ccc44014 | ||
|
|
85397c4e28 | ||
|
|
0253f7d069 | ||
|
|
ddcb709fd1 | ||
|
|
a7d11c6670 | ||
|
|
bbb4959f22 | ||
|
|
254b3fe9aa | ||
|
|
35c016482b | ||
|
|
62895eedb7 | ||
|
|
32809ae7d4 | ||
|
|
73e1e3422d | ||
|
|
2d9041c045 | ||
|
|
60d6bb79b3 | ||
|
|
6afb3a06ac | ||
|
|
3cdf22e9b2 | ||
|
|
b05b41c7cc | ||
|
|
114a340527 | ||
|
|
cfa5163590 | ||
|
|
53535dd82d | ||
|
|
34f17074ca | ||
|
|
157d404019 | ||
|
|
d1ef987dca | ||
|
|
fd8c7c99bd | ||
|
|
4295437b3e | ||
|
|
92a08a1865 | ||
|
|
fbd7abf4e2 | ||
|
|
306ec09118 | ||
|
|
a5e41b224f | ||
|
|
4abcf75b34 | ||
|
|
7131a505be | ||
|
|
9b42af0149 | ||
|
|
27b9748f86 | ||
|
|
1301aa5c35 | ||
|
|
1b80789288 | ||
|
|
ca0232ae7b | ||
|
|
2fb0ecc446 | ||
|
|
0c41db76e2 | ||
|
|
e120b149dc | ||
|
|
e33596960a | ||
|
|
d04fb645ec | ||
|
|
87c5ee67c0 | ||
|
|
a53a046351 | ||
|
|
afda182b4e | ||
|
|
d883e3e661 | ||
|
|
c9526130b7 | ||
|
|
d6e3c7d1b7 | ||
|
|
0fdf6bfbb2 | ||
|
|
e628c68f09 | ||
|
|
8f858c2ddf | ||
|
|
f8f6e201b9 | ||
|
|
52f56e1bb0 | ||
|
|
187f7e591e | ||
|
|
272e8eac4f | ||
|
|
d423d741b2 | ||
|
|
102b2d76f4 | ||
|
|
bb31cedcba | ||
|
|
bf02e04ae5 | ||
|
|
c66884be0a | ||
|
|
e7b94d3132 | ||
|
|
b219161011 | ||
|
|
fd7d30333f | ||
|
|
d734cdaf48 | ||
|
|
f60d0b10e0 | ||
|
|
a1c6e32e28 | ||
|
|
156cb03069 | ||
|
|
cdb75729cb | ||
|
|
d1a812f04c | ||
|
|
b190ba1268 | ||
|
|
87ed90a825 | ||
|
|
a03a4af1e6 | ||
|
|
e48e4e7139 | ||
|
|
6ef31b7983 | ||
|
|
d378658a62 | ||
|
|
ed02758940 | ||
|
|
b28c45c84c | ||
|
|
42af530c63 | ||
|
|
7c3de1e976 | ||
|
|
afdd43c5e3 | ||
|
|
5bc756716e | ||
|
|
6a6c069896 | ||
|
|
9499799f80 | ||
|
|
5632f9786c | ||
|
|
6f79e07e90 | ||
|
|
601f18bbab | ||
|
|
6f750cf584 | ||
|
|
3b7c92d022 | ||
|
|
af12b47c90 | ||
|
|
2d9140e56c | ||
|
|
0351ff30b4 | ||
|
|
6d13dac2c3 | ||
|
|
e2174b9ad4 | ||
|
|
2038785b09 | ||
|
|
0c8650d2e5 | ||
|
|
3e7edea7be | ||
|
|
b3d625b9fc | ||
|
|
d02fb69ca7 | ||
|
|
8cf4bc58a7 | ||
|
|
b8aed0f004 | ||
|
|
03a6473bd4 | ||
|
|
7d95c02b57 | ||
|
|
767507d195 | ||
|
|
1d96cbeb07 | ||
|
|
0bf33ec7e9 | ||
|
|
37196b42ed | ||
|
|
5f7453d031 | ||
|
|
88b7ef5345 | ||
|
|
213d591eb0 | ||
|
|
c04f73e86b | ||
|
|
2033c171f0 | ||
|
|
8ce708dade | ||
|
|
fa60322ff1 | ||
|
|
e9bcd170c0 | ||
|
|
118b5c8a7d | ||
|
|
b5bcfa8d90 | ||
|
|
e496ab06b2 | ||
|
|
d7c960e150 | ||
|
|
9de7b4ba35 | ||
|
|
bb1a18f187 | ||
|
|
68cdd6b8a9 | ||
|
|
34dab0c498 | ||
|
|
f9511aba17 | ||
|
|
d96015f2c1 | ||
|
|
e66d577f21 | ||
|
|
47653eeba7 | ||
|
|
a0463c85a1 | ||
|
|
519093dceb | ||
|
|
08c5d0e4c1 | ||
|
|
cae4526b3b | ||
|
|
79381d7fd2 | ||
|
|
d8c141b1c9 | ||
|
|
3b3f4044cb | ||
|
|
c0256428f9 | ||
|
|
85a5ddb980 | ||
|
|
06bc58f383 | ||
|
|
b739c00414 | ||
|
|
d65bd97956 | ||
|
|
b952f9da4a | ||
|
|
388b3257fc | ||
|
|
6318077278 | ||
|
|
9fee51bafb | ||
|
|
397ec9587b | ||
|
|
862d08f57d | ||
|
|
ebb541e4f5 | ||
|
|
e05d780bec | ||
|
|
84b383154d | ||
|
|
f458826643 | ||
|
|
5d808992e6 | ||
|
|
03f9a9fcac | ||
|
|
7ea9b5b2f3 | ||
|
|
b8fc24f093 | ||
|
|
bddba15403 | ||
|
|
894f1c4f28 | ||
|
|
19665d4ad9 | ||
|
|
18a8e86b2a | ||
|
|
3af724a941 | ||
|
|
5b378ee9dd | ||
|
|
d5889a90d4 | ||
|
|
81c8eb2830 | ||
|
|
507a073203 | ||
|
|
abe67d9e4e | ||
|
|
741f44e8b7 | ||
|
|
c715b9488a | ||
|
|
5107ef5119 | ||
|
|
ca28eeb596 | ||
|
|
f51eb96c69 | ||
|
|
b032299b05 | ||
|
|
5a2576bc29 | ||
|
|
3375629d06 | ||
|
|
37232bc222 | ||
|
|
d2f0deec9c | ||
|
|
5a25e44177 | ||
|
|
08d9d58894 | ||
|
|
0254a75c95 | ||
|
|
a7cfcefee2 | ||
|
|
f78c057b45 | ||
|
|
4bd3084403 | ||
|
|
9d817f0c77 | ||
|
|
68b8cc1d20 | ||
|
|
fb8b0f4f65 | ||
|
|
94d45fc77f | ||
|
|
5144f62da9 | ||
|
|
a58ba7fcc5 | ||
|
|
17bec06c98 | ||
|
|
d531ec846f | ||
|
|
07849922b8 | ||
|
|
63ba267da0 | ||
|
|
93526c8a44 | ||
|
|
c7c2f4a482 | ||
|
|
dc0e739667 | ||
|
|
e74b2e32c9 | ||
|
|
a2dd9b1d48 | ||
|
|
474fdc0473 | ||
|
|
e6ab3ac9f9 | ||
|
|
ae59780297 | ||
|
|
cbdafbf264 | ||
|
|
76fa4a3090 | ||
|
|
896c6b79db | ||
|
|
bb052f30d6 | ||
|
|
ebbff7f615 | ||
|
|
9d76ac96bc | ||
|
|
1926a40277 | ||
|
|
6cd108badf | ||
|
|
5993d74eec | ||
|
|
84b98a2265 | ||
|
|
0df071b4db | ||
|
|
bfd092b499 | ||
|
|
23f2ea5031 | ||
|
|
29165b8ef4 | ||
|
|
c862179465 | ||
|
|
7a371f8b26 | ||
|
|
7a7b968c1b | ||
|
|
9571404907 | ||
|
|
ae946f6821 | ||
|
|
6128625706 | ||
|
|
ea2a3c1980 | ||
|
|
9b6814aee9 | ||
|
|
6544659251 | ||
|
|
dcff39da25 | ||
|
|
94ba32af57 | ||
|
|
cc08613304 | ||
|
|
95941f4dc5 | ||
|
|
3f2e20fe44 | ||
|
|
67124a4104 | ||
|
|
174bcf56d3 | ||
|
|
d9fd3b47e1 | ||
|
|
89492f8904 | ||
|
|
42dc73964c | ||
|
|
449316257a | ||
|
|
3b621adcb2 | ||
|
|
8d9d4e67ca | ||
|
|
eb43a02bce | ||
|
|
7f034f60d6 | ||
|
|
c4cf10b6e6 | ||
|
|
b2a1404ce0 | ||
|
|
52a97db259 | ||
|
|
e5ccb4271e | ||
|
|
27a2614b7d | ||
|
|
5cc39848ff | ||
|
|
b9d719d636 | ||
|
|
3e6b3a2755 | ||
|
|
88167fb3ae | ||
|
|
b5685a9d76 | ||
|
|
4c652a87c0 | ||
|
|
1f8bd69aef | ||
|
|
71d9bb18e5 | ||
|
|
067b3364ee | ||
|
|
b2494ebaf7 | ||
|
|
f73ca10b6c | ||
|
|
fe23e099fe | ||
|
|
333411535e | ||
|
|
cc6272e84a | ||
|
|
c7857835c7 | ||
|
|
9bfaf10468 | ||
|
|
095d5c9442 | ||
|
|
4fa6f85c2e | ||
|
|
76a9978fc5 | ||
|
|
4b46b2776a | ||
|
|
907505ccf9 | ||
|
|
3e35dafefb | ||
|
|
11ee5126ef | ||
|
|
37bdb1ba2f | ||
|
|
a9fc1083c7 | ||
|
|
9be804fb35 | ||
|
|
38a466fc21 | ||
|
|
aa5ee45034 | ||
|
|
5b1e1d0d6a | ||
|
|
a818a09469 | ||
|
|
0d439a08fc | ||
|
|
9032f25d64 | ||
|
|
28efea7ac1 | ||
|
|
f2f0badc77 | ||
|
|
00e3d5c0d2 | ||
|
|
c08edc207c | ||
|
|
6477e2e1bb | ||
|
|
694da178c4 | ||
|
|
d80b890cd0 | ||
|
|
6b9c038b31 | ||
|
|
b071f920e9 | ||
|
|
3cd28d1559 | ||
|
|
72563d8ef1 | ||
|
|
375798a344 | ||
|
|
b401c65684 | ||
|
|
d7c78b3ce2 | ||
|
|
4a4c03a225 | ||
|
|
85b31701f4 | ||
|
|
28b3110895 | ||
|
|
011e6d895b | ||
|
|
0f7099acfa | ||
|
|
96296fe211 | ||
|
|
51a60e637c | ||
|
|
078f13fdb1 | ||
|
|
c210afd086 | ||
|
|
81d9071b01 | ||
|
|
94ca9c4df9 | ||
|
|
bff2ae319f | ||
|
|
5bd30381cf | ||
|
|
121a615ce3 | ||
|
|
5877b66c83 | ||
|
|
1ba86a91f9 | ||
|
|
e56847ee8d | ||
|
|
1fbbfd1063 | ||
|
|
1fed66fff3 | ||
|
|
c607a7e35d | ||
|
|
06d6968951 | ||
|
|
7ed8a9f638 | ||
|
|
52595138cd | ||
|
|
eca2b01307 | ||
|
|
df758679cc | ||
|
|
6b6300d117 | ||
|
|
b535e11f5a | ||
|
|
a4ad8d0a61 | ||
|
|
e66eb537d8 | ||
|
|
b8ef2e68ba | ||
|
|
30b10d3b6b | ||
|
|
f09347841c | ||
|
|
f87e8ca522 | ||
|
|
2871f64f68 | ||
|
|
cd1c5f5799 | ||
|
|
890fcac73f | ||
|
|
d7851ed090 | ||
|
|
678df3cc46 | ||
|
|
2f48b2e302 | ||
|
|
a816a6ff8d | ||
|
|
b189d2a39b | ||
|
|
5424682fdb | ||
|
|
ad3ce7c536 | ||
|
|
d4dd300e28 | ||
|
|
99fd6b97db | ||
|
|
684e9e3537 | ||
|
|
1bde863124 | ||
|
|
5c34e3d988 | ||
|
|
b7c4e084f3 | ||
|
|
36395ae355 | ||
|
|
f4881d11c7 | ||
|
|
bbde89e0f9 | ||
|
|
fb22107be8 | ||
|
|
5ada93b46c | ||
|
|
b798b43733 | ||
|
|
1669eb3759 | ||
|
|
071316c606 | ||
|
|
d8b5dd7bd2 | ||
|
|
868c1cface | ||
|
|
8e83baf72b | ||
|
|
54858c63f5 | ||
|
|
bc3d03c462 | ||
|
|
c94476b9a2 | ||
|
|
73812dc400 | ||
|
|
cfdc7eb74a | ||
|
|
a0ad331023 | ||
|
|
2561358f9d | ||
|
|
3fd7dae8f9 | ||
|
|
426a0933b1 | ||
|
|
2f8062d296 | ||
|
|
d18165ebe9 | ||
|
|
38796f9d0c | ||
|
|
79b887d189 | ||
|
|
8dc92e7ccf | ||
|
|
e04e5913de | ||
|
|
5c734cdabc | ||
|
|
f924e99f70 | ||
|
|
e179825896 | ||
|
|
94a561f0e4 | ||
|
|
56e14fc107 | ||
|
|
29f0b74824 | ||
|
|
de682d5530 | ||
|
|
571a3341da | ||
|
|
8edf7f2d60 | ||
|
|
5408545c07 | ||
|
|
d504fb209f | ||
|
|
ee53c3a71e | ||
|
|
bcf88d24f3 | ||
|
|
b2935139b4 | ||
|
|
635e7cfeec | ||
|
|
49b6c3bed7 | ||
|
|
3f7ab67506 | ||
|
|
df26833eb1 | ||
|
|
df1ebaebf9 | ||
|
|
26bd04857d | ||
|
|
4c4ed14af5 | ||
|
|
f0c5a1b382 | ||
|
|
59ebc49d46 | ||
|
|
bfde101f6b | ||
|
|
2a0dce848c | ||
|
|
d759fed5e4 | ||
|
|
f731abe4e8 | ||
|
|
74111212a3 | ||
|
|
f86c1a87f9 | ||
|
|
1f2493914f | ||
|
|
e3efa7dc3d | ||
|
|
6612782021 | ||
|
|
bdaabcea93 | ||
|
|
e6b6be2624 | ||
|
|
b1f1c10878 | ||
|
|
d5b0bb021f | ||
|
|
dd70275b41 | ||
|
|
6aa782bd8b | ||
|
|
029545703f | ||
|
|
8183674fc6 | ||
|
|
7391f64776 | ||
|
|
776385cdc9 | ||
|
|
17bafc037c | ||
|
|
e785352050 | ||
|
|
43e4408df1 | ||
|
|
78a68bb361 | ||
|
|
ec2a2d3505 | ||
|
|
2625477d35 | ||
|
|
a655dd639d | ||
|
|
3e61fd2452 | ||
|
|
c11753d91c | ||
|
|
5884e6b3cf | ||
|
|
46e4ba4518 | ||
|
|
a9c9e48cdb | ||
|
|
e32c1a4447 | ||
|
|
fb99c25594 | ||
|
|
6011aa2ac9 | ||
|
|
048c1ecf72 | ||
|
|
40360da454 | ||
|
|
0ce9ff4557 | ||
|
|
625bfbb6fe | ||
|
|
4290d94841 | ||
|
|
8ff2151448 | ||
|
|
610d69fb2e | ||
|
|
822308b3a4 | ||
|
|
c1d1e562ad | ||
|
|
379a0bd785 | ||
|
|
a477443c8d | ||
|
|
ed693c03ab |
123
.gitignore
vendored
Normal file
123
.gitignore
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
|
||||
# no slash at the end to handle also symlinks
|
||||
/toolkit
|
||||
/conf
|
||||
/env-*
|
||||
|
||||
# composer reserver directory, from sources, populate/update using "composer install"
|
||||
vendor/*
|
||||
test/vendor/*
|
||||
|
||||
# all datas but listing prevention
|
||||
/data/**
|
||||
!/data/.htaccess
|
||||
!/data/index.php
|
||||
!/data/web.config
|
||||
|
||||
# iTop extensions
|
||||
/extensions/**
|
||||
!/extensions/readme.txt
|
||||
|
||||
# all logs but listing prevention
|
||||
/log/**
|
||||
!/log/.htaccess
|
||||
!/log/index.php
|
||||
!/log/web.config
|
||||
|
||||
|
||||
# Jetbrains
|
||||
/.idea/**
|
||||
!/.idea/encodings.xml
|
||||
!/.idea/codeStyles
|
||||
!/.idea/codeStyles/*
|
||||
!/.idea/inspectionProfiles
|
||||
!/.idea/inspectionProfiles/*
|
||||
|
||||
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
### Eclipse template
|
||||
|
||||
.metadata
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.settings/
|
||||
.loadpath
|
||||
.recommenders
|
||||
.project
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# PyDev specific (Python IDE for Eclipse)
|
||||
*.pydevproject
|
||||
|
||||
# CDT-specific (C/C++ Development Tooling)
|
||||
.cproject
|
||||
|
||||
# CDT- autotools
|
||||
.autotools
|
||||
|
||||
# Java annotation processor (APT)
|
||||
.factorypath
|
||||
|
||||
# PDT-specific (PHP Development Tools)
|
||||
.buildpath
|
||||
|
||||
# sbteclipse plugin
|
||||
.target
|
||||
|
||||
# Tern plugin
|
||||
.tern-project
|
||||
|
||||
# TeXlipse plugin
|
||||
.texlipse
|
||||
|
||||
# STS (Spring Tool Suite)
|
||||
.springBeans
|
||||
|
||||
# Code Recommenders
|
||||
.recommenders/
|
||||
|
||||
# Annotation Processing
|
||||
.apt_generated/
|
||||
|
||||
# Scala IDE specific (Scala & Java development for Eclipse)
|
||||
.cache-main
|
||||
.scala_dependencies
|
||||
.worksheet
|
||||
45
.idea/codeStyles/Project.xml
generated
Normal file
45
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<PHPCodeStyleSettings>
|
||||
<option name="CONCAT_SPACES" value="false" />
|
||||
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
|
||||
<option name="PHPDOC_BLANK_LINES_AROUND_PARAMETERS" value="true" />
|
||||
<option name="PHPDOC_WRAP_LONG_LINES" value="true" />
|
||||
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
|
||||
<option name="LOWER_CASE_NULL_CONST" value="true" />
|
||||
<option name="BLANK_LINES_BEFORE_RETURN_STATEMENT" value="1" />
|
||||
<option name="KEEP_RPAREN_AND_LBRACE_ON_ONE_LINE" value="true" />
|
||||
<option name="PHPDOC_USE_FQCN" value="true" />
|
||||
</PHPCodeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="BRACE_STYLE" value="2" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="PHP">
|
||||
<option name="BLANK_LINES_AFTER_PACKAGE" value="1" />
|
||||
<option name="BRACE_STYLE" value="2" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="5" />
|
||||
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
|
||||
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Combodo" />
|
||||
</state>
|
||||
</component>
|
||||
6
.idea/encodings.xml
generated
Normal file
6
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
154
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
154
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,154 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="BladeControlDirectives" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CheckEmptyScriptTag" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CheckImageSize" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CheckNodeTest" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CheckTagEmptyBody" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CheckValidXmlInScriptTagBody" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CoffeeScriptArgumentsOutsideFunction" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CoffeeScriptFunctionSignatures" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CoffeeScriptInfiniteLoop" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CoffeeScriptLiteralNotFunction" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CoffeeScriptSillyAssignment" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CoffeeScriptSwitchStatementWithNoDefaultBranch" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CoffeeScriptUnusedLocalSymbols" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ComposeFileStructure" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssFloatPxLength" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidAtRule" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidCharsetRule" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidElement" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidFunction" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidHtmlTagReference" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidImport" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidMediaFeature" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidPropertyValue" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssInvalidPseudoSelector" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssMissingComma" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssNegativeValue" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssNoGenericFontName" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssOptimizeSimilarProperties" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssOverwrittenProperties" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssRedundantUnit" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssUnitlessNumber" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CssUnknownProperty" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="myCustomPropertiesEnabled" value="false" />
|
||||
<option name="myIgnoreVendorSpecificProperties" value="false" />
|
||||
<option name="myCustomPropertiesList">
|
||||
<value>
|
||||
<list size="0" />
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CssUnknownTarget" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssUnresolvedClass" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssUnresolvedCustomProperty" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssUnresolvedCustomPropertySet" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CssUnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CucumberExamplesColon" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CucumberMissedExamples" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="CucumberTableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CucumberUndefinedStep" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="DockerFileArgumentCount" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="DuplicateKeyInSection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="DuplicateSectionInFile" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="EmptyEventHandler" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="FileHeaderInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="GherkinBrokenTableInspection" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="GherkinMisplacedBackground" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="HamlNestedTagContent" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HardwiredNamespacePrefix" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlDeprecatedTag" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlExtraClosingTag" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlFormInputWithoutLabel" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlMissingClosingTag" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlPresentationalElement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlUnknownAnchorTarget" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlUnknownAttribute" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="0" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="myCustomValuesEnabled" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HtmlUnknownBooleanAttribute" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlUnknownTag" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="6">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
||||
<item index="4" class="java.lang.String" itemvalue="embed" />
|
||||
<item index="5" class="java.lang.String" itemvalue="script" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myCustomValuesEnabled" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ImplicitTypeConversion" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="BITS" value="1720" />
|
||||
<option name="FLAG_EXPLICIT_CONVERSION" value="true" />
|
||||
<option name="IGNORE_NODESET_TO_BOOLEAN_VIA_STRING" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IndexZeroUsage" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JSLastCommaInArrayLiteral" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JSLastCommaInObjectLiteral" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JSUnresolvedFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JSUnresolvedVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="LossyEncoding" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="MissingSinceTagDocInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="MysqlParsingInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="NonAsciiCharacters" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpDocMissingReturnTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpIncludeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUndefinedClassConstantInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUndefinedClassInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUndefinedConstantInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUndefinedFieldInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUndefinedFunctionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUndefinedGotoLabelInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUndefinedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUndefinedNamespaceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpUnusedParameterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="RedundantTypeConversion" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="CHECK_ANY" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RequiredAttributes" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="myAdditionalRequiredHtmlAttributes" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SqlAddNotNullColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlAmbiguousColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlAutoIncrementDuplicateInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlCheckUsingColumnsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlConstantConditionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlDeprecateTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlDerivedTableAliasInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlDialectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlDropIndexedColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlIdentifierInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlInsertValuesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlNullComparisonInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlPostgresqlSelectFromProcedureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlResolveInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlShouldBeInGroupByInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlSignatureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlStorageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlUnusedVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="TaskProblemsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="TypeScriptPreferShortImport" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="XmlDefaultAttributeValue" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="XmlUnboundNsPrefix" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="XmlUnusedNamespaceDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="XsltUnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="XsltVariableShadowing" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
0
.jenkins/bin/archive/gather_external_files.sh
Executable file
0
.jenkins/bin/archive/gather_external_files.sh
Executable file
14
.jenkins/bin/init/append_files.sh
Executable file
14
.jenkins/bin/init/append_files.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
# create target dirs
|
||||
mkdir -p var
|
||||
mkdir -p toolkit
|
||||
|
||||
# cleanup target dirs
|
||||
rm -rf toolkit/*
|
||||
|
||||
# fill target dirs
|
||||
curl http://www.combodo.com/documentation/iTopDataModelToolkit-2.3.zip | tar xvz --directory toolkit
|
||||
cp -r .jenkins/configuration/default-environment/unattended_install/* toolkit
|
||||
11
.jenkins/bin/init/composer_install.sh
Executable file
11
.jenkins/bin/init/composer_install.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
# on the root dir
|
||||
composer install
|
||||
|
||||
|
||||
# under the test dir
|
||||
cd test
|
||||
composer install
|
||||
13
.jenkins/bin/init/debug.sh
Executable file
13
.jenkins/bin/init/debug.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
whoami
|
||||
pwd
|
||||
ls
|
||||
|
||||
echo "$BRANCH_NAME:${BRANCH_NAME}"
|
||||
|
||||
echo "printenv :"
|
||||
printenv
|
||||
|
||||
8
.jenkins/bin/tests/phpunit.sh
Executable file
8
.jenkins/bin/tests/phpunit.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
|
||||
cd test
|
||||
|
||||
export DEBUG_UNIT_TEST="0"
|
||||
|
||||
php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity
|
||||
6
.jenkins/bin/unattended_install/default_env.sh
Executable file
6
.jenkins/bin/unattended_install/default_env.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
cd toolkit
|
||||
php unattended_install.php default-params.xml
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* Configuration file, generated by the iTop configuration wizard
|
||||
*
|
||||
* The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
// access_message: Message displayed to the users when there is any access restriction
|
||||
// default: 'iTop is temporarily frozen, please wait... (the admin team)'
|
||||
'access_message' => 'iTop is temporarily frozen, please wait... (the admin team)',
|
||||
|
||||
// access_mode: Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3
|
||||
// default: 3
|
||||
'access_mode' => 3,
|
||||
|
||||
'allowed_login_types' => 'form|basic|external',
|
||||
|
||||
// apc_cache.enabled: If set, the APC cache is allowed (the PHP extension must also be active)
|
||||
// default: true
|
||||
'apc_cache.enabled' => true,
|
||||
|
||||
// apc_cache.query_ttl: Time to live set in APC for the prepared queries (seconds - 0 means no timeout)
|
||||
// default: 3600
|
||||
'apc_cache.query_ttl' => 3600,
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => 'http://127.0.0.1/itop/svn/trunk/',
|
||||
|
||||
// buttons_position: Position of the forms buttons: bottom | top | both
|
||||
// default: 'both'
|
||||
'buttons_position' => 'both',
|
||||
|
||||
// cas_include_path: The path where to find the phpCAS library
|
||||
// default: '/usr/share/php'
|
||||
'cas_include_path' => '/usr/share/php',
|
||||
|
||||
// cron_max_execution_time: Duration (seconds) of the page cron.php, must be shorter than php setting max_execution_time and shorter than the web server response timeout
|
||||
// default: 600
|
||||
'cron_max_execution_time' => 600,
|
||||
|
||||
// csv_file_default_charset: Character set used by default for downloading and uploading data as a CSV file. Warning: it is case sensitive (uppercase is preferable).
|
||||
// default: 'ISO-8859-1'
|
||||
'csv_file_default_charset' => 'ISO-8859-1',
|
||||
|
||||
'csv_import_charsets' => array (
|
||||
),
|
||||
|
||||
// csv_import_history_display: Display the history tab in the import wizard
|
||||
// default: false
|
||||
'csv_import_history_display' => false,
|
||||
|
||||
// date_and_time_format: Format for date and time display (per language)
|
||||
// default: array (
|
||||
// 'default' =>
|
||||
// array (
|
||||
// 'date' => 'Y-m-d',
|
||||
// 'time' => 'H:i:s',
|
||||
// 'date_time' => '$date $time',
|
||||
// ),
|
||||
// )
|
||||
'date_and_time_format' => array (
|
||||
'default' =>
|
||||
array (
|
||||
'date' => 'Y-m-d',
|
||||
'time' => 'H:i:s',
|
||||
'date_time' => '$date $time',
|
||||
),
|
||||
'FR FR' =>
|
||||
array (
|
||||
'date' => 'd/m/Y',
|
||||
'time' => 'H:i:s',
|
||||
'date_time' => '$date $time',
|
||||
),
|
||||
),
|
||||
|
||||
'db_host' => '',
|
||||
|
||||
'db_name' => 'itop_ci_main',
|
||||
|
||||
'db_pwd' => 'IKnowYouSeeMeInJenkinsConf',
|
||||
|
||||
'db_subname' => '',
|
||||
|
||||
'db_user' => 'jenkins_itop',
|
||||
|
||||
// deadline_format: The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$
|
||||
// default: '$difference$'
|
||||
'deadline_format' => '$difference$',
|
||||
|
||||
'default_language' => 'EN US',
|
||||
|
||||
// disable_attachments_download_legacy_portal: Disable attachments download from legacy portal
|
||||
// default: true
|
||||
'disable_attachments_download_legacy_portal' => true,
|
||||
|
||||
// draft_attachments_lifetime: Lifetime (in seconds) of drafts' attachments and inline images: after this duration, the garbage collector will delete them.
|
||||
// default: 3600
|
||||
'draft_attachments_lifetime' => 3600,
|
||||
|
||||
// email_asynchronous: If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode
|
||||
// default: false
|
||||
'email_asynchronous' => false,
|
||||
|
||||
// email_default_sender_address: Default address provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_address' => '',
|
||||
|
||||
// email_default_sender_label: Default label provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_label' => '',
|
||||
|
||||
// email_transport: Mean to send emails: PHPMail (uses the function mail()) or SMTP (implements the client protocole)
|
||||
// default: 'PHPMail'
|
||||
'email_transport' => 'SMTP',
|
||||
|
||||
// email_transport_smtp.host: host name or IP address (optional)
|
||||
// default: 'localhost'
|
||||
'email_transport_smtp.host' => 'smtp.combodo.com',
|
||||
|
||||
// email_transport_smtp.password: Authentication password (optional)
|
||||
// default: ''
|
||||
'email_transport_smtp.password' => 'IDoNotWork',
|
||||
|
||||
// email_transport_smtp.port: port number (optional)
|
||||
// default: 25
|
||||
'email_transport_smtp.port' => 25,
|
||||
|
||||
// email_transport_smtp.username: Authentication user (optional)
|
||||
// default: ''
|
||||
'email_transport_smtp.username' => 'test2@combodo.com',
|
||||
|
||||
// email_validation_pattern: Regular expression to validate/detect the format of an eMail address
|
||||
// default: '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}'
|
||||
'email_validation_pattern' => '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}',
|
||||
|
||||
'encryption_key' => '@iT0pEncr1pti0n!',
|
||||
|
||||
'ext_auth_variable' => '$_SERVER[\'REMOTE_USER\']',
|
||||
|
||||
'fast_reload_interval' => '60',
|
||||
|
||||
// graphviz_path: Path to the Graphviz "dot" executable for graphing objects lifecycle
|
||||
// default: '/usr/bin/dot'
|
||||
'graphviz_path' => '/usr/bin/dot',
|
||||
|
||||
// inline_image_max_display_width: The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.
|
||||
// default: '250'
|
||||
'inline_image_max_display_width' => 250,
|
||||
|
||||
// inline_image_max_storage_width: The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.
|
||||
// default: '1600'
|
||||
'inline_image_max_storage_width' => 1600,
|
||||
|
||||
// link_set_attribute_qualifier: Link set from string: attribute qualifier (encloses both the attcode and the value)
|
||||
// default: '\''
|
||||
'link_set_attribute_qualifier' => '\'',
|
||||
|
||||
// link_set_attribute_separator: Link set from string: attribute separator
|
||||
// default: ';'
|
||||
'link_set_attribute_separator' => ';',
|
||||
|
||||
// link_set_item_separator: Link set from string: line separator
|
||||
// default: '|'
|
||||
'link_set_item_separator' => '|',
|
||||
|
||||
// link_set_value_separator: Link set from string: value separator (between the attcode and the value itself
|
||||
// default: ':'
|
||||
'link_set_value_separator' => ':',
|
||||
|
||||
'log_global' => true,
|
||||
|
||||
'log_issue' => true,
|
||||
|
||||
'log_notification' => true,
|
||||
|
||||
'log_web_service' => true,
|
||||
|
||||
// max_combo_length: The maximum number of elements in a drop-down list. If more then an autocomplete will be used
|
||||
// default: 50
|
||||
'max_combo_length' => 50,
|
||||
|
||||
'max_display_limit' => '15',
|
||||
|
||||
// max_linkset_output: Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.
|
||||
// default: 100
|
||||
'max_linkset_output' => 100,
|
||||
|
||||
'min_display_limit' => '10',
|
||||
|
||||
// online_help: Hyperlink to the online-help web page
|
||||
// default: 'http://www.combodo.com/itop-help'
|
||||
'online_help' => 'http://www.combodo.com/itop-help',
|
||||
|
||||
// php_path: Path to the php executable in CLI mode
|
||||
// default: 'php'
|
||||
'php_path' => 'php',
|
||||
|
||||
// portal_tickets: CSV list of classes supported in the portal
|
||||
// default: 'UserRequest'
|
||||
'portal_tickets' => 'UserRequest',
|
||||
|
||||
'query_cache_enabled' => true,
|
||||
|
||||
// search_manual_submit: Force manual submit of search requests (class => true)
|
||||
// default: false
|
||||
'search_manual_submit' => array (
|
||||
'Person' => true,
|
||||
),
|
||||
|
||||
'secure_connection_required' => false,
|
||||
|
||||
// session_name: The name of the cookie used to store the PHP session id
|
||||
// default: 'iTop'
|
||||
'session_name' => 'iTop',
|
||||
|
||||
// shortcut_actions: Actions that are available as direct buttons next to the "Actions" menu
|
||||
// default: 'UI:Menu:Modify,UI:Menu:New'
|
||||
'shortcut_actions' => 'UI:Menu:Modify,UI:Menu:New',
|
||||
|
||||
// source_dir: Source directory for the datamodel files. (which gets compiled to env-production).
|
||||
// default: ''
|
||||
'source_dir' => 'datamodels/2.x/',
|
||||
|
||||
'standard_reload_interval' => '300',
|
||||
|
||||
// synchro_trace: Synchronization details: none, display, save (includes 'display')
|
||||
// default: 'none'
|
||||
'synchro_trace' => 'none',
|
||||
|
||||
// timezone: Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitely configured in PHP
|
||||
// default: 'Europe/Paris'
|
||||
'timezone' => 'Europe/Paris',
|
||||
|
||||
// tracking_level_linked_set_default: Default tracking level if not explicitely set at the attribute level, for AttributeLinkedSet (defaults to NONE in case of a fresh install, LIST otherwise - this to preserve backward compatibility while upgrading from a version older than 2.0.3 - see TRAC #936)
|
||||
// default: 1
|
||||
'tracking_level_linked_set_default' => 0,
|
||||
|
||||
// url_validation_pattern: Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)
|
||||
// default: '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9%+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\\$_.-]*)?'
|
||||
'url_validation_pattern' => '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9%+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\\$_.-]*)?',
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
'itop-attachments' => array (
|
||||
'allowed_classes' => array (
|
||||
0 => 'Ticket',
|
||||
),
|
||||
'position' => 'relations',
|
||||
'preview_max_width' => 290,
|
||||
),
|
||||
'itop-backup' => array (
|
||||
'mysql_bindir' => '',
|
||||
'week_days' => 'monday, tuesday, wednesday, thursday, friday',
|
||||
'time' => '23:30',
|
||||
'retention_count' => 5,
|
||||
'enabled' => true,
|
||||
'debug' => false,
|
||||
),
|
||||
'molkobain-console-tooltips' => array (
|
||||
'decoration_class' => 'fas fa-question',
|
||||
'enabled' => true,
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
'addons' => array (
|
||||
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
|
||||
),
|
||||
);
|
||||
?>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installation>
|
||||
<!-- On manual installs, this file is generated in setup/install-*.xml -->
|
||||
<mode>upgrade</mode>
|
||||
<preinstall>
|
||||
<copies type="array"/>
|
||||
</preinstall>
|
||||
<source_dir>datamodels/2.x/</source_dir>
|
||||
<datamodel_version>2.5.0</datamodel_version>
|
||||
<previous_configuration_file>/var/lib/jenkins/workspace/iTop-CI/unattended_install/default-config-itop.php</previous_configuration_file>
|
||||
<extensions_dir>extensions</extensions_dir>
|
||||
<target_env>production</target_env>
|
||||
<workspace_dir></workspace_dir>
|
||||
<database>
|
||||
<server></server>
|
||||
<user>jenkins_itop</user>
|
||||
<pwd>IKnowYouSeeMeInJenkinsConf</pwd>
|
||||
<name>itop_ci</name>
|
||||
<db_tls_enabled></db_tls_enabled>
|
||||
<db_tls_ca></db_tls_ca>
|
||||
<prefix></prefix>
|
||||
</database>
|
||||
<url>http://127.0.0.1/itop/svn/trunk/</url>
|
||||
<graphviz_path>/usr/bin/dot</graphviz_path>
|
||||
<admin_account>
|
||||
<user>admin</user>
|
||||
<pwd>admin</pwd>
|
||||
<language>EN US</language>
|
||||
</admin_account>
|
||||
<language>EN US</language>
|
||||
<selected_modules type="array">
|
||||
<item>authent-external</item>
|
||||
<item>authent-local</item>
|
||||
<item>itop-backup</item>
|
||||
<item>itop-config</item>
|
||||
<item>itop-profiles-itil</item>
|
||||
<item>itop-sla-computation</item>
|
||||
<item>itop-tickets</item>
|
||||
<item>itop-welcome-itil</item>
|
||||
<item>itop-config-mgmt</item>
|
||||
<item>itop-attachments</item>
|
||||
<item>itop-datacenter-mgmt</item>
|
||||
<item>itop-endusers-devices</item>
|
||||
<item>itop-storage-mgmt</item>
|
||||
<item>itop-virtualization-mgmt</item>
|
||||
<item>itop-bridge-virtualization-storage</item>
|
||||
<item>itop-service-mgmt</item>
|
||||
<item>itop-request-mgmt</item>
|
||||
<item>itop-portal</item>
|
||||
<item>itop-portal-base</item>
|
||||
<item>itop-change-mgmt</item>
|
||||
<item>itop-knownerror-mgmt</item>
|
||||
</selected_modules>
|
||||
<selected_extensions type="array">
|
||||
<item>itop-config-mgmt-core</item>
|
||||
<item>itop-config-mgmt-datacenter</item>
|
||||
<item>itop-config-mgmt-end-user</item>
|
||||
<item>itop-config-mgmt-storage</item>
|
||||
<item>itop-config-mgmt-virtualization</item>
|
||||
<item>itop-service-mgmt-enterprise</item>
|
||||
<item>itop-ticket-mgmt-simple-ticket</item>
|
||||
<item>itop-ticket-mgmt-simple-ticket-enhanced-portal</item>
|
||||
<item>itop-change-mgmt-simple</item>
|
||||
<item>itop-kown-error-mgmt</item>
|
||||
</selected_extensions>
|
||||
<sample_data>1</sample_data>
|
||||
<old_addon></old_addon>
|
||||
<options>
|
||||
<generate_config>1</generate_config>
|
||||
</options>
|
||||
<mysql_bindir></mysql_bindir>
|
||||
</installation>
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
//this scrit will be run under the ./toolkit directory, relatively to the document root
|
||||
|
||||
require_once('../approot.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
require_once(APPROOT.'/application/clipage.class.inc.php');
|
||||
require_once(APPROOT.'/core/config.class.inc.php');
|
||||
require_once(APPROOT.'/core/log.class.inc.php');
|
||||
require_once(APPROOT.'/core/kpi.class.inc.php');
|
||||
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
|
||||
require_once(APPROOT.'/setup/applicationinstaller.class.inc.php');
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
$sParamFile = utils::ReadParam('response_file', 'default-params.xml', true /* CLI allowed */, 'raw_data');
|
||||
$bCheckConsistency = (utils::ReadParam('check_consistency', '0', true /* CLI allowed */) == '1');
|
||||
|
||||
$oParams = new XMLParameters($sParamFile);
|
||||
$sMode = $oParams->Get('mode');
|
||||
|
||||
if ($sMode == 'install')
|
||||
{
|
||||
echo "Installation mode detected.\n";
|
||||
$bClean = utils::ReadParam('clean', false, true /* CLI allowed */);
|
||||
if ($bClean)
|
||||
{
|
||||
echo "Cleanup mode detected.\n";
|
||||
$sTargetEnvironment = $oParams->Get('target_env', '');
|
||||
if ($sTargetEnvironment == '')
|
||||
{
|
||||
$sTargetEnvironment = 'production';
|
||||
}
|
||||
$sTargetDir = APPROOT.'env-'.$sTargetEnvironment;
|
||||
|
||||
// Configuration file
|
||||
$sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE;
|
||||
if (file_exists($sConfigFile))
|
||||
{
|
||||
echo "Trying to delete the configuration file: '$sConfigFile'.\n";
|
||||
@chmod($sConfigFile, 0770); // RWX for owner and group, nothing for others
|
||||
unlink($sConfigFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "No config file to delete ($sConfigFile does not exist).\n";
|
||||
}
|
||||
|
||||
// env-xxx directory
|
||||
if (file_exists($sTargetDir))
|
||||
{
|
||||
if (is_dir($sTargetDir))
|
||||
{
|
||||
echo "Emptying the target directory '$sTargetDir'.\n";
|
||||
SetupUtils::tidydir($sTargetDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
die("ERROR the target dir '$sTargetDir' exists, but is NOT a directory !!!\nExiting.\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "No target directory to delete ($sTargetDir does not exist).\n";
|
||||
}
|
||||
|
||||
// Database
|
||||
$aDBSettings = $oParams->Get('database', array());
|
||||
$sDBServer = $aDBSettings['server'];
|
||||
$sDBUser = $aDBSettings['user'];
|
||||
$sDBPwd = $aDBSettings['pwd'];
|
||||
$sDBName = $aDBSettings['name'];
|
||||
$sDBPrefix = $aDBSettings['prefix'];
|
||||
|
||||
if ($sDBPrefix != '')
|
||||
{
|
||||
die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting.");
|
||||
}
|
||||
|
||||
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
|
||||
if ($oMysqli->connect_errno)
|
||||
{
|
||||
die("Cannot connect to the MySQL server (".$mysqli->connect_errno . ") ".$mysqli->connect_error."\nExiting");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oMysqli->select_db($sDBName))
|
||||
{
|
||||
echo "Deleting database '$sDBName'\n";
|
||||
$oMysqli->query("DROP DATABASE `$sDBName`");
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$bHasErrors = false;
|
||||
$aChecks = SetupUtils::CheckBackupPrerequisites(APPROOT.'data'); // mmm should be the backup destination dir
|
||||
|
||||
$aSelectedModules = $oParams->Get('selected_modules');
|
||||
$sSourceDir = $oParams->Get('source_dir', 'datamodels/latest');
|
||||
$sExtensionDir = $oParams->Get('extensions_dir', 'extensions');
|
||||
$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules));
|
||||
|
||||
|
||||
foreach($aChecks as $oCheckResult)
|
||||
{
|
||||
switch($oCheckResult->iSeverity)
|
||||
{
|
||||
case CheckResult::ERROR:
|
||||
$bHasErrors = true;
|
||||
$sHeader = "Error";
|
||||
break;
|
||||
|
||||
case CheckResult::WARNING:
|
||||
$sHeader = "Warning";
|
||||
break;
|
||||
|
||||
case CheckResult::INFO:
|
||||
default:
|
||||
$sHeader = "Info";
|
||||
break;
|
||||
}
|
||||
echo $sHeader.": ".$oCheckResult->sLabel;
|
||||
if (strlen($oCheckResult->sDescription))
|
||||
{
|
||||
echo ' - '.$oCheckResult->sDescription;
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
if ($bHasErrors)
|
||||
{
|
||||
echo "Encountered stopper issues. Aborting...\n";
|
||||
die;
|
||||
}
|
||||
|
||||
$bFoundIssues = false;
|
||||
|
||||
$bInstall = utils::ReadParam('install', true, true /* CLI allowed */);
|
||||
if ($bInstall)
|
||||
{
|
||||
echo "Starting the unattended installation...\n";
|
||||
$oWizard = new ApplicationInstaller($oParams);
|
||||
$bRes = $oWizard->ExecuteAllSteps();
|
||||
if (!$bRes)
|
||||
{
|
||||
echo "\nencountered installation issues!";
|
||||
$bFoundIssues = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "No installation requested.\n";
|
||||
}
|
||||
if (!$bFoundIssues && $bCheckConsistency)
|
||||
{
|
||||
echo "Checking data model consistency.\n";
|
||||
ob_start();
|
||||
$sCheckRes = '';
|
||||
try
|
||||
{
|
||||
MetaModel::CheckDefinitions(false);
|
||||
$sCheckRes = ob_get_clean();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$sCheckRes = ob_get_clean()."\nException: ".$e->getMessage();
|
||||
}
|
||||
if (strlen($sCheckRes) > 0)
|
||||
{
|
||||
echo $sCheckRes;
|
||||
echo "\nfound consistency issues!";
|
||||
$bFoundIssues = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bFoundIssues)
|
||||
{
|
||||
// last line: used to check the install
|
||||
// the only way to track issues in case of Fatal error or even parsing error!
|
||||
echo "\ninstalled!";
|
||||
exit;
|
||||
}
|
||||
65
Jenkinsfile
vendored
Normal file
65
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
|
||||
stage('init') {
|
||||
parallel {
|
||||
stage('debug') {
|
||||
steps {
|
||||
sh './.jenkins/bin/init/debug.sh'
|
||||
}
|
||||
}
|
||||
stage('append files to project') {
|
||||
steps {
|
||||
sh './.jenkins/bin/init/append_files.sh'
|
||||
}
|
||||
}
|
||||
stage('composer install') {
|
||||
steps {
|
||||
sh './.jenkins/bin/init/composer_install.sh'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('unattended_install') {
|
||||
parallel {
|
||||
stage('unattended_install default env') {
|
||||
steps {
|
||||
sh './.jenkins/bin/unattended_install/default_env.sh'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('test') {
|
||||
parallel {
|
||||
stage('phpunit') {
|
||||
steps {
|
||||
sh './.jenkins/bin/tests/phpunit.sh'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
junit 'var/test/phpunit-log.junit.xml'
|
||||
}
|
||||
failure {
|
||||
slackSend(channel: "#jenkins-itop", color: '#FF0000', message: "Ho no! Build failed! (${currentBuild.result}), Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
|
||||
}
|
||||
fixed {
|
||||
slackSend(channel: "#jenkins-itop", color: '#FFa500', message: "Yes! Build repaired! (${currentBuild.result}), Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
|
||||
}
|
||||
}
|
||||
|
||||
environment {
|
||||
DEBUG_UNIT_TEST = '0'
|
||||
}
|
||||
options {
|
||||
timeout(time: 20, unit: 'MINUTES')
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "addon/userrights",
|
||||
"category" => "addon/userrights,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -75,8 +75,8 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name','description')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array ('name','description'));
|
||||
}
|
||||
|
||||
protected static $m_aCacheProfiles = null;
|
||||
@@ -137,11 +137,8 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
$oUserRights = UserRights::GetModuleInstance();
|
||||
|
||||
$aDisplayData = array();
|
||||
foreach (MetaModel::GetClasses('bizmodel') as $sClass)
|
||||
foreach (MetaModel::GetClasses('bizmodel,grant_by_profile') as $sClass)
|
||||
{
|
||||
// Skip non instantiable classes
|
||||
if (MetaModel::IsAbstract($sClass)) continue;
|
||||
|
||||
$aStimuli = array();
|
||||
foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus)
|
||||
{
|
||||
@@ -203,6 +200,12 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
// preserve DB integrity by deleting links to users
|
||||
protected function OnDelete()
|
||||
{
|
||||
// Don't remove admin profile
|
||||
if ($this->Get('name') === ADMIN_PROFILE_NAME)
|
||||
{
|
||||
throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin'));
|
||||
}
|
||||
|
||||
// Note: this may break the rule that says: "a user must have at least ONE profile" !
|
||||
$oLnkSet = $this->Get('user_list');
|
||||
while($oLnk = $oLnkSet->Fetch())
|
||||
@@ -239,7 +242,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "addon/userrights",
|
||||
"category" => "addon/userrights,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "userid",
|
||||
"state_attcode" => "",
|
||||
@@ -284,6 +287,59 @@ class URP_UserProfile extends UserRightsBaseClassGUI
|
||||
}
|
||||
return parent::CheckToDelete($oDeletionPlan);
|
||||
}
|
||||
|
||||
protected function OnInsert()
|
||||
{
|
||||
$this->CheckIfProfileIsAllowed(UR_ACTION_CREATE);
|
||||
}
|
||||
|
||||
protected function OnUpdate()
|
||||
{
|
||||
$this->CheckIfProfileIsAllowed(UR_ACTION_MODIFY);
|
||||
}
|
||||
|
||||
protected function OnDelete()
|
||||
{
|
||||
$this->CheckIfProfileIsAllowed(UR_ACTION_DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $iActionCode
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \SecurityException
|
||||
*/
|
||||
protected function CheckIfProfileIsAllowed($iActionCode)
|
||||
{
|
||||
// When initializing or admin, we need to let everything pass trough
|
||||
if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; }
|
||||
|
||||
// Only administrators can manage administrators
|
||||
$iOrigUserId = $this->GetOriginal('userid');
|
||||
if (!empty($iOrigUserId))
|
||||
{
|
||||
$oUser = MetaModel::GetObject('User', $iOrigUserId, true, true);
|
||||
if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator())
|
||||
{
|
||||
throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted'));
|
||||
}
|
||||
}
|
||||
$oUser = MetaModel::GetObject('User', $this->Get('userid'), true, true);
|
||||
if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator())
|
||||
{
|
||||
throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted'));
|
||||
}
|
||||
if (!UserRights::IsActionAllowed(get_class($this), $iActionCode, DBObjectSet::FromObject($this)))
|
||||
{
|
||||
throw new SecurityException(Dict::Format('UI:Error:ObjectCannotBeUpdated'));
|
||||
}
|
||||
if (!UserRights::IsAdministrator() && ($this->Get('profile') === ADMIN_PROFILE_NAME))
|
||||
{
|
||||
throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
@@ -292,7 +348,7 @@ class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "addon/userrights",
|
||||
"category" => "addon/userrights,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "userid",
|
||||
"state_attcode" => "",
|
||||
@@ -324,6 +380,42 @@ class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
{
|
||||
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name'));
|
||||
}
|
||||
|
||||
|
||||
protected function OnInsert()
|
||||
{
|
||||
$this->CheckIfOrgIsAllowed();
|
||||
}
|
||||
|
||||
protected function OnUpdate()
|
||||
{
|
||||
$this->CheckIfOrgIsAllowed();
|
||||
}
|
||||
|
||||
protected function OnDelete()
|
||||
{
|
||||
$this->CheckIfOrgIsAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected function CheckIfOrgIsAllowed()
|
||||
{
|
||||
if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; }
|
||||
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$oAddon = UserRights::GetModuleInstance();
|
||||
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
|
||||
if (count($aOrgs) > 0)
|
||||
{
|
||||
$iOrigOrgId = $this->GetOriginal('allowed_org_id');
|
||||
if ((!empty($iOrigOrgId) && !in_array($iOrigOrgId, $aOrgs)) || !in_array($this->Get('allowed_org_id'), $aOrgs))
|
||||
{
|
||||
throw new SecurityException(Dict::Format('Class:User/Error:OrganizationNotAllowed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -413,10 +505,14 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
/**
|
||||
* Read and cache organizations allowed to the given user
|
||||
*
|
||||
* @param oUser
|
||||
* @param sClass -not used here but can be used in overloads
|
||||
* @param $oUser
|
||||
* @param $sClass (not used here but can be used in overloads)
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GetUserOrgs($oUser, $sClass)
|
||||
public function GetUserOrgs($oUser, $sClass)
|
||||
{
|
||||
$iUser = $oUser->GetKey();
|
||||
if (!array_key_exists($iUser, $this->m_aUserOrgs))
|
||||
@@ -430,7 +526,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
$oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery), array(), array('userid' => $iUser));
|
||||
while ($aRow = $oUserOrgSet->FetchAssoc())
|
||||
{
|
||||
$oUserOrg = $aRow['UserOrg'];
|
||||
$oOrg = $aRow['Org'];
|
||||
$this->m_aUserOrgs[$iUser][] = $oOrg->GetKey();
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name', 'description')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array ('name', 'description'));
|
||||
}
|
||||
|
||||
protected $m_bCheckReservedNames = true;
|
||||
@@ -614,10 +614,14 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
/**
|
||||
* Read and cache organizations allowed to the given user
|
||||
*
|
||||
* @param oUser
|
||||
* @param sClass -not used here but can be used in overloads
|
||||
* @param $oUser
|
||||
* @param $sClass (not used here but can be used in overloads)
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GetUserOrgs($oUser, $sClass)
|
||||
public function GetUserOrgs($oUser, $sClass)
|
||||
{
|
||||
$iUser = $oUser->GetKey();
|
||||
if (!array_key_exists($iUser, $this->m_aUserOrgs))
|
||||
@@ -631,7 +635,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
$oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery), array(), array('userid' => $iUser));
|
||||
while ($aRow = $oUserOrgSet->FetchAssoc())
|
||||
{
|
||||
$oUserOrg = $aRow['UserOrg'];
|
||||
$oOrg = $aRow['Org'];
|
||||
$this->m_aUserOrgs[$iUser][] = $oOrg->GetKey();
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ class URP_Profiles extends UserRightsBaseClass
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name', 'description')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array ('name', 'description'));
|
||||
}
|
||||
|
||||
function GetGrantAsHtml($oUserRights, $sClass, $sAction)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
// Copyright (C) 2010-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -248,6 +248,15 @@ EOF
|
||||
echo implode("\n", $this->a_scripts);
|
||||
echo "\n</script>\n";
|
||||
}
|
||||
if (count($this->a_linked_scripts) > 0)
|
||||
{
|
||||
echo "<script type=\"text/javascript\">\n";
|
||||
foreach($this->a_linked_scripts as $sScriptUrl)
|
||||
{
|
||||
echo '$.getScript('.json_encode($sScriptUrl).");\n";
|
||||
}
|
||||
echo "\n</script>\n";
|
||||
}
|
||||
if (!empty($this->s_deferred_content))
|
||||
{
|
||||
echo "<script type=\"text/javascript\">\n";
|
||||
@@ -260,6 +269,16 @@ EOF
|
||||
echo $this->m_sReadyScript; // Ready Scripts are output as simple scripts
|
||||
echo "\n</script>\n";
|
||||
}
|
||||
if(count($this->a_linked_stylesheets) > 0)
|
||||
{
|
||||
echo "<script type=\"text/javascript\">";
|
||||
foreach($this->a_linked_stylesheets as $aStylesheet)
|
||||
{
|
||||
$sStylesheetUrl = $aStylesheet['link'];
|
||||
echo "if (!$('link[href=\"{$sStylesheetUrl}\"]').length) $('<link href=\"{$sStylesheetUrl}\" rel=\"stylesheet\">').appendTo('head');\n";
|
||||
}
|
||||
echo "\n</script>\n";
|
||||
}
|
||||
|
||||
if (trim($s_captured_output) != "")
|
||||
{
|
||||
|
||||
@@ -118,7 +118,7 @@ class ApplicationContext
|
||||
$oSearchFilter = new DBObjectSearch('Organization');
|
||||
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
|
||||
$oSet = new CMDBObjectSet($oSearchFilter);
|
||||
$iCount = $oSet->Count();
|
||||
$iCount = $oSet->CountWithLimit(2);
|
||||
if ($iCount == 1)
|
||||
{
|
||||
// Only one possible value for org_id, set it in the context
|
||||
|
||||
@@ -817,6 +817,10 @@ class RestResult
|
||||
* Result: the requested operation cannot be performed because it can cause data (integrity) loss
|
||||
*/
|
||||
const UNSAFE = 12;
|
||||
/**
|
||||
* Result: the request page number is not valid. It must be an integer greater than 0
|
||||
*/
|
||||
const INVALID_PAGE = 13;
|
||||
/**
|
||||
* Result: the operation could not be performed, see the message for troubleshooting
|
||||
*/
|
||||
@@ -865,7 +869,8 @@ class RestUtils
|
||||
*
|
||||
* @param StdClass $oData Structured input data. Must contain the entry defined by sParamName.
|
||||
* @param string $sParamName Name of the parameter to fetch from the input data
|
||||
* @return void
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception If the parameter is missing
|
||||
* @api
|
||||
*/
|
||||
@@ -910,7 +915,8 @@ class RestUtils
|
||||
*
|
||||
* @param StdClass $oData Structured input data. Must contain the entry defined by sParamName.
|
||||
* @param string $sParamName Name of the parameter to fetch from the input data
|
||||
* @return void
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception If the parameter is missing or the class is unknown
|
||||
* @api
|
||||
*/
|
||||
@@ -1079,10 +1085,13 @@ class RestUtils
|
||||
*
|
||||
* @param string $sClass Name of the class
|
||||
* @param mixed $key Either search criteria (substructure), or an object or an OQL string.
|
||||
* @param int $iLimit The limit of results to return
|
||||
* @param int $iOffset The offset of results to return
|
||||
*
|
||||
* @return DBObjectSet The search result set
|
||||
* @throws Exception If the input structure is not valid
|
||||
*/
|
||||
public static function GetObjectSetFromKey($sClass, $key)
|
||||
public static function GetObjectSetFromKey($sClass, $key, $iLimit = 0, $iOffset = 0)
|
||||
{
|
||||
if (is_object($key))
|
||||
{
|
||||
@@ -1117,7 +1126,7 @@ class RestUtils
|
||||
{
|
||||
throw new Exception("Wrong format for key");
|
||||
}
|
||||
$oObjectSet = new DBObjectSet($oSearch);
|
||||
$oObjectSet = new DBObjectSet($oSearch, array(), array(), null, $iLimit, $iOffset);
|
||||
return $oObjectSet;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class AuditCategory extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "application",
|
||||
"category" => "application, grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -53,8 +53,8 @@ class AuditCategory extends cmdbAbstractObject
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'rules_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('description', )); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name', 'description')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'definition_set')); // Criteria of the advanced search form
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'definition_set')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -35,7 +35,7 @@ class AuditRule extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "application",
|
||||
"category" => "application, grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -57,8 +57,8 @@ class AuditRule extends cmdbAbstractObject
|
||||
MetaModel::Init_SetZListItems('details', array('category_id', 'name', 'description', 'query', 'valid_flag')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('category_id', 'description', 'valid_flag')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('category_id', 'name', 'description', 'valid_flag')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('category_id', 'name', 'description', 'valid_flag', 'query')); // Criteria of the advanced search form
|
||||
MetaModel::Init_SetZListItems('standard_search', array('category_id', 'name', 'description', 'valid_flag', 'query')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'category_id')); // Criteria of the advanced search form
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
// Copyright (C) 2010-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -45,6 +45,11 @@ require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
|
||||
require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php');
|
||||
require_once(APPROOT.'/application/datatable.class.inc.php');
|
||||
require_once(APPROOT.'/sources/renderer/console/consoleformrenderer.class.inc.php');
|
||||
require_once(APPROOT.'/sources/application/search/searchform.class.inc.php');
|
||||
require_once(APPROOT.'/sources/application/search/criterionparser.class.inc.php');
|
||||
require_once(APPROOT.'/sources/application/search/criterionconversionabstract.class.inc.php');
|
||||
require_once(APPROOT.'/sources/application/search/criterionconversion/criteriontooql.class.inc.php');
|
||||
require_once(APPROOT.'/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php');
|
||||
|
||||
abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
|
||||
{
|
||||
@@ -409,26 +414,8 @@ EOF
|
||||
$sLinkedClass = $oAttDef->GetLinkedClass();
|
||||
|
||||
// Filter out links pointing to obsolete objects (if relevant)
|
||||
$oLinkSearch = $this->Get($sAttCode)->GetFilter();
|
||||
if ($oAttDef->IsIndirect())
|
||||
{
|
||||
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sExtKeyToRemote);
|
||||
$sTargetClass = $oLinkingAttDef->GetTargetClass();
|
||||
if (!utils::ShowObsoleteData() && MetaModel::IsObsoletable($sTargetClass))
|
||||
{
|
||||
$oNotObsolete = new BinaryExpression(
|
||||
new FieldExpression('obsolescence_flag', $sTargetClass),
|
||||
'=',
|
||||
new ScalarExpression(0)
|
||||
);
|
||||
$oNotObsoleteRemote = new DBObjectSearch($sTargetClass);
|
||||
$oNotObsoleteRemote->AddConditionExpression($oNotObsolete);
|
||||
$oLinkSearch->AddCondition_PointingTo($oNotObsoleteRemote, $sExtKeyToRemote);
|
||||
}
|
||||
}
|
||||
$oLinkSet = new DBObjectSet($oLinkSearch);
|
||||
$oLinkSet->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
$oOrmLinkSet = $this->Get($sAttCode);
|
||||
$oLinkSet = $oOrmLinkSet->ToDBObjectSet(utils::ShowObsoleteData());
|
||||
|
||||
$iCount = $oLinkSet->Count();
|
||||
$sCount = '';
|
||||
@@ -643,20 +630,29 @@ EOF
|
||||
{
|
||||
$aDetails[$sTab] = array();
|
||||
$aTableStyles[] = 'vertical-align:top';
|
||||
$aTableClasses = array();
|
||||
$aColStyles[] = 'vertical-align:top';
|
||||
$aColClasses = array();
|
||||
|
||||
ksort($aCols);
|
||||
$iColCount = count($aCols);
|
||||
if ($iColCount > 1)
|
||||
{
|
||||
$aTableStyles[] = 'width: 100%';
|
||||
$aTableClasses[] = 'n-cols-details';
|
||||
$aTableClasses[] = $iColCount.'-cols-details';
|
||||
|
||||
$aColStyles[] = 'width:'.floor(100 / $iColCount).'%';
|
||||
}
|
||||
else
|
||||
{
|
||||
$aTableClasses[] = 'one-col-details';
|
||||
}
|
||||
// Else, will size regarding the largest field of the column
|
||||
|
||||
$oPage->SetCurrentTab(Dict::S($sTab));
|
||||
$oPage->add('<table style="'.implode('; ', $aTableStyles).'" data-mode="'.$sEditMode.'"><tr>');
|
||||
$oPage->add('<table style="'.implode('; ', $aTableStyles).'" class="'.implode(' ', $aTableClasses).'" data-mode="'.$sEditMode.'"><tr>');
|
||||
foreach($aCols as $sColIndex => $aFieldsets)
|
||||
{
|
||||
$oPage->add('<td style="vertical-align:top">');
|
||||
$oPage->add('<td style="'.implode('; ', $aColStyles).'" class="'.implode(' ', $aColClasses).'">');
|
||||
//$aDetails[$sTab][$sColIndex] = array();
|
||||
$sLabel = '';
|
||||
$sPreviousLabel = '';
|
||||
@@ -752,13 +748,20 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode)."</span>", 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode);
|
||||
$val = array(
|
||||
'label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>',
|
||||
'value' => "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode)."</span>",
|
||||
'comments' => $sComments,
|
||||
'infos' => $sInfos,
|
||||
'attcode' => $sAttCode
|
||||
);
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
}
|
||||
|
||||
// Checking how the field should be rendered
|
||||
// Note: For view mode, this is done in cmdbAbstractObject::GetFieldAsHtml()
|
||||
if(in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields')))
|
||||
// Note 2: Shouldn't this be a property of the AttDef instead an array that we have to maintain?
|
||||
if (in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression')))
|
||||
{
|
||||
$val['layout'] = 'large';
|
||||
}
|
||||
@@ -772,9 +775,6 @@ EOF
|
||||
$val = null; // Skip this field
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -804,6 +804,7 @@ EOF
|
||||
}
|
||||
$oPage->add('</tr></table>');
|
||||
}
|
||||
|
||||
return $aFieldsMap;
|
||||
}
|
||||
|
||||
@@ -822,6 +823,7 @@ EOF
|
||||
{
|
||||
// Object's details
|
||||
// template not found display the object using the *old style*
|
||||
$oPage->add('<div id="search-widget-results-outer">');
|
||||
$this->DisplayBareHeader($oPage, $bEditMode);
|
||||
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB);
|
||||
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
|
||||
@@ -831,6 +833,7 @@ EOF
|
||||
//$oPage->SetCurrentTab(Dict::S('UI:HistoryTab'));
|
||||
//$this->DisplayBareHistory($oPage, $bEditMode);
|
||||
$oPage->AddAjaxTab(Dict::S('UI:HistoryTab'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=history&class='.get_class($this).'&id='.$this->GetKey());
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -884,10 +887,14 @@ EOF
|
||||
|
||||
/**
|
||||
* Get the HTML fragment corresponding to the display of a table representing a set of objects
|
||||
*
|
||||
* @param WebPage $oPage The page object is used for out-of-band information (mostly scripts) output
|
||||
* @param CMDBObjectSet The set of objects to display
|
||||
* @param Hash $aExtraParams Some extra configuration parameters to tweak the behavior of the display
|
||||
* @return String The HTML fragment representing the table of objects
|
||||
* @param array $aExtraParams Some extra configuration parameters to tweak the behavior of the display
|
||||
*
|
||||
* @return String The HTML fragment representing the table of objects. <b>Warning</b> : no JS added to handled pagination or table sorting !
|
||||
*
|
||||
* @see DisplayBlock to get a similar table but with the JS for pagination & sorting
|
||||
*/
|
||||
public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
|
||||
{
|
||||
@@ -1057,6 +1064,7 @@ EOF
|
||||
|
||||
$aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array();
|
||||
$aExtraFields = array();
|
||||
$sAttCode = '';
|
||||
foreach ($aExtraFieldsRaw as $sFieldName)
|
||||
{
|
||||
// Ignore attributes not of the main queried class
|
||||
@@ -1081,7 +1089,7 @@ EOF
|
||||
$aAuthorizedClasses = array();
|
||||
foreach($aClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if ( (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS)) &&
|
||||
if ( (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) &&
|
||||
( (count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases))) )
|
||||
{
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
@@ -1170,7 +1178,7 @@ EOF
|
||||
$aAuthorizedClasses = array();
|
||||
foreach($aClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO)
|
||||
{
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
@@ -1306,7 +1314,7 @@ EOF
|
||||
$aAuthorizedClasses = array();
|
||||
foreach($aClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO)
|
||||
{
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
@@ -1469,7 +1477,7 @@ EOF
|
||||
$aAuthorizedClasses = array();
|
||||
foreach($aClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO)
|
||||
{
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
@@ -1531,237 +1539,20 @@ EOF
|
||||
$oPage->add(self::GetSearchForm($oPage, $oSet, $aExtraParams));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param CMDBObjectSet $oSet
|
||||
* @param array $aExtraParams
|
||||
*
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public static function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
|
||||
{
|
||||
static $iSearchFormId = 0;
|
||||
$bMultiSelect = false;
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sHtml = '';
|
||||
$numCols=4;
|
||||
$sClassName = $oSet->GetFilter()->GetClass();
|
||||
$oSearchForm = new \Combodo\iTop\Application\Search\SearchForm();
|
||||
|
||||
// Romain: temporarily removed the tab "OQL query" because it was not finalized
|
||||
// (especially when used to add a link)
|
||||
/*
|
||||
$sHtml .= "<div class=\"mini_tabs\" id=\"mini_tabs{$iSearchFormId}\"><ul>
|
||||
<li><a href=\"#\" onClick=\"$('div.mini_tab{$iSearchFormId}').toggle();$('#mini_tabs{$iSearchFormId} ul li a').toggleClass('selected');\">".Dict::S('UI:OQLQueryTab')."</a></li>
|
||||
<li><a class=\"selected\" href=\"#\" onClick=\"$('div.mini_tab{$iSearchFormId}').toggle();$('#mini_tabs{$iSearchFormId} ul li a').toggleClass('selected');\">".Dict::S('UI:SimpleSearchTab')."</a></li>
|
||||
</ul></div>\n";
|
||||
*/
|
||||
// Simple search form
|
||||
if (isset($aExtraParams['currentId']))
|
||||
{
|
||||
$sSearchFormId = $aExtraParams['currentId'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$iSearchFormId = $oPage->GetUniqueId();
|
||||
$sSearchFormId = 'SimpleSearchForm'.$iSearchFormId;
|
||||
$sHtml .= "<div id=\"ds_$sSearchFormId\" class=\"mini_tab{$iSearchFormId}\">\n";
|
||||
}
|
||||
// Check if the current class has some sub-classes
|
||||
if (isset($aExtraParams['baseClass']))
|
||||
{
|
||||
$sRootClass = $aExtraParams['baseClass'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRootClass = $sClassName;
|
||||
}
|
||||
$aSubClasses = MetaModel::GetSubclasses($sRootClass);
|
||||
if (count($aSubClasses) > 0)
|
||||
{
|
||||
$aOptions = array();
|
||||
$aOptions[MetaModel::GetName($sRootClass)] = "<option value=\"$sRootClass\">".MetaModel::GetName($sRootClass)."</options>\n";
|
||||
foreach($aSubClasses as $sSubclassName)
|
||||
{
|
||||
$aOptions[MetaModel::GetName($sSubclassName)] = "<option value=\"$sSubclassName\">".MetaModel::GetName($sSubclassName)."</options>\n";
|
||||
}
|
||||
$aOptions[MetaModel::GetName($sClassName)] = "<option selected value=\"$sClassName\">".MetaModel::GetName($sClassName)."</options>\n";
|
||||
ksort($aOptions);
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
$sClassesCombo = "<select name=\"class\" onChange=\"ReloadSearchForm('$sSearchFormId', this.value, '$sRootClass', '$sContext')\">\n".implode('', $aOptions)."</select>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sClassesCombo = MetaModel::GetName($sClassName);
|
||||
}
|
||||
$oUnlimitedFilter = new DBObjectSearch($sClassName);
|
||||
$sAction = (isset($aExtraParams['action'])) ? $aExtraParams['action'] : utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
|
||||
$sHtml .= "<form id=\"fs_{$sSearchFormId}\" action=\"{$sAction}\">\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php)
|
||||
$sHtml .= "<h2>".Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo)."</h2>\n";
|
||||
$index = 0;
|
||||
$sHtml .= "<div>\n";
|
||||
$aMapCriteria = array();
|
||||
$aList = MetaModel::GetZListItems($sClassName, 'standard_search');
|
||||
$aConsts = $oSet->ListConstantFields(); // Some fields are constants based on the query/context
|
||||
$sClassAlias = $oSet->GetFilter()->GetClassAlias();
|
||||
foreach($aList as $sFilterCode)
|
||||
{
|
||||
//$oAppContext->Reset($sFilterCode); // Make sure the same parameter will not be passed twice
|
||||
$sHtml .= '<div class="SearchAttribute" style="white-space: nowrap;padding:5px;display:inline-block;">';
|
||||
$sFilterValue = isset($aConsts[$sClassAlias][$sFilterCode]) ? $aConsts[$sClassAlias][$sFilterCode] : '';
|
||||
$sFilterValue = utils::ReadParam($sFilterCode, $sFilterValue, false, 'raw_data');
|
||||
$sFilterOpCode = null; // Use the default 'loose' OpCode
|
||||
if (empty($sFilterValue))
|
||||
{
|
||||
if (isset($aMapCriteria[$sFilterCode]))
|
||||
{
|
||||
if (count($aMapCriteria[$sFilterCode]) > 1)
|
||||
{
|
||||
$sFilterValue = Dict::S('UI:SearchValue:Mixed');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sFilterValue = $aMapCriteria[$sFilterCode][0]['value'];
|
||||
$sFilterOpCode = $aMapCriteria[$sFilterCode][0]['opcode'];
|
||||
}
|
||||
// Todo: Investigate...
|
||||
if ($sFilterCode != 'company')
|
||||
{
|
||||
$oUnlimitedFilter->AddCondition($sFilterCode, $sFilterValue, $sFilterOpCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sFilterCode);
|
||||
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
|
||||
{
|
||||
$oKeyAttDef = $oAttDef->GetFinalAttDef();
|
||||
$sKeyAttClass = $oKeyAttDef->GetHostClass();
|
||||
$sKeyAttCode = $oKeyAttDef->GetCode();
|
||||
|
||||
$sTargetClass = $oKeyAttDef->GetTargetClass();
|
||||
$oSearch = new DBObjectSearch($sTargetClass);
|
||||
$oSearch->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
|
||||
$oAllowedValues = new DBObjectSet($oSearch);
|
||||
|
||||
$iFieldSize = $oKeyAttDef->GetMaxSize();
|
||||
$iMaxComboLength = $oKeyAttDef->GetMaximumComboLength();
|
||||
$sHtml .= "<label>".MetaModel::GetFilterLabel($sKeyAttClass, $sKeyAttCode).":</label> ";
|
||||
$aExtKeyParams = $aExtraParams;
|
||||
$aExtKeyParams['iFieldSize'] = $oKeyAttDef->GetMaxSize();
|
||||
$aExtKeyParams['iMinChars'] = $oKeyAttDef->GetMinAutoCompleteChars();
|
||||
$sHtml .= UIExtKeyWidget::DisplayFromAttCode($oPage, $sKeyAttCode, $sKeyAttClass, $oAttDef->GetLabel(), $oAllowedValues, $sFilterValue, $sSearchFormId.'search_'.$sFilterCode, false, $sFilterCode, '', $aExtKeyParams, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAllowedValues = MetaModel::GetAllowedValues_flt($sClassName, $sFilterCode, $aExtraParams);
|
||||
if (is_null($aAllowedValues))
|
||||
{
|
||||
// Any value is possible, display an input box
|
||||
$sHtml .= "<label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label> <input class=\"textSearch\" name=\"$sFilterCode\" value=\"".htmlentities($sFilterValue, ENT_QUOTES, 'utf-8')."\"/>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
//Enum field, display a multi-select combo
|
||||
$sValue = "<select class=\"multiselect\" size=\"1\" name=\"{$sFilterCode}[]\" multiple>\n";
|
||||
$bMultiSelect = true;
|
||||
//$sValue .= "<option value=\"\">".Dict::S('UI:SearchValue:Any')."</option>\n";
|
||||
asort($aAllowedValues);
|
||||
foreach($aAllowedValues as $key => $value)
|
||||
{
|
||||
if (is_array($sFilterValue) && in_array($key, $sFilterValue))
|
||||
{
|
||||
$sSelected = ' selected';
|
||||
}
|
||||
else if ($sFilterValue == $key)
|
||||
{
|
||||
$sSelected = ' selected';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSelected = '';
|
||||
}
|
||||
$sValue .= "<option value=\"$key\"$sSelected>$value</option>\n";
|
||||
}
|
||||
$sValue .= "</select>\n";
|
||||
$sHtml .= "<label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label> $sValue\n";
|
||||
}
|
||||
}
|
||||
unset($aExtraParams[$sFilterCode]);
|
||||
|
||||
// Finally, add a tooltip if one is defined for this attribute definition
|
||||
$sTip = $oAttDef->GetHelpOnSmartSearch();
|
||||
if (strlen($sTip) > 0)
|
||||
{
|
||||
$sTip = addslashes($sTip);
|
||||
$sTip = str_replace(array("\n", "\r"), " ", $sTip);
|
||||
// :input does represent in form visible input (INPUT, SELECT, TEXTAREA)
|
||||
$oPage->add_ready_script("$('form#fs_$sSearchFormId :input[name={$sFilterCode}]').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
}
|
||||
$index++;
|
||||
$sHtml .= '</div> ';
|
||||
}
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "<p align=\"right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Search')."\"></p>\n";
|
||||
if (isset($aExtraParams['table_id']))
|
||||
{
|
||||
// Rename to avoid collisions...
|
||||
$aExtraParams['_table_id_'] = $aExtraParams['table_id'];
|
||||
unset($aExtraParams['table_id']);
|
||||
}
|
||||
foreach($aExtraParams as $sName => $sValue)
|
||||
{
|
||||
if (is_scalar($sValue))
|
||||
{
|
||||
$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
|
||||
}
|
||||
}
|
||||
$sHtml .= "<input type=\"hidden\" name=\"class\" value=\"$sClassName\" />\n";
|
||||
$sHtml .= "<input type=\"hidden\" name=\"dosearch\" value=\"1\" />\n";
|
||||
$sHtml .= "<input type=\"hidden\" name=\"operation\" value=\"search_form\" />\n";
|
||||
$sHtml .= $oAppContext->GetForForm();
|
||||
$sHtml .= "</form>\n";
|
||||
if (!isset($aExtraParams['currentId']))
|
||||
{
|
||||
$sHtml .= "</div><!-- Simple search form -->\n";
|
||||
}
|
||||
if ($bMultiSelect)
|
||||
{
|
||||
$aOptions = 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,
|
||||
);
|
||||
$sJSOptions = json_encode($aOptions);
|
||||
$oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);");
|
||||
}
|
||||
/*
|
||||
// OQL query builder
|
||||
$sHtml .= "<div id=\"OQLQuery{$iSearchFormId}\" style=\"display:none\" class=\"mini_tab{$iSearchFormId}\">\n";
|
||||
$sHtml .= "<h1>".Dict::S('UI:OQLQueryBuilderTitle')."</h1>\n";
|
||||
$sHtml .= "<form id=\"formOQL{$iSearchFormId}\"><table style=\"width:80%;\"><tr style=\"vertical-align:top\">\n";
|
||||
$sHtml .= "<td style=\"text-align:right\"><label>SELECT </label><select name=\"oql_class\">";
|
||||
$aClasses = MetaModel::EnumChildClasses($sClassName, ENUM_CHILD_CLASSES_ALL);
|
||||
$sSelectedClass = utils::ReadParam('oql_class', $sClassName, false, 'class');
|
||||
$sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data');
|
||||
asort($aClasses);
|
||||
foreach($aClasses as $sChildClass)
|
||||
{
|
||||
$sSelected = ($sChildClass == $sSelectedClass) ? 'selected' : '';
|
||||
$sHtml.= "<option value=\"$sChildClass\" $sSelected>".MetaModel::GetName($sChildClass)."</option>\n";
|
||||
}
|
||||
$sHtml .= "</select> </td><td>\n";
|
||||
$sHtml .= "<textarea name=\"oql_clause\" style=\"width:100%\">$sOQLClause</textarea></td></tr>\n";
|
||||
$sHtml .= "<tr><td colspan=\"2\" style=\"text-align:right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Query')."\"></td></tr>\n";
|
||||
$sHtml .= "<input type=\"hidden\" name=\"dosearch\" value=\"1\" />\n";
|
||||
foreach($aExtraParams as $sName => $sValue)
|
||||
{
|
||||
if (is_scalar($sValue))
|
||||
{
|
||||
$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n";
|
||||
}
|
||||
}
|
||||
$sHtml .= "<input type=\"hidden\" name=\"operation\" value=\"search_oql\" />\n";
|
||||
$sHtml .= $oAppContext->GetForForm();
|
||||
$sHtml .= "</table></form>\n";
|
||||
$sHtml .= "</div><!-- OQL query form -->\n";
|
||||
*/
|
||||
return $sHtml;
|
||||
return $oSearchForm->GetSearchForm($oPage, $oSet, $aExtraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1942,7 +1733,51 @@ EOF
|
||||
$sHidden = "<input type=\"hidden\" id=\"{$iId}_count\" value=\"$iEntriesCount\"/>"; // To know how many entries the case log already contains
|
||||
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_caselog caselog\" $sStyle>$sHeader<textarea class=\"htmlEditor\" style=\"border:0;width:100%\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sPreviousLog</div>{$sValidationSpan}{$sReloadSpan}$sHidden";
|
||||
$oPage->add_ready_script("$('#$iId').bind('keyup change validate', function(evt, sFormId) { return ValidateCaseLogField('$iId', $bMandatory, sFormId) } );"); // Custom validation function
|
||||
|
||||
// Note: This should be refactored for all types of attribute (see at the end of this function) but as we are doing this for a maintenance release, we are scheduling it for the next main release in to order to avoid regressions as much as possible.
|
||||
$sNullValue = $oAttDef->GetNullValue();
|
||||
if (!is_numeric($sNullValue))
|
||||
{
|
||||
$sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number
|
||||
}
|
||||
$sOriginalValue = ($iFlags & OPT_ATT_MUSTCHANGE) ? json_encode($value->GetModifiedEntry('html')) : 'undefined';
|
||||
|
||||
$oPage->add_ready_script("$('#$iId').bind('keyup change validate', function(evt, sFormId) { return ValidateCaseLogField('$iId', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );"); // Custom validation function
|
||||
|
||||
// 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()));
|
||||
$aConfig['language'] = $sLanguage;
|
||||
$aConfig['contentsLanguage'] = $sLanguage;
|
||||
$aConfig['extraPlugins'] = 'disabler';
|
||||
$aConfig['placeholder'] = Dict::S('UI:CaseLogTypeYourTextHere');
|
||||
$sConfigJS = json_encode($aConfig);
|
||||
|
||||
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
|
||||
|
||||
$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
|
||||
);
|
||||
break;
|
||||
|
||||
case 'HTML':
|
||||
@@ -1954,11 +1789,11 @@ EOF
|
||||
case 'LinkedSet':
|
||||
if ($oAttDef->IsIndirect())
|
||||
{
|
||||
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed(), $aArgs);
|
||||
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed());
|
||||
}
|
||||
else
|
||||
{
|
||||
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix, $aArgs);
|
||||
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix);
|
||||
}
|
||||
$aEventsList[] ='validate';
|
||||
$aEventsList[] ='change';
|
||||
@@ -2134,14 +1969,16 @@ EOF
|
||||
case 'radio':
|
||||
case 'radio_horizontal':
|
||||
case 'radio_vertical':
|
||||
$sHTMLValue = '';
|
||||
$bVertical = ($sDisplayStyle != 'radio_horizontal');
|
||||
$sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $iId, "attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}", $bMandatory, $bVertical, $sValidationSpan.$sReloadSpan);
|
||||
$aEventsList[] ='change';
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_{$sDisplayStyle}\">";
|
||||
$bVertical = ($sDisplayStyle != 'radio_horizontal');
|
||||
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $iId, "attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}", $bMandatory, $bVertical, '');
|
||||
$sHTMLValue .= "</div>{$sValidationSpan}{$sReloadSpan}\n";
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
default:
|
||||
$aEventsList[] ='change';
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_string\"><select title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" id=\"$iId\">\n";
|
||||
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
|
||||
foreach($aAllowedValues as $key => $display_value)
|
||||
@@ -2158,7 +1995,7 @@ EOF
|
||||
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
|
||||
}
|
||||
$sHTMLValue .= "</select></div>{$sValidationSpan}{$sReloadSpan}\n";
|
||||
$aEventsList[] ='change';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2166,6 +2003,31 @@ EOF
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_string\"><input title=\"$sHelpText\" type=\"text\" maxlength=\"$iFieldSize\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/></div>{$sValidationSpan}{$sReloadSpan}";
|
||||
$aEventsList[] ='keyup';
|
||||
$aEventsList[] ='change';
|
||||
|
||||
// Adding tooltip so we can read the whole value when its very long (eg. URL)
|
||||
if(!empty($sDisplayValue))
|
||||
{
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#{$iId}').qtip( { content: $('#{$iId}').val(), show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'bottomLeft' }, position: { corner: { target: 'topLeft', tooltip: 'bottomLeft' }, adjust: { y: -15}} } );
|
||||
|
||||
$('#{$iId}').bind('keyup', function(evt, sFormId){
|
||||
var oQTipAPI = $(this).qtip('api');
|
||||
|
||||
if($(this).val() === '')
|
||||
{
|
||||
oQTipAPI.hide();
|
||||
oQTipAPI.disable(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
oQTipAPI.disable(false);
|
||||
}
|
||||
oQTipAPI.updateContent($(this).val());
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2204,7 +2066,7 @@ EOF
|
||||
$LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
|
||||
if ($LockEnabled)
|
||||
{
|
||||
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data');
|
||||
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data');
|
||||
if ($sOwnershipToken !== null)
|
||||
{
|
||||
// We're probably inside something like "apply_modify" where the validation failed and we must prompt the user again to edit the object
|
||||
@@ -2357,7 +2219,7 @@ EOF
|
||||
$sJSToken = json_encode($sOwnershipToken);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$(window).unload(function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
|
||||
$(window).on('unload',function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
|
||||
window.onbeforeunload = function() {
|
||||
if (!window.bInSubmit && !window.bInCancel)
|
||||
{
|
||||
@@ -2480,12 +2342,7 @@ EOF
|
||||
|
||||
if ($oObjectToClone == null)
|
||||
{
|
||||
$oObj = MetaModel::NewObject($sClass);
|
||||
if (!empty($sStateAttCode))
|
||||
{
|
||||
$sTargetState = MetaModel::GetDefaultState($sClass);
|
||||
$oObj->Set($sStateAttCode, $sTargetState);
|
||||
}
|
||||
$oObj = DBObject::MakeDefaultInstance($sClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2521,8 +2378,9 @@ EOF
|
||||
{
|
||||
if ($oAttDef->IsExternalKey())
|
||||
{
|
||||
/** @var DBObjectSet $oAllowedValues */
|
||||
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs);
|
||||
if ($oAllowedValues->Count() == 1)
|
||||
if ($oAllowedValues->CountWithLimit(2) == 1)
|
||||
{
|
||||
$oRemoteObj = $oAllowedValues->Fetch();
|
||||
$oObj->Set($sAttCode, $oRemoteObj->GetKey());
|
||||
@@ -2531,7 +2389,7 @@ EOF
|
||||
else
|
||||
{
|
||||
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
|
||||
if (count($aAllowedValues) == 1)
|
||||
if (is_array($aAllowedValues) && (count($aAllowedValues) == 1))
|
||||
{
|
||||
$aValues = array_keys($aAllowedValues);
|
||||
$oObj->Set($sAttCode, $aValues[0]);
|
||||
@@ -2543,7 +2401,7 @@ EOF
|
||||
return $oObj->DisplayModifyForm( $oPage, $aExtraParams);
|
||||
}
|
||||
|
||||
public function DisplayStimulusForm(WebPage $oPage, $sStimulus)
|
||||
public function DisplayStimulusForm(WebPage $oPage, $sStimulus, $aPrefillFormParam = null)
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
$iKey = $this->GetKey();
|
||||
@@ -2559,7 +2417,7 @@ EOF
|
||||
$sOwnershipToken = null;
|
||||
if ($LockEnabled)
|
||||
{
|
||||
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data');
|
||||
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data');
|
||||
$aLockInfo = iTopOwnershipLock::AcquireLock($sClass, $iKey);
|
||||
if ($aLockInfo['success'])
|
||||
{
|
||||
@@ -2585,6 +2443,12 @@ EOF
|
||||
$oPage->add("<h1>$sActionDetails</h1>\n");
|
||||
$sTargetState = $aTransitions[$sStimulus]['target_state'];
|
||||
$aExpectedAttributes = $this->GetTransitionAttributes($sStimulus /*, current state*/);
|
||||
if ($aPrefillFormParam != null)
|
||||
{
|
||||
$aPrefillFormParam['expected_attributes'] = $aExpectedAttributes;
|
||||
$this->PrefillForm('state_change', $aPrefillFormParam);
|
||||
$aExpectedAttributes = $aPrefillFormParam['expected_attributes'];
|
||||
}
|
||||
$sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position');
|
||||
if ($sButtonsPosition == 'bottom')
|
||||
{
|
||||
@@ -2594,7 +2458,7 @@ EOF
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
$oPage->add("<div class=\"wizContainer\">\n");
|
||||
$oPage->add("<form id=\"apply_stimulus\" method=\"post\" onSubmit=\"return OnSubmit('apply_stimulus');\">\n");
|
||||
$oPage->add("<form id=\"apply_stimulus\" method=\"post\" enctype=\"multipart/form-data\" onSubmit=\"return OnSubmit('apply_stimulus');\">\n");
|
||||
$aDetails = array();
|
||||
$iFieldIndex = 0;
|
||||
$aFieldsMap = array();
|
||||
@@ -2640,8 +2504,9 @@ EOF
|
||||
{
|
||||
if ($oAttDef->IsExternalKey())
|
||||
{
|
||||
/** @var DBObjectSet $oAllowedValues */
|
||||
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '', $this->Get($sAttCode));
|
||||
if ($oAllowedValues->Count() == 1)
|
||||
if ($oAllowedValues->CountWithLimit(2) == 1)
|
||||
{
|
||||
$oRemoteObj = $oAllowedValues->Fetch();
|
||||
$this->Set($sAttCode, $oRemoteObj->GetKey());
|
||||
@@ -2650,7 +2515,7 @@ EOF
|
||||
else
|
||||
{
|
||||
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
|
||||
if (count($aAllowedValues) == 1)
|
||||
if (is_array($aAllowedValues) && count($aAllowedValues) == 1)
|
||||
{
|
||||
$aValues = array_keys($aAllowedValues);
|
||||
$this->Set($sAttCode, $aValues[0]);
|
||||
@@ -2709,7 +2574,7 @@ EOF
|
||||
<<<EOF
|
||||
// Starts the validation when the page is ready
|
||||
CheckFields('apply_stimulus', false);
|
||||
$(window).unload(function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
|
||||
$(window).on('unload', function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
|
||||
EOF
|
||||
);
|
||||
|
||||
@@ -2717,6 +2582,10 @@ EOF
|
||||
{
|
||||
$this->GetOwnershipJSHandler($oPage, $sOwnershipToken);
|
||||
}
|
||||
|
||||
// Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug n°1240.
|
||||
$sTempId = session_id().'_'.$iTransactionId;
|
||||
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
|
||||
}
|
||||
|
||||
public static function ProcessZlist($aList, $aDetails, $sCurrentTab, $sCurrentCol, $sCurrentSet)
|
||||
@@ -2834,7 +2703,8 @@ EOF
|
||||
|
||||
// Checking how the field should be rendered
|
||||
// Note: For edit mode, this is done in self::GetBareProperties()
|
||||
if(in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields')))
|
||||
// Note 2: Shouldn't this be a AttDef property instead of an array to maintain?
|
||||
if(in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression')))
|
||||
{
|
||||
$retVal['layout'] = 'large';
|
||||
}
|
||||
@@ -3538,11 +3408,31 @@ EOF
|
||||
{
|
||||
$res = parent::DBUpdate();
|
||||
|
||||
// Protection against reentrance (e.g. cascading the update of ticket logs)
|
||||
// Note: This is based on the fix made on r 3190 in DBObject::DBUpdate()
|
||||
static $aUpdateReentrance = array();
|
||||
$sKey = get_class($this).'::'.$this->GetKey();
|
||||
if(array_key_exists($sKey, $aUpdateReentrance))
|
||||
{
|
||||
return $res;
|
||||
}
|
||||
$aUpdateReentrance[$sKey] = true;
|
||||
|
||||
try
|
||||
{
|
||||
// Invoke extensions after the update (could be before)
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
unset($aUpdateReentrance[$sKey]);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
unset($aUpdateReentrance[$sKey]);
|
||||
return $res;
|
||||
}
|
||||
|
||||
@@ -3600,7 +3490,7 @@ EOF
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
|
||||
if (count($aNewIssues) > 0)
|
||||
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
|
||||
{
|
||||
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
|
||||
}
|
||||
@@ -3642,7 +3532,7 @@ EOF
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
|
||||
if (count($aNewIssues) > 0)
|
||||
if (is_array($aNewIssues) && count($aNewIssues) > 0)
|
||||
{
|
||||
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
|
||||
}
|
||||
@@ -3722,21 +3612,6 @@ EOF
|
||||
}
|
||||
$sHTMLValue .= "<span style=\"font-family:Tahoma,Verdana,Arial,Helvetica;font-size:12px;\" id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
|
||||
// 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()));
|
||||
$aConfig['language'] = $sLanguage;
|
||||
$aConfig['contentsLanguage'] = $sLanguage;
|
||||
$aConfig['extraPlugins'] = 'disabler';
|
||||
$aConfig['placeholder'] = Dict::S('UI:CaseLogTypeYourTextHere');
|
||||
$sConfigJS = json_encode($aConfig);
|
||||
|
||||
$oPage->add_ready_script("$('#$sInputId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
|
||||
|
||||
}
|
||||
//$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
|
||||
$oPage->add('<fieldset><legend>'.$oAttDef->GetLabel().'</legend>');
|
||||
@@ -3745,6 +3620,14 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sCurrentState
|
||||
* @param $sStimulus
|
||||
* @param $bOnlyNewOnes
|
||||
* @return array
|
||||
* @throws ApplicationException
|
||||
* @deprecated Since iTop 2.4, use DBObject::GetTransitionAttributes() instead.
|
||||
*/
|
||||
public function GetExpectedAttributes($sCurrentState, $sStimulus, $bOnlyNewOnes)
|
||||
{
|
||||
$aTransitions = $this->EnumTransitions();
|
||||
@@ -3827,7 +3710,7 @@ EOF
|
||||
$currValue = $oObj->Get($sAttCode);
|
||||
if ($oAttDef instanceof AttributeCaseLog)
|
||||
{
|
||||
$currValue = ' '; // Don't put an empty string, in case the field would be considered as mandatory...
|
||||
$currValue = ''; // Put a single scalar value to force caselog to mock a new entry. For more info see N°1059.
|
||||
}
|
||||
if (is_object($currValue)) continue; // Skip non scalar values...
|
||||
if(!array_key_exists($currValue, $aValues[$sAttCode]))
|
||||
@@ -4029,7 +3912,7 @@ EOF
|
||||
$bResult = (count($aErrors) == 0);
|
||||
if ($bResult)
|
||||
{
|
||||
list($bResult, $aErrors) = $oObj->CheckToWrite(true /* Enforce Read-only fields */);
|
||||
list($bResult, $aErrors) = $oObj->CheckToWrite();
|
||||
}
|
||||
if ($bPreview)
|
||||
{
|
||||
|
||||
@@ -115,15 +115,13 @@ abstract class Dashboard
|
||||
$aDashletOrder = array();
|
||||
foreach($oDashletList as $oDomNode)
|
||||
{
|
||||
$sDashletClass = $oDomNode->getAttribute('xsi:type');
|
||||
$oRank = $oDomNode->getElementsByTagName('rank')->item(0);
|
||||
if ($oRank)
|
||||
{
|
||||
$iRank = (float)$oRank->textContent;
|
||||
}
|
||||
$sId = $oDomNode->getAttribute('id');
|
||||
$oNewDashlet = new $sDashletClass($this->oMetaModel, $sId);
|
||||
$oNewDashlet->FromDOMNode($oDomNode);
|
||||
|
||||
$oNewDashlet = $this->InitDashletFromDOMNode($oDomNode);
|
||||
$aDashletOrder[] = array('rank' => $iRank, 'dashlet' => $oNewDashlet);
|
||||
}
|
||||
usort($aDashletOrder, array(get_class($this), 'SortOnRank'));
|
||||
@@ -147,6 +145,20 @@ abstract class Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
protected function InitDashletFromDOMNode($oDomNode)
|
||||
{
|
||||
$sId = $oDomNode->getAttribute('id');
|
||||
$sDashletType = $oDomNode->getAttribute('xsi:type');
|
||||
|
||||
// Test if dashlet can be instanciated, otherwise (uninstalled, broken, ...) we display a placeholder
|
||||
$sClass = static::GetDashletClassFromType($sDashletType);
|
||||
$oNewDashlet = new $sClass($this->oMetaModel, $sId);
|
||||
$oNewDashlet->SetDashletType($sDashletType);
|
||||
$oNewDashlet->FromDOMNode($oDomNode);
|
||||
|
||||
return $oNewDashlet;
|
||||
}
|
||||
|
||||
static function SortOnRank($aItem1, $aItem2)
|
||||
{
|
||||
return ($aItem1['rank'] > $aItem2['rank']) ? +1 : -1;
|
||||
@@ -220,7 +232,7 @@ abstract class Dashboard
|
||||
$oNode = $oDoc->createElement('dashlet');
|
||||
$oDashletsNode->appendChild($oNode);
|
||||
$oNode->setAttribute('id', $oDashlet->GetID());
|
||||
$oNode->setAttribute('xsi:type', get_class($oDashlet));
|
||||
$oNode->setAttribute('xsi:type', $oDashlet->GetDashletType());
|
||||
$oDashletRank = $oDoc->createElement('rank', $iDashletRank);
|
||||
$oNode->appendChild($oDashletRank);
|
||||
$iDashletRank++;
|
||||
@@ -245,7 +257,10 @@ abstract class Dashboard
|
||||
$sDashletClass = $aDashletParams['dashlet_class'];
|
||||
$sId = $aDashletParams['dashlet_id'];
|
||||
$oNewDashlet = new $sDashletClass($this->oMetaModel, $sId);
|
||||
|
||||
if (isset($aDashletParams['dashlet_type']))
|
||||
{
|
||||
$oNewDashlet->SetDashletType($aDashletParams['dashlet_type']);
|
||||
}
|
||||
$oForm = $oNewDashlet->GetForm();
|
||||
$oForm->SetParamsContainer($sId);
|
||||
$oForm->SetPrefix('');
|
||||
@@ -414,25 +429,12 @@ EOF
|
||||
$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">'.Dict::S('UI:DashboardEdit:Dashlets').'</div>');
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
|
||||
$oPage->add('<div id="select_dashlet" style="text-align:center">');
|
||||
foreach( get_declared_classes() as $sDashletClass)
|
||||
$oPage->add('<div id="select_dashlet" style="text-align:center; max-height:120px; overflow-y:auto;">');
|
||||
$aAvailableDashlets = $this->GetAvailableDashlets();
|
||||
foreach($aAvailableDashlets as $sDashletClass => $aInfo)
|
||||
{
|
||||
if (is_subclass_of($sDashletClass, 'Dashlet'))
|
||||
{
|
||||
$oReflection = new ReflectionClass($sDashletClass);
|
||||
if (!$oReflection->isAbstract())
|
||||
{
|
||||
$aCallSpec = array($sDashletClass, 'IsVisible');
|
||||
$bVisible = call_user_func($aCallSpec);
|
||||
if ($bVisible)
|
||||
{
|
||||
$aCallSpec = array($sDashletClass, 'GetInfo');
|
||||
$aInfo = call_user_func($aCallSpec);
|
||||
$oPage->add('<span dashlet_class="'.$sDashletClass.'" class="dashlet_icon ui-widget-content ui-corner-all" id="dashlet_'.$sDashletClass.'" title="'.$aInfo['label'].'" style="width:34px; height:34px; display:inline-block; margin:2px;"><img src="'.$sUrl.$aInfo['icon'].'" /></span>');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$oPage->add('</div>');
|
||||
|
||||
$oPage->add('</div>');
|
||||
@@ -466,6 +468,38 @@ EOF
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of dashlets available for selection.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function GetAvailableDashlets()
|
||||
{
|
||||
$aDashlets = array();
|
||||
|
||||
foreach( get_declared_classes() as $sDashletClass)
|
||||
{
|
||||
// DashletUnknown is not among the selection as it is just a fallback for dashlets that can't instanciated.
|
||||
if ( is_subclass_of($sDashletClass, 'Dashlet') && !in_array($sDashletClass, array('DashletUnknown', 'DashletProxy')) )
|
||||
{
|
||||
$oReflection = new ReflectionClass($sDashletClass);
|
||||
if (!$oReflection->isAbstract())
|
||||
{
|
||||
$aCallSpec = array($sDashletClass, 'IsVisible');
|
||||
$bVisible = call_user_func($aCallSpec);
|
||||
if ($bVisible)
|
||||
{
|
||||
$aCallSpec = array($sDashletClass, 'GetInfo');
|
||||
$aInfo = call_user_func($aCallSpec);
|
||||
$aDashlets[$sDashletClass] = $aInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aDashlets;
|
||||
}
|
||||
|
||||
protected function GetNewDashletId()
|
||||
{
|
||||
$iNewId = 0;
|
||||
@@ -480,6 +514,15 @@ EOF
|
||||
}
|
||||
|
||||
abstract protected function SetFormParams($oForm);
|
||||
|
||||
public static function GetDashletClassFromType($sType, $oFactory = null)
|
||||
{
|
||||
if (is_subclass_of($sType, 'Dashlet'))
|
||||
{
|
||||
return $sType;
|
||||
}
|
||||
return 'DashletUnknown';
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeDashboard extends Dashboard
|
||||
@@ -755,16 +798,45 @@ 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();
|
||||
$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)
|
||||
{
|
||||
// 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);
|
||||
@@ -776,12 +848,15 @@ EOF
|
||||
{
|
||||
$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);
|
||||
@@ -842,7 +917,7 @@ EOF
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#dashlet_creation_dlg').dialog({
|
||||
width: 400,
|
||||
width: 600,
|
||||
modal: true,
|
||||
title: '$sDialogTitle',
|
||||
buttons: [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2012-2017 Combodo SARL
|
||||
// Copyright (C) 2012-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -32,6 +32,7 @@ abstract class Dashlet
|
||||
protected $bFormRedrawNeeded;
|
||||
protected $aProperties; // array of {property => value}
|
||||
protected $aCSSClasses;
|
||||
protected $sDashletType;
|
||||
|
||||
public function __construct(ModelReflection $oModelReflection, $sId)
|
||||
{
|
||||
@@ -41,6 +42,7 @@ abstract class Dashlet
|
||||
$this->bFormRedrawNeeded = false; // By default: no need to redraw the form (independent fields)
|
||||
$this->aProperties = array(); // By default: there is no property
|
||||
$this->aCSSClasses = array('dashlet');
|
||||
$this->sDashletType = get_class($this);
|
||||
}
|
||||
|
||||
// Assuming that a property has the type of its default value, set in the constructor
|
||||
@@ -132,7 +134,14 @@ abstract class Dashlet
|
||||
public function FromXml($sXml)
|
||||
{
|
||||
$oDomDoc = new DOMDocument('1.0', 'UTF-8');
|
||||
libxml_clear_errors();
|
||||
$oDomDoc->loadXml($sXml);
|
||||
$aErrors = libxml_get_errors();
|
||||
if (count($aErrors) > 0)
|
||||
{
|
||||
throw new DOMException("Malformed XML");
|
||||
}
|
||||
|
||||
$this->FromDOMNode($oDomDoc->firstChild);
|
||||
}
|
||||
|
||||
@@ -214,9 +223,10 @@ abstract class Dashlet
|
||||
if ($bEditMode)
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
$sType = $this->sDashletType;
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#dashlet_$sId').dashlet({dashlet_id: '$sId', dashlet_class: '$sClass'});
|
||||
$('#dashlet_$sId').dashlet({dashlet_id: '$sId', dashlet_class: '$sClass', 'dashlet_type': '$sType'});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
@@ -280,10 +290,12 @@ EOF
|
||||
);
|
||||
}
|
||||
|
||||
public function GetForm()
|
||||
public function GetForm($aInfo = array())
|
||||
{
|
||||
$oForm = new DesignerForm();
|
||||
$oForm->SetPrefix("dashlet_". $this->GetID());
|
||||
$sPrefix = "dashlet_".$this->GetID();
|
||||
$oForm->SetPrefix($sPrefix);
|
||||
$oForm->SetHierarchyPath($sPrefix);
|
||||
$oForm->SetParamsContainer('params');
|
||||
|
||||
$this->GetPropertiesFields($oForm);
|
||||
@@ -291,6 +303,9 @@ EOF
|
||||
$oDashletClassField = new DesignerHiddenField('dashlet_class', '', get_class($this));
|
||||
$oForm->AddField($oDashletClassField);
|
||||
|
||||
$oDashletTypeField = new DesignerHiddenField('dashlet_type', '', $this->sDashletType);
|
||||
$oForm->AddField($oDashletTypeField);
|
||||
|
||||
$oDashletIdField = new DesignerHiddenField('dashlet_id', '', $this->GetID());
|
||||
$oForm->AddField($oDashletIdField);
|
||||
|
||||
@@ -311,6 +326,317 @@ EOF
|
||||
{
|
||||
// Default: do nothing since it's not supported
|
||||
}
|
||||
|
||||
|
||||
protected function GetGroupByOptions($sOql)
|
||||
{
|
||||
$aGroupBy = array();
|
||||
try
|
||||
{
|
||||
$oQuery = $this->oModelReflection->GetQuery($sOql);
|
||||
$sClass = $oQuery->GetClass();
|
||||
foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
|
||||
{
|
||||
if ($sAttType == 'AttributeLinkedSet')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (is_subclass_of($sAttType, 'AttributeLinkedSet'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ($sAttType == 'AttributeFriendlyName')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
|
||||
if (is_subclass_of($sAttType, 'AttributeDateTime') || $sAttType == 'AttributeDateTime')
|
||||
{
|
||||
$aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel);
|
||||
$aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month',
|
||||
$sLabel);
|
||||
$aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek',
|
||||
$sLabel);
|
||||
$aGroupBy[$sAttCode.':day_of_month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth',
|
||||
$sLabel);
|
||||
}
|
||||
}
|
||||
asort($aGroupBy);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// Bad OQL is ignored
|
||||
}
|
||||
return $aGroupBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetDashletType()
|
||||
{
|
||||
return $this->sDashletType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sDashletType
|
||||
*/
|
||||
public function SetDashletType($sDashletType)
|
||||
{
|
||||
$this->sDashletType = $sDashletType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class DashletUnknown
|
||||
*
|
||||
* Used as a fallback in iTop for unknown dashlet classes.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class DashletUnknown extends Dashlet
|
||||
{
|
||||
static protected $aClassList = null;
|
||||
|
||||
protected $sOriginalDashletXML;
|
||||
|
||||
public function __construct($oModelReflection, $sId)
|
||||
{
|
||||
parent::__construct($oModelReflection, $sId);
|
||||
$this->sOriginalDashletXML = '';
|
||||
$this->aCSSClasses[] = 'dashlet-unknown';
|
||||
}
|
||||
|
||||
public function FromDOMNode($oDOMNode)
|
||||
{
|
||||
// Parent won't do anything as there is no property declared
|
||||
parent::FromDOMNode($oDOMNode);
|
||||
|
||||
// Build properties from XML
|
||||
$this->sOriginalDashletXML = "";
|
||||
foreach($oDOMNode->childNodes as $oDOMChildNode)
|
||||
{
|
||||
if($oDOMChildNode instanceof DOMElement)
|
||||
{
|
||||
$sProperty = $oDOMChildNode->tagName;
|
||||
|
||||
// For all properties but "rank" as it is handle by the dashboard.
|
||||
if($sProperty !== 'rank')
|
||||
{
|
||||
// We need to initialize the property before setting it, otherwise it will guessed as NULL and not used.
|
||||
$this->aProperties[$sProperty] = '';
|
||||
$this->aProperties[$sProperty] = $this->PropertyFromDOMNode($oDOMChildNode, $sProperty);
|
||||
|
||||
// And build the original XML
|
||||
$this->sOriginalDashletXML .= $oDOMChildNode->ownerDocument->saveXML($oDOMChildNode)."\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->OnUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oDOMNode
|
||||
*
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
public function ToDOMNode($oDOMNode)
|
||||
{
|
||||
$oDoc = new DOMDocument();
|
||||
libxml_clear_errors();
|
||||
$oDoc->loadXML('<root>'.$this->sOriginalDashletXML.'</root>');
|
||||
$aErrors = libxml_get_errors();
|
||||
if (count($aErrors) > 0)
|
||||
{
|
||||
throw new DOMFormatException('Dashlet definition not correctly formatted!');
|
||||
}
|
||||
foreach($oDoc->documentElement->childNodes as $oDOMChildNode)
|
||||
{
|
||||
$oPropNode = $oDOMNode->ownerDocument->importNode($oDOMChildNode, true);
|
||||
$oDOMNode->appendChild($oPropNode);
|
||||
}
|
||||
}
|
||||
|
||||
public function FromParams($aParams)
|
||||
{
|
||||
// For unknown dashlet, parameters are not parsed but passed as a raw xml
|
||||
if(array_key_exists('xml', $aParams))
|
||||
{
|
||||
// A namespace must be present for the "xsi:type" attribute, otherwise a warning will be thrown.
|
||||
$sXML = '<dashlet id="'.$aParams['dashlet_id'].'" xsi:type="'.$aParams['dashlet_type'].'" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$aParams['xml'].'</dashlet>';
|
||||
$this->FromXml($sXML);
|
||||
}
|
||||
$this->OnUpdate();
|
||||
}
|
||||
|
||||
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
|
||||
{
|
||||
$aInfos = static::GetInfo();
|
||||
|
||||
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
|
||||
$sExplainText = ($bEditMode) ? Dict::Format('UI:DashletUnknown:RenderText:Edit', $this->GetDashletType()) : Dict::S('UI:DashletUnknown:RenderText:View');
|
||||
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
|
||||
$oPage->add('<div class="dashlet-ukn-image"><img src="'.utils::HtmlEntities($sIconUrl).'" /></div>');
|
||||
$oPage->add('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
|
||||
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
|
||||
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
|
||||
{
|
||||
$aInfos = static::GetInfo();
|
||||
|
||||
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
|
||||
$sExplainText = Dict::Format('UI:DashletUnknown:RenderNoDataText:Edit', $this->GetDashletType());
|
||||
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
|
||||
$oPage->add('<div class="dashlet-ukn-image"><img src="'.utils::HtmlEntities($sIconUrl).'" /></div>');
|
||||
$oPage->add('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
|
||||
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
|
||||
public function GetForm($aInfo = array())
|
||||
{
|
||||
if (isset($aInfo['configuration']) && empty($this->sOriginalDashletXML))
|
||||
{
|
||||
$this->sOriginalDashletXML = $aInfo['configuration'];
|
||||
}
|
||||
return parent::GetForm($aInfo);
|
||||
}
|
||||
|
||||
public function GetPropertiesFields(DesignerForm $oForm)
|
||||
{
|
||||
$oField = new DesignerLongTextField('xml', Dict::S('UI:DashletUnknown:Prop-XMLConfiguration'), $this->sOriginalDashletXML);
|
||||
$oForm->AddField($oField);
|
||||
}
|
||||
|
||||
protected function PropertyFromDOMNode($oDOMNode, $sProperty)
|
||||
{
|
||||
$bHasSubProperties = false;
|
||||
foreach($oDOMNode->childNodes as $oDOMChildNode)
|
||||
{
|
||||
if($oDOMChildNode->nodeType === XML_ELEMENT_NODE)
|
||||
{
|
||||
$bHasSubProperties = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($bHasSubProperties)
|
||||
{
|
||||
$sTmp = $oDOMNode->ownerDocument->saveXML($oDOMNode, LIBXML_NOENT);
|
||||
$sTmp = trim(preg_replace("/(<".$oDOMNode->tagName."[^>]*>|<\/".$oDOMNode->tagName.">)/", "", $sTmp));
|
||||
return $sTmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return parent::PropertyFromDOMNode($oDOMNode, $sProperty);
|
||||
}
|
||||
}
|
||||
|
||||
protected function PropertyToDOMNode($oDOMNode, $sProperty, $value)
|
||||
{
|
||||
// Save subnodes
|
||||
if(preg_match('/<(.*)>/', $value))
|
||||
{
|
||||
/** @var \DOMDocumentFragment $oDOMFragment */
|
||||
$oDOMFragment = $oDOMNode->ownerDocument->createDocumentFragment();
|
||||
$oDOMFragment->appendXML($value);
|
||||
$oDOMNode->appendChild($oDOMFragment);
|
||||
}
|
||||
else
|
||||
{
|
||||
parent::PropertyToDOMNode($oDOMNode, $sProperty, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function Update($aValues, $aUpdatedFields)
|
||||
{
|
||||
$this->FromParams($aValues);
|
||||
// OnUpdate() already done in FromParams()
|
||||
return $this;
|
||||
}
|
||||
|
||||
static public function GetInfo()
|
||||
{
|
||||
return array(
|
||||
'label' => Dict::S('UI:DashletUnknown:Label'),
|
||||
'icon' => 'images/dashlet-unknown.png',
|
||||
'description' => Dict::S('UI:DashletUnknown:Description'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DashletProxy extends DashletUnknown
|
||||
{
|
||||
public function __construct($oModelReflection, $sId)
|
||||
{
|
||||
parent::__construct($oModelReflection, $sId);
|
||||
|
||||
// Remove DashletUnknown class
|
||||
if( ($key = array_search('dashlet-unknown', $this->aCSSClasses)) !== false )
|
||||
{
|
||||
unset($this->aCSSClasses[$key]);
|
||||
}
|
||||
|
||||
$this->aCSSClasses[] = 'dashlet-proxy';
|
||||
}
|
||||
|
||||
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
|
||||
{
|
||||
// This should never be called.
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$oPage->add('<div>This dashlet is not supposed to be rendered as it is just a proxy for third-party widgets.</div>');
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
|
||||
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
|
||||
{
|
||||
$aInfos = static::GetInfo();
|
||||
|
||||
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
|
||||
$sExplainText = Dict::Format('UI:DashletProxy:RenderNoDataText:Edit', $this->GetDashletType());
|
||||
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
|
||||
$oPage->add('<div class="dashlet-pxy-image"><img src="'.utils::HtmlEntities($sIconUrl).'" /></div>');
|
||||
$oPage->add('<div class="dashlet-pxy-text">'.$sExplainText.'</div>');
|
||||
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
|
||||
static public function GetInfo()
|
||||
{
|
||||
return array(
|
||||
'label' => Dict::S('UI:DashletProxy:Label'),
|
||||
'icon' => 'images/dashlet-proxy.png',
|
||||
'description' => Dict::S('UI:DashletProxy:Description'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DashletEmptyCell extends Dashlet
|
||||
@@ -485,12 +811,23 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$this->aProperties['query'] = 'SELECT Contact';
|
||||
$this->aProperties['group_by'] = 'status';
|
||||
$this->aProperties['style'] = 'table';
|
||||
$this->aProperties['aggregation_function'] = 'count';
|
||||
$this->aProperties['aggregation_attribute'] = '';
|
||||
$this->aProperties['limit'] = '';
|
||||
$this->aProperties['order_by'] = '';
|
||||
$this->aProperties['order_direction'] = '';
|
||||
}
|
||||
|
||||
protected $sGroupByLabel = null;
|
||||
protected $sGroupByExpr = null;
|
||||
protected $sGroupByAttCode = null;
|
||||
protected $sFunction = null;
|
||||
protected $sAggregationFunction = null;
|
||||
protected $sAggregationAttribute = null;
|
||||
protected $sLimit = null;
|
||||
protected $sOrderBy = null;
|
||||
protected $sOrderDirection = null;
|
||||
protected $sClass = null;
|
||||
|
||||
/**
|
||||
* Compute Grouping
|
||||
@@ -501,24 +838,42 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$this->sGroupByLabel = null;
|
||||
$this->sGroupByAttCode = null;
|
||||
$this->sFunction = null;
|
||||
$this->sClass = null;
|
||||
|
||||
$sQuery = $this->aProperties['query'];
|
||||
$sGroupBy = $this->aProperties['group_by'];
|
||||
$sStyle = $this->aProperties['style'];
|
||||
|
||||
$this->sAggregationFunction = $this->aProperties['aggregation_function'];
|
||||
$this->sAggregationAttribute = $this->aProperties['aggregation_attribute'];
|
||||
|
||||
$this->sLimit = $this->aProperties['limit'];
|
||||
$this->sOrderBy = $this->aProperties['order_by'];
|
||||
if (empty($this->sOrderBy))
|
||||
{
|
||||
if ($this->aProperties['style'] == 'pie')
|
||||
{
|
||||
$this->sOrderBy = 'function';
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->sOrderBy = 'attribute';
|
||||
}
|
||||
}
|
||||
|
||||
// First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
|
||||
try
|
||||
{
|
||||
$oQuery = $this->oModelReflection->GetQuery($sQuery);
|
||||
$sClass = $oQuery->GetClass();
|
||||
$this->sClass = $oQuery->GetClass();
|
||||
$sClassAlias = $oQuery->GetClassAlias();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// Invalid query, let the user edit the dashlet/dashboard anyhow
|
||||
$sClass = '';
|
||||
$this->sClass = null;
|
||||
$sClassAlias = '';
|
||||
}
|
||||
|
||||
// Check groupby... it can be wrong at this stage
|
||||
if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches))
|
||||
{
|
||||
@@ -530,9 +885,31 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$this->sGroupByAttCode = $sGroupBy;
|
||||
$this->sFunction = null;
|
||||
}
|
||||
if (($sClass != '') && $this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
|
||||
|
||||
if (empty($this->aProperties['order_direction']))
|
||||
{
|
||||
$sAttLabel = $this->oModelReflection->GetLabel($sClass, $this->sGroupByAttCode);
|
||||
$aAttributeTypes = $this->oModelReflection->ListAttributes($this->sClass);
|
||||
if (isset($aAttributeTypes[$this->sGroupByAttCode]))
|
||||
{
|
||||
$sAttributeType = $aAttributeTypes[$this->sGroupByAttCode];
|
||||
if (is_subclass_of($sAttributeType, 'AttributeDateTime') || $sAttributeType == 'AttributeDateTime')
|
||||
{
|
||||
$this->sOrderDirection = 'asc';
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->sOrderDirection = 'desc';
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->sOrderDirection = $this->aProperties['order_direction'];
|
||||
}
|
||||
|
||||
if ((!is_null($this->sClass)) && $this->oModelReflection->IsValidAttCode($this->sClass, $this->sGroupByAttCode))
|
||||
{
|
||||
$sAttLabel = $this->oModelReflection->GetLabel($this->sClass, $this->sGroupByAttCode);
|
||||
if (!is_null($this->sFunction))
|
||||
{
|
||||
switch($this->sFunction)
|
||||
@@ -578,7 +955,6 @@ abstract class DashletGroupBy extends Dashlet
|
||||
{
|
||||
$sTitle = $this->aProperties['title'];
|
||||
$sQuery = $this->aProperties['query'];
|
||||
$sGroupBy = $this->aProperties['group_by'];
|
||||
$sStyle = $this->aProperties['style'];
|
||||
|
||||
// First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
|
||||
@@ -586,7 +962,6 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
|
||||
$sClass = $oFilter->GetClass();
|
||||
$sClassAlias = $oFilter->GetClassAlias();
|
||||
|
||||
if (!$this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
|
||||
{
|
||||
@@ -603,6 +978,11 @@ abstract class DashletGroupBy extends Dashlet
|
||||
'chart_title' => $sTitle,
|
||||
'group_by' => $this->sGroupByExpr,
|
||||
'group_by_label' => $this->sGroupByLabel,
|
||||
'aggregation_function' => $this->sAggregationFunction,
|
||||
'aggregation_attribute' => $this->sAggregationAttribute,
|
||||
'limit' => $this->sLimit,
|
||||
'order_direction' => $this->sOrderDirection,
|
||||
'order_by' => $this->sOrderBy,
|
||||
);
|
||||
$sHtmlTitle = ''; // done in the itop block
|
||||
break;
|
||||
@@ -614,6 +994,11 @@ abstract class DashletGroupBy extends Dashlet
|
||||
'chart_title' => $sTitle,
|
||||
'group_by' => $this->sGroupByExpr,
|
||||
'group_by_label' => $this->sGroupByLabel,
|
||||
'aggregation_function' => $this->sAggregationFunction,
|
||||
'aggregation_attribute' => $this->sAggregationAttribute,
|
||||
'limit' => $this->sLimit,
|
||||
'order_direction' => $this->sOrderDirection,
|
||||
'order_by' => $this->sOrderBy,
|
||||
);
|
||||
$sHtmlTitle = ''; // done in the itop block
|
||||
break;
|
||||
@@ -625,6 +1010,11 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$aExtraParams = array(
|
||||
'group_by' => $this->sGroupByExpr,
|
||||
'group_by_label' => $this->sGroupByLabel,
|
||||
'aggregation_function' => $this->sAggregationFunction,
|
||||
'aggregation_attribute' => $this->sAggregationAttribute,
|
||||
'limit' => $this->sLimit,
|
||||
'order_direction' => $this->sOrderDirection,
|
||||
'order_by' => $this->sOrderBy,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -644,7 +1034,6 @@ abstract class DashletGroupBy extends Dashlet
|
||||
protected function MakeSimulatedData()
|
||||
{
|
||||
$sQuery = $this->aProperties['query'];
|
||||
$sGroupBy = $this->aProperties['group_by'];
|
||||
|
||||
$oQuery = $this->oModelReflection->GetQuery($sQuery);
|
||||
$sClass = $oQuery->GetClass();
|
||||
@@ -721,36 +1110,10 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
|
||||
protected function GetGroupByOptions($sOql)
|
||||
{
|
||||
$oQuery = $this->oModelReflection->GetQuery($sOql);
|
||||
$sClass = $oQuery->GetClass();
|
||||
$aGroupBy = array();
|
||||
foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
|
||||
{
|
||||
if ($sAttType == 'AttributeLinkedSet') continue;
|
||||
if (is_subclass_of($sAttType, 'AttributeLinkedSet')) continue;
|
||||
if ($sAttType == 'AttributeFriendlyName') continue;
|
||||
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;
|
||||
|
||||
if (is_subclass_of($sAttType, 'AttributeDateTime') || $sAttType == 'AttributeDateTime')
|
||||
{
|
||||
$aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel);
|
||||
$aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month', $sLabel);
|
||||
$aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek', $sLabel);
|
||||
$aGroupBy[$sAttCode.':day_of_month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth', $sLabel);
|
||||
}
|
||||
}
|
||||
asort($aGroupBy);
|
||||
return $aGroupBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DesignerForm $oForm
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public function GetPropertiesFields(DesignerForm $oForm)
|
||||
{
|
||||
$oField = new DesignerTextField('title', Dict::S('UI:DashletGroupBy:Prop-Title'), $this->aProperties['title']);
|
||||
@@ -773,6 +1136,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
{
|
||||
$oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), $this->aProperties['group_by']);
|
||||
$oField->SetReadOnly();
|
||||
$aGroupBy = array();
|
||||
}
|
||||
$oForm->AddField($oField);
|
||||
|
||||
@@ -786,6 +1150,135 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$oField->SetMandatory();
|
||||
$oField->SetAllowedValues($aStyles);
|
||||
$oForm->AddField($oField);
|
||||
|
||||
$aFunctionAttributes = $this->GetNumericAttributes($this->aProperties['query']);
|
||||
$aFunctions = $this->GetAllowedFunctions($aFunctionAttributes);
|
||||
$oSelectorField = new DesignerFormSelectorField('aggregation_function', Dict::S('UI:DashletGroupBy:Prop-Function'), $this->aProperties['aggregation_function']);
|
||||
$oForm->AddField($oSelectorField);
|
||||
$oSelectorField->SetMandatory();
|
||||
// Count sub-menu
|
||||
$oSubForm = new DesignerForm();
|
||||
$oSelectorField->AddSubForm($oSubForm, Dict::S('UI:GroupBy:count'), 'count');
|
||||
foreach($aFunctions as $sFct => $sLabel)
|
||||
{
|
||||
$oSubForm = new DesignerForm();
|
||||
$oField = new DesignerComboField('aggregation_attribute', Dict::S('UI:DashletGroupBy:Prop-FunctionAttribute'), $this->aProperties['aggregation_attribute']);
|
||||
$oField->SetMandatory();
|
||||
$oField->SetAllowedValues($aFunctionAttributes);
|
||||
$oSubForm->AddField($oField);
|
||||
$oSelectorField->AddSubForm($oSubForm, $sLabel, $sFct);
|
||||
}
|
||||
|
||||
$aOrderField = array();
|
||||
|
||||
if (isset($this->aProperties['group_by']) && isset($aGroupBy[$this->aProperties['group_by']]))
|
||||
{
|
||||
$aOrderField['attribute'] = $aGroupBy[$this->aProperties['group_by']];
|
||||
}
|
||||
|
||||
if ($this->aProperties['aggregation_function'] == 'count')
|
||||
{
|
||||
$aOrderField['function'] = Dict::S('UI:GroupBy:count');
|
||||
}
|
||||
else
|
||||
{
|
||||
$aOrderField['function'] = $aFunctions[$this->aProperties['aggregation_function']];
|
||||
}
|
||||
$oSelectorField = new DesignerFormSelectorField('order_by', Dict::S('UI:DashletGroupBy:Prop-OrderField'), $this->aProperties['order_by']);
|
||||
$oForm->AddField($oSelectorField);
|
||||
$oSelectorField->SetMandatory();
|
||||
foreach($aOrderField as $sField => $sLabel)
|
||||
{
|
||||
$oSubForm = new DesignerForm();
|
||||
if ($sField == 'function')
|
||||
{
|
||||
$oField = new DesignerIntegerField('limit', Dict::S('UI:DashletGroupBy:Prop-Limit'), $this->aProperties['limit']);
|
||||
$oSubForm->AddField($oField);
|
||||
}
|
||||
$oSelectorField->AddSubForm($oSubForm, $sLabel, $sField);
|
||||
}
|
||||
|
||||
$aOrderDirections = array(
|
||||
'asc' => Dict::S('UI:DashletGroupBy:Order:asc'),
|
||||
'desc' => Dict::S('UI:DashletGroupBy:Order:desc'),
|
||||
);
|
||||
$sOrderDirection = empty($this->aProperties['order_direction']) ? $this->sOrderDirection : $this->aProperties['order_direction'];
|
||||
$oField = new DesignerComboField('order_direction', Dict::S('UI:DashletGroupBy:Prop-OrderDirection'), $sOrderDirection);
|
||||
$oField->SetMandatory();
|
||||
$oField->SetAllowedValues($aOrderDirections);
|
||||
$oForm->AddField($oField);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
protected function GetOrderBy()
|
||||
{
|
||||
if (is_null($this->sClass))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
return array(
|
||||
$this->aProperties['group_by'] => $this->oModelReflection->GetLabel($this->sClass, $this->aProperties['group_by']),
|
||||
'_itop_'.$this->aProperties['aggregation_function'].'_' => Dict::S('UI:GroupBy:'.$this->aProperties['aggregation_function']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
protected function GetAllowedFunctions($aFunctionAttributes)
|
||||
{
|
||||
$aFunctions = array();
|
||||
|
||||
if (!empty($aFunctionAttributes) || is_null($this->sClass))
|
||||
{
|
||||
$aFunctions['sum'] = Dict::S('UI:GroupBy:sum');
|
||||
$aFunctions['avg'] = Dict::S('UI:GroupBy:avg');
|
||||
$aFunctions['min'] = Dict::S('UI:GroupBy:min');
|
||||
$aFunctions['max'] = Dict::S('UI:GroupBy:max');
|
||||
}
|
||||
|
||||
return $aFunctions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function GetNumericAttributes($sOql)
|
||||
{
|
||||
$aFunctionAttributes = array();
|
||||
try
|
||||
{
|
||||
$oQuery = $this->oModelReflection->GetQuery($sOql);
|
||||
$sClass = $oQuery->GetClass();
|
||||
if (is_null($sClass))
|
||||
{
|
||||
return $aFunctionAttributes;
|
||||
}
|
||||
foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
|
||||
{
|
||||
switch ($sAttType)
|
||||
{
|
||||
case 'AttributeDecimal':
|
||||
case 'AttributeDuration':
|
||||
case 'AttributeInteger':
|
||||
case 'AttributePercentage':
|
||||
case 'AttributeSubItem': // TODO: Known limitation: no unit displayed (values in sec)
|
||||
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
|
||||
$aFunctionAttributes[$sAttCode] = $sLabel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Ignore bad OQL
|
||||
}
|
||||
|
||||
return $aFunctionAttributes;
|
||||
}
|
||||
|
||||
public function Update($aValues, $aUpdatedFields)
|
||||
@@ -837,6 +1330,15 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$oDashlet->bRedrawNeeded = true;
|
||||
$oDashlet->bFormRedrawNeeded = true;
|
||||
}
|
||||
if (in_array('aggregation_attribute', $aUpdatedFields) || in_array('order_direction', $aUpdatedFields) || in_array('order_by', $aUpdatedFields) || in_array('limit', $aUpdatedFields))
|
||||
{
|
||||
$oDashlet->bRedrawNeeded = true;
|
||||
}
|
||||
if (in_array('group_by', $aUpdatedFields) || in_array('aggregation_function', $aUpdatedFields))
|
||||
{
|
||||
$oDashlet->bRedrawNeeded = true;
|
||||
$oDashlet->bFormRedrawNeeded = true;
|
||||
}
|
||||
return $oDashlet;
|
||||
}
|
||||
|
||||
@@ -984,7 +1486,6 @@ class DashletGroupByBars extends DashletGroupBy
|
||||
$sJSNames = json_encode($aNames);
|
||||
|
||||
$sJson = json_encode($aDisplayValues);
|
||||
$sJSCount = json_encode(Dict::S('UI:GroupBy:Count'));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
window.setTimeout(function() {
|
||||
@@ -1056,7 +1557,6 @@ class DashletGroupByTable extends DashletGroupBy
|
||||
|
||||
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
|
||||
{
|
||||
$sTitle = $this->aProperties['title'];
|
||||
|
||||
$aDisplayValues = $this->MakeSimulatedData();
|
||||
$iTotal = 0;
|
||||
@@ -1116,7 +1616,7 @@ class DashletHeaderStatic extends Dashlet
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$oPage->add('<div class="main_header">');
|
||||
|
||||
$oPage->add('<img src="'.$sIconPath.'">');
|
||||
$oPage->add('<img src="'.utils::HtmlEntities($sIconPath).'">');
|
||||
$oPage->add('<h1>'.$this->oModelReflection->DictString($sTitle).'</h1>');
|
||||
|
||||
$oPage->add('</div>');
|
||||
@@ -1248,7 +1748,7 @@ class DashletHeaderDynamic extends Dashlet
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$oPage->add('<div class="main_header">');
|
||||
|
||||
$oPage->add('<img src="'.$sIconPath.'">');
|
||||
$oPage->add('<img src="'.utils::HtmlEntities($sIconPath).'">');
|
||||
|
||||
$oFilter = DBObjectSearch::FromOQL($sQuery);
|
||||
$oBlock = new DisplayBlock($oFilter, 'summary');
|
||||
@@ -1266,7 +1766,6 @@ class DashletHeaderDynamic extends Dashlet
|
||||
$sSubtitle = $this->aProperties['subtitle'];
|
||||
$sQuery = $this->aProperties['query'];
|
||||
$sGroupBy = $this->aProperties['group_by'];
|
||||
$aValues = $this->aProperties['values'];
|
||||
|
||||
$oQuery = $this->oModelReflection->GetQuery($sQuery);
|
||||
$sClass = $oQuery->GetClass();
|
||||
@@ -1277,20 +1776,12 @@ class DashletHeaderDynamic extends Dashlet
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$oPage->add('<div class="main_header">');
|
||||
|
||||
$oPage->add('<img src="'.$sIconPath.'">');
|
||||
$oPage->add('<img src="'.utils::HtmlEntities($sIconPath).'">');
|
||||
|
||||
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
|
||||
|
||||
$iTotal = 0;
|
||||
$aValues = $this->GetValues();
|
||||
if (count($aValues) > 0)
|
||||
{
|
||||
// Stats grouped by <group_by>
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple stats
|
||||
}
|
||||
|
||||
$oPage->add('<div class="display_block" id="'.$sBlockId.'">');
|
||||
$oPage->add('<div class="summary-details">');
|
||||
@@ -1315,7 +1806,6 @@ class DashletHeaderDynamic extends Dashlet
|
||||
|
||||
$sTitle = $this->oModelReflection->DictString($sTitle);
|
||||
$sSubtitle = $this->oModelReflection->DictFormat($sSubtitle, $iTotal);
|
||||
// $sSubtitle = "original: $sSubtitle, S:".$this->oModelReflection->DictString($sSubtitle).", Format: '".$this->oModelReflection->DictFormat($sSubtitle, $iTotal)."'";
|
||||
|
||||
$oPage->add('<h1>'.$sTitle.'</h1>');
|
||||
$oPage->add('<a class="summary">'.$sSubtitle.'</a>');
|
||||
@@ -1345,16 +1835,7 @@ class DashletHeaderDynamic extends Dashlet
|
||||
// Group by field: build the list of possible values (attribute codes + ...)
|
||||
$oQuery = $this->oModelReflection->GetQuery($this->aProperties['query']);
|
||||
$sClass = $oQuery->GetClass();
|
||||
$aGroupBy = array();
|
||||
foreach($this->oModelReflection->ListAttributes($sClass, 'AttributeEnum,AttributeFinalClass') as $sAttCode => $sAttType)
|
||||
{
|
||||
if (is_subclass_of($sAttType, 'AttributeFinalClass') || ($sAttType == 'AttributeFinalClass'))
|
||||
{
|
||||
if (!$this->oModelReflection->HasChildrenClasses($sClass)) continue;
|
||||
}
|
||||
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
|
||||
$aGroupBy[$sAttCode] = $sLabel;
|
||||
}
|
||||
$aGroupBy = $this->GetGroupByOptions($this->aProperties['query']);
|
||||
$oField = new DesignerComboField('group_by', Dict::S('UI:DashletHeaderDynamic:Prop-GroupBy'), $this->aProperties['group_by']);
|
||||
$oField->SetMandatory();
|
||||
$oField->SetAllowedValues($aGroupBy);
|
||||
@@ -1473,7 +1954,7 @@ class DashletBadge extends Dashlet
|
||||
$aExtraParams = array(
|
||||
'context_filter' => 1,
|
||||
);
|
||||
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
|
||||
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
|
||||
$oBlock->Display($oPage, $sBlockId, $aExtraParams);
|
||||
|
||||
$oPage->add('</div>');
|
||||
@@ -1490,7 +1971,7 @@ class DashletBadge extends Dashlet
|
||||
|
||||
$oPage->add('<div id="block_fake_'.$this->sId.'" class="display_block">');
|
||||
$oPage->add('<p>');
|
||||
$oPage->add(' <a class="actions"><img src="'.$sIconUrl.'" style="vertical-align:middle;float;left;margin-right:10px;border:0;">'.$sClassLabel.': 947</a>');
|
||||
$oPage->add(' <a class="actions"><img src="'.utils::HtmlEntities($sIconUrl).'" style="vertical-align:middle;float;left;margin-right:10px;border:0;">'.$sClassLabel.': 947</a>');
|
||||
$oPage->add('</p>');
|
||||
$oPage->add('<p>');
|
||||
$oPage->add(' <a>'.Dict::Format('UI:ClickToCreateNew', $sClassLabel).'</a>');
|
||||
@@ -1522,7 +2003,6 @@ class DashletBadge extends Dashlet
|
||||
foreach($aClasses as $sClass => $sLabel)
|
||||
{
|
||||
$sIconUrl = $this->oModelReflection->GetClassIcon($sClass, false);
|
||||
$sIconFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), APPROOT, $sIconUrl);
|
||||
if ($sIconUrl == '')
|
||||
{
|
||||
// The icon does not exist, let's use a transparent one of the same size.
|
||||
@@ -1547,4 +2027,3 @@ class DashletBadge extends Dashlet
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
|
||||
<portals>
|
||||
<portal id="legacy_portal" _delta="define">
|
||||
<url>portal/index.php</url>
|
||||
@@ -19,4 +19,9 @@
|
||||
</deny>
|
||||
</portal>
|
||||
</portals>
|
||||
<menus>
|
||||
<menu id="AdminTools" xsi:type="MenuGroup" _delta="define">
|
||||
<rank>80</rank>
|
||||
</menu>
|
||||
</menus>
|
||||
</itop_design>
|
||||
|
||||
@@ -174,6 +174,7 @@ class DataTable
|
||||
}
|
||||
$sJSOptions = json_encode($aOptions);
|
||||
$oPage->add_ready_script("$('#datatable_{$this->iListId}').datatable($sJSOptions);");
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -297,7 +298,7 @@ EOF;
|
||||
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>';
|
||||
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png?t='.utils::GetCacheBusterTimestamp().'"><ul>';
|
||||
|
||||
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
|
||||
$aActions = array(
|
||||
@@ -430,7 +431,7 @@ EOF;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRow['form::select'] = "<input type=\"checkBox\" $sDisabled class=\"selectList{$this->iListId}\" name=\"selectObject[]\" value=\"".$aObjects[$sAlias]->GetKey()."\"></input>";
|
||||
$aRow['form::select'] = "<input type=\"checkbox\" $sDisabled class=\"selectList{$this->iListId}\" name=\"selectObject[]\" value=\"".$aObjects[$sAlias]->GetKey()."\"></input>";
|
||||
}
|
||||
}
|
||||
foreach($aColumns[$sAlias] as $sAttCode => $aData)
|
||||
@@ -564,40 +565,13 @@ EOF;
|
||||
<<<EOF
|
||||
var oTable = $('#{$this->iListId} table.listResults');
|
||||
oTable.tableHover();
|
||||
oTable.tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$this->iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectModeJS', displayKey: $sDisplayKey, columns: $sJSColumns, class_aliases: $sJSClassAliases $sCssCount});
|
||||
oTable.tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$this->iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectModeJS', displayKey: $sDisplayKey, table_id: '{$this->iListId}', columns: $sJSColumns, class_aliases: $sJSClassAliases $sCssCount});
|
||||
EOF
|
||||
);
|
||||
if ($sFakeSortList != '')
|
||||
{
|
||||
$oPage->add_ready_script("oTable.trigger(\"fakesorton\", [$sFakeSortList]);");
|
||||
}
|
||||
//if ($iNbPages == 1)
|
||||
if (false)
|
||||
{
|
||||
if (isset($aExtraParams['cssCount']))
|
||||
{
|
||||
$sCssCount = $aExtraParams['cssCount'];
|
||||
if ($sSelectMode == 'single')
|
||||
{
|
||||
$sSelectSelector = ":radio[name^=selectObj]";
|
||||
}
|
||||
else if ($sSelectMode == 'multiple')
|
||||
{
|
||||
$sSelectSelector = ":checkbox[name^=selectObj]";
|
||||
}
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#{$this->iListId} table.listResults $sSelectSelector').change(function() {
|
||||
var c = $('{$sCssCount}');
|
||||
var v = $('#{$this->iListId} table.listResults $sSelectSelector:checked').length;
|
||||
c.val(v);
|
||||
$('#{$this->iListId} .selectedCount').text(v);
|
||||
c.trigger('change');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -606,7 +580,7 @@ EOF
|
||||
$iPageSize = ($iDefaultPageSize < 1) ? 1 : $iDefaultPageSize;
|
||||
$iPageIndex = 1 + floor($iStart / $iPageSize);
|
||||
$sHtml = $this->GetPager($oPage, $iPageSize, $iDefaultPageSize, $iPageIndex);
|
||||
$oPage->add_ready_script("$('#pager{$this->iListId}').html('".str_replace("\n", ' ', addslashes($sHtml))."');");
|
||||
$oPage->add_ready_script("$('#pager{$this->iListId}').html('".json_encode($sHtml)."');");
|
||||
if ($iDefaultPageSize < 1)
|
||||
{
|
||||
$oPage->add_ready_script("$('#pager{$this->iListId}').parent().hide()");
|
||||
|
||||
@@ -43,6 +43,7 @@ require_once(APPROOT.'/application/utils.inc.php');
|
||||
class DisplayBlock
|
||||
{
|
||||
const TAG_BLOCK = 'itopblock';
|
||||
/** @var \DBSearch */
|
||||
protected $m_oFilter;
|
||||
protected $m_aConditions; // Conditions added to the filter -> avoid duplicate conditions
|
||||
protected $m_sStyle;
|
||||
@@ -74,10 +75,17 @@ class DisplayBlock
|
||||
{
|
||||
return $this->m_oFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a DisplayBlock object from a DBObjectSet already in memory
|
||||
* @param $oSet DBObjectSet
|
||||
*
|
||||
* @param DBObjectSet $oSet
|
||||
* @param string $sStyle
|
||||
* @param array $aParams
|
||||
*
|
||||
* @return DisplayBlock The DisplayBlock object, or null if the creation failed
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function FromObjectSet(DBObjectSet $oSet, $sStyle, $aParams = array())
|
||||
{
|
||||
@@ -103,8 +111,12 @@ class DisplayBlock
|
||||
|
||||
/**
|
||||
* Constructs a DisplayBlock object from an XML template
|
||||
*
|
||||
* @param $sTemplate string The XML template
|
||||
*
|
||||
* @return DisplayBlock The DisplayBlock object, or null if the template is invalid
|
||||
* @throws \ApplicationException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public static function FromTemplate($sTemplate)
|
||||
{
|
||||
@@ -114,7 +126,6 @@ class DisplayBlock
|
||||
$aParams = array();
|
||||
|
||||
if (($iStartPos === false) || ($iEndPos === false)) return null; // invalid template
|
||||
$sITopBlock = substr($sTemplate,$iStartPos, $iEndPos-$iStartPos+strlen('</'.self::TAG_BLOCK.'>'));
|
||||
$sITopData = substr($sTemplate, 1+$iEndTag, $iEndPos - $iEndTag - 1);
|
||||
$sITopTag = substr($sTemplate, $iStartPos + strlen('<'.self::TAG_BLOCK), $iEndTag - $iStartPos - strlen('<'.self::TAG_BLOCK));
|
||||
|
||||
@@ -135,10 +146,6 @@ class DisplayBlock
|
||||
{
|
||||
$sBlockClass = $aMatches[1];
|
||||
}
|
||||
if (preg_match('/ objectclass="(.*)"/U',$sITopTag, $aMatches))
|
||||
{
|
||||
$sObjectClass = $aMatches[1];
|
||||
}
|
||||
if (preg_match('/ encoding="(.*)"/U',$sITopTag, $aMatches))
|
||||
{
|
||||
$sEncoding = strtolower($aMatches[1]);
|
||||
@@ -187,6 +194,7 @@ class DisplayBlock
|
||||
}
|
||||
|
||||
}
|
||||
$oFilter = null;
|
||||
switch($sEncoding)
|
||||
{
|
||||
case 'text/serialize':
|
||||
@@ -212,47 +220,20 @@ class DisplayBlock
|
||||
$aExtraParams['currentId'] = $sId;
|
||||
$sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
|
||||
|
||||
$bAutoReload = false;
|
||||
if (isset($aExtraParams['auto_reload']))
|
||||
{
|
||||
if ($aExtraParams['auto_reload'] === true)
|
||||
{
|
||||
// Note: does not work in the switch (case true) because a positive number evaluates to true!!!
|
||||
$aExtraParams['auto_reload'] = 'standard';
|
||||
}
|
||||
switch($aExtraParams['auto_reload'])
|
||||
{
|
||||
case 'fast':
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = MetaModel::GetConfig()->GetFastReloadInterval()*1000;
|
||||
break;
|
||||
|
||||
case 'standard':
|
||||
case 'true':
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = MetaModel::GetConfig()->GetStandardReloadInterval()*1000;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (is_numeric($aExtraParams['auto_reload']) && ($aExtraParams['auto_reload'] > 0))
|
||||
{
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = max(MetaModel::GetConfig()->Get('min_reload_interval'), $aExtraParams['auto_reload'])*1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// incorrect config, ignore it
|
||||
$bAutoReload = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sFilter = $this->m_oFilter->serialize(); // Used either for asynchronous or auto_reload
|
||||
if (!$this->m_bAsynchronous)
|
||||
{
|
||||
// render now
|
||||
$sHtml .= "<div id=\"$sId\" class=\"display_block\" >\n";
|
||||
try
|
||||
{
|
||||
$sHtml .= $this->GetRenderContent($oPage, $aExtraParams, $sId);
|
||||
} catch (Exception $e)
|
||||
{
|
||||
|
||||
}
|
||||
$sHtml .= "</div>\n";
|
||||
}
|
||||
else
|
||||
@@ -265,20 +246,49 @@ class DisplayBlock
|
||||
$.post("ajax.render.php?style='.$this->m_sStyle.'",
|
||||
{ operation: "ajax", filter: "'.$sFilter.'", extra_params: "'.$sExtraParams.'" },
|
||||
function(data){
|
||||
$("#'.$sId.'").empty();
|
||||
$("#'.$sId.'").append(data);
|
||||
$("#'.$sId.'").removeClass("loading");
|
||||
$("#'.$sId.'")
|
||||
.empty()
|
||||
.append(data)
|
||||
.removeClass("loading")
|
||||
;
|
||||
}
|
||||
);
|
||||
');
|
||||
}
|
||||
if (($bAutoReload) && ($this->m_sStyle != 'search')) // Search form do NOT auto-reload
|
||||
|
||||
|
||||
if ($this->m_sStyle == 'list') // Search form need to extract result list extra data, the simplest way is to expose this configuration
|
||||
{
|
||||
$oPage->add_script('setInterval("ReloadBlock(\''.$sId.'\', \''.$this->m_sStyle.'\', \''.$sFilter.'\', \"'.$sExtraParams.'\")", '.$iReloadInterval.');');
|
||||
|
||||
$listJsonExtraParams = json_encode(json_encode($aExtraParams));
|
||||
$oPage->add_ready_script("
|
||||
$('#$sId').data('sExtraParams', ".$listJsonExtraParams.");
|
||||
// console.debug($('#$sId').data());
|
||||
// console.debug($('#$sId'));
|
||||
// console.debug('#$sId');
|
||||
");
|
||||
|
||||
|
||||
|
||||
|
||||
// $oPage->add_ready_script("console.debug($('#Menu_UserRequest_OpenRequests').data());");
|
||||
|
||||
}
|
||||
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
*
|
||||
* @throws \ApplicationException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreWarning
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
if (!isset($aExtraParams['currentId']))
|
||||
@@ -292,6 +302,18 @@ class DisplayBlock
|
||||
$oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @param $sId
|
||||
* @return string
|
||||
* @throws ApplicationException
|
||||
* @throws CoreException
|
||||
* @throws CoreWarning
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws MySQLException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
@@ -304,7 +326,8 @@ class DisplayBlock
|
||||
{
|
||||
$aQueryParams = $aExtraParams['query_params'];
|
||||
}
|
||||
if ($this->m_sStyle != 'links')
|
||||
// In case of search, the context filtering is done by the search itself
|
||||
if (($this->m_sStyle != 'links') && ($this->m_sStyle != 'search'))
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sClass = $this->m_oFilter->GetClass();
|
||||
@@ -406,37 +429,8 @@ class DisplayBlock
|
||||
case 'count':
|
||||
if (isset($aExtraParams['group_by']))
|
||||
{
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
{
|
||||
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
|
||||
$sGroupByLabel = $aExtraParams['group_by_label'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Backward compatibility: group_by is simply a field id
|
||||
$sAlias = $this->m_oFilter->GetClassAlias();
|
||||
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
|
||||
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
|
||||
}
|
||||
$this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql);
|
||||
|
||||
// Security filtering
|
||||
$aFields = $oGroupByExp->ListRequiredFields();
|
||||
foreach($aFields as $sFieldAlias)
|
||||
{
|
||||
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
|
||||
{
|
||||
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
|
||||
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
|
||||
if ($oAttDef instanceof AttributeOneWayPassword)
|
||||
{
|
||||
throw new Exception('Grouping on password fields is not supported.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aGroupBy = array();
|
||||
$aGroupBy['grouped_by_1'] = $oGroupByExp;
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true);
|
||||
$aRes = CMDBSource::QueryToArray($sSql);
|
||||
|
||||
$aGroupBy = array();
|
||||
@@ -449,7 +443,7 @@ class DisplayBlock
|
||||
$aValues[$iRow] = $sValue;
|
||||
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
|
||||
$aLabels[$iRow] = $sHtmlValue;
|
||||
$aGroupBy[$iRow] = (int) $aRow['_itop_count_'];
|
||||
$aGroupBy[$iRow] = (int) $aRow[$sFctVar];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
}
|
||||
|
||||
@@ -470,7 +464,7 @@ class DisplayBlock
|
||||
}
|
||||
$aAttribs =array(
|
||||
'group' => array('label' => $sGroupByLabel, 'description' => ''),
|
||||
'value' => array('label'=> Dict::S('UI:GroupBy:Count'), 'description' => Dict::S('UI:GroupBy:Count+'))
|
||||
'value' => array('label'=> Dict::S('UI:GroupBy:'.$sAggregationFunction), 'description' => Dict::Format('UI:GroupBy:'.$sAggregationFunction.'+', $sAggregationAttr))
|
||||
);
|
||||
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
|
||||
$sHtml .= $oPage->GetP(Dict::Format($sFormat, $iTotalCount));
|
||||
@@ -594,14 +588,14 @@ class DisplayBlock
|
||||
// Check the classes that can be read (i.e authorized) by this user...
|
||||
foreach($aClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) != UR_ALLOWED_NO)
|
||||
{
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
}
|
||||
if (count($aAuthorizedClasses) > 0)
|
||||
{
|
||||
if($this->m_oSet->Count() > 0)
|
||||
if($this->m_oSet->CountWithLimit(1) > 0)
|
||||
{
|
||||
$sHtml .= cmdbAbstractObject::GetDisplayExtendedSet($oPage, $this->m_oSet, $aExtraParams);
|
||||
}
|
||||
@@ -620,7 +614,7 @@ class DisplayBlock
|
||||
else
|
||||
{
|
||||
// The list is made of only 1 class of objects, actions on the list are possible
|
||||
if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
|
||||
if ( ($this->m_oSet->CountWithLimit(1)> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
|
||||
{
|
||||
$sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams);
|
||||
}
|
||||
@@ -655,13 +649,32 @@ class DisplayBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($aExtraParams['update_history']) && true == $aExtraParams['update_history'])
|
||||
{
|
||||
$sSearchFilter = $this->m_oSet->GetFilter()->serialize();
|
||||
// Limit the size of the URL (N°1585 - request uri too long)
|
||||
if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH)
|
||||
{
|
||||
$seventAttachedData = json_encode(array(
|
||||
'filter' => $sSearchFilter,
|
||||
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
|
||||
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
|
||||
'breadcrumb_max_count' => utils::GetConfig()->Get('breadcrumb.max_count'),
|
||||
'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(),
|
||||
'breadcrumb_icon' => utils::GetAbsoluteUrlAppRoot().'images/breadcrumb-search.png'
|
||||
));
|
||||
|
||||
$oPage->add_ready_script("$('body').trigger('update_history.itop', [$seventAttachedData])");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'links':
|
||||
//$bDashboardMode = isset($aExtraParams['dashboard']) ? ($aExtraParams['dashboard'] == 'true') : false;
|
||||
//$bSelectMode = isset($aExtraParams['select']) ? ($aExtraParams['select'] == 'true') : false;
|
||||
if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
|
||||
if ( ($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
|
||||
{
|
||||
//$sLinkage = isset($aExtraParams['linkage']) ? $aExtraParams['linkage'] : '';
|
||||
$sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams);
|
||||
@@ -677,8 +690,6 @@ class DisplayBlock
|
||||
{
|
||||
if ((UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sParams = $oAppContext->GetForLink();
|
||||
$sDefaults = '';
|
||||
if (isset($this->m_aParams['default']))
|
||||
{
|
||||
@@ -706,7 +717,6 @@ class DisplayBlock
|
||||
$bContextFilter = isset($aExtraParams['context_filter']) ? isset($aExtraParams['context_filter']) != 0 : false;
|
||||
if ($bContextFilter)
|
||||
{
|
||||
$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass()));
|
||||
foreach($oAppContext->GetNames() as $sFilterCode)
|
||||
{
|
||||
$sContextParamValue = $oAppContext->GetCurrentValue($sFilterCode, null);
|
||||
@@ -750,7 +760,6 @@ class DisplayBlock
|
||||
$bContextFilter = isset($aExtraParams['context_filter']) ? isset($aExtraParams['context_filter']) != 0 : false;
|
||||
if ($bContextFilter)
|
||||
{
|
||||
$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass()));
|
||||
foreach($oAppContext->GetNames() as $sFilterCode)
|
||||
{
|
||||
$sContextParamValue = $oAppContext->GetCurrentValue($sFilterCode, null);
|
||||
@@ -774,21 +783,47 @@ class DisplayBlock
|
||||
{
|
||||
$aStates = explode(',', $sStatesList);
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
|
||||
|
||||
// Generate one count + group by query [#1330]
|
||||
$sClassAlias = $this->m_oFilter->GetClassAlias();
|
||||
$oGroupByExpr = Expression::FromOQL($sClassAlias.'.'.$sStateAttrCode);
|
||||
$aGroupBy = array('group1' => $oGroupByExpr);
|
||||
$oGroupBySearch = $this->m_oFilter->DeepClone();
|
||||
if (isset($this->m_bShowObsoleteData))
|
||||
{
|
||||
$oGroupBySearch->SetShowObsoleteData($this->m_bShowObsoleteData);
|
||||
}
|
||||
$sCountGroupByQuery = $oGroupBySearch->MakeGroupByQuery(array(), $aGroupBy, false);
|
||||
$aCountGroupByResults = CMDBSource::QueryToArray($sCountGroupByQuery);
|
||||
$aCountsQueryResults = array();
|
||||
foreach ($aCountGroupByResults as $aCountGroupBySingleResult)
|
||||
{
|
||||
$aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1];
|
||||
}
|
||||
|
||||
foreach($aStates as $sStateValue)
|
||||
{
|
||||
$oFilter = $this->m_oFilter->DeepClone();
|
||||
$oFilter->AddCondition($sStateAttrCode, $sStateValue, '=');
|
||||
$oSet = new DBObjectSet($oFilter);
|
||||
$oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
|
||||
$aCounts[$sStateValue] = $oSet->Count();
|
||||
$aStateLabels[$sStateValue] = htmlentities($oAttDef->GetValueLabel($sStateValue), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$aCounts[$sStateValue] = (array_key_exists($sStateValue, $aCountsQueryResults))
|
||||
? $aCountsQueryResults[$sStateValue]
|
||||
: 0;
|
||||
|
||||
if ($aCounts[$sStateValue] == 0)
|
||||
{
|
||||
$aCounts[$sStateValue] = '-';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($oFilter->serialize());
|
||||
$oSingleGroupByValueFilter = $this->m_oFilter->DeepClone();
|
||||
$oSingleGroupByValueFilter->AddCondition($sStateAttrCode, $sStateValue, '=');
|
||||
if (isset($this->m_bShowObsoleteData))
|
||||
{
|
||||
$oSingleGroupByValueFilter->SetShowObsoleteData($this->m_bShowObsoleteData);
|
||||
}
|
||||
$sHyperlink = utils::GetAbsoluteUrlAppRoot()
|
||||
.'pages/UI.php?operation=search&'.$oAppContext->GetForLink()
|
||||
.'&filter='.urlencode($oSingleGroupByValueFilter->serialize());
|
||||
$aCounts[$sStateValue] = "<a href=\"$sHyperlink\">{$aCounts[$sStateValue]}</a>";
|
||||
}
|
||||
}
|
||||
@@ -829,36 +864,6 @@ class DisplayBlock
|
||||
}
|
||||
$sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php';
|
||||
|
||||
/*
|
||||
$sCSVData = cmdbAbstractObject::GetSetAsCSV($this->m_oSet, array('fields_advanced' => $bAdvancedMode));
|
||||
$sCharset = MetaModel::GetConfig()->Get('csv_file_default_charset');
|
||||
if ($sCharset == 'UTF-8')
|
||||
{
|
||||
$bLostChars = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sConverted = @iconv('UTF-8', $sCharset, $sCSVData);
|
||||
$sRestored = @iconv($sCharset, 'UTF-8', $sConverted);
|
||||
$bLostChars = ($sRestored != $sCSVData);
|
||||
}
|
||||
|
||||
if ($bLostChars)
|
||||
{
|
||||
$sCharsetNotice = " <span id=\"csv_charset_issue\">";
|
||||
$sCharsetNotice .= '<img src="../images/error.png" style="vertical-align:middle"/>';
|
||||
$sCharsetNotice .= "</span>";
|
||||
|
||||
$sTip = "<p>".htmlentities(Dict::S('UI:CSVExport:LostChars'), ENT_QUOTES, 'UTF-8')."</p>";
|
||||
$sTip .= "<p>".htmlentities(Dict::Format('UI:CSVExport:LostChars+', $sCharset), ENT_QUOTES, 'UTF-8')."</p>";
|
||||
$oPage->add_ready_script("$('#csv_charset_issue').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
}
|
||||
else
|
||||
{
|
||||
$sCharsetNotice = '';
|
||||
}
|
||||
|
||||
*/
|
||||
$sCharsetNotice = false;
|
||||
$sHtml .= "<div>";
|
||||
$sHtml .= '<table style="width:100%" class="transparent">';
|
||||
@@ -895,21 +900,10 @@ class DisplayBlock
|
||||
case 'search':
|
||||
if (!$oPage->IsPrintableVersion())
|
||||
{
|
||||
$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed';
|
||||
$sHtml .= "<div id=\"ds_$sId\" class=\"$sStyle\">\n";
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#dh_$sId").click( function() {
|
||||
$("#ds_$sId").slideToggle('normal', function() { $("#ds_$sId").parent().resize(); FixSearchFormsDisposition(); $("#dh_$sId").trigger('toggle_complete'); } );
|
||||
$("#dh_$sId").toggleClass('open');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
$sHtml .= "<div id=\"ds_$sId\" class=\"search_box\">\n";
|
||||
$aExtraParams['currentId'] = $sId;
|
||||
$sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams);
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "<div class=\"HRDrawer\"></div>\n";
|
||||
$sHtml .= "<div id=\"dh_$sId\" class=\"DrawerHandle\">".Dict::S('UI:SearchToggle')."</div>\n";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -925,18 +919,21 @@ EOF
|
||||
$sFilter = $this->m_oFilter->serialize();
|
||||
$oContext = new ApplicationContext();
|
||||
$sContextParam = $oContext->GetForLink();
|
||||
$sAggregationFunction = isset($aExtraParams['aggregation_function']) ? $aExtraParams['aggregation_function'] : '';
|
||||
$sAggregationAttr = isset($aExtraParams['aggregation_attribute']) ? $aExtraParams['aggregation_attribute'] : '';
|
||||
$sLimit = isset($aExtraParams['limit']) ? $aExtraParams['limit'] : '';
|
||||
$sOrderBy = isset($aExtraParams['order_by']) ? $aExtraParams['order_by'] : '';
|
||||
$sOrderDirection = isset($aExtraParams['order_direction']) ? $aExtraParams['order_direction'] : '';
|
||||
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
{
|
||||
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[group_by_label]={$aExtraParams['group_by_label']}¶ms[chart_type]=$sChartType¶ms[currentId]=$sId{$iChartCounter}&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
|
||||
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[group_by_label]={$aExtraParams['group_by_label']}¶ms[chart_type]=$sChartType¶ms[currentId]=$sId{$iChartCounter}¶ms[order_direction]=$sOrderDirection¶ms[order_by]=$sOrderBy¶ms[limit]=$sLimit¶ms[aggregation_function]=$sAggregationFunction¶ms[aggregation_attribute]=$sAggregationAttr&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[chart_type]=$sChartType¶ms[currentId]=$sId{$iChartCounter}&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
|
||||
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[chart_type]=$sChartType¶ms[currentId]=$sId{$iChartCounter}¶ms[order_direction]=$sOrderDirection¶ms[order_by]=$sOrderBy¶ms[limit]=$sLimit¶ms[aggregation_function]=$sAggregationFunction¶ms[aggregation_attribute]=$sAggregationAttr&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
|
||||
}
|
||||
|
||||
$sType = ($sChartType == 'pie') ? 'pie' : 'bar';
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$.post($sUrl, {}, function(data) {
|
||||
@@ -953,22 +950,7 @@ EOF
|
||||
|
||||
if (isset($aExtraParams['group_by']))
|
||||
{
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
{
|
||||
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
|
||||
$sGroupByLabel = $aExtraParams['group_by_label'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Backward compatibility: group_by is simply a field id
|
||||
$sAlias = $this->m_oFilter->GetClassAlias();
|
||||
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
|
||||
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
|
||||
}
|
||||
|
||||
$aGroupBy = array();
|
||||
$aGroupBy['grouped_by_1'] = $oGroupByExp;
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true);
|
||||
$this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql);
|
||||
$aRes = CMDBSource::QueryToArray($sSql);
|
||||
$oContext = new ApplicationContext();
|
||||
$sContextParam = $oContext->GetForLink();
|
||||
@@ -981,9 +963,9 @@ EOF
|
||||
{
|
||||
$sValue = $aRow['grouped_by_1'];
|
||||
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
|
||||
$aGroupBy[(int)$iRow] = (int) $aRow['_itop_count_'];
|
||||
$aGroupBy[(int)$iRow] = (int) $aRow[$sFctVar];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
$aValues[] = array('label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label_html' => $sHtmlValue, 'value' => (int) $aRow['_itop_count_']);
|
||||
$aValues[] = array('label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label_html' => $sHtmlValue, 'value' => (int) $aRow[$sFctVar]);
|
||||
|
||||
// Build the search for this subset
|
||||
$oSubsetSearch = $this->m_oFilter->DeepClone();
|
||||
@@ -1005,7 +987,6 @@ EOF
|
||||
$sJSNames = json_encode($aNames);
|
||||
|
||||
$sJson = json_encode($aValues);
|
||||
$sJSCount = json_encode(Dict::S('UI:GroupBy:Count'));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
var chart = c3.generate({
|
||||
@@ -1081,6 +1062,7 @@ var chart = c3.generate({
|
||||
var aURLs = $sJSURLs;
|
||||
window.location.href= aURLs[d.index];
|
||||
},
|
||||
order: null,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
@@ -1102,12 +1084,71 @@ EOF
|
||||
// Unsupported style, do nothing.
|
||||
$sHtml .= Dict::format('UI:Error:UnsupportedStyleOfBlock', $this->m_sStyle);
|
||||
}
|
||||
|
||||
|
||||
$bAutoReload = false;
|
||||
if (isset($aExtraParams['auto_reload']))
|
||||
{
|
||||
if ($aExtraParams['auto_reload'] === true)
|
||||
{
|
||||
// Note: does not work in the switch (case true) because a positive number evaluates to true!!!
|
||||
$aExtraParams['auto_reload'] = 'standard';
|
||||
}
|
||||
switch($aExtraParams['auto_reload'])
|
||||
{
|
||||
case 'fast':
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = MetaModel::GetConfig()->GetFastReloadInterval()*1000;
|
||||
break;
|
||||
|
||||
case 'standard':
|
||||
case 'true':
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = MetaModel::GetConfig()->GetStandardReloadInterval()*1000;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (is_numeric($aExtraParams['auto_reload']) && ($aExtraParams['auto_reload'] > 0))
|
||||
{
|
||||
$bAutoReload = true;
|
||||
$iReloadInterval = max(MetaModel::GetConfig()->Get('min_reload_interval'), $aExtraParams['auto_reload'])*1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// incorrect config, ignore it
|
||||
$bAutoReload = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (($bAutoReload) && ($this->m_sStyle != 'search')) // Search form do NOT auto-reload
|
||||
{
|
||||
$sFilter = $this->m_oFilter->serialize(); // Used either for asynchronous or auto_reload
|
||||
$sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
|
||||
|
||||
$oPage->add_script('if (typeof window.oAutoReloadBlock == "undefined") {
|
||||
window.oAutoReloadBlock = {};
|
||||
}
|
||||
if (typeof window.oAutoReloadBlock[\''.$sId.'\'] != "undefined") {
|
||||
clearInterval(window.oAutoReloadBlock[\''.$sId.'\']);
|
||||
}
|
||||
window.oAutoReloadBlock[\''.$sId.'\'] = setInterval("ReloadBlock(\''.$sId.'\', \''.$this->m_sStyle.'\', \''.$sFilter.'\', \"'.$sExtraParams.'\")", '.$iReloadInterval.');');
|
||||
}
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a condition (restriction) to the current DBSearch on which the display block is based
|
||||
* taking into account the hierarchical keys for which the condition is based on the 'below' operator
|
||||
*
|
||||
* @param string $sFilterCode
|
||||
* @param array $condition
|
||||
* @param string $sOpCode
|
||||
* @param bool $bParseSearchString
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \CoreWarning
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function AddCondition($sFilterCode, $condition, $sOpCode = null, $bParseSearchString = false)
|
||||
{
|
||||
@@ -1164,7 +1205,7 @@ EOF
|
||||
// In all other cases, just add the condition directly
|
||||
if (!$bConditionAdded)
|
||||
{
|
||||
$this->m_oFilter->AddCondition($sFilterCode, $condition, null, $bParseSearchString); // Use the default 'loose' operator
|
||||
$this->m_oFilter->AddCondition($sFilterCode, $condition, null); // Use the default 'loose' operator
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1185,6 +1226,97 @@ EOF
|
||||
{
|
||||
return $this->m_oSet->Count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aExtraParams
|
||||
* @param $oGroupByExp
|
||||
* @param $sGroupByLabel
|
||||
* @param $aGroupBy
|
||||
* @param $sAggregationFunction
|
||||
* @param $sFctVar
|
||||
* @param $sAggregationAttr
|
||||
* @param $sSql
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function MakeGroupByQuery(&$aExtraParams, &$oGroupByExp, &$sGroupByLabel, &$aGroupBy, &$sAggregationFunction, &$sFctVar, &$sAggregationAttr, &$sSql)
|
||||
{
|
||||
$sAlias = $this->m_oFilter->GetClassAlias();
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
{
|
||||
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
|
||||
$sGroupByLabel = $aExtraParams['group_by_label'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Backward compatibility: group_by is simply a field id
|
||||
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
|
||||
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
|
||||
}
|
||||
|
||||
// Security filtering
|
||||
$aFields = $oGroupByExp->ListRequiredFields();
|
||||
foreach($aFields as $sFieldAlias)
|
||||
{
|
||||
$aMatches = array();
|
||||
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
|
||||
{
|
||||
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
|
||||
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
|
||||
if ($oAttDef instanceof AttributeOneWayPassword)
|
||||
{
|
||||
throw new Exception('Grouping on password fields is not supported.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aGroupBy = array();
|
||||
$aGroupBy['grouped_by_1'] = $oGroupByExp;
|
||||
$aQueryParams = array();
|
||||
if (isset($aExtraParams['query_params']))
|
||||
{
|
||||
$aQueryParams = $aExtraParams['query_params'];
|
||||
}
|
||||
$aFunctions = array();
|
||||
$sAggregationFunction = 'count';
|
||||
$sFctVar = '_itop_count_';
|
||||
$sAggregationAttr = '';
|
||||
if (isset($aExtraParams['aggregation_function']) && !empty($aExtraParams['aggregation_attribute']))
|
||||
{
|
||||
$sAggregationFunction = $aExtraParams['aggregation_function'];
|
||||
$sAggregationAttr = $aExtraParams['aggregation_attribute'];
|
||||
$oAttrExpr = Expression::FromOQL('`'.$sAlias.'`.`'.$sAggregationAttr.'`');
|
||||
$oFctExpr = new FunctionExpression(strtoupper($sAggregationFunction), array($oAttrExpr));
|
||||
$sFctVar = '_itop_'.$sAggregationFunction.'_';
|
||||
$aFunctions = array($sFctVar => $oFctExpr);
|
||||
}
|
||||
|
||||
if (!empty($sAggregationAttr))
|
||||
{
|
||||
$sClass = $this->m_oFilter->GetClass();
|
||||
$sAggregationAttr = MetaModel::GetLabel($sClass, $sAggregationAttr);
|
||||
}
|
||||
$iLimit = 0;
|
||||
if (isset($aExtraParams['limit']))
|
||||
{
|
||||
$iLimit = intval($aExtraParams['limit']);
|
||||
}
|
||||
$aOrderBy = array();
|
||||
if (isset($aExtraParams['order_direction']) && isset($aExtraParams['order_by']))
|
||||
{
|
||||
switch ($aExtraParams['order_by'])
|
||||
{
|
||||
case 'attribute':
|
||||
$aOrderBy = array('grouped_by_1' => ($aExtraParams['order_direction'] === 'asc'));
|
||||
break;
|
||||
case 'function':
|
||||
$aOrderBy = array($sFctVar => ($aExtraParams['order_direction'] === 'asc'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true, $aFunctions, $aOrderBy, $iLimit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1345,6 +1477,16 @@ class MenuBlock extends DisplayBlock
|
||||
* displayed, to correspond to the current hash/fragment in the page. This allows modifying
|
||||
* an object in with the same tab active by default as the tab that was active when selecting
|
||||
* the "Modify..." action.
|
||||
*
|
||||
* @param \WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @param string $sId
|
||||
*
|
||||
* @return string
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \Exception
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
|
||||
{
|
||||
@@ -1363,7 +1505,6 @@ class MenuBlock extends DisplayBlock
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$oSet = new CMDBObjectSet($this->m_oFilter);
|
||||
$sFilter = $this->m_oFilter->serialize();
|
||||
$sFilterDesc = $this->m_oFilter->ToOql(true);
|
||||
$aActions = array();
|
||||
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
|
||||
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
@@ -1482,6 +1623,7 @@ class MenuBlock extends DisplayBlock
|
||||
if ($bLocked && $bRawModifiedAllowed)
|
||||
{
|
||||
// Add a special menu to kill the lock, but only to allowed users who can also modify this object
|
||||
/** @var array $aAllowedProfiles */
|
||||
$aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles');
|
||||
$bCanKill = false;
|
||||
|
||||
@@ -1550,7 +1692,6 @@ class MenuBlock extends DisplayBlock
|
||||
$sTargetAttr = $aExtraParams['target_attr'];
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
|
||||
if ($bIsModifyAllowed) { $aActions['UI:Menu:Add'] = array ('label' => Dict::S('UI:Menu:Add'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true{$sContext}") + $aActionParams; }
|
||||
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:Manage'] = array ('label' => Dict::S('UI:Menu:Manage'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id{$sContext}") + $aActionParams; }
|
||||
//if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#") + $aActionParams; }
|
||||
@@ -1567,7 +1708,7 @@ class MenuBlock extends DisplayBlock
|
||||
// Do not perform time consuming computations if there are too may objects in the list
|
||||
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
|
||||
|
||||
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->Count() < $iLimit)))
|
||||
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->CountWithLimit($iLimit + 1) < $iLimit)))
|
||||
{
|
||||
// Life cycle actions may be available... if all objects are in the same state
|
||||
//
|
||||
@@ -1641,7 +1782,8 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$param = null;
|
||||
$iMenuId = null;
|
||||
// New extensions based on iPopupMenuItem interface
|
||||
switch($this->m_sStyle)
|
||||
{
|
||||
@@ -1674,10 +1816,6 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aShortcutActions = array();
|
||||
}
|
||||
|
||||
if (!$oPage->IsPrintableVersion())
|
||||
{
|
||||
@@ -1691,10 +1829,25 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
|
||||
$sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions);
|
||||
|
||||
if ($this->m_sStyle == 'details')
|
||||
{
|
||||
$sSearchAction = "window.location=\"{$sRootUrl}pages/UI.php?operation=search_form&do_search=0&class=$sClass{$sContext}\"";
|
||||
$sHtml .= "<div class=\"actions_button icon_actions_button\" title=\"".htmlentities(Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass)), ENT_QUOTES, 'UTF-8')."\"><span class=\"search-button fa fa-search\" onclick='$sSearchAction'></span></div>";
|
||||
}
|
||||
|
||||
|
||||
if (empty($sRefreshAction) && $this->m_sStyle == 'list')
|
||||
{
|
||||
//for the detail page this var is defined way beyond this line
|
||||
$sRefreshAction = "window.location.reload();";
|
||||
}
|
||||
if (!$oPage->IsPrintableVersion() && ($sRefreshAction!=''))
|
||||
{
|
||||
$sHtml .= "<div class=\"actions_button\" title=\"".htmlentities(Dict::S('UI:Button:Refresh'), ENT_QUOTES, 'UTF-8')."\"><span class=\"refresh-button\" onclick=\"$sRefreshAction\"></span></div>";
|
||||
$sHtml .= "<div class=\"actions_button icon_actions_button\" title=\"".htmlentities(Dict::S('UI:Button:Refresh'), ENT_QUOTES, 'UTF-8')."\"><span class=\"refresh-button fa fa-refresh\" onclick=\"$sRefreshAction\"></span></div>";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static $bPopupScript = false;
|
||||
@@ -1709,7 +1862,7 @@ class MenuBlock extends DisplayBlock
|
||||
|
||||
/**
|
||||
* Appends a menu separator to the current list of actions
|
||||
* @param Hash $aActions The current actions list
|
||||
* @param array $aActions The current actions list
|
||||
* @return void
|
||||
*/
|
||||
protected function AddMenuSeparator(&$aActions)
|
||||
|
||||
@@ -462,7 +462,7 @@ class ExcelExporter
|
||||
$this->aAuthorizedClasses = array();
|
||||
foreach($aClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO)
|
||||
{
|
||||
$this->aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
|
||||
@@ -360,6 +360,7 @@ EOF
|
||||
<<<EOF
|
||||
$('#$sDialogId').dialog({
|
||||
height: 'auto',
|
||||
maxHeight: $(window).height() - 8,
|
||||
width: $iDialogWidth,
|
||||
modal: true,
|
||||
autoOpen: $sAutoOpen,
|
||||
@@ -1524,7 +1525,6 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
|
||||
public function AddSubForm($oSubForm, $sLabel, $sValue)
|
||||
{
|
||||
$idx = count($this->aSubForms);
|
||||
$this->aSubForms[] = array('form' => $oSubForm, 'label' => $sLabel, 'value' => $sValue);
|
||||
if ($sValue == $this->defaultRealValue)
|
||||
{
|
||||
@@ -1554,8 +1554,6 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
$aSelected = array();
|
||||
$aHiddenValues = array();
|
||||
$sDisplayValue = '';
|
||||
$sHiddenValue = '';
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
@@ -1571,8 +1569,6 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
{
|
||||
@@ -1588,7 +1584,6 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
{
|
||||
$sHtml .= '</td><td class="prop_icon prop_apply"><span title="Apply" class="ui-icon ui-icon-circle-check"/></td><td class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
|
||||
}
|
||||
|
||||
foreach($this->aSubForms as $sKey => $aFormData)
|
||||
{
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
@@ -1614,25 +1609,7 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
$oSubForm->SetHierarchyPath($sPath);
|
||||
|
||||
$oSubForm->SetDisplayed($sKey == $this->defaultValue);
|
||||
$sState = ($sKey == $this->defaultValue) ? 'visible' : 'hidden';
|
||||
//$sHtml .= "</tbody><tbody data-selector=\"$sSelector\" data-path=\"$sPath\" data-state=\"$sState\" $sStyle>";
|
||||
$sHtml .= $oSubForm->RenderAsPropertySheet($oP, true);
|
||||
|
||||
$sState = $this->oForm->IsDisplayed() ? 'visible' : 'hidden';
|
||||
$sParentStyle = '';
|
||||
if ($oParent = $this->oForm->GetParentForm())
|
||||
{
|
||||
$sParentStyle = ($oParent->IsDisplayed()) ? '' : 'style="display:none"';
|
||||
$sParentSelector = $oParent->GetHierarchyParent();
|
||||
$sParentPath = $oParent->GetHierarchyPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sParentSelector = '';
|
||||
$sParentPath = '';
|
||||
}
|
||||
|
||||
//$sHtml .= "</tbody><tbody data-selector=\"$sParentSelector\" data-path=\"$sParentPath\" data-state=\"$sState\" $sParentStyle>";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1680,7 +1657,6 @@ EOF
|
||||
if ($selectedValue == $aFormData['value'])
|
||||
{
|
||||
$this->defaultValue =$iKey;
|
||||
$aDefaultValues = $this->oForm->GetDefaultValues();
|
||||
$oSubForm = $aFormData['form'];
|
||||
$oSubForm->SetDefaultValues($aAllDefaultValues);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
// Copyright (C) 2010-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,13 +20,14 @@
|
||||
/**
|
||||
* Class iTopWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT . "/application/nicewebpage.class.inc.php");
|
||||
require_once(APPROOT . "/application/applicationcontext.class.inc.php");
|
||||
require_once(APPROOT . "/application/user.preferences.class.inc.php");
|
||||
|
||||
/**
|
||||
* Web page with some associated CSS and scripts (jquery) for a fancier display
|
||||
*/
|
||||
@@ -35,7 +36,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
private $m_sMenu;
|
||||
// private $m_currentOrganization;
|
||||
private $m_aMessages;
|
||||
private $m_sInitScript;
|
||||
private $m_aInitScript = array();
|
||||
protected $m_oTabs;
|
||||
protected $bBreadCrumbEnabled;
|
||||
protected $sBreadCrumbEntryId;
|
||||
@@ -45,6 +46,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
protected $sBreadCrumbEntryIcon;
|
||||
protected $oCtx;
|
||||
|
||||
protected $bHasCollapsibleSection = false;
|
||||
|
||||
public function __construct($sTitle, $bPrintable = false)
|
||||
{
|
||||
parent::__construct($sTitle, $bPrintable);
|
||||
@@ -53,13 +56,10 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
|
||||
ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
|
||||
|
||||
if ((count($_POST) == 0) || (array_key_exists('loginop', $_POST)))
|
||||
{
|
||||
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
|
||||
{
|
||||
} else {
|
||||
$this->bBreadCrumbEnabled = false;
|
||||
}
|
||||
|
||||
@@ -109,11 +109,15 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$sSearchAny = addslashes(Dict::S('UI:SearchValue:Any'));
|
||||
$sSearchNbSelected = addslashes(Dict::S('UI:SearchValue:NbSelected'));
|
||||
$this->add_dict_entry('UI:FillAllMandatoryFields');
|
||||
$this->add_dict_entry('UI:Button:Cancel');
|
||||
$this->add_dict_entry('UI:Button:Done');
|
||||
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
$this->add_dict_entries('Error:');
|
||||
$this->add_dict_entries('UI:Button:');
|
||||
$this->add_dict_entries('UI:Search:');
|
||||
$this->add_dict_entry('UI:UndefinedObject');
|
||||
$this->add_dict_entries('Enum:Undefined');
|
||||
|
||||
|
||||
if (!$this->IsPrintableVersion()) {
|
||||
$this->PrepareLayout();
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
@@ -149,18 +153,12 @@ EOF
|
||||
protected function IsMenuPaneVisible()
|
||||
{
|
||||
$bLeftPaneOpen = true;
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
if (MetaModel::GetConfig()->Get('demo_mode')) {
|
||||
// Leave the pane opened
|
||||
}
|
||||
else
|
||||
{
|
||||
if (utils::ReadParam('force_menu_pane', null) === 0)
|
||||
{
|
||||
} else {
|
||||
if (utils::ReadParam('force_menu_pane', null) === 0) {
|
||||
$bLeftPaneOpen = false;
|
||||
}
|
||||
elseif (appUserPreferences::GetPref('menu_pane', 'open') == 'closed')
|
||||
{
|
||||
} elseif (appUserPreferences::GetPref('menu_pane', 'open') == 'closed') {
|
||||
$bLeftPaneOpen = false;
|
||||
}
|
||||
}
|
||||
@@ -169,16 +167,16 @@ EOF
|
||||
|
||||
protected function PrepareLayout()
|
||||
{
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
if (MetaModel::GetConfig()->Get('demo_mode')) {
|
||||
// No pin button
|
||||
$sConfigureWestPane = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sConfigureWestPane =
|
||||
<<<EOF
|
||||
if (typeof myLayout !== "undefined")
|
||||
{
|
||||
myLayout.addPinBtn( "#tPinMenu", "west" );
|
||||
}
|
||||
EOF;
|
||||
}
|
||||
$sInitClosed = $this->IsMenuPaneVisible() ? '' : 'initClosed: true,';
|
||||
@@ -217,8 +215,7 @@ EOF;
|
||||
$aPickerOptions['controlType'] = 'select';
|
||||
$aPickerOptions['closeText'] = Dict::S('UI:Button:Ok');
|
||||
$sJSDateTimePickerOptions = json_encode($aPickerOptions);
|
||||
if ($sJSLangShort != '"en"')
|
||||
{
|
||||
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,
|
||||
@@ -264,14 +261,16 @@ EOF;
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->m_sInitScript =
|
||||
$this->add_init_script(
|
||||
<<< EOF
|
||||
try
|
||||
{
|
||||
var myLayout; // a var is required because this page utilizes: myLayout.allowOverflow() method
|
||||
|
||||
// Layout
|
||||
paneSize = GetUserPreference('menu_size', 300)
|
||||
paneSize = GetUserPreference('menu_size', 300);
|
||||
if ($('body').length > 0)
|
||||
{
|
||||
myLayout = $('body').layout({
|
||||
west : {
|
||||
$sInitClosed minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "click", hideTogglerOnSlide: true, enableCursorHotkey: false,
|
||||
@@ -317,13 +316,15 @@ EOF
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
window.clearTimeout(iPaneVisWatchDog);
|
||||
//myLayout.open( "west" );
|
||||
$('.ui-layout-resizer-west .ui-layout-toggler').css({background: 'transparent'});
|
||||
$sConfigureWestPane
|
||||
|
||||
if ($('#left-pane').length > 0)
|
||||
{
|
||||
$('#left-pane').layout({ resizable: false, spacing_open: 0, south: { size: 94 }, enableCursorHotkey: false });
|
||||
|
||||
}
|
||||
// Tabs, using JQuery BBQ to store the history
|
||||
// The "tab widgets" to handle.
|
||||
var tabs = $('div[id^=tabbedContent]');
|
||||
@@ -331,31 +332,6 @@ EOF
|
||||
// This selector will be reused when selecting actual tab widget A elements.
|
||||
var tab_a_selector = 'ul.ui-tabs-nav a';
|
||||
|
||||
// This helper will be used to resize tab width
|
||||
var resizeTab = function(oElem){
|
||||
var iTableWidth = (oElem.children('table:first').length > 0) ? oElem.children('table:first').outerWidth() : 0;
|
||||
var oLayoutContentElem = oElem.closest('.ui-layout-content');
|
||||
var bEditMode = (oLayoutContentElem.find('.wizContainer').length > 0);
|
||||
var oContainerElem = (bEditMode) ? oLayoutContentElem.find('.wizContainer:first') : oLayoutContentElem.find('.ui-tabs:first');
|
||||
|
||||
// Resizing wizard container
|
||||
oContainerElem.css('min-width',
|
||||
parseInt(iTableWidth) +
|
||||
parseInt(oElem.css('margin-left'))*2 +
|
||||
parseInt(oElem.css('padding-left'))*2 +
|
||||
parseInt(tabs.css('margin-left'))*2 +
|
||||
parseInt(tabs.css('padding-left'))*2
|
||||
)
|
||||
|
||||
// Resizing header according to content container
|
||||
var iLayoutContentWidth = parseInt(oContainerElem.width());
|
||||
if(bEditMode)
|
||||
{
|
||||
iLayoutContentWidth += parseInt(oContainerElem.css('margin-left'))*2 + parseInt(oContainerElem.css('padding-left'))*2
|
||||
}
|
||||
oLayoutContentElem.find('.page_header').css('min-width', iLayoutContentWidth);
|
||||
};
|
||||
|
||||
// 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
|
||||
@@ -379,9 +355,6 @@ EOF
|
||||
event: 'change', 'show': function(event, ui) {
|
||||
$('.resizable', ui.panel).resizable(); // Make resizable everything that claims to be resizable !
|
||||
},
|
||||
create: function( event, ui ) {
|
||||
resizeTab(ui.panel);
|
||||
},
|
||||
beforeLoad: function( event, ui ) {
|
||||
if ( ui.tab.data('loaded') && (ui.tab.attr('data-cache') == 'true')) {
|
||||
event.preventDefault();
|
||||
@@ -391,13 +364,8 @@ EOF
|
||||
ui.jqXHR.success(function() {
|
||||
ui.tab.data( "loaded", true );
|
||||
});
|
||||
},
|
||||
activate: function( event, ui ) {
|
||||
resizeTab(ui.newPanel);
|
||||
}
|
||||
});
|
||||
|
||||
$('.resizable').filter(':visible').resizable();
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
@@ -405,7 +373,7 @@ EOF
|
||||
alert(err);
|
||||
}
|
||||
EOF
|
||||
;
|
||||
);
|
||||
|
||||
$this->add_ready_script(
|
||||
<<< EOF
|
||||
@@ -654,6 +622,7 @@ EOF
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sId Identifies the item, to search after it in the current breadcrumb
|
||||
* @param string $sLabel Label of the breadcrumb item
|
||||
@@ -694,8 +663,7 @@ EOF
|
||||
// List of visible Organizations
|
||||
$iCount = 0;
|
||||
$oSet = null;
|
||||
if (MetaModel::IsValidClass('Organization'))
|
||||
{
|
||||
if (MetaModel::IsValidClass('Organization')) {
|
||||
// Display the list of *favorite* organizations... but keeping in mind what is the real number of organizations
|
||||
$aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null);
|
||||
$oSearchFilter = new DBObjectSearch('Organization');
|
||||
@@ -706,14 +674,12 @@ EOF
|
||||
// Now get the list of Orgs to be displayed in the menu
|
||||
$oSearchFilter = DBObjectSearch::FromOQL(ApplicationMenu::GetFavoriteSiloQuery());
|
||||
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
|
||||
if (!empty($aFavoriteOrgs))
|
||||
{
|
||||
if (!empty($aFavoriteOrgs)) {
|
||||
$oSearchFilter->AddCondition('id', $aFavoriteOrgs, 'IN');
|
||||
}
|
||||
$oSet = new CMDBObjectSet($oSearchFilter); // List of favorite orgs
|
||||
}
|
||||
switch($iCount)
|
||||
{
|
||||
switch ($iCount) {
|
||||
case 0:
|
||||
// No such dimension/silo => nothing to select
|
||||
$sHtml = '<div id="SiloSelection"><!-- nothing to select --></div>';
|
||||
@@ -772,26 +738,22 @@ EOF
|
||||
|
||||
// Call the extensions to add content to the page, so that they can also add styles or scripts
|
||||
$sBannerExtraHtml = '';
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance) {
|
||||
$sBannerExtraHtml .= $oExtensionInstance->GetBannerHtml($this);
|
||||
}
|
||||
|
||||
$sNorthPane = '';
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance) {
|
||||
$sNorthPane .= $oExtensionInstance->GetNorthPaneHtml($this);
|
||||
}
|
||||
|
||||
if (UserRights::IsAdministrator() && ExecutionKPI::IsEnabled())
|
||||
{
|
||||
if (UserRights::IsAdministrator() && ExecutionKPI::IsEnabled()) {
|
||||
$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>';
|
||||
$sSouthPane = '';
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance) {
|
||||
$sSouthPane .= $oExtensionInstance->GetSouthPaneHtml($this);
|
||||
}
|
||||
|
||||
@@ -818,20 +780,15 @@ EOF
|
||||
$('table.listResults').each( function() { FixTableSorter($(this)); } );
|
||||
|
||||
$('.multiselect').multiselect($sJSMultiselectOptions);
|
||||
|
||||
FixSearchFormsDisposition();
|
||||
EOF
|
||||
);
|
||||
|
||||
$iBreadCrumbMaxCount = utils::GetConfig()->Get('breadcrumb.max_count');
|
||||
if ($iBreadCrumbMaxCount > 1)
|
||||
{
|
||||
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))
|
||||
{
|
||||
$siTopInstanceId = json_encode($oConfig->GetItopInstanceid());
|
||||
if ($this->bBreadCrumbEnabled) {
|
||||
if (is_null($this->sBreadCrumbEntryId)) {
|
||||
$this->sBreadCrumbEntryId = $this->s_title;
|
||||
$this->sBreadCrumbEntryLabel = $this->s_title;
|
||||
$this->sBreadCrumbEntryDescription = $this->s_title;
|
||||
@@ -839,9 +796,7 @@ EOF
|
||||
$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
|
||||
{
|
||||
} else {
|
||||
$sNewEntry = 'null';
|
||||
}
|
||||
|
||||
@@ -852,10 +807,10 @@ EOF
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
foreach($this->a_headers as $s_header)
|
||||
{
|
||||
$this->outputCollapsibleSectionInit();
|
||||
|
||||
if ($this->GetOutputFormat() == 'html') {
|
||||
foreach ($this->a_headers as $s_header) {
|
||||
header($s_header);
|
||||
}
|
||||
}
|
||||
@@ -870,53 +825,46 @@ EOF
|
||||
$sHtml .= $this->get_base_tag();
|
||||
// Stylesheets MUST be loaded before any scripts otherwise
|
||||
// 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;
|
||||
foreach ($this->a_linked_stylesheets as $a_stylesheet) {
|
||||
if (strpos($a_stylesheet['link'], '?') === false) {
|
||||
$s_stylesheet = $a_stylesheet['link'] . "?t=".utils::GetCacheBusterTimestamp();
|
||||
} else {
|
||||
$s_stylesheet = $a_stylesheet['link'] . "&t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
$s_stylesheet = $a_stylesheet['link']."&itopversion=".ITOP_VERSION;
|
||||
}
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
if ($a_stylesheet['condition'] != "") {
|
||||
$sHtml .= "<!--[if {$a_stylesheet['condition']}]>\n";
|
||||
}
|
||||
$sHtml .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$s_stylesheet}\" />\n";
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
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?itopversion=".ITOP_VERSION."\" />\n";
|
||||
$sHtml .= "<link rel=\"stylesheet\" media=\"print\" type=\"text/css\" href=\"../css/print.css?t=".utils::GetCacheBusterTimestamp() . "\" />\n";
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
if ($this->GetOutputFormat() == 'html') {
|
||||
$sHtml .= $this->output_dict_entries(true); // before any script so that they can benefit from the translations
|
||||
foreach($this->a_linked_scripts as $s_script)
|
||||
{
|
||||
foreach ($this->a_linked_scripts as $s_script) {
|
||||
// Make sure that the URL to the script contains the application's version number
|
||||
// so that the new script do NOT get reloaded from the cache when the application is upgraded
|
||||
if (strpos($s_script, '?') === false)
|
||||
{
|
||||
$s_script .= "?itopversion=".ITOP_VERSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
$s_script .= "&itopversion=".ITOP_VERSION;
|
||||
if (strpos($s_script, '?') === false) {
|
||||
$s_script .= "?t=".utils::GetCacheBusterTimestamp();
|
||||
} else {
|
||||
$s_script .= "&t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
$sHtml .= "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
|
||||
}
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
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())
|
||||
{
|
||||
$sInitScripts = "";
|
||||
if (count($this->m_aInitScript) > 0) {
|
||||
foreach ($this->m_aInitScript as $m_sInitScript) {
|
||||
$sInitScripts .= "$m_sInitScript\n";
|
||||
}
|
||||
}
|
||||
$this->add_script("\$(document).ready(function() {\n{$sInitScripts};\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">';
|
||||
@@ -944,42 +892,35 @@ $('legend').css('cursor', 'pointer').click(function(){
|
||||
EOF
|
||||
);
|
||||
}
|
||||
if (count($this->m_aReadyScripts)>0)
|
||||
{
|
||||
if (count($this->m_aReadyScripts) > 0) {
|
||||
$this->add_script("\nonDelayedReady = function() {\n" . implode("\n", $this->m_aReadyScripts) . "\n}\n");
|
||||
}
|
||||
if (count($this->a_scripts)>0)
|
||||
{
|
||||
if (count($this->a_scripts) > 0) {
|
||||
$sHtml .= "<script type=\"text/javascript\">\n";
|
||||
foreach($this->a_scripts as $s_script)
|
||||
{
|
||||
foreach ($this->a_scripts as $s_script) {
|
||||
$sHtml .= "$s_script\n";
|
||||
}
|
||||
$sHtml .= "</script>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (count($this->a_styles)>0)
|
||||
{
|
||||
if (count($this->a_styles) > 0) {
|
||||
$sHtml .= "<style>\n";
|
||||
foreach($this->a_styles as $s_style)
|
||||
{
|
||||
foreach ($this->a_styles as $s_style) {
|
||||
$sHtml .= "$s_style\n";
|
||||
}
|
||||
$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?itopversion=".ITOP_VERSION."\" />\n";
|
||||
$sHtml .= "<link rel=\"shortcut icon\" href=\"" . utils::GetAbsoluteUrlAppRoot() . "images/favicon.ico?t=".utils::GetCacheBusterTimestamp(). "\" />\n";
|
||||
|
||||
$sHtml .= "</head>\n";
|
||||
$sBodyClass = "";
|
||||
if ($this->IsPrintableVersion())
|
||||
{
|
||||
if ($this->IsPrintableVersion()) {
|
||||
$sBodyClass = 'printable-version';
|
||||
}
|
||||
$sHtml .= "<body class=\"$sBodyClass\">\n";
|
||||
if ($this->IsPrintableVersion())
|
||||
{
|
||||
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>";
|
||||
@@ -990,13 +931,10 @@ EOF
|
||||
}
|
||||
|
||||
// Render the revision number
|
||||
if (ITOP_REVISION == '$WCREV$')
|
||||
{
|
||||
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_APPLICATION, ITOP_VERSION);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// This is a build made from SVN, let display the full information
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
|
||||
}
|
||||
@@ -1004,29 +942,20 @@ EOF
|
||||
// Render the text of the global search form
|
||||
$sText = htmlentities(utils::ReadParam('text', '', false, 'raw_data'), ENT_QUOTES, 'UTF-8');
|
||||
$sOnClick = " onclick=\"if ($('#global-search-input').val() != '') { $('#global-search form').submit(); } \"";
|
||||
if (empty($sText))
|
||||
{
|
||||
$sText = Dict::S("UI:YourSearch");
|
||||
}
|
||||
$sDefaultPlaceHolder = Dict::S("UI:YourSearch");
|
||||
|
||||
if ($this->IsPrintableVersion())
|
||||
{
|
||||
if ($this->IsPrintableVersion()) {
|
||||
$sHtml .= ' <!-- Beginning of page content -->';
|
||||
$sHtml .= self::FilterXSS($this->s_content);
|
||||
$sHtml .= ' <!-- End of page content -->';
|
||||
}
|
||||
elseif ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
} elseif ($this->GetOutputFormat() == 'html') {
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
$sUserName = UserRights::GetUser();
|
||||
$sIsAdmin = UserRights::IsAdministrator() ? '(Administrator)' : '';
|
||||
if (UserRights::IsAdministrator())
|
||||
{
|
||||
if (UserRights::IsAdministrator()) {
|
||||
$sLogonMessage = Dict::Format('UI:LoggedAsMessage+Admin', $sUserName);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sLogonMessage = Dict::Format('UI:LoggedAsMessage', $sUserName);
|
||||
}
|
||||
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li><img src=\"../images/on-off-menu.png\"><ul>";
|
||||
@@ -1034,13 +963,10 @@ EOF
|
||||
$aActions = array();
|
||||
|
||||
$aAllowedPortals = UserRights::GetAllowedPortals();
|
||||
if(count($aAllowedPortals) > 1)
|
||||
{
|
||||
if (count($aAllowedPortals) > 1) {
|
||||
// Adding portals
|
||||
foreach($aAllowedPortals as $aAllowedPortal)
|
||||
{
|
||||
if($aAllowedPortal['id'] !== 'backoffice')
|
||||
{
|
||||
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();
|
||||
}
|
||||
@@ -1053,26 +979,21 @@ EOF
|
||||
$oPrefs = new URLPopupMenuItem('UI:Preferences', Dict::S('UI:Preferences'), utils::GetAbsoluteUrlAppRoot() . "pages/preferences.php?" . $oAppContext->GetForLink());
|
||||
$aActions[$oPrefs->GetUID()] = $oPrefs->GetMenuItem();
|
||||
|
||||
if (utils::IsArchiveMode())
|
||||
{
|
||||
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())
|
||||
{
|
||||
} elseif (UserRights::CanBrowseArchive()) {
|
||||
$oBrowseArchive = new JSPopupMenuItem('UI:ArchiveModeOn', Dict::S('UI:ArchiveModeOn'), 'return ArchiveMode(true);');
|
||||
$aActions[$oBrowseArchive->GetUID()] = $oBrowseArchive->GetMenuItem();
|
||||
}
|
||||
if (utils::CanLogOff())
|
||||
{
|
||||
if (utils::CanLogOff()) {
|
||||
$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())
|
||||
{
|
||||
if (UserRights::CanChangePassword()) {
|
||||
$oChangePwd = new URLPopupMenuItem('UI:ChangePwdMenu', Dict::S('UI:ChangePwdMenu'), utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?loginop=change_pwd');
|
||||
$aActions[$oChangePwd->GetUID()] = $oChangePwd->GetMenuItem();
|
||||
}
|
||||
@@ -1085,20 +1006,15 @@ EOF
|
||||
|
||||
|
||||
$sRestrictions = '';
|
||||
if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE))
|
||||
{
|
||||
if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE))
|
||||
{
|
||||
if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) {
|
||||
if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) {
|
||||
$sRestrictions = Dict::S('UI:AccessRO-All');
|
||||
}
|
||||
}
|
||||
elseif (!MetaModel::DBHasAccess(ACCESS_USER_WRITE))
|
||||
{
|
||||
} elseif (!MetaModel::DBHasAccess(ACCESS_USER_WRITE)) {
|
||||
$sRestrictions = Dict::S('UI:AccessRO-Users');
|
||||
}
|
||||
|
||||
if (strlen($sRestrictions) > 0)
|
||||
{
|
||||
if (strlen($sRestrictions) > 0) {
|
||||
$sIcon =
|
||||
<<<EOF
|
||||
<span class="fa-stack fa-sm">
|
||||
@@ -1108,16 +1024,14 @@ EOF
|
||||
EOF;
|
||||
|
||||
$sAdminMessage = trim(MetaModel::GetConfig()->Get('access_message'));
|
||||
if (strlen($sAdminMessage) > 0)
|
||||
{
|
||||
if (strlen($sAdminMessage) > 0) {
|
||||
$sRestrictions .= ' ' . $sAdminMessage;
|
||||
}
|
||||
$this->AddApplicationMessage($sRestrictions, $sIcon);
|
||||
}
|
||||
|
||||
$sApplicationMessages = '';
|
||||
foreach ($this->m_aMessages as $aMessage)
|
||||
{
|
||||
foreach ($this->m_aMessages as $aMessage) {
|
||||
$sHtmlIcon = $aMessage['icon'] ? $aMessage['icon'] : '';
|
||||
$sHtmlMessage = $aMessage['message'];
|
||||
$sTitleAttr = $aMessage['tip'] ? 'title="' . htmlentities($aMessage['tip'], ENT_QUOTES, 'UTF-8') . '"' : '';
|
||||
@@ -1126,13 +1040,11 @@ EOF;
|
||||
|
||||
$sApplicationBanner = "<div class=\"app-banner ui-helper-clearfix\">$sApplicationMessages$sBannerExtraHtml</div>";
|
||||
|
||||
if (!empty($sNorthPane))
|
||||
{
|
||||
if (!empty($sNorthPane)) {
|
||||
$sNorthPane = '<div id="top-pane" class="ui-layout-north">' . $sNorthPane . '</div>';
|
||||
}
|
||||
|
||||
if (!empty($sSouthPane))
|
||||
{
|
||||
if (!empty($sSouthPane)) {
|
||||
$sSouthPane = '<div id="bottom-pane" class="ui-layout-south">' . $sSouthPane . '</div>';
|
||||
}
|
||||
|
||||
@@ -1140,10 +1052,9 @@ 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?itopversion='.ITOP_VERSION;
|
||||
if (file_exists(MODULESROOT.'branding/main-logo.png'))
|
||||
{
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/main-logo.png?itopversion='.ITOP_VERSION;
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot() . 'images/itop-logo.png?t=' . utils::GetCacheBusterTimestamp();
|
||||
if (file_exists(MODULESROOT . 'branding/main-logo.png')) {
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot() . 'branding/main-logo.png?t=' . utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
|
||||
$sHtml .= $sNorthPane;
|
||||
@@ -1154,8 +1065,7 @@ EOF;
|
||||
$sHtml .= ' <div id="top-left"></div><div id="logo"><a href="' . htmlentities($sIconUrl, ENT_QUOTES, 'UTF-8') . '"><img src="' . $sDisplayIcon . '" title="' . htmlentities($sVersionString, ENT_QUOTES, 'UTF-8') . '" style="border:0; margin-top:16px; margin-right:40px;"/></a></div>';
|
||||
$sHtml .= ' </div>';
|
||||
$sHtml .= ' <div class="header-menu">';
|
||||
if (!MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
if (!MetaModel::GetConfig()->Get('demo_mode')) {
|
||||
$sHtml .= ' <div class="icon ui-state-default ui-corner-all"><span id="tPinMenu" class="ui-icon ui-icon-pin-w">pin</span></div>';
|
||||
}
|
||||
$sHtml .= ' <div style="text-align:center;">' . self::FilterXSS($sForm) . '</div>';
|
||||
@@ -1170,7 +1080,7 @@ 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?itopversion='.ITOP_VERSION.'"/></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?t=' . utils::GetCacheBusterTimestamp() . '"/></a></div></div>';
|
||||
$sHtml .= '<!-- End of the left pane -->';
|
||||
$sHtml .= '</div>';
|
||||
|
||||
@@ -1196,8 +1106,8 @@ EOF;
|
||||
$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-global-search-cell"><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="' . $sDefaultPlaceHolder . '" value="'. $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?t=' . utils::GetCacheBusterTimestamp() . '"/></td>';
|
||||
$sHtml .= ' <td id="top-left-logoff-cell">' . self::FilterXSS($sLogOffMenu) . '</td>';
|
||||
$sHtml .= ' </tr></table></form></div>';
|
||||
$sHtml .= ' </td>';
|
||||
@@ -1205,7 +1115,7 @@ EOF;
|
||||
$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><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?t='.utils::GetCacheBusterTimestamp().'"/></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>';
|
||||
|
||||
@@ -1219,50 +1129,40 @@ EOF;
|
||||
$sHtml .= $sSouthPane;
|
||||
|
||||
// Add the captured output
|
||||
if (trim($s_captured_output) != "")
|
||||
{
|
||||
if (trim($s_captured_output) != "") {
|
||||
$sHtml .= "<div id=\"rawOutput\" title=\"Debug Output\"><div style=\"height:500px; overflow-y:auto;\">" . self::FilterXSS($s_captured_output) . "</div></div>\n";
|
||||
}
|
||||
$sHtml .= "<div id=\"at_the_end\">" . self::FilterXSS($this->s_deferred_content) . "</div>";
|
||||
$sHtml .= "<div style=\"display:none\" title=\"ex2\" id=\"ex2\">Please wait...</div>\n"; // jqModal Window
|
||||
$sHtml .= "<div style=\"display:none\" title=\"dialog\" id=\"ModalDlg\"></div>";
|
||||
$sHtml .= "<div style=\"display:none\" id=\"ajax_content\"></div>";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sHtml .= self::FilterXSS($this->s_content);
|
||||
}
|
||||
|
||||
$sHtml .= "</body>\n";
|
||||
$sHtml .= "</html>\n";
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
if ($this->GetOutputFormat() == 'html') {
|
||||
$oKPI = new ExecutionKPI();
|
||||
echo $sHtml;
|
||||
$oKPI->ComputeAndReport('Echoing (' . round(strlen($sHtml) / 1024) . ' Kb)');
|
||||
}
|
||||
else if ($this->GetOutputFormat() == 'pdf' && $this->IsOutputFormatAvailable('pdf') )
|
||||
{
|
||||
if (@is_readable(APPROOT.'lib/MPDF/mpdf.php'))
|
||||
{
|
||||
} else if ($this->GetOutputFormat() == 'pdf' && $this->IsOutputFormatAvailable('pdf')) {
|
||||
if (@is_readable(APPROOT . 'lib/MPDF/mpdf.php')) {
|
||||
require_once(APPROOT . 'lib/MPDF/mpdf.php');
|
||||
$oMPDF = new mPDF('c');
|
||||
$oMPDF->mirroMargins = false;
|
||||
if ($this->a_base['href'] != '')
|
||||
{
|
||||
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'))
|
||||
{
|
||||
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'))
|
||||
{
|
||||
if ($this->GetOutputOption('pdf', 'output_name')) {
|
||||
$sOutputName = $this->GetOutputOption('pdf', 'output_name');
|
||||
}
|
||||
$oMPDF->Output($sOutputName, 'I');
|
||||
@@ -1272,6 +1172,37 @@ EOF;
|
||||
ExecutionKPI::ReportStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds init scripts for the collapsible sections
|
||||
*/
|
||||
private function outputCollapsibleSectionInit()
|
||||
{
|
||||
if (!$this->bHasCollapsibleSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->add_script(<<<'EOD'
|
||||
function initCollapsibleSection(iSectionId, bOpenedByDefault, sSectionStateStorageKey)
|
||||
{
|
||||
var bStoredSectionState = JSON.parse(localStorage.getItem(sSectionStateStorageKey));
|
||||
var bIsSectionOpenedInitially = (bStoredSectionState == null) ? bOpenedByDefault : bStoredSectionState;
|
||||
|
||||
if (bIsSectionOpenedInitially) {
|
||||
$("#LnkCollapse_"+iSectionId).toggleClass("open");
|
||||
$("#Collapse_"+iSectionId).toggle();
|
||||
}
|
||||
|
||||
$("#LnkCollapse_"+iSectionId).click(function(e) {
|
||||
localStorage.setItem(sSectionStateStorageKey, !($("#Collapse_"+iSectionId).is(":visible")));
|
||||
$("#LnkCollapse_"+iSectionId).toggleClass("open");
|
||||
$("#Collapse_"+iSectionId).slideToggle("normal");
|
||||
e.preventDefault(); // we don't want to do anything more (see #1030 : a non wanted tab switching was triggered)
|
||||
});
|
||||
}
|
||||
EOD
|
||||
);
|
||||
}
|
||||
|
||||
public function AddTabContainer($sTabContainer, $sPrefix = '')
|
||||
{
|
||||
$this->add($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
|
||||
@@ -1302,6 +1233,7 @@ EOF;
|
||||
* @param string $sTabLabel The (localised) label of the tab
|
||||
* @param string $sUrl The URL to load (on the same server)
|
||||
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause the tab to be reloaded upon each activation.
|
||||
*
|
||||
* @since 2.0.3
|
||||
*/
|
||||
public function AddAjaxTab($sTabLabel, $sUrl, $bCache = true)
|
||||
@@ -1339,21 +1271,30 @@ EOF;
|
||||
$this->add_ready_script($this->m_oTabs->SelectTab($sTabContainer, $sTabLabel));
|
||||
}
|
||||
|
||||
public function StartCollapsibleSection($sSectionLabel, $bOpen = false)
|
||||
public function StartCollapsibleSection(
|
||||
$sSectionLabel, $bOpenedByDefault = false, $sSectionStateStorageBusinessKey = ''
|
||||
)
|
||||
{
|
||||
$this->add($this->GetStartCollapsibleSection($sSectionLabel, $bOpen));
|
||||
$this->add($this->GetStartCollapsibleSection($sSectionLabel, $bOpenedByDefault,
|
||||
$sSectionStateStorageBusinessKey));
|
||||
}
|
||||
|
||||
public function GetStartCollapsibleSection($sSectionLabel, $bOpen = false)
|
||||
private function GetStartCollapsibleSection(
|
||||
$sSectionLabel, $bOpenedByDefault = false, $sSectionStateStorageBusinessKey = ''
|
||||
)
|
||||
{
|
||||
$this->bHasCollapsibleSection = true;
|
||||
$sHtml = '';
|
||||
static $iSectionId = 0;
|
||||
$sImgStyle = $bOpen ? ' open' : '';
|
||||
$sHtml .= "<a id=\"LnkCollapse_$iSectionId\" class=\"CollapsibleLabel{$sImgStyle}\" href=\"#\">$sSectionLabel</a></br>\n";
|
||||
$sStyle = $bOpen ? '' : 'style="display:none" ';
|
||||
$sHtml .= "<div id=\"Collapse_$iSectionId\" $sStyle>";
|
||||
$this->add_ready_script("\$(\"#LnkCollapse_$iSectionId\").click(function() {\$(\"#Collapse_$iSectionId\").slideToggle('normal'); $(\"#LnkCollapse_$iSectionId\").toggleClass('open');});");
|
||||
//$this->add_ready_script("$('#LnkCollapse_$iSectionId').hide();");
|
||||
$sHtml .= '<a id="LnkCollapse_' . $iSectionId . '" class="CollapsibleLabel" href="#">' . $sSectionLabel . '</a></br>' . "\n";
|
||||
$sHtml .= '<div id="Collapse_' . $iSectionId . '" style="display:none">' . "\n";
|
||||
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$sSectionStateStorageKey = $oConfig->GetItopInstanceid() . '/' . $sSectionStateStorageBusinessKey . '/collapsible-' . $iSectionId;
|
||||
$sSectionStateStorageKey = json_encode($sSectionStateStorageKey);
|
||||
$sOpenedByDefault = ($bOpenedByDefault) ? 'true' : 'false';
|
||||
$this->add_ready_script("initCollapsibleSection($iSectionId, $sOpenedByDefault, '$sSectionStateStorageKey');");
|
||||
|
||||
$iSectionId++;
|
||||
return $sHtml;
|
||||
}
|
||||
@@ -1370,12 +1311,9 @@ EOF;
|
||||
|
||||
public function add($sHtml)
|
||||
{
|
||||
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != ''))
|
||||
{
|
||||
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != '')) {
|
||||
$this->m_oTabs->AddToCurrentTab($sHtml);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
parent::add($sHtml);
|
||||
}
|
||||
}
|
||||
@@ -1389,13 +1327,10 @@ EOF;
|
||||
$sCurrentTabContainer = $this->m_oTabs->GetCurrentTabContainer();
|
||||
$sCurrentTab = $this->m_oTabs->GetCurrentTab();
|
||||
|
||||
if (!empty($sCurrentTabContainer) && !empty($sCurrentTab))
|
||||
{
|
||||
if (!empty($sCurrentTabContainer) && !empty($sCurrentTab)) {
|
||||
$iOffset = $this->m_oTabs->GetCurrentTabLength();
|
||||
return array('tc' => $sCurrentTabContainer, 'tab' => $sCurrentTab, 'offset' => $iOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return parent::start_capture();
|
||||
}
|
||||
}
|
||||
@@ -1403,24 +1338,20 @@ EOF;
|
||||
/**
|
||||
* Returns the part of the html output that occurred since the call to start_capture
|
||||
* and removes this part from the current html output
|
||||
*
|
||||
* @param $offset mixed The value returned by start_capture
|
||||
*
|
||||
* @return string The part of the html output that was added since the call to start_capture
|
||||
*/
|
||||
public function end_capture($offset)
|
||||
{
|
||||
if (is_array($offset))
|
||||
{
|
||||
if ($this->m_oTabs->TabExists($offset['tc'], $offset['tab']))
|
||||
{
|
||||
if (is_array($offset)) {
|
||||
if ($this->m_oTabs->TabExists($offset['tc'], $offset['tab'])) {
|
||||
$sCaptured = $this->m_oTabs->TruncateTab($offset['tc'], $offset['tab'], $offset['offset']);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sCaptured = '';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sCaptured = parent::end_capture($offset);
|
||||
}
|
||||
return $sCaptured;
|
||||
@@ -1440,8 +1371,7 @@ EOF;
|
||||
*/
|
||||
public function AddApplicationMessage($sHtmlMessage, $sHtmlIcon = null, $sTip = null)
|
||||
{
|
||||
if (strlen($sHtmlMessage))
|
||||
{
|
||||
if (strlen($sHtmlMessage)) {
|
||||
$this->m_aMessages[] = array(
|
||||
'icon' => $sHtmlIcon,
|
||||
'message' => $sHtmlMessage,
|
||||
@@ -1449,4 +1379,11 @@ EOF;
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Adds a script to be executed when the DOM is ready (typical JQuery use), right before add_ready_script
|
||||
* @return void
|
||||
*/
|
||||
public function add_init_script($sScript){
|
||||
$this->m_aInitScript[] = $sScript;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ class LoginWebPage extends NiceWebPage
|
||||
const EXIT_CODE_WRONGCREDENTIALS = 3;
|
||||
const EXIT_CODE_MUSTBEADMIN = 4;
|
||||
const EXIT_CODE_PORTALUSERNOTAUTHORIZED = 5;
|
||||
const EXIT_CODE_NOTAUTHORIZED = 6;
|
||||
|
||||
protected static $sHandlerClass = __class__;
|
||||
public static function RegisterHandler($sClass)
|
||||
@@ -97,10 +98,10 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?itopversion='.ITOP_VERSION;
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
|
||||
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
|
||||
{
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?itopversion='.ITOP_VERSION;
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?t='.utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES, 'UTF-8')."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
|
||||
}
|
||||
@@ -194,7 +195,7 @@ class LoginWebPage extends NiceWebPage
|
||||
*/
|
||||
public function ForgotPwdLink()
|
||||
{
|
||||
$sUrl = '?loginop=forgot_pwd';
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?loginop=forgot_pwd';
|
||||
$sHtml = "<a href=\"$sUrl\" target=\"_blank\">".Dict::S('UI:Login:ForgotPwd')."</a>";
|
||||
return $sHtml;
|
||||
}
|
||||
@@ -229,6 +230,7 @@ class LoginWebPage extends NiceWebPage
|
||||
try
|
||||
{
|
||||
UserRights::Login($sAuthUser); // Set the user's language (if possible!)
|
||||
/** @var UserInternal $oUser */
|
||||
$oUser = UserRights::GetUserObject();
|
||||
if ($oUser == null)
|
||||
{
|
||||
@@ -253,15 +255,12 @@ class LoginWebPage extends NiceWebPage
|
||||
$sToken = substr(md5(APPROOT.uniqid()), 0, 16);
|
||||
$oUser->Set('reset_pwd_token', $sToken);
|
||||
CMDBObject::SetTrackInfo('Reset password');
|
||||
$oUser->AllowWrite(true);
|
||||
$oUser->DBUpdate();
|
||||
|
||||
$oEmail = new Email();
|
||||
$oEmail->SetRecipientTO($sTo);
|
||||
$sFrom = MetaModel::GetConfig()->Get('forgot_password_from');
|
||||
if ($sFrom == '')
|
||||
{
|
||||
$sFrom = $sTo;
|
||||
}
|
||||
$oEmail->SetRecipientFrom($sFrom);
|
||||
$oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject'));
|
||||
$sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken);
|
||||
@@ -301,6 +300,9 @@ class LoginWebPage extends NiceWebPage
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sToken = utils::ReadParam('token', '', false, 'raw_data');
|
||||
|
||||
$sAuthUserForDisplay = utils::HtmlEntities($sAuthUser);
|
||||
$sTokenForDisplay = utils::HtmlEntities($sToken);
|
||||
|
||||
UserRights::Login($sAuthUser); // Set the user's language
|
||||
$oUser = UserRights::GetUserObject();
|
||||
|
||||
@@ -309,7 +311,7 @@ class LoginWebPage extends NiceWebPage
|
||||
$this->add("<h1>".Dict::S('UI:ResetPwd-Title')."</h1>\n");
|
||||
if ($oUser == null)
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUserForDisplay)."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -321,7 +323,8 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
|
||||
$sUserNameForDisplay = utils::HtmlEntities($oUser->GetFriendlyName());
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $sUserNameForDisplay)."</p>\n");
|
||||
|
||||
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
|
||||
$this->add_script(
|
||||
@@ -344,8 +347,8 @@ EOF
|
||||
$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("<input type=\"hidden\" name=\"auth_user\" value=\"".$sAuthUserForDisplay."\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"token\" value=\"".$sTokenForDisplay."\" />\n");
|
||||
$this->add("</form>\n");
|
||||
$this->add("</div\n");
|
||||
}
|
||||
@@ -379,6 +382,7 @@ EOF
|
||||
{
|
||||
// Trash the token and change the password
|
||||
$oUser->Set('reset_pwd_token', '');
|
||||
$oUser->AllowWrite(true);
|
||||
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
|
||||
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
|
||||
@@ -707,10 +711,15 @@ EOF
|
||||
/**
|
||||
* 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
|
||||
* - if $bIsAllowedToPortalUsers is false and the user has only access to the portal, then the user is redirected to the portal
|
||||
* - if $bIsAllowedToPortalUsers is false and the user has only access to the portal, then the user is redirected
|
||||
* to the portal
|
||||
*
|
||||
* @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page
|
||||
* @param bool $bIsAllowedToPortalUsers Whether or not the current page is considered as part of the portal
|
||||
* @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...)
|
||||
*
|
||||
* @return int|mixed|string
|
||||
* @throws \Exception
|
||||
*/
|
||||
static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false, $iOnExit = self::EXIT_PROMPT)
|
||||
{
|
||||
@@ -719,10 +728,15 @@ EOF
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is already authentified, if yes, then performs some additional validations to redirect towards the desired "portal"
|
||||
* 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_...)
|
||||
*
|
||||
* @return int|mixed|string
|
||||
* @throws \Exception
|
||||
*/
|
||||
static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT)
|
||||
{
|
||||
@@ -829,8 +843,8 @@ EOF
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
UserRights::Login($sAuthUser); // Set the user's language
|
||||
$sOldPwd = utils::ReadPostedParam('old_pwd', '', false, 'raw_data');
|
||||
$sNewPwd = utils::ReadPostedParam('new_pwd', '', false, 'raw_data');
|
||||
$sOldPwd = utils::ReadPostedParam('old_pwd', '', 'raw_data');
|
||||
$sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data');
|
||||
if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd))))
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
|
||||
@@ -61,9 +61,21 @@ require_once(APPROOT."/application/user.dashboard.class.inc.php");
|
||||
|
||||
class ApplicationMenu
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
static $bAdditionalMenusLoaded = false;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
static $aRootMenus = array();
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
static $aMenusIndex = array();
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
static $sFavoriteSiloQuery = 'SELECT Organization';
|
||||
|
||||
static public function LoadAdditionalMenus()
|
||||
@@ -96,7 +108,7 @@ class ApplicationMenu
|
||||
/**
|
||||
* Set the query used to limit the list of displayed organizations in the drop-down menu
|
||||
* @param $sOQL string The OQL query returning a list of Organization objects
|
||||
* @return none
|
||||
* @return void
|
||||
*/
|
||||
static public function SetFavoriteSiloQuery($sOQL)
|
||||
{
|
||||
@@ -112,10 +124,33 @@ class ApplicationMenu
|
||||
return self::$sFavoriteSiloQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether a menu Id is enabled or not
|
||||
* @param $sMenuId
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
static public function CheckMenuIdEnabled($sMenuId)
|
||||
{
|
||||
self::LoadAdditionalMenus();
|
||||
$oMenuNode = self::GetMenuNode(self::GetMenuIndexById($sMenuId));
|
||||
if (is_null($oMenuNode) || !$oMenuNode->IsEnabled())
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessRestricted')."</h1>\n");
|
||||
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
|
||||
$oP->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to add a menu entry into the application, can be called during the definition
|
||||
* of the data model objects
|
||||
* @param MenuNode $oMenuNode
|
||||
* @param $iParentIndex
|
||||
* @param $fRank
|
||||
* @return int
|
||||
*/
|
||||
static public function InsertMenu(MenuNode $oMenuNode, $iParentIndex, $fRank)
|
||||
{
|
||||
@@ -148,6 +183,7 @@ class ApplicationMenu
|
||||
// the menu already exists, let's combine the conditions that make it visible
|
||||
self::$aMenusIndex[$index]['node']->AddCondition($oMenuNode);
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
@@ -162,6 +198,9 @@ class ApplicationMenu
|
||||
|
||||
/**
|
||||
* Entry point to display the whole menu into the web page, used by iTopWebPage
|
||||
* @param $oPage
|
||||
* @param $aExtraParams
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
static public function DisplayMenu($oPage, $aExtraParams)
|
||||
{
|
||||
@@ -172,14 +211,12 @@ class ApplicationMenu
|
||||
$iActiveMenu = self::GetMenuIndexById(self::GetActiveNodeId());
|
||||
foreach(self::$aRootMenus as $aMenu)
|
||||
{
|
||||
if (!self::CanDisplayMenu($aMenu)) { continue; }
|
||||
$oMenuNode = self::GetMenuNode($aMenu['index']);
|
||||
if (!$oMenuNode->IsEnabled()) continue; // Don't display a non-enabled menu
|
||||
$oPage->AddToMenu('<h3 id="Menu_'.$oMenuNode->GetMenuID().'">'.$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)
|
||||
{
|
||||
$oPage->AddToMenu('<ul>');
|
||||
$aChildren = self::GetChildren($aMenu['index']);
|
||||
$bActive = self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu);
|
||||
$oPage->AddToMenu('</ul>');
|
||||
if ($bActive)
|
||||
@@ -191,15 +228,48 @@ $oPage->add_ready_script(
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
$oPage->AddToMenu('</div>');
|
||||
$iAccordion++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively check if the menu and at least one of his sub-menu is enabled
|
||||
* @param array $aMenu menu entry
|
||||
* @return bool true if at least one menu is enabled
|
||||
*/
|
||||
static private function CanDisplayMenu($aMenu)
|
||||
{
|
||||
$oMenuNode = self::GetMenuNode($aMenu['index']);
|
||||
if ($oMenuNode->IsEnabled())
|
||||
{
|
||||
$aChildren = self::GetChildren($aMenu['index']);
|
||||
if (count($aChildren) > 0)
|
||||
{
|
||||
foreach($aChildren as $aSubMenu)
|
||||
{
|
||||
if (self::CanDisplayMenu($aSubMenu))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the display of the sub-menus (called recursively if necessary)
|
||||
* @param WebPage $oPage
|
||||
* @param array $aMenus
|
||||
* @param array $aExtraParams
|
||||
* @param int $iActiveMenu
|
||||
* @return true if the currently selected menu is one of the submenus
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
static protected function DisplaySubMenu($oPage, $aMenus, $aExtraParams, $iActiveMenu = -1)
|
||||
{
|
||||
@@ -217,13 +287,12 @@ EOF
|
||||
$sHyperlink = $oMenu->GetHyperlink($aExtraParams);
|
||||
if ($sHyperlink != '')
|
||||
{
|
||||
$oPage->AddToMenu('<li id="Menu_'.$oMenu->GetMenuID().'"'.$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 id="Menu_'.$oMenu->GetMenuID().'"'.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
|
||||
$oPage->AddToMenu('<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'" '.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
|
||||
}
|
||||
$aCurrentMenu = self::$aMenusIndex[$index];
|
||||
if ($iActiveMenu == $index)
|
||||
{
|
||||
$bActive = true;
|
||||
@@ -238,8 +307,12 @@ EOF
|
||||
}
|
||||
return $bActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to sort the menus based on their rank
|
||||
* @param $a
|
||||
* @param $b
|
||||
* @return int
|
||||
*/
|
||||
static public function CompareOnRank($a, $b)
|
||||
{
|
||||
@@ -257,6 +330,8 @@ EOF
|
||||
|
||||
/**
|
||||
* Helper function to retrieve the MenuNode Object based on its ID
|
||||
* @param int $index
|
||||
* @return MenuNode|null
|
||||
*/
|
||||
static public function GetMenuNode($index)
|
||||
{
|
||||
@@ -265,6 +340,8 @@ EOF
|
||||
|
||||
/**
|
||||
* Helper function to get the list of child(ren) of a menu
|
||||
* @param int $index
|
||||
* @return array
|
||||
*/
|
||||
static public function GetChildren($index)
|
||||
{
|
||||
@@ -305,6 +382,9 @@ EOF
|
||||
return $sMenuId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
static public function GetDefaultMenuId()
|
||||
{
|
||||
static $sDefaultMenuId = null;
|
||||
@@ -320,6 +400,25 @@ EOF
|
||||
}
|
||||
return $sDefaultMenuId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sMenuId
|
||||
* @return string
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,8 +449,17 @@ EOF
|
||||
*/
|
||||
abstract class MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sMenuId;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $index;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $iParentIndex;
|
||||
|
||||
/**
|
||||
@@ -388,9 +496,8 @@ abstract class MenuNode
|
||||
* @param mixed $iActionCode UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
|
||||
* @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them...
|
||||
* @param string $sEnableStimulus The user can see this menu if she/he has enough rights to apply this stimulus
|
||||
* @return MenuNode
|
||||
*/
|
||||
public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0.0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
$this->sMenuId = $sMenuId;
|
||||
$this->iParentIndex = $iParentIndex;
|
||||
@@ -409,21 +516,43 @@ abstract class MenuNode
|
||||
$this->index = ApplicationMenu::InsertMenu($this, $iParentIndex, $fRank);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function ReflectionProperties()
|
||||
{
|
||||
return $this->aReflectionProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetMenuId()
|
||||
{
|
||||
return $this->sMenuId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function GetParentIndex()
|
||||
{
|
||||
return $this->iParentIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public function GetTitle()
|
||||
{
|
||||
return Dict::S("Menu:$this->sMenuId", str_replace('_', ' ', $this->sMenuId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public function GetLabel()
|
||||
{
|
||||
$sRet = Dict::S("Menu:$this->sMenuId+", "");
|
||||
@@ -443,6 +572,9 @@ abstract class MenuNode
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function GetIndex()
|
||||
{
|
||||
return $this->index;
|
||||
@@ -458,6 +590,10 @@ abstract class MenuNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aExtraParams
|
||||
* @return string
|
||||
*/
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
$aExtraParams['c[menu]'] = $this->GetMenuId();
|
||||
@@ -500,7 +636,10 @@ abstract class MenuNode
|
||||
}
|
||||
if ($this->m_aEnableActions[$index] != null)
|
||||
{
|
||||
// Menus access rights ignore the archive mode
|
||||
utils::PushArchiveMode(false);
|
||||
$iResult = UserRights::IsActionAllowed($sClass, $this->m_aEnableActions[$index]);
|
||||
utils::PopArchiveMode();
|
||||
if (!($iResult & $this->m_aEnableActionResults[$index]))
|
||||
{
|
||||
return false;
|
||||
@@ -516,8 +655,18 @@ abstract class MenuNode
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @return mixed
|
||||
*/
|
||||
public abstract function RenderContent(WebPage $oPage, $aExtraParams = array());
|
||||
|
||||
/**
|
||||
* @param $sHyperlink
|
||||
* @param $aExtraParams
|
||||
* @return string
|
||||
*/
|
||||
protected function AddParams($sHyperlink, $aExtraParams)
|
||||
{
|
||||
if (count($aExtraParams) > 0)
|
||||
@@ -551,13 +700,17 @@ class MenuGroup extends MenuNode
|
||||
* @param string $sEnableClass Name of class of object
|
||||
* @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
|
||||
* @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them...
|
||||
* @return MenuGroup
|
||||
* @param string $sEnableStimulus
|
||||
*/
|
||||
public function __construct($sMenuId, $fRank, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
parent::__construct($sMenuId, -1 /* no parent, groups are at root level */, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
assert(false); // Shall never be called, groups do not display any content
|
||||
@@ -570,6 +723,9 @@ class MenuGroup extends MenuNode
|
||||
*/
|
||||
class TemplateMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sTemplateFile;
|
||||
|
||||
/**
|
||||
@@ -581,23 +737,34 @@ class TemplateMenuNode extends MenuNode
|
||||
* @param string $sEnableClass Name of class of object
|
||||
* @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
|
||||
* @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
|
||||
* @param string $sEnableStimulus
|
||||
*/
|
||||
public function __construct($sMenuId, $sTemplateFile, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
public function __construct($sMenuId, $sTemplateFile, $iParentIndex, $fRank = 0.0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
$this->sTemplateFile = $sTemplateFile;
|
||||
$this->aReflectionProperties['template_file'] = $sTemplateFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aExtraParams
|
||||
* @return string
|
||||
*/
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
if ($this->sTemplateFile == '') return '';
|
||||
return parent::GetHyperlink($aExtraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @return mixed|void
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
|
||||
$sTemplate = @file_get_contents($this->sTemplateFile);
|
||||
if ($sTemplate !== false)
|
||||
{
|
||||
@@ -618,9 +785,21 @@ class TemplateMenuNode extends MenuNode
|
||||
*/
|
||||
class OQLMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sPageTitle;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sOQL;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $bSearch;
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $bSearchFormOpen;
|
||||
|
||||
/**
|
||||
@@ -639,22 +818,16 @@ class OQLMenuNode extends MenuNode
|
||||
* @param string $sEnableClass Name of class of object
|
||||
* @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
|
||||
* @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
|
||||
* @param string $sEnableStimulus
|
||||
* @param bool $bSearchFormOpen
|
||||
*/
|
||||
public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null, $bSearchFormOpen = null)
|
||||
public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0.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;
|
||||
@@ -675,8 +848,17 @@ class OQLMenuNode extends MenuNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @return mixed|void
|
||||
* @throws CoreException
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws OQLException
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
|
||||
OQLMenuNode::RenderOQLSearch
|
||||
(
|
||||
$this->sOQL,
|
||||
@@ -690,6 +872,19 @@ class OQLMenuNode extends MenuNode
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sOql
|
||||
* @param $sTitle
|
||||
* @param $sUsageId
|
||||
* @param $bSearchPane
|
||||
* @param $bSearchOpen
|
||||
* @param WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @param bool $bEnableBreadcrumb
|
||||
* @throws CoreException
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws OQLException
|
||||
*/
|
||||
public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array(), $bEnableBreadcrumb = false)
|
||||
{
|
||||
$sUsageId = utils::GetSafeId($sUsageId);
|
||||
@@ -725,22 +920,28 @@ class OQLMenuNode extends MenuNode
|
||||
*/
|
||||
class SearchMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sPageTitle;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sClass;
|
||||
|
||||
/**
|
||||
* Create a menu item based on an OQL query and inserts it into the application's main menu
|
||||
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
|
||||
* @param string $sClass The class of objects to search for
|
||||
* @param string $sPageTitle Title displayed into the page's content (will be looked-up in the dictionnary for translation)
|
||||
* @param integer $iParentIndex ID of the parent menu
|
||||
* @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value
|
||||
* @param bool $bSearch (not used)
|
||||
* @param string $sEnableClass Name of class of object
|
||||
* @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
|
||||
* @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
|
||||
* @param string $sEnableStimulus
|
||||
*/
|
||||
public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0.0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
$this->sPageTitle = "Menu:$sMenuId+";
|
||||
@@ -748,12 +949,20 @@ class SearchMenuNode extends MenuNode
|
||||
$this->aReflectionProperties['class'] = $sClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @return mixed|void
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws Exception
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
|
||||
$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);
|
||||
$aParams = array_merge(array('table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
|
||||
$oBlock->Display($oPage, 0);
|
||||
}
|
||||
@@ -768,6 +977,9 @@ class SearchMenuNode extends MenuNode
|
||||
*/
|
||||
class WebPageMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sHyperlink;
|
||||
|
||||
/**
|
||||
@@ -779,21 +991,29 @@ class WebPageMenuNode extends MenuNode
|
||||
* @param string $sEnableClass Name of class of object
|
||||
* @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
|
||||
* @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
|
||||
* @param string $sEnableStimulus
|
||||
*/
|
||||
public function __construct($sMenuId, $sHyperlink, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
public function __construct($sMenuId, $sHyperlink, $iParentIndex, $fRank = 0.0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
$this->sHyperlink = $sHyperlink;
|
||||
$this->aReflectionProperties['url'] = $sHyperlink;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aExtraParams
|
||||
* @return string
|
||||
*/
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
$aExtraParams['c[menu]'] = $this->GetMenuId();
|
||||
return $this->AddParams( $this->sHyperlink, $aExtraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
assert(false); // Shall never be called, the external web page will handle the display by itself
|
||||
@@ -808,6 +1028,9 @@ class WebPageMenuNode extends MenuNode
|
||||
*/
|
||||
class NewObjectMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sClass;
|
||||
|
||||
/**
|
||||
@@ -817,15 +1040,22 @@ class NewObjectMenuNode extends MenuNode
|
||||
* @param string $sClass URL to the page to load. Use relative URL if you want to keep the application portable !
|
||||
* @param integer $iParentIndex ID of the parent menu
|
||||
* @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value
|
||||
* @return MenuNode
|
||||
* @param string $sEnableClass
|
||||
* @param int|null $iActionCode
|
||||
* @param int $iAllowedResults
|
||||
* @param string $sEnableStimulus
|
||||
*/
|
||||
public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0)
|
||||
public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0.0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank);
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
$this->sClass = $sClass;
|
||||
$this->aReflectionProperties['class'] = $sClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $aExtraParams
|
||||
* @return string
|
||||
*/
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$this->sClass;
|
||||
@@ -836,6 +1066,7 @@ class NewObjectMenuNode extends MenuNode
|
||||
/**
|
||||
* Overload the check of the "enable" state of this menu to take into account
|
||||
* derived classes of objects
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function IsEnabled()
|
||||
{
|
||||
@@ -855,6 +1086,11 @@ class NewObjectMenuNode extends MenuNode
|
||||
}
|
||||
return $bActionIsAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string[] $aExtraParams
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
assert(false); // Shall never be called, the external web page will handle the display by itself
|
||||
@@ -867,32 +1103,44 @@ require_once(APPROOT.'application/dashboard.class.inc.php');
|
||||
*/
|
||||
class DashboardMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sDashboardFile;
|
||||
|
||||
/**
|
||||
* Create a menu item based on a custom template and inserts it into the application's main menu
|
||||
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
|
||||
* @param string $sTemplateFile Path (or URL) to the file that will be used as a template for displaying the page's content
|
||||
* @param string $sDashboardFile
|
||||
* @param integer $iParentIndex ID of the parent menu
|
||||
* @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value
|
||||
* @param string $sEnableClass Name of class of object
|
||||
* @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
|
||||
* @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
|
||||
* @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS
|
||||
* @param string $sEnableStimulus
|
||||
*/
|
||||
public function __construct($sMenuId, $sDashboardFile, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
public function __construct($sMenuId, $sDashboardFile, $iParentIndex, $fRank = 0.0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
$this->sDashboardFile = $sDashboardFile;
|
||||
$this->aReflectionProperties['definition_file'] = $sDashboardFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $aExtraParams
|
||||
* @return string
|
||||
*/
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
if ($this->sDashboardFile == '') return '';
|
||||
return parent::GetHyperlink($aExtraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|RuntimeDashboard
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetDashboard()
|
||||
{
|
||||
$sDashboardDefinition = @file_get_contents($this->sDashboardFile);
|
||||
@@ -924,8 +1172,15 @@ class DashboardMenuNode extends MenuNode
|
||||
return $oDashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string[] $aExtraParams
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
|
||||
$oDashboard = $this->GetDashboard();
|
||||
if ($oDashboard != null)
|
||||
{
|
||||
@@ -1000,6 +1255,11 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function RenderEditor(WebPage $oPage)
|
||||
{
|
||||
$oDashboard = $this->GetDashboard();
|
||||
@@ -1013,6 +1273,10 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oDashlet
|
||||
* @throws Exception
|
||||
*/
|
||||
public function AddDashlet($oDashlet)
|
||||
{
|
||||
$oDashboard = $this->GetDashboard();
|
||||
@@ -1034,15 +1298,28 @@ EOF
|
||||
*/
|
||||
class ShortcutContainerMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @param string[] $aExtraParams
|
||||
* @return string
|
||||
*/
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string[] $aExtraParams
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function PopulateChildMenus()
|
||||
{
|
||||
// Load user shortcuts in DB
|
||||
@@ -1054,7 +1331,7 @@ class ShortcutContainerMenuNode extends MenuNode
|
||||
while ($oShortcut = $oBMSet->Fetch())
|
||||
{
|
||||
$sName = $this->GetMenuId().'_'.$oShortcut->GetKey();
|
||||
$oShortcutMenu = new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++);
|
||||
new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++);
|
||||
}
|
||||
|
||||
// Complete the tree
|
||||
@@ -1070,6 +1347,9 @@ require_once(APPROOT.'application/shortcut.class.inc.php');
|
||||
*/
|
||||
class ShortcutMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var Shortcut
|
||||
*/
|
||||
protected $oShortcut;
|
||||
|
||||
/**
|
||||
@@ -1081,15 +1361,20 @@ class ShortcutMenuNode extends MenuNode
|
||||
* @param string $sEnableClass Name of class of object
|
||||
* @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
|
||||
* @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
|
||||
* @param string $sEnableStimulus
|
||||
*/
|
||||
public function __construct($sMenuId, $oShortcut, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
public function __construct($sMenuId, $oShortcut, $iParentIndex, $fRank = 0.0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
$this->oShortcut = $oShortcut;
|
||||
$this->aReflectionProperties['shortcut'] = $oShortcut->GetKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $aExtraParams
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
$sContext = $this->oShortcut->Get('context');
|
||||
@@ -1105,16 +1390,31 @@ class ShortcutMenuNode extends MenuNode
|
||||
return parent::GetHyperlink($aExtraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string[] $aExtraParams
|
||||
* @return mixed|void
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
|
||||
$this->oShortcut->RenderContent($oPage, $aExtraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function GetTitle()
|
||||
{
|
||||
return $this->oShortcut->Get('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function GetLabel()
|
||||
{
|
||||
return $this->oShortcut->Get('name');
|
||||
|
||||
@@ -37,10 +37,11 @@ class NiceWebPage extends WebPage
|
||||
{
|
||||
parent::__construct($s_title, $bPrintable);
|
||||
$this->m_aReadyScripts = array();
|
||||
$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/jquery-1.12.4.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate-1.4.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.11.4.custom.css');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-1.11.4.custom.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/hovertip.js');
|
||||
// table sorting
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.js');
|
||||
@@ -50,6 +51,24 @@ class NiceWebPage extends WebPage
|
||||
$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_linked_script(utils::GetAbsoluteUrlAppRoot().'js/searchformforeignkeys.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/latinise/latinise.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_handler.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_handler_history.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_raw.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_string.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_field.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_numeric.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_enum.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_key.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_hierarchical_key.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_abstract.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_time.js');
|
||||
|
||||
$this->add_dict_entries('UI:Combo');
|
||||
|
||||
$this->add_ready_script(
|
||||
<<< EOF
|
||||
//add new widget called TruncatedList to properly display truncated lists when they are sorted
|
||||
|
||||
@@ -952,7 +952,7 @@ EOF
|
||||
$sTransactionId = utils::GetNewTransactionId();
|
||||
$this->SetTransactionId($sTransactionId);
|
||||
$this->add("<input type=\"hidden\" id=\"transaction_id\" name=\"transaction_id\" value=\"$sTransactionId\">\n");
|
||||
$this->add_ready_script("$(window).unload(function() { OnUnload('$sTransactionId') } );\n");
|
||||
$this->add_ready_script("$(window).on('unload', function() { OnUnload('$sTransactionId') } );\n");
|
||||
}
|
||||
|
||||
public function WizardFormButtons($iButtonFlags)
|
||||
|
||||
@@ -32,7 +32,7 @@ abstract class Query extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,view_in_gui,application",
|
||||
"category" => "core/cmdb,view_in_gui,application,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -54,6 +54,7 @@ abstract class Query extends cmdbAbstractObject
|
||||
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'fields')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
|
||||
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
}
|
||||
}
|
||||
@@ -64,7 +65,7 @@ class QueryOQL extends Query
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,view_in_gui,application",
|
||||
"category" => "core/cmdb,view_in_gui,application,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -83,7 +84,6 @@ class QueryOQL extends Query
|
||||
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'fields', 'oql')); // Criteria of the std search form
|
||||
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
|
||||
|
||||
@@ -197,7 +197,7 @@ class ShortcutOQL extends Shortcut
|
||||
}
|
||||
|
||||
$bSearchPane = true;
|
||||
$bSearchOpen = false;
|
||||
$bSearchOpen = true;
|
||||
try
|
||||
{
|
||||
OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams, true);
|
||||
|
||||
@@ -30,10 +30,23 @@ require_once(APPROOT.'/core/contexttag.class.inc.php');
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
session_start();
|
||||
$sSwitchEnv = utils::ReadParam('switch_env', null);
|
||||
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)))
|
||||
$bAllowCache = true;
|
||||
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)) && isset($_SESSION['itop_env']) && ($_SESSION['itop_env'] !== $sSwitchEnv))
|
||||
{
|
||||
$_SESSION['itop_env'] = $sSwitchEnv;
|
||||
$sEnv = $sSwitchEnv;
|
||||
$bAllowCache = false;
|
||||
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
|
||||
if (function_exists('opcache_reset'))
|
||||
{
|
||||
// Zend opcode cache
|
||||
opcache_reset();
|
||||
}
|
||||
if (function_exists('apc_clear_cache'))
|
||||
{
|
||||
// APC(u) cache
|
||||
apc_clear_cache();
|
||||
}
|
||||
// TODO: reset the credentials as well ??
|
||||
}
|
||||
else if (isset($_SESSION['itop_env']))
|
||||
@@ -46,4 +59,4 @@ else
|
||||
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
|
||||
}
|
||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
||||
@@ -63,6 +63,9 @@ require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
|
||||
class UIExtKeyWidget
|
||||
{
|
||||
const ENUM_OUTPUT_FORMAT_CSV = 'csv';
|
||||
const ENUM_OUTPUT_FORMAT_JSON = 'json';
|
||||
|
||||
protected $iId;
|
||||
protected $sTargetClass;
|
||||
protected $sAttCode;
|
||||
@@ -98,7 +101,7 @@ class UIExtKeyWidget
|
||||
/**
|
||||
* Get the HTML fragment corresponding to the ext key editing widget
|
||||
* @param WebPage $oP The web page used for all the output
|
||||
* @param Hash $aArgs Extra context arguments
|
||||
* @param array $aArgs Extra context arguments
|
||||
* @return string The HTML fragment to be inserted into the page
|
||||
*/
|
||||
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
|
||||
@@ -142,7 +145,12 @@ class UIExtKeyWidget
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
}
|
||||
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
if ($oAllowedValues->Count() < $iMaxComboLength)
|
||||
// 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->CountExceeds($iMaxComboLength))
|
||||
{
|
||||
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
||||
switch($sDisplayStyle)
|
||||
@@ -167,7 +175,6 @@ class UIExtKeyWidget
|
||||
case 'select':
|
||||
case 'list':
|
||||
default:
|
||||
$sSelectMode = 'true';
|
||||
|
||||
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
|
||||
$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
|
||||
@@ -226,7 +233,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') } );
|
||||
@@ -238,8 +245,6 @@ EOF
|
||||
else
|
||||
{
|
||||
// Too many choices, use an autocomplete
|
||||
$sSelectMode = 'false';
|
||||
|
||||
// Check that the given value is allowed
|
||||
$oSearch = $oAllowedValues->GetFilter();
|
||||
$oSearch->AddCondition('id', $value);
|
||||
@@ -258,11 +263,10 @@ EOF
|
||||
$sDisplayValue = $this->GetObjectName($value);
|
||||
}
|
||||
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
|
||||
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 20; //@@@ $this->oAttDef->GetMaxSize();
|
||||
|
||||
// the input for the auto-complete
|
||||
$sHTMLValue .= "<input class=\"field_autocomplete\" count=\"".$oAllowedValues->Count()."\" 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>";
|
||||
$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?t=".utils::GetCacheBusterTimestamp()."\" 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=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
|
||||
@@ -271,7 +275,7 @@ EOF
|
||||
// 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 !
|
||||
@@ -286,7 +290,7 @@ EOF
|
||||
}
|
||||
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
|
||||
{
|
||||
$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>";
|
||||
$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?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/></span>";
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
if ($('#ac_tree_{$this->iId}').length == 0)
|
||||
@@ -300,7 +304,7 @@ EOF
|
||||
{
|
||||
$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>";
|
||||
$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?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"/></span>";
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
if ($('#ajax_{$this->iId}').length == 0)
|
||||
@@ -338,10 +342,18 @@ EOF
|
||||
$aParams = array();
|
||||
$oFilter = new DBObjectSearch($this->sTargetClass);
|
||||
}
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
|
||||
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => $bOpen, 'currentId' => $this->iId));
|
||||
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId,
|
||||
array(
|
||||
'menu' => false,
|
||||
'currentId' => $this->iId,
|
||||
'table_id' => "dr_{$this->iId}",
|
||||
'table_inner_id' => "{$this->iId}_results",
|
||||
'selection_mode' => true,
|
||||
'selection_type' => 'single',
|
||||
'cssCount' => '#count_'.$this->iId)
|
||||
);
|
||||
$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";
|
||||
@@ -365,9 +377,13 @@ EOF
|
||||
|
||||
/**
|
||||
* Search for objects to be selected
|
||||
*
|
||||
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
||||
* @param $sFilter
|
||||
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
|
||||
* @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search
|
||||
* @param null $oObj
|
||||
*
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function SearchObjectsToSelect(WebPage $oP, $sFilter, $sRemoteClass = '', $oObj = null)
|
||||
{
|
||||
@@ -392,12 +408,18 @@ EOF
|
||||
|
||||
/**
|
||||
* Search for objects to be selected
|
||||
*
|
||||
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
||||
* @param string $sFilter The OQL expression used to define/limit limit the scope of possible values
|
||||
* @param DBObject $oObj The current object for the OQL context
|
||||
* @param string $sContains The text of the autocomplete to filter the results
|
||||
* @param string $sOutputFormat
|
||||
* @param null $sOperation for the values @see ValueSetObjects->LoadValues()
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws OQLException
|
||||
*/
|
||||
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains)
|
||||
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null)
|
||||
{
|
||||
if (is_null($sFilter))
|
||||
{
|
||||
@@ -408,12 +430,46 @@ EOF
|
||||
$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, 'current_extkey_id' => $iCurrentExtKeyId), $sContains);
|
||||
$oValuesSet->SetLimit($iMax);
|
||||
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
|
||||
asort($aValuesContains);
|
||||
$aValues = array();
|
||||
foreach($aValuesContains as $sKey => $sFriendlyName)
|
||||
{
|
||||
if (!isset($aValues[$sKey]))
|
||||
{
|
||||
$aValues[$sKey] = $sFriendlyName;
|
||||
}
|
||||
}
|
||||
|
||||
switch($sOutputFormat)
|
||||
{
|
||||
case static::ENUM_OUTPUT_FORMAT_JSON:
|
||||
|
||||
$aJsonMap = array();
|
||||
foreach ($aValues as $sKey => $sLabel)
|
||||
{
|
||||
$aJsonMap[] = array('value' => $sKey, 'label' => $sLabel);
|
||||
}
|
||||
|
||||
$oP->SetContentType('application/json');
|
||||
$oP->add(json_encode($aJsonMap));
|
||||
break;
|
||||
|
||||
case static::ENUM_OUTPUT_FORMAT_CSV:
|
||||
foreach($aValues as $sKey => $sFriendlyName)
|
||||
{
|
||||
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid output format, "'.$sOutputFormat.'" given.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,6 +496,9 @@ EOF
|
||||
* Note: Inspired from UILinksWidgetDirect::GetObjectCreationDialog()
|
||||
*
|
||||
* @param WebPage $oPage
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*/
|
||||
public function GetClassSelectionForm(WebPage $oPage)
|
||||
{
|
||||
@@ -480,7 +539,7 @@ EOF
|
||||
/**
|
||||
* Get the form to create a new object of the 'target' class
|
||||
*/
|
||||
public function GetObjectCreationForm(WebPage $oPage, $oCurrObject)
|
||||
public function GetObjectCreationForm(WebPage $oPage, $oCurrObject, $aPrefillFormParam)
|
||||
{
|
||||
// Set all the default values in an object and clone this "default" object
|
||||
$oNewObj = MetaModel::NewObject($this->sTargetClass);
|
||||
@@ -488,7 +547,7 @@ EOF
|
||||
// 1st - set context values
|
||||
$oAppContext = new ApplicationContext();
|
||||
$oAppContext->InitObjectFromContext($oNewObj);
|
||||
|
||||
$oNewObj->PrefillForm('creation_from_extkey', $aPrefillFormParam);
|
||||
// 2nd set the default values from the constraint on the external key... if any
|
||||
if ( ($oCurrObject != null) && ($this->sAttCode != ''))
|
||||
{
|
||||
@@ -542,21 +601,11 @@ EOF
|
||||
{
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
$oFilter = DBObjectSearch::FromOQL($sFilter);
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
|
||||
}
|
||||
catch(MissingQueryArgument $e)
|
||||
{
|
||||
// When used in a search form the $this parameter may be missing, in this case return all possible values...
|
||||
// TODO check if we can improve this behavior...
|
||||
$sOQL = 'SELECT '.$this->m_sTargetClass;
|
||||
$oFilter = DBObjectSearch::FromOQL($sOQL);
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oSet = new DBObjectSet($oFilter);
|
||||
}
|
||||
|
||||
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
|
||||
$sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);
|
||||
|
||||
@@ -31,6 +31,13 @@ class UILinksWidgetDirect
|
||||
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;
|
||||
@@ -74,12 +81,12 @@ class UILinksWidgetDirect
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param DBObjectSet|ormLinkSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param $sFormPrefix
|
||||
* @param $oCurrentObj
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
*/
|
||||
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
switch($oLinksetDef->GetEditMode())
|
||||
@@ -127,11 +134,11 @@ class UILinksWidgetDirect
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param $sFormPrefix
|
||||
* @param $oCurrentObj
|
||||
* @param $bDisplayMenu
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param bool $bDisplayMenu
|
||||
*/
|
||||
protected function DisplayAsBlock(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sTargetClass = $oLinksetDef->GetLinkedClass();
|
||||
@@ -168,15 +175,72 @@ class UILinksWidgetDirect
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string $sProposedRealClass
|
||||
*/
|
||||
public function GetObjectCreationDlg(WebPage $oPage, $sProposedRealClass = '', $oSourceObj = null)
|
||||
{
|
||||
// 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
|
||||
$sRealClass = '';
|
||||
$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div>');
|
||||
$aSubClasses = MetaModel::EnumChildClasses($this->sLinkedClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
|
||||
$aPossibleClasses = array();
|
||||
foreach($aSubClasses as $sCandidateClass)
|
||||
{
|
||||
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
|
||||
{
|
||||
if ($sCandidateClass == $sProposedRealClass)
|
||||
{
|
||||
$sRealClass = $sProposedRealClass;
|
||||
}
|
||||
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
|
||||
}
|
||||
}
|
||||
// Only one of the subclasses can be instantiated...
|
||||
if (count($aPossibleClasses) == 1)
|
||||
{
|
||||
$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);
|
||||
$oObj = DBObject::MakeDefaultInstance($sRealClass);
|
||||
$aPrefillParam = array('source_obj' => $oSourceObj);
|
||||
$oObj->PrefillForm('creation_from_editinplace', $aPrefillParam);
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, array(), array('formPrefix' => $this->sInputid, 'noRelations' => true, 'fieldsFlags' => $aFieldFlags));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sClassLabel = MetaModel::GetName($this->sLinkedClass);
|
||||
$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="button" onclick="$(\'#'.$this->sInputid.'\').directlinks(\'subclassSelected\');">'.Dict::S('UI:Button:Apply').'</button><span class="indicator" style="display:inline-block;width:16px"></span></nobr></p>');
|
||||
}
|
||||
$oPage->add('</div></div>');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param $sFormPrefix
|
||||
* @param $oCurrentObj
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param array $aButtons
|
||||
*/
|
||||
protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
|
||||
@@ -211,65 +275,45 @@ class UILinksWidgetDirect
|
||||
$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 });");
|
||||
// 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});");
|
||||
}
|
||||
|
||||
public function GetObjectCreationDlg(WebPage $oPage, $sProposedRealClass = '')
|
||||
{
|
||||
// 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
|
||||
$sRealClass = '';
|
||||
$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div>');
|
||||
$aSubClasses = MetaModel::EnumChildClasses($this->sLinkedClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
|
||||
$aPossibleClasses = array();
|
||||
foreach($aSubClasses as $sCandidateClass)
|
||||
{
|
||||
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
|
||||
{
|
||||
if ($sCandidateClass == $sProposedRealClass)
|
||||
{
|
||||
$sRealClass = $sProposedRealClass;
|
||||
}
|
||||
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
|
||||
}
|
||||
}
|
||||
// Only one of the subclasses can be instantiated...
|
||||
if (count($aPossibleClasses) == 1)
|
||||
{
|
||||
$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));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sClassLabel = MetaModel::GetName($this->sLinkedClass);
|
||||
$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="button" onclick="$(\'#'.$this->sInputid.'\').directlinks(\'subclassSelected\');">'.Dict::S('UI:Button:Apply').'</button><span class="indicator" style="display:inline-block;width:16px"></span></nobr></p>');
|
||||
}
|
||||
$oPage->add('</div></div>');
|
||||
}
|
||||
|
||||
public function GetObjectsSelectionDlg($oPage, $oCurrentObj)
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param $aAlreadyLinked
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function GetObjectsSelectionDlg($oPage, $oCurrentObj, $aAlreadyLinked)
|
||||
{
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinksetDef->GetValuesDef();
|
||||
$oHiddenFilter = new DBObjectSearch($this->sLinkedClass);
|
||||
if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($this->sLinkedClass, $this->sClass))
|
||||
{
|
||||
// Prevent linking to self if the linked object is of the same family
|
||||
// and already present in the database
|
||||
if (!$oCurrentObj->IsNew())
|
||||
{
|
||||
$oHiddenFilter->AddCondition('id', $oCurrentObj->GetKey(), '!=');
|
||||
}
|
||||
}
|
||||
if (count($aAlreadyLinked) > 0)
|
||||
{
|
||||
$oHiddenFilter->AddCondition('id', $aAlreadyLinked, 'NOTIN');
|
||||
}
|
||||
$oHiddenCriteria = $oHiddenFilter->GetCriteria();
|
||||
$aArgs = $oHiddenFilter->GetInternalParams();
|
||||
$sHiddenCriteria = $oHiddenCriteria->Render($aArgs);
|
||||
|
||||
$oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinkSetDef->GetValuesDef();
|
||||
if ($valuesDef === null)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($this->sLinkedClass);
|
||||
@@ -282,13 +326,26 @@ class UILinksWidgetDirect
|
||||
}
|
||||
$oFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression());
|
||||
}
|
||||
|
||||
if ($oCurrentObj != null)
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
|
||||
$aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams());
|
||||
$oFilter->SetInternalParams($aArgs);
|
||||
}
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => $bOpen));
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}",
|
||||
array(
|
||||
'result_list_outer_selector' => "SearchResultsToAdd_{$this->sInputid}",
|
||||
'table_id' => "add_{$this->sInputid}",
|
||||
'table_inner_id' => "ResultsToAdd_{$this->sInputid}",
|
||||
'selection_mode' => true,
|
||||
'cssCount' => "#count_{$this->sInputid}",
|
||||
'query_params' => $oFilter->GetInternalParams(),
|
||||
'hidden_criteria' => $sHiddenCriteria,
|
||||
)
|
||||
);
|
||||
$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";
|
||||
@@ -306,6 +363,7 @@ class UILinksWidgetDirect
|
||||
* @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)
|
||||
{
|
||||
@@ -313,8 +371,8 @@ class UILinksWidgetDirect
|
||||
{
|
||||
$sRemoteClass = $this->sLinkedClass;
|
||||
}
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinksetDef->GetValuesDef();
|
||||
$oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinkSetDef->GetValuesDef();
|
||||
if ($valuesDef === null)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($sRemoteClass);
|
||||
@@ -331,7 +389,7 @@ class UILinksWidgetDirect
|
||||
if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($sRemoteClass, $this->sClass))
|
||||
{
|
||||
// Prevent linking to self if the linked object is of the same family
|
||||
// and laready present in the database
|
||||
// and already present in the database
|
||||
if (!$oCurrentObj->IsNew())
|
||||
{
|
||||
$oFilter->AddCondition('id', $oCurrentObj->GetKey(), '!=');
|
||||
@@ -343,6 +401,8 @@ class UILinksWidgetDirect
|
||||
}
|
||||
if ($oCurrentObj != null)
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
|
||||
$aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams());
|
||||
$oFilter->SetInternalParams($aArgs);
|
||||
}
|
||||
@@ -350,6 +410,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);
|
||||
@@ -378,6 +442,13 @@ 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 == '')
|
||||
@@ -390,6 +461,12 @@ 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();
|
||||
@@ -405,7 +482,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 DBSearch $oSearch
|
||||
* @param DBSearch|DBObjectSearch $oSearch
|
||||
*/
|
||||
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
|
||||
{
|
||||
@@ -424,7 +501,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
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
require_once(APPROOT.'application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'application/displayblock.class.inc.php');
|
||||
|
||||
class UILinksWidget
|
||||
{
|
||||
@@ -39,7 +39,22 @@ class UILinksWidget
|
||||
protected $m_sLinkedClass;
|
||||
protected $m_sRemoteClass;
|
||||
protected $m_bDuplicatesAllowed;
|
||||
protected $m_aEditableFields;
|
||||
protected $m_aTableConfig;
|
||||
|
||||
/**
|
||||
* UILinksWidget constructor.
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param int $iInputId
|
||||
* @param string $sNameSuffix
|
||||
* @param bool $bDuplicatesAllowed
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '', $bDuplicatesAllowed = false)
|
||||
{
|
||||
$this->m_sClass = $sClass;
|
||||
@@ -49,10 +64,13 @@ class UILinksWidget
|
||||
$this->m_bDuplicatesAllowed = $bDuplicatesAllowed;
|
||||
$this->m_aEditableFields = array();
|
||||
|
||||
/** @var AttributeLinkedSetIndirect $oAttDef */
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
|
||||
$this->m_sLinkedClass = $oAttDef->GetLinkedClass();
|
||||
$this->m_sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
|
||||
$this->m_sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
||||
|
||||
/** @var AttributeExternalKey $oLinkingAttDef */
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote);
|
||||
$this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass();
|
||||
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
||||
@@ -95,14 +113,20 @@ class UILinksWidget
|
||||
|
||||
/**
|
||||
* A one-row form for editing a link record
|
||||
*
|
||||
* @param WebPage $oP Web page used for the ouput
|
||||
* @param DBObject $oLinkedObj Remote object
|
||||
* @param mixed $linkObjOrId Either the object linked or a unique number for new link records to add
|
||||
* @param array|Hash $aArgs Extra context arguments
|
||||
* @param $oCurrentObj The object to which all the elements of the linked set refer to
|
||||
* @param $iUniqueId A unique identifier of new links
|
||||
* @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 string The HTML fragment of the one-row form
|
||||
*
|
||||
* @return array The HTML fragment of the one-row form
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false)
|
||||
{
|
||||
@@ -138,7 +162,9 @@ class UILinksWidget
|
||||
$sValue = $linkObjOrId->Get($sFieldCode);
|
||||
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId, $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, $sNameSuffix, 0, $aArgs).
|
||||
'</div></div></div>';
|
||||
$aFieldsMap[$sFieldCode] = $sSafeId;
|
||||
}
|
||||
}
|
||||
@@ -166,9 +192,10 @@ class UILinksWidget
|
||||
$sPrefix .= "[-$iUniqueId][";
|
||||
$sNameSuffix = "]"; // To make a tabular form
|
||||
$aArgs['prefix'] = $sPrefix;
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".$iUniqueId;
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".($iUniqueId < 0 ? -$iUniqueId : $iUniqueId);
|
||||
$aArgs['this'] = $oNewLinkObj;
|
||||
$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\">";
|
||||
$sInputValue = $iUniqueId > 0 ? "-$iUniqueId" : "$iUniqueId";
|
||||
$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=\"$sInputValue\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.-$iUniqueId.']';
|
||||
@@ -176,18 +203,25 @@ class UILinksWidget
|
||||
$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, $sValue, $sDisplayValue, $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(
|
||||
// Rows created with ajax call need OnLinkAdded call.
|
||||
// Rows added before loading the form cannot call OnLinkAdded.
|
||||
if ($iUniqueId > 0)
|
||||
{
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
PrepareWidgets();
|
||||
oWidget{$this->m_iInputId}.OnLinkAdded($iUniqueId, $iRemoteObjKey);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(!$bReadOnly)
|
||||
{
|
||||
@@ -220,7 +254,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)
|
||||
{
|
||||
@@ -238,8 +276,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)
|
||||
@@ -280,22 +318,27 @@ EOF
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*/
|
||||
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
|
||||
$aAddedLinks = array();
|
||||
while($oCurrentLink = $oValue->Fetch())
|
||||
{
|
||||
// We try to retrieve the remote object as usual
|
||||
@@ -315,6 +358,8 @@ EOF
|
||||
if ($oCurrentLink->IsNew())
|
||||
{
|
||||
$key = -($iAddedId++);
|
||||
$iUniqueId = -$key;
|
||||
$aAddedLinks[] = array('iAddedId' => $iUniqueId, 'iRemote' => $oCurrentLink->Get($this->m_sExtKeyToRemote));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -324,12 +369,24 @@ EOF
|
||||
}
|
||||
$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();
|
||||
EOF
|
||||
);
|
||||
|
||||
foreach ($aAddedLinks as $aAddedLink)
|
||||
{
|
||||
$oPage->add_ready_script(<<<EOF
|
||||
oWidget{$this->m_iInputId}.AddLink({$aAddedLink['iAddedId']}, {$aAddedLink['iRemote']});
|
||||
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();\" >";
|
||||
$sHtmlValue .= " <input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget{$this->m_iInputId}.AddObjects();\"><span id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_indicatorAdd\"></span></span>\n";
|
||||
$sHtmlValue .= "<span style=\"clear:both;\"><p> </p></span>\n";
|
||||
@@ -338,13 +395,24 @@ EOF
|
||||
return $sHtmlValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected static function GetTargetClass($sClass, $sAttCode)
|
||||
{
|
||||
/** @var AttributeLinkedSet $oAttDef */
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$sLinkedClass = $oAttDef->GetLinkedClass();
|
||||
$sTargetClass = '';
|
||||
switch(get_class($oAttDef))
|
||||
{
|
||||
case 'AttributeLinkedSetIndirect':
|
||||
/** @var AttributeExternalKey $oLinkingAttDef */
|
||||
/** @var AttributeLinkedSetIndirect $oAttDef */
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote());
|
||||
$sTargetClass = $oLinkingAttDef->GetTargetClass();
|
||||
break;
|
||||
@@ -357,20 +425,59 @@ EOF
|
||||
return $sTargetClass;
|
||||
}
|
||||
|
||||
public function GetObjectPickerDialog($oPage, $oCurrentObj)
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param $sJson
|
||||
* @param array $aAlreadyLinkedIds
|
||||
*
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetObjectPickerDialog($oPage, $oCurrentObj, $sJson, $aAlreadyLinkedIds = array(), $aPrefillFormParam = array())
|
||||
{
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
|
||||
$oAlreadyLinkedFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0)
|
||||
{
|
||||
$oAlreadyLinkedFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
|
||||
$oAlreadyLinkedExpression = $oAlreadyLinkedFilter->GetCriteria();
|
||||
$sAlreadyLinkedExpression = $oAlreadyLinkedExpression->Render();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sAlreadyLinkedExpression = '';
|
||||
}
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
|
||||
if(!empty($oCurrentObj))
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
$aPrefillFormParam['filter'] = $oFilter;
|
||||
$aPrefillFormParam['dest_class'] = $this->m_sRemoteClass;
|
||||
$oCurrentObj->PrefillForm('search', $aPrefillFormParam);
|
||||
}
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$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 .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
|
||||
array(
|
||||
'menu' => false,
|
||||
'result_list_outer_selector' => "SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
|
||||
'table_id' => 'add_'.$this->m_sAttCode,
|
||||
'table_inner_id' => "ResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
|
||||
'selection_mode' => true,
|
||||
'json' => $sJson,
|
||||
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
|
||||
'query_params' => $oFilter->GetInternalParams(),
|
||||
'hidden_criteria' => $sAlreadyLinkedExpression,
|
||||
));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\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";
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"0\"/>";
|
||||
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\"> <input id=\"btn_ok_{$this->m_sAttCode}{$this->m_sNameSuffix}\" disabled=\"disabled\" type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">";
|
||||
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\"> <input id=\"btn_ok_{$this->m_sAttCode}{$this->m_sNameSuffix}\" disabled=\"disabled\" type=\"button\" onclick=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\" value=\"".Dict::S('UI:Button:Add')."\">";
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "</form>\n";
|
||||
$oPage->add($sHtml);
|
||||
@@ -382,11 +489,17 @@ EOF
|
||||
|
||||
/**
|
||||
* 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 m_sRemoteClass
|
||||
* @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search
|
||||
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of
|
||||
* m_sRemoteClass
|
||||
* @param array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of
|
||||
* the search
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinkedIds = array())
|
||||
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinkedIds = array(), $oCurrentObj = null)
|
||||
{
|
||||
if ($sRemoteClass != '')
|
||||
{
|
||||
@@ -402,10 +515,20 @@ EOF
|
||||
{
|
||||
$oFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
|
||||
}
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $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
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oP
|
||||
* @param int $iMaxAddedId
|
||||
* @param $oFullSetFilter
|
||||
* @param DBObject $oCurrentObj
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function DoAddObjects(WebPage $oP, $iMaxAddedId, $oFullSetFilter, $oCurrentObj)
|
||||
{
|
||||
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
|
||||
@@ -429,8 +552,12 @@ EOF
|
||||
|
||||
/**
|
||||
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
|
||||
*
|
||||
* @param DBObject $oSourceObj
|
||||
* @param DBSearch $oSearch
|
||||
* @param DBSearch|DBObjectSearch $oSearch
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
|
||||
{
|
||||
@@ -449,7 +576,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
|
||||
@@ -462,6 +588,29 @@ EOF
|
||||
}
|
||||
|
||||
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue))
|
||||
{
|
||||
// Add Hierarchical condition if hierarchical key
|
||||
$oAttDef = MetaModel::GetAttributeDef($sDestClass, $sAttCode);
|
||||
if (isset($oAttDef) && ($oAttDef->IsExternalKey()))
|
||||
{
|
||||
try
|
||||
{
|
||||
/** @var AttributeExternalKey $oAttDef */
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass);
|
||||
if ($sHierarchicalKeyCode !== false)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($sTargetClass);
|
||||
$oFilter->AddCondition('id', $defaultValue);
|
||||
$oHKFilter = new DBObjectSearch($sTargetClass);
|
||||
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW);
|
||||
$oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode);
|
||||
}
|
||||
} catch (Exception $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oSearch->AddCondition($sAttCode, $defaultValue);
|
||||
}
|
||||
@@ -469,3 +618,4 @@ EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
115
application/ui.searchformforeignkeys.class.inc.php
Normal file
115
application/ui.searchformforeignkeys.class.inc.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* Copyright (C) 2010-2018 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(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
|
||||
class UISearchFormForeignKeys
|
||||
{
|
||||
public function __construct($sTargetClass, $iInputId = null)
|
||||
{
|
||||
$this->m_sRemoteClass = $sTargetClass;
|
||||
$this->m_iInputId = $iInputId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
*
|
||||
* @param $sTitle
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ShowModalSearchForeignKeys($oPage, $sTitle)
|
||||
{
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_iInputId}",
|
||||
array(
|
||||
'menu' => false,
|
||||
'result_list_outer_selector' => "SearchResultsToAdd_{$this->m_iInputId}",
|
||||
'table_id' => "add_{$this->m_iInputId}",
|
||||
'table_inner_id' => "ResultsToAdd_{$this->m_iInputId}",
|
||||
'selection_mode' => true,
|
||||
'cssCount' => "#count_{$this->m_iInputId}",
|
||||
'query_params' => $oFilter->GetInternalParams(),
|
||||
));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_iInputId}\">\n";
|
||||
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_iInputId}\" 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";
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->m_iInputId}\" value=\"0\"/>";
|
||||
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_iInputId}').dialog('close');\"> <input id=\"btn_ok_{$this->m_iInputId}\" disabled=\"disabled\" type=\"button\" onclick=\"return oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);\" value=\"".Dict::S('UI:Button:Add')."\">";
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "</form>\n";
|
||||
$oPage->add($sHtml);
|
||||
$oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes });");
|
||||
$oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});");
|
||||
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_iInputId} form').bind('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);");
|
||||
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_iInputId}').resize(oForeignKeysWidget{$this->m_iInputId}.UpdateSizes);");
|
||||
}
|
||||
|
||||
public function GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter)
|
||||
{
|
||||
try
|
||||
{
|
||||
$aLinkedObjects = utils::ReadMultipleSelectionWithFriendlyname($oFullSetFilter);
|
||||
$oPage->add(json_encode($aLinkedObjects));
|
||||
}
|
||||
catch (CoreException $e)
|
||||
{
|
||||
http_response_code(500);
|
||||
$oPage->add(json_encode(array('error' => $e->GetMessage())));
|
||||
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 m_sRemoteClass
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ListResultsSearchForeignKeys(WebPage $oP, $sRemoteClass = '')
|
||||
{
|
||||
if ($sRemoteClass != '')
|
||||
{
|
||||
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
|
||||
$oFilter = new DBObjectSearch($sRemoteClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No remote class specified use the one defined in the linkedset
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
}
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oBlock->Display($oP, "ResultsToAdd_{$this->m_iInputId}",
|
||||
array('menu' => false, 'cssCount' => "#count_{$this->m_iInputId}", 'selection_mode' => true, 'table_id' => "add_{$this->m_iInputId}"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
use Html2Text\Html2Text;
|
||||
|
||||
use Leafo\ScssPhp\Compiler;
|
||||
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
@@ -26,8 +27,9 @@ use Leafo\ScssPhp\Compiler;
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'/core/config.class.inc.php');
|
||||
require_once(APPROOT.'/application/transaction.class.inc.php');
|
||||
require_once(APPROOT.'core/metamodel.class.php');
|
||||
require_once(APPROOT.'core/config.class.inc.php');
|
||||
require_once(APPROOT.'application/transaction.class.inc.php');
|
||||
require_once(APPROOT.'application/Html2Text.php');
|
||||
require_once(APPROOT.'application/Html2TextException.php');
|
||||
|
||||
@@ -36,6 +38,8 @@ define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE
|
||||
|
||||
define('SERVER_NAME_PLACEHOLDER', '$SERVER_NAME$');
|
||||
|
||||
define('SERVER_MAX_URL_LENGTH', 2048);
|
||||
|
||||
class FileUploadException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -58,11 +62,11 @@ class utils
|
||||
{
|
||||
if (!file_exists($sParamFile))
|
||||
{
|
||||
throw new Exception("Could not find the parameter file: '$sParamFile'");
|
||||
throw new Exception("Could not find the parameter file: '".utils::HtmlEntities($sParamFile)."'");
|
||||
}
|
||||
if (!is_readable($sParamFile))
|
||||
{
|
||||
throw new Exception("Could not load parameter file: '$sParamFile'");
|
||||
throw new Exception("Could not load parameter file: '".utils::HtmlEntities($sParamFile)."'");
|
||||
}
|
||||
$sParams = file_get_contents($sParamFile);
|
||||
|
||||
@@ -141,6 +145,9 @@ class utils
|
||||
}
|
||||
|
||||
protected static $bPageMode = null;
|
||||
/**
|
||||
* @var boolean[]
|
||||
*/
|
||||
protected static $aModes = array();
|
||||
|
||||
public static function InitArchiveMode()
|
||||
@@ -164,6 +171,10 @@ class utils
|
||||
self::$bPageMode = ($iCurrent == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $bMode if true then activate archive mode (archived objects are visible), otherwise archived objects are
|
||||
* hidden (archive = "soft deletion")
|
||||
*/
|
||||
public static function PushArchiveMode($bMode)
|
||||
{
|
||||
array_push(self::$aModes, $bMode);
|
||||
@@ -174,6 +185,9 @@ class utils
|
||||
array_pop(self::$aModes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean true if archive mode is enabled
|
||||
*/
|
||||
public static function IsArchiveMode()
|
||||
{
|
||||
if (count(self::$aModes) > 0)
|
||||
@@ -260,6 +274,17 @@ class utils
|
||||
return $retValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[] $value
|
||||
* @param string $sSanitizationFilter one of : integer, class, string, context_param, parameter, field_name,
|
||||
* transaction_id, parameter, raw_data
|
||||
*
|
||||
* @return string|string[]|bool boolean for :
|
||||
* * the 'class' filter (true if valid, false otherwise)
|
||||
* * if the filter fails (@see \filter_var())
|
||||
*
|
||||
* @since 2.5.2 2.6.0 new 'transaction_id' filter
|
||||
*/
|
||||
protected static function Sanitize_Internal($value, $sSanitizationFilter)
|
||||
{
|
||||
switch($sSanitizationFilter)
|
||||
@@ -300,8 +325,16 @@ class utils
|
||||
{
|
||||
switch($sSanitizationFilter)
|
||||
{
|
||||
case 'transaction_id':
|
||||
// same as parameter type but keep the dot character
|
||||
// see N°1835 : when using file transaction_id on Windows you get *.tmp tokens
|
||||
// it must be included at the regexp beginning otherwise you'll get an invalid character error
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
|
||||
array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
|
||||
break;
|
||||
|
||||
case 'parameter':
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[ A-Za-z0-9_=-]*$/'))); // the '=' equal character is used in serialized filters
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^([ A-Za-z0-9_=-]|%3D|%2B|%2F)*$/'))); // the '=', '%3D, '%2B', '%2F' characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
|
||||
break;
|
||||
|
||||
case 'field_name':
|
||||
@@ -403,8 +436,10 @@ class utils
|
||||
|
||||
/**
|
||||
* Interprets the results posted by a normal or paginated list (in multiple selection mode)
|
||||
*
|
||||
* @param $oFullSetFilter DBSearch The criteria defining the whole sets of objects being selected
|
||||
* @return Array An arry of object IDs corresponding to the objects selected in the set
|
||||
*
|
||||
* @return Array An array of object IDs corresponding to the objects selected in the set
|
||||
*/
|
||||
public static function ReadMultipleSelection($oFullSetFilter)
|
||||
{
|
||||
@@ -438,6 +473,51 @@ class utils
|
||||
return $aSelectedObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interprets the results posted by a normal or paginated list (in multiple selection mode)
|
||||
*
|
||||
* @param DBSearch $oFullSetFilter The criteria defining the whole sets of objects being selected
|
||||
*
|
||||
* @return Array An array of object IDs:friendlyname corresponding to the objects selected in the set
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function ReadMultipleSelectionWithFriendlyname($oFullSetFilter)
|
||||
{
|
||||
$sSelectionMode = utils::ReadParam('selectionMode', '');
|
||||
|
||||
if ($sSelectionMode != 'positive' && $sSelectionMode != 'negative')
|
||||
{
|
||||
throw new CoreException('selectionMode must be either positive or negative');
|
||||
}
|
||||
|
||||
// Paginated selection
|
||||
$aSelectedIds = utils::ReadParam('storedSelection', array());
|
||||
if (count($aSelectedIds) > 0 )
|
||||
{
|
||||
if ($sSelectionMode == 'positive')
|
||||
{
|
||||
// Only the explicitly listed items are selected
|
||||
$oFullSetFilter->AddCondition('id', $aSelectedIds, 'IN');
|
||||
}
|
||||
else
|
||||
{
|
||||
// All items of the set are selected, except the one explicitly listed
|
||||
$oFullSetFilter->AddCondition('id', $aSelectedIds, 'NOTIN');
|
||||
}
|
||||
}
|
||||
|
||||
$aSelectedObj = array();
|
||||
$oFullSet = new DBObjectSet($oFullSetFilter);
|
||||
$sClassAlias = $oFullSetFilter->GetClassAlias();
|
||||
$oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field
|
||||
while ($oObj = $oFullSet->Fetch())
|
||||
{
|
||||
$aSelectedObj[$oObj->GetKey()] = $oObj->Get('friendlyname');
|
||||
}
|
||||
|
||||
return $aSelectedObj;
|
||||
}
|
||||
|
||||
public static function GetNewTransactionId()
|
||||
{
|
||||
return privUITransaction::GetNewTransactionId();
|
||||
@@ -601,26 +681,51 @@ class utils
|
||||
return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Config from the current environement, or if not existing from the production env, else new Config made from scratch
|
||||
* @uses \MetaModel::GetConfig() don't forget to add the needed <code>require_once(APPROOT.'core/metamodel.class.php');</code>
|
||||
*/
|
||||
static public function GetConfig()
|
||||
{
|
||||
if (self::$oConfig == null)
|
||||
{
|
||||
self::$oConfig = MetaModel::GetConfig();
|
||||
|
||||
if (self::$oConfig == null)
|
||||
{
|
||||
$sConfigFile = self::GetConfigFilePath();
|
||||
if (file_exists($sConfigFile))
|
||||
if (!file_exists($sConfigFile))
|
||||
{
|
||||
self::$oConfig = new Config($sConfigFile);
|
||||
$sConfigFile = self::GetConfigFilePath('production');
|
||||
if (!file_exists($sConfigFile))
|
||||
{
|
||||
$sConfigFile = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// When executing the setup, the config file may be still missing
|
||||
self::$oConfig = new Config();
|
||||
}
|
||||
|
||||
self::$oConfig = new Config($sConfigFile);
|
||||
}
|
||||
}
|
||||
return self::$oConfig;
|
||||
}
|
||||
|
||||
public static function InitTimeZone() {
|
||||
$oConfig = self::GetConfig();
|
||||
$sItopTimeZone = $oConfig->Get('timezone');
|
||||
|
||||
if (!empty($sItopTimeZone))
|
||||
{
|
||||
date_default_timezone_set($sItopTimeZone);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Leave as is... up to the admin to set a value somewhere...
|
||||
// see http://php.net/manual/en/datetime.configuration.php#ini.date.timezone
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute URL to the application root path
|
||||
* @return string The absolute URL to the application root, without the first slash
|
||||
* @return string The absolute URL to the application, including host and path
|
||||
*/
|
||||
static public function GetAbsoluteUrlAppRoot()
|
||||
{
|
||||
@@ -917,7 +1022,7 @@ class utils
|
||||
*/
|
||||
public static function GetCachePath()
|
||||
{
|
||||
return APPROOT.'data/cache-'.self::GetCurrentEnvironment().'/';
|
||||
return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
|
||||
}
|
||||
/**
|
||||
* Merge standard menu items with plugin provided menus items
|
||||
@@ -941,13 +1046,17 @@ class utils
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
|
||||
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
|
||||
|
||||
$aResult = array(
|
||||
new SeparatorPopupMenuItem(),
|
||||
$aResult = array();
|
||||
if (strlen($sUrl) < SERVER_MAX_URL_LENGTH)
|
||||
{
|
||||
$aResult[] = new SeparatorPopupMenuItem();
|
||||
// Static menus: Email this page, CSV Export & Add to Dashboard
|
||||
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
|
||||
$aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'),
|
||||
"mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
|
||||
);
|
||||
}
|
||||
|
||||
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO)
|
||||
{
|
||||
// Bulk export actions
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
|
||||
@@ -958,7 +1067,7 @@ class utils
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
|
||||
}
|
||||
}
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
|
||||
|
||||
break;
|
||||
@@ -999,10 +1108,12 @@ class utils
|
||||
$sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
|
||||
$sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
|
||||
$sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
|
||||
$sUploadDashboardTransactId = utils::GetNewTransactionId();
|
||||
$aResult = array(
|
||||
new SeparatorPopupMenuItem(),
|
||||
new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sMenuId),
|
||||
new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sMenuId', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn' })"),
|
||||
new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'),
|
||||
"UploadDashboard({dashboard_id: '$sMenuId', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn', transaction: '$sUploadDashboardTransactId' })"),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -1042,7 +1153,7 @@ class utils
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get target configuration file name (including full path)
|
||||
* @return string target configuration file name (including full path)
|
||||
*/
|
||||
public static function GetConfigFilePath($sEnvironment = null)
|
||||
{
|
||||
@@ -1052,10 +1163,20 @@ class utils
|
||||
}
|
||||
return APPCONF.$sEnvironment.'/'.ITOP_CONFIG_FILE;
|
||||
}
|
||||
/**
|
||||
* @return string target configuration file name (including relative path)
|
||||
*/
|
||||
public static function GetConfigFilePathRelative($sEnvironment = null)
|
||||
{
|
||||
if (is_null($sEnvironment))
|
||||
{
|
||||
$sEnvironment = self::GetCurrentEnvironment();
|
||||
}
|
||||
return "conf/".$sEnvironment.'/'.ITOP_CONFIG_FILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute URL to the modules root path
|
||||
* @return string ...
|
||||
* @return string the absolute URL to the modules root path
|
||||
*/
|
||||
static public function GetAbsoluteUrlModulesRoot()
|
||||
{
|
||||
@@ -1064,32 +1185,66 @@ class utils
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to a page that will execute the requested module page
|
||||
* To be compatible with this mechanism, the called page must include approot with an absolute path OR not include
|
||||
* it at all (losing the direct access to the page) :
|
||||
*
|
||||
* To be compatible with this mechanism, the called page must include approot
|
||||
* with an absolute path OR not include it at all (losing the direct access to the page)
|
||||
* ```php
|
||||
* if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
|
||||
* require_once(__DIR__.'/../../approot.inc.php');
|
||||
* ```
|
||||
*
|
||||
* @return string ...
|
||||
* @param string $sModule
|
||||
* @param string $sPage
|
||||
* @param string[] $aArguments
|
||||
* @param string $sEnvironment
|
||||
*
|
||||
* @return string the URL to a page that will execute the requested module page, with query string values url encoded
|
||||
*
|
||||
* @see GetExecPageArguments can be used to submit using the GET method (see bug in N.1108)
|
||||
* @see GetAbsoluteUrlExecPage
|
||||
*/
|
||||
static public function GetAbsoluteUrlModulePage($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
|
||||
{
|
||||
$aArgs = self::GetExecPageArguments($sModule, $sPage, $aArguments, $sEnvironment);
|
||||
$sArgs = http_build_query($aArgs);
|
||||
|
||||
return self::GetAbsoluteUrlExecPage()."?".$sArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sModule
|
||||
* @param string $sPage
|
||||
* @param string[] $aArguments
|
||||
* @param string $sEnvironment
|
||||
*
|
||||
* @return string[] key/value pair for the exec page query string. <b>Warning</b> : values are not url encoded !
|
||||
* @throws \Exception if one of the argument has a reserved name
|
||||
*/
|
||||
static public function GetExecPageArguments($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
|
||||
{
|
||||
$sEnvironment = is_null($sEnvironment) ? self::GetCurrentEnvironment() : $sEnvironment;
|
||||
$aArgs = array();
|
||||
$aArgs[] = 'exec_module='.$sModule;
|
||||
$aArgs[] = 'exec_page='.$sPage;
|
||||
$aArgs[] = 'exec_env='.$sEnvironment;
|
||||
$aArgs['exec_module'] = $sModule;
|
||||
$aArgs['exec_page'] = $sPage;
|
||||
$aArgs['exec_env'] = $sEnvironment;
|
||||
foreach($aArguments as $sName => $sValue)
|
||||
{
|
||||
if (($sName == 'exec_module') || ($sName == 'exec_page') || ($sName == 'exec_env'))
|
||||
{
|
||||
throw new Exception("Module page: $sName is a reserved page argument name");
|
||||
}
|
||||
$aArgs[] = $sName.'='.urlencode($sValue);
|
||||
$aArgs[$sName] = $sValue;
|
||||
}
|
||||
$sArgs = implode('&', $aArgs);
|
||||
return self::GetAbsoluteUrlAppRoot().'pages/exec.php?'.$sArgs;
|
||||
|
||||
return $aArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
static public function GetAbsoluteUrlExecPage()
|
||||
{
|
||||
return self::GetAbsoluteUrlAppRoot().'pages/exec.php';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1276,6 +1431,16 @@ class utils
|
||||
return $aPossibleEncodings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to encapsulation iTop's htmlentities
|
||||
* @param string $sValue
|
||||
* @return string
|
||||
*/
|
||||
static public function HtmlEntities($sValue)
|
||||
{
|
||||
return htmlentities($sValue, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string containing some (valid) HTML markup to plain text
|
||||
* @param string $sHtml
|
||||
@@ -1590,4 +1755,239 @@ class utils
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given path/url is an http(s) URL
|
||||
* @param string $sPath
|
||||
* @return boolean
|
||||
*/
|
||||
public static function IsURL($sPath)
|
||||
{
|
||||
$bRet = false;
|
||||
if ((substr($sPath, 0, 7) == 'http://') || (substr($sPath, 0, 8) == 'https://') || (substr($sPath, 0, 8) == 'ftp://'))
|
||||
{
|
||||
$bRet = true;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given URL is a link to download a document/image on the CURRENT iTop
|
||||
* In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
|
||||
* @param string $sPath
|
||||
* @return false|ormDocument
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function IsSelfURL($sPath)
|
||||
{
|
||||
$result = false;
|
||||
$sPageUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php';
|
||||
if (substr($sPath, 0, strlen($sPageUrl)) == $sPageUrl)
|
||||
{
|
||||
// If the URL is an URL pointing to this instance of iTop, then
|
||||
// extract the "query" part of the URL and analyze it
|
||||
$sQuery = parse_url($sPath, PHP_URL_QUERY);
|
||||
if ($sQuery !== null)
|
||||
{
|
||||
$aParams = array();
|
||||
foreach(explode('&', $sQuery) as $sChunk)
|
||||
{
|
||||
$aParts = explode('=', $sChunk);
|
||||
if (count($aParts) != 2) continue;
|
||||
$aParams[$aParts[0]] = urldecode($aParts[1]);
|
||||
}
|
||||
$result = array_key_exists('operation', $aParams) && array_key_exists('class', $aParams) && array_key_exists('id', $aParams) && array_key_exists('field', $aParams) && ($aParams['operation'] == 'download_document');
|
||||
if ($result)
|
||||
{
|
||||
// This is a 'download_document' operation, let's retrieve the document directly from the database
|
||||
$sClass = $aParams['class'];
|
||||
$iKey = $aParams['id'];
|
||||
$sAttCode = $aParams['field'];
|
||||
|
||||
$oObj = MetaModel::GetObject($sClass, $iKey, false /* must exist */); // Users rights apply here !!
|
||||
if ($oObj)
|
||||
{
|
||||
/**
|
||||
* @var ormDocument $result
|
||||
*/
|
||||
$result = clone $oObj->Get($sAttCode);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Exception('Invalid URL. This iTop URL is not pointing to a valid Document/Image.');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the content of a file (and retrieve its MIME type) from either:
|
||||
* - an URL pointing to a blob (image/document) on the current iTop server
|
||||
* - an http(s) URL
|
||||
* - the local file system (but only if you are an administrator)
|
||||
* @param string $sPath
|
||||
* @return ormDocument|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function FileGetContentsAndMIMEType($sPath)
|
||||
{
|
||||
$oUploadedDoc = null;
|
||||
$aKnownExtensions = array(
|
||||
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
|
||||
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
|
||||
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'pdf' => 'application/pdf',
|
||||
'doc' => 'application/msword',
|
||||
'dot' => 'application/msword',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'ppt' => 'application/vnd.ms-powerpoint',
|
||||
'vsd' => 'application/x-visio',
|
||||
'vdx' => 'application/visio.drawing',
|
||||
'odt' => 'application/vnd.oasis.opendocument.text',
|
||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'odp' => 'application/vnd.oasis.opendocument.presentation',
|
||||
'zip' => 'application/zip',
|
||||
'txt' => 'text/plain',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'exe' => 'application/octet-stream'
|
||||
);
|
||||
|
||||
$sData = null;
|
||||
$sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
|
||||
$sFileName = 'uploaded-file'; // Default name for downloaded-files
|
||||
$sExtension = '.txt'; // Default file extension in case we don't know the MIME Type
|
||||
|
||||
if(empty($sPath))
|
||||
{
|
||||
// Empty path (NULL or '') means that there is no input, making an empty document.
|
||||
$oUploadedDoc = new ormDocument('', '', '');
|
||||
}
|
||||
elseif (static::IsURL($sPath))
|
||||
{
|
||||
if ($oUploadedDoc = static::IsSelfURL($sPath))
|
||||
{
|
||||
// Nothing more to do, we've got it !!
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remote file, let's use the HTTP headers to find the MIME Type
|
||||
$sData = @file_get_contents($sPath);
|
||||
if ($sData === false)
|
||||
{
|
||||
throw new Exception("Failed to load the file from the URL '$sPath'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isset($http_response_header))
|
||||
{
|
||||
$aHeaders = static::ParseHeaders($http_response_header);
|
||||
$sMimeType = array_key_exists('Content-Type', $aHeaders) ? strtolower($aHeaders['Content-Type']) : 'application/x-octet-stream';
|
||||
// Compute the file extension from the MIME Type
|
||||
foreach($aKnownExtensions as $sExtValue => $sMime)
|
||||
{
|
||||
if ($sMime === $sMimeType)
|
||||
{
|
||||
$sExtension = '.'.$sExtValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$sFileName .= $sExtension;
|
||||
}
|
||||
$oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
|
||||
}
|
||||
}
|
||||
else if (UserRights::IsAdministrator())
|
||||
{
|
||||
// Only administrators are allowed to read local files
|
||||
$sData = @file_get_contents($sPath);
|
||||
if ($sData === false)
|
||||
{
|
||||
throw new Exception("Failed to load the file '$sPath'. The file does not exist or the current process is not allowed to access it.");
|
||||
}
|
||||
$sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
|
||||
$sFileName = basename($sPath);
|
||||
|
||||
if (array_key_exists($sExtension, $aKnownExtensions))
|
||||
{
|
||||
$sMimeType = $aKnownExtensions[$sExtension];
|
||||
}
|
||||
else if (extension_loaded('fileinfo'))
|
||||
{
|
||||
$finfo = new finfo(FILEINFO_MIME);
|
||||
$sMimeType = $finfo->file($sPath);
|
||||
}
|
||||
$oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
|
||||
}
|
||||
return $oUploadedDoc;
|
||||
}
|
||||
|
||||
protected static function ParseHeaders($aHeaders)
|
||||
{
|
||||
$aCleanHeaders = array();
|
||||
foreach( $aHeaders as $sKey => $sValue )
|
||||
{
|
||||
$aTokens = explode(':', $sValue, 2);
|
||||
if(isset($aTokens[1]))
|
||||
{
|
||||
$aCleanHeaders[trim($aTokens[0])] = trim($aTokens[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The header is not in the form Header-Code: Value
|
||||
$aCleanHeaders[] = $sValue; // Store the value as-is
|
||||
$aMatches = array();
|
||||
// Check if it's not the HTTP response code
|
||||
if( preg_match("|HTTP/[0-9\.]+\s+([0-9]+)|", $sValue, $aMatches) )
|
||||
{
|
||||
$aCleanHeaders['reponse_code'] = intval($aMatches[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $aCleanHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string based on compilation time or (if not available because the datamodel has not been loaded)
|
||||
* the version of iTop. This string is useful to prevent browser side caching of content that may vary at each
|
||||
* (re)installation of iTop (especially during development).
|
||||
* @return string
|
||||
*/
|
||||
public static function GetCacheBusterTimestamp()
|
||||
{
|
||||
if(!defined('COMPILATION_TIMESTAMP'))
|
||||
{
|
||||
return ITOP_VERSION;
|
||||
}
|
||||
return COMPILATION_TIMESTAMP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given class if configured as a high cardinality class.
|
||||
*
|
||||
* @param $sClass
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function IsHighCardinality($sClass)
|
||||
{
|
||||
if (utils::GetConfig()->Get('search_manual_submit'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
$aHugeClasses = MetaModel::GetConfig()->Get('high_cardinality_classes');
|
||||
return in_array($sClass, $aHugeClasses);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,25 +31,32 @@
|
||||
Interface Page
|
||||
{
|
||||
public function output();
|
||||
|
||||
public function add($sText);
|
||||
|
||||
public function p($sText);
|
||||
|
||||
public function pre($sText);
|
||||
|
||||
public function add_comment($sText);
|
||||
|
||||
public function table($aConfig, $aData, $aParams = array());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Simple helper class to ease the production of HTML pages
|
||||
* <p>Simple helper class to ease the production of HTML pages
|
||||
*
|
||||
* This class provide methods to add content, scripts, includes... to a web page
|
||||
* <p>This class provide methods to add content, scripts, includes... to a web page
|
||||
* and renders the full web page by putting the elements in the proper place & order
|
||||
* when the output() method is called.
|
||||
* Usage:
|
||||
*
|
||||
* <p>Usage:
|
||||
* ```php
|
||||
* $oPage = new WebPage("Title of my page");
|
||||
* $oPage->p("Hello World !");
|
||||
* $oPage->output();
|
||||
* ```
|
||||
*/
|
||||
class WebPage implements Page
|
||||
{
|
||||
@@ -58,9 +65,10 @@ class WebPage implements Page
|
||||
protected $s_deferred_content;
|
||||
protected $a_scripts;
|
||||
protected $a_dict_entries;
|
||||
protected $a_dict_entries_prefixes;
|
||||
protected $a_styles;
|
||||
protected $a_include_scripts;
|
||||
protected $a_include_stylesheets;
|
||||
protected $a_linked_scripts;
|
||||
protected $a_linked_stylesheets;
|
||||
protected $a_headers;
|
||||
protected $a_base;
|
||||
protected $iNextId;
|
||||
@@ -80,6 +88,7 @@ class WebPage implements Page
|
||||
$this->s_deferred_content = '';
|
||||
$this->a_scripts = array();
|
||||
$this->a_dict_entries = array();
|
||||
$this->a_dict_entries_prefixes = array();
|
||||
$this->a_styles = array();
|
||||
$this->a_linked_scripts = array();
|
||||
$this->a_linked_stylesheets = array();
|
||||
@@ -155,6 +164,7 @@ class WebPage implements Page
|
||||
{
|
||||
$this->add('<!--'.$sText.'-->');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a paragraph to the body of the page
|
||||
*/
|
||||
@@ -165,9 +175,12 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Adds a tabular content to the web page
|
||||
* @param Hash $aConfig Configuration of the table: hash array of 'column_id' => 'Column Label'
|
||||
* @param Hash $aData Hash array. Data to display in the table: each row is made of 'column_id' => Data. A column 'pkey' is expected for each row
|
||||
* @param Hash $aParams Hash array. Extra parameters for the table.
|
||||
*
|
||||
* @param string[] $aConfig Configuration of the table: hash array of 'column_id' => 'Column Label'
|
||||
* @param string[] $aData Hash array. Data to display in the table: each row is made of 'column_id' => Data. A
|
||||
* column 'pkey' is expected for each row
|
||||
* @param array $aParams Hash array. Extra parameters for the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function table($aConfig, $aData, $aParams = array())
|
||||
@@ -198,6 +211,7 @@ class WebPage implements Page
|
||||
}
|
||||
$sHtml .= "</tbody>\n";
|
||||
$sHtml .= "</table>\n";
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -219,6 +233,7 @@ class WebPage implements Page
|
||||
$sHtml .= "<td $sClass>$sValue</td>";
|
||||
}
|
||||
$sHtml .= "</tr>";
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -239,11 +254,51 @@ class WebPage implements Page
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a dictionary entry for the Javascript side
|
||||
* Allow a dictionnary entry to be used client side with Dict.S()
|
||||
*
|
||||
* @param string $s_entryId a translation label key
|
||||
*
|
||||
* @see \WebPage::add_dict_entries()
|
||||
* @see utils.js
|
||||
*/
|
||||
public function add_dict_entry($s_entryId)
|
||||
{
|
||||
$this->a_dict_entries[$s_entryId] = Dict::S($s_entryId);
|
||||
$this->a_dict_entries[] = $s_entryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a set of dictionary entries (based on the given prefix) for the Javascript side
|
||||
*
|
||||
* @param string $s_entriesPrefix translation label prefix (eg 'UI:Button:' to add all keys beginning with this)
|
||||
*
|
||||
* @see \WebPage::add_dict_entry()
|
||||
* @see utils.js
|
||||
*/
|
||||
public function add_dict_entries($s_entriesPrefix)
|
||||
{
|
||||
$this->a_dict_entries_prefixes[] = $s_entriesPrefix;
|
||||
}
|
||||
|
||||
protected function get_dict_signature()
|
||||
{
|
||||
return str_replace('_', '', Dict::GetUserLanguage()).'-'.md5(implode(',',
|
||||
$this->a_dict_entries).'|'.implode(',', $this->a_dict_entries_prefixes));
|
||||
}
|
||||
|
||||
protected function get_dict_file_content()
|
||||
{
|
||||
$aEntries = array();
|
||||
foreach ($this->a_dict_entries as $sCode)
|
||||
{
|
||||
$aEntries[$sCode] = Dict::S($sCode);
|
||||
}
|
||||
foreach ($this->a_dict_entries_prefixes as $sPrefix)
|
||||
{
|
||||
$aEntries = array_merge($aEntries, Dict::ExportEntries($sPrefix));
|
||||
}
|
||||
$sJSFile = 'var aDictEntries = '.json_encode($aEntries);
|
||||
|
||||
return $sJSFile;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,6 +338,7 @@ class WebPage implements Page
|
||||
$sCSSUrl = $sRootUrl.$sCssRelPath;
|
||||
$this->add_linked_stylesheet($sCSSUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some custom header to the page
|
||||
*/
|
||||
@@ -311,6 +367,7 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Whether or not the page is a PDF page
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_pdf()
|
||||
@@ -320,6 +377,7 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Records the current state of the 'html' part of the page output
|
||||
*
|
||||
* @return mixed The current state of the 'html' output
|
||||
*/
|
||||
public function start_capture()
|
||||
@@ -330,13 +388,16 @@ class WebPage implements Page
|
||||
/**
|
||||
* Returns the part of the html output that occurred since the call to start_capture
|
||||
* and removes this part from the current html output
|
||||
*
|
||||
* @param $offset mixed The value returned by start_capture
|
||||
*
|
||||
* @return string The part of the html output that was added since the call to start_capture
|
||||
*/
|
||||
public function end_capture($offset)
|
||||
{
|
||||
$sCaptured = substr($this->s_content, $offset);
|
||||
$this->s_content = substr($this->s_content, 0, $offset);
|
||||
|
||||
return $sCaptured;
|
||||
}
|
||||
|
||||
@@ -345,7 +406,7 @@ class WebPage implements Page
|
||||
*/
|
||||
public function GetDetails($aFields)
|
||||
{
|
||||
$sHtml = "<div class=\"details\">\n";
|
||||
$sHtml = "<div class=\"details\" id='search-widget-results-outer'>\n";
|
||||
foreach ($aFields as $aAttrib)
|
||||
{
|
||||
$sDataAttCode = isset($aAttrib['attcode']) ? "data-attcode=\"{$aAttrib['attcode']}\"" : '';
|
||||
@@ -379,11 +440,13 @@ class WebPage implements Page
|
||||
$sHtml .= "</div>\n";
|
||||
}
|
||||
$sHtml .= "</div>\n";
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of radio buttons suitable for editing a field/attribute of an object (including its validation)
|
||||
*
|
||||
* @param $aAllowedValues hash Array of value => display_value
|
||||
* @param $value mixed Current value for the field/attribute
|
||||
* @param $iId mixed Unique Id for the input control in the page
|
||||
@@ -391,10 +454,12 @@ class WebPage implements Page
|
||||
* @param $bMandatory bool Whether or not the field is mandatory
|
||||
* @param $bVertical bool Disposition of the radio buttons vertical or horizontal
|
||||
* @param $sValidationField string HTML fragment holding the validation field (exclamation icon...)
|
||||
*
|
||||
* @return string The HTML fragment corresponding to the radio buttons
|
||||
*/
|
||||
public function GetRadioButtons($aAllowedValues, $value, $iId, $sFieldName, $bMandatory, $bVertical, $sValidationField)
|
||||
{
|
||||
public function GetRadioButtons(
|
||||
$aAllowedValues, $value, $iId, $sFieldName, $bMandatory, $bVertical, $sValidationField
|
||||
) {
|
||||
$idx = 0;
|
||||
$sHTMLValue = '';
|
||||
foreach ($aAllowedValues as $key => $display_value)
|
||||
@@ -426,6 +491,7 @@ class WebPage implements Page
|
||||
// Validation icon at the end of the line
|
||||
$sHTMLValue .= " {$sValidationField}\n";
|
||||
}
|
||||
|
||||
return $sHTMLValue;
|
||||
}
|
||||
|
||||
@@ -478,6 +544,7 @@ class WebPage implements Page
|
||||
$sOutput = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $sOutput;
|
||||
}
|
||||
|
||||
@@ -499,17 +566,20 @@ class WebPage implements Page
|
||||
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();
|
||||
|
||||
$this->output_dict_entries();
|
||||
|
||||
foreach ($this->a_linked_scripts as $s_script)
|
||||
{
|
||||
// Make sure that the URL to the script contains the application's version number
|
||||
// so that the new script do NOT get reloaded from the cache when the application is upgraded
|
||||
if (strpos($s_script, '?') === false)
|
||||
{
|
||||
$s_script .= "?itopversion=".ITOP_VERSION;
|
||||
$s_script .= "?t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
$s_script .= "&itopversion=".ITOP_VERSION;
|
||||
$s_script .= "&t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
echo "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
|
||||
}
|
||||
@@ -522,16 +592,15 @@ class WebPage implements Page
|
||||
}
|
||||
echo "</script>\n";
|
||||
}
|
||||
$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;
|
||||
$s_stylesheet = $a_stylesheet['link']."?t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
$s_stylesheet = $a_stylesheet['link']."&itopversion=".ITOP_VERSION;
|
||||
$s_stylesheet = $a_stylesheet['link']."&t=".utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
@@ -555,7 +624,7 @@ class WebPage implements Page
|
||||
}
|
||||
if (class_exists('MetaModel') && MetaModel::GetConfig())
|
||||
{
|
||||
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico?itopversion=".ITOP_VERSION."\" />\n";
|
||||
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico?t=".utils::GetCacheBusterTimestamp()."\" />\n";
|
||||
}
|
||||
echo "</head>\n";
|
||||
echo "<body>\n";
|
||||
@@ -610,11 +679,13 @@ class WebPage implements Page
|
||||
}
|
||||
$sTag .= " />\n";
|
||||
}
|
||||
|
||||
return $sTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ID (for any kind of HTML tag) that is guaranteed unique in this page
|
||||
*
|
||||
* @return int The unique ID (in this page)
|
||||
*/
|
||||
public function GetUniqueId()
|
||||
@@ -624,7 +695,9 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Set the content-type (mime type) for the page's content
|
||||
*
|
||||
* @param $sContentType string
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetContentType($sContentType)
|
||||
@@ -634,8 +707,10 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Set the content-disposition (mime type) for the page's content
|
||||
*
|
||||
* @param $sDisposition string The disposition: 'inline' or 'attachment'
|
||||
* @param $sFileName string The original name of the file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetContentDisposition($sDisposition, $sFileName)
|
||||
@@ -646,7 +721,9 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Set the transactionId of the current form
|
||||
*
|
||||
* @param $iTransactionId integer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetTransactionId($iTransactionId)
|
||||
@@ -656,6 +733,7 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Returns the transactionId of the current form
|
||||
*
|
||||
* @return integer The current transactionID
|
||||
*/
|
||||
public function GetTransactionId()
|
||||
@@ -670,6 +748,7 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* What is the currently selected output format
|
||||
*
|
||||
* @return string The selected output format: html, pdf...
|
||||
*/
|
||||
public function GetOutputFormat()
|
||||
@@ -679,7 +758,9 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Check whether the desired output format is possible or not
|
||||
*
|
||||
* @param string $sOutputFormat The desired output format: html, pdf...
|
||||
*
|
||||
* @return bool True if the format is Ok, false otherwise
|
||||
*/
|
||||
function IsOutputFormatAvailable($sOutputFormat)
|
||||
@@ -695,11 +776,13 @@ class WebPage implements Page
|
||||
$bResult = @is_readable(APPROOT.'lib/MPDF/mpdf.php');
|
||||
break;
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the output must be printable (using print.css, for sure!)
|
||||
*
|
||||
* @return bool ...
|
||||
*/
|
||||
public function IsPrintableVersion()
|
||||
@@ -709,8 +792,10 @@ class WebPage implements Page
|
||||
|
||||
/**
|
||||
* Retrieves the value of a named output option for the given format
|
||||
*
|
||||
* @param string $sFormat The format: html or pdf
|
||||
* @param string $sOptionName The name of the option
|
||||
*
|
||||
* @return mixed false if the option was never set or the options's value
|
||||
*/
|
||||
public function GetOutputOption($sFormat, $sOptionName)
|
||||
@@ -719,10 +804,13 @@ class WebPage implements Page
|
||||
{
|
||||
return $this->a_OutputOptions[$sFormat][$sOptionName];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a named output option for the given format
|
||||
*
|
||||
* @param string $sFormat The format for which to set the option: html or pdf
|
||||
* @param string $sOptionName the name of the option
|
||||
* @param mixed $sValue The value of the option
|
||||
@@ -748,7 +836,8 @@ class WebPage implements Page
|
||||
foreach ($aActions as $aAction)
|
||||
{
|
||||
$sClass = isset($aAction['css_classes']) ? ' class="'.implode(' ', $aAction['css_classes']).'"' : '';
|
||||
$sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : '';
|
||||
$sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES,
|
||||
"UTF-8").'"' : '';
|
||||
$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
|
||||
if (empty($aAction['url']))
|
||||
{
|
||||
@@ -771,41 +860,28 @@ class WebPage implements Page
|
||||
$sHtml .= "<div class=\"actions_button\"><a $sTarget href='{$aAction['url']}'>{$aAction['label']}</a></div>";
|
||||
}
|
||||
}
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
protected function output_dict_entries($bReturnOutput = false)
|
||||
{
|
||||
$sHtml = '';
|
||||
if (count($this->a_dict_entries)>0)
|
||||
if ((count($this->a_dict_entries) > 0) || (count($this->a_dict_entries_prefixes) > 0))
|
||||
{
|
||||
$sHtml .= "<script type=\"text/javascript\">\n";
|
||||
$sHtml .= "var Dict = {};\n";
|
||||
$sHtml .= "Dict._entries = {};\n";
|
||||
$sHtml .= "Dict.S = function(sEntry) {\n";
|
||||
$sHtml .= " if (sEntry in Dict._entries)\n";
|
||||
$sHtml .= " {\n";
|
||||
$sHtml .= " return Dict._entries[sEntry];\n";
|
||||
$sHtml .= " }\n";
|
||||
$sHtml .= " else\n";
|
||||
$sHtml .= " {\n";
|
||||
$sHtml .= " return sEntry;\n";
|
||||
$sHtml .= " }\n";
|
||||
$sHtml .= "};\n";
|
||||
foreach($this->a_dict_entries as $s_entry => $s_value)
|
||||
if (class_exists('Dict'))
|
||||
{
|
||||
$sHtml .= "Dict._entries['$s_entry'] = '".addslashes($s_value)."';\n";
|
||||
// The dictionary may not be available for example during the setup...
|
||||
// Create a specific dictionary file and load it as a JS script
|
||||
$sSignature = $this->get_dict_signature();
|
||||
$sJSFileName = utils::GetCachePath().$sSignature.'.js';
|
||||
if (!file_exists($sJSFileName) && is_writable(utils::GetCachePath()))
|
||||
{
|
||||
file_put_contents($sJSFileName, $this->get_dict_file_content());
|
||||
}
|
||||
$sHtml .= "</script>\n";
|
||||
// Load the dictionary as the first javascript file, so that other JS file benefit from the translations
|
||||
array_unshift($this->a_linked_scripts,
|
||||
utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php?operation=dict&s='.$sSignature);
|
||||
}
|
||||
|
||||
if ($bReturnOutput)
|
||||
{
|
||||
return $sHtml;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo $sHtml;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -825,12 +901,14 @@ interface iTabbedPage
|
||||
* Add a tab which content will be loaded asynchronously via the supplied URL
|
||||
*
|
||||
* Limitations:
|
||||
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to pull content from another server.
|
||||
* Static content cannot be added inside such tabs.
|
||||
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to
|
||||
* pull content from another server. Static content cannot be added inside such tabs.
|
||||
*
|
||||
* @param string $sTabLabel The (localised) label of the tab
|
||||
* @param string $sUrl The URL to load (on the same server)
|
||||
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause the tab to be reloaded upon each activation.
|
||||
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause
|
||||
* the tab to be reloaded upon each activation.
|
||||
*
|
||||
* @since 2.0.3
|
||||
*/
|
||||
public function AddAjaxTab($sTabLabel, $sUrl, $bCache = true);
|
||||
@@ -841,6 +919,7 @@ interface iTabbedPage
|
||||
|
||||
/**
|
||||
* Finds the tab whose title matches a given pattern
|
||||
*
|
||||
* @return mixed The name of the tab as a string or false if not found
|
||||
*/
|
||||
public function FindTab($sPattern, $sTabContainer = null);
|
||||
@@ -865,6 +944,7 @@ class TabManager
|
||||
public function AddTabContainer($sTabContainer, $sPrefix = '')
|
||||
{
|
||||
$this->m_aTabs[$sTabContainer] = array('prefix' => $sPrefix, 'tabs' => array());
|
||||
|
||||
return "\$Tabs:$sTabContainer\$";
|
||||
}
|
||||
|
||||
@@ -876,20 +956,26 @@ class TabManager
|
||||
public function GetCurrentTabLength($sHtml)
|
||||
{
|
||||
$iLength = isset($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html']) ? strlen($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html']) : 0;
|
||||
|
||||
return $iLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates the given tab to the specifed length and returns the truncated part
|
||||
*
|
||||
* @param string $sTabContainer The tab container in which to truncate the tab
|
||||
* @param string $sTab The name/identifier of the tab to truncate
|
||||
* @param integer $iLength The length/offset at which to truncate the tab
|
||||
*
|
||||
* @return string The truncated part
|
||||
*/
|
||||
public function TruncateTab($sTabContainer, $sTab, $iLength)
|
||||
{
|
||||
$sResult = substr($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'], $iLength);
|
||||
$this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'] = substr($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'], 0, $iLength);
|
||||
$sResult = substr($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'],
|
||||
$iLength);
|
||||
$this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'] = substr($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'],
|
||||
0, $iLength);
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
@@ -922,6 +1008,7 @@ class TabManager
|
||||
// Append to the content of the tab
|
||||
$this->m_aTabs[$sTabContainer]['tabs'][$sTabLabel]['html'] .= $sHtml;
|
||||
}
|
||||
|
||||
return ''; // Nothing to add to the page for now
|
||||
}
|
||||
|
||||
@@ -929,6 +1016,7 @@ class TabManager
|
||||
{
|
||||
$sPreviousTabContainer = $this->m_sCurrentTabContainer;
|
||||
$this->m_sCurrentTabContainer = $sTabContainer;
|
||||
|
||||
return $sPreviousTabContainer;
|
||||
}
|
||||
|
||||
@@ -936,6 +1024,7 @@ class TabManager
|
||||
{
|
||||
$sPreviousTab = $this->m_sCurrentTab;
|
||||
$this->m_sCurrentTab = $sTabLabel;
|
||||
|
||||
return $sPreviousTab;
|
||||
}
|
||||
|
||||
@@ -943,12 +1032,14 @@ class TabManager
|
||||
* Add a tab which content will be loaded asynchronously via the supplied URL
|
||||
*
|
||||
* Limitations:
|
||||
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to pull content from another server.
|
||||
* Static content cannot be added inside such tabs.
|
||||
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to
|
||||
* pull content from another server. Static content cannot be added inside such tabs.
|
||||
*
|
||||
* @param string $sTabLabel The (localised) label of the tab
|
||||
* @param string $sUrl The URL to load (on the same server)
|
||||
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause the tab to be reloaded upon each activation.
|
||||
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause
|
||||
* the tab to be reloaded upon each activation.
|
||||
*
|
||||
* @since 2.0.3
|
||||
*/
|
||||
public function AddAjaxTab($sTabLabel, $sUrl, $bCache = true)
|
||||
@@ -959,6 +1050,7 @@ class TabManager
|
||||
'url' => $sUrl,
|
||||
'cache' => $bCache,
|
||||
);
|
||||
|
||||
return ''; // Nothing to add to the page for now
|
||||
}
|
||||
|
||||
@@ -994,6 +1086,7 @@ class TabManager
|
||||
|
||||
/**
|
||||
* Finds the tab whose title matches a given pattern
|
||||
*
|
||||
* @return mixed The actual name of the tab (as a string) or false if not found
|
||||
*/
|
||||
public function FindTab($sPattern, $sTabContainer = null)
|
||||
@@ -1011,6 +1104,7 @@ class TabManager
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -1041,6 +1135,7 @@ class TabManager
|
||||
$container_index++;
|
||||
}
|
||||
$sSelector = '#tabbedContent_'.$container_index.' > ul';
|
||||
|
||||
return "window.setTimeout(\"$('$sSelector').tabs('select', $tab_index);\", 100);"; // Let the time to the tabs widget to initialize
|
||||
}
|
||||
|
||||
@@ -1085,7 +1180,9 @@ EOF
|
||||
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";
|
||||
$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';
|
||||
@@ -1106,12 +1203,14 @@ EOF
|
||||
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";
|
||||
$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";
|
||||
$sTabs .= "<li><a href=\"#tab_{$sPrefix}{$container_index}$i\" class=\"tab\"><span>".htmlentities($sTabName,
|
||||
ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
@@ -1138,6 +1237,7 @@ EOF
|
||||
$sContent = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $sContent);
|
||||
$container_index++;
|
||||
}
|
||||
|
||||
return $sContent;
|
||||
}
|
||||
}
|
||||
@@ -86,13 +86,21 @@ class WizardHelper
|
||||
}
|
||||
elseif($sLinkedAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$sDateClass = get_class($sLinkedAttDef);
|
||||
$sDate = $aLinkedObject[$sLinkedAttCode];
|
||||
if($sDate !== null && $sDate !== '')
|
||||
{
|
||||
$oDateTimeFormat = AttributeDateTime::GetFormat();
|
||||
$oDateTimeFormat = $sDateClass::GetFormat();
|
||||
$oDate = $oDateTimeFormat->Parse($sDate);
|
||||
if ($sDateClass == "AttributeDate")
|
||||
{
|
||||
$sDate = $oDate->format('Y-m-d');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDate = $oDate->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
|
||||
$oLinkedObj->Set($sLinkedAttCode, $sDate);
|
||||
}
|
||||
|
||||
19
composer.json
Normal file
19
composer.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"ext-mysql": "*",
|
||||
"ext-ldap": "*",
|
||||
"ext-mcrypt": "*",
|
||||
"ext-cli": "*",
|
||||
"ext-soap": "*",
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-gd": "*"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ abstract class Action extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"category" => "grant_by_profile,core/cmdb",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -60,7 +60,7 @@ abstract class Action extends cmdbAbstractObject
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // 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('default_search', array('name', 'description', 'status')); // Criteria of the std search form
|
||||
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ abstract class ActionNotification extends Action
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"category" => "grant_by_profile,core/cmdb",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -136,7 +136,7 @@ class ActionEmail extends ActionNotification
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,application",
|
||||
"category" => "grant_by_profile,core/cmdb,application",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -262,22 +262,34 @@ class ActionEmail extends ActionNotification
|
||||
{
|
||||
$sPrefix = '';
|
||||
}
|
||||
|
||||
if ($oLog)
|
||||
{
|
||||
$oLog->Set('message', $sPrefix . $sRes);
|
||||
$oLog->DBUpdate();
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
if ($oLog)
|
||||
{
|
||||
$oLog->Set('message', 'Error: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
if ($oLog)
|
||||
|
||||
try
|
||||
{
|
||||
$oLog->DBUpdate();
|
||||
}
|
||||
catch (Exception $eSecondTryUpdate)
|
||||
{
|
||||
IssueLog::Error("Failed to process email ".$oLog->GetKey()." - reason: ".$e->getMessage()."\nTrace:\n".$e->getTraceAsString());
|
||||
|
||||
$oLog->Set('message', 'Error: more details in the log for email "'.$oLog->GetKey().'"');
|
||||
$oLog->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
|
||||
@@ -348,7 +360,7 @@ class ActionEmail extends ActionNotification
|
||||
$sTestBody .= "</div>\n";
|
||||
$oEmail->SetBody($sTestBody, 'text/html', $sStyles);
|
||||
$oEmail->SetRecipientTO($this->Get('test_recipient'));
|
||||
$oEmail->SetRecipientFrom($this->Get('test_recipient'));
|
||||
$oEmail->SetRecipientFrom($sFrom);
|
||||
$oEmail->SetReferences($sReference);
|
||||
$oEmail->SetMessageId($sMessageId);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
// Copyright (C) 2016-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -67,3 +67,32 @@ if (!function_exists('apc_store') && function_exists('apcu_store'))
|
||||
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');
|
||||
}
|
||||
333
core/apc-emulation.php
Normal file
333
core/apc-emulation.php
Normal file
@@ -0,0 +1,333 @@
|
||||
<?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 = apcFile::GetCacheFileName();
|
||||
$aInfo['cache_list'] = apcFile::GetCacheEntries($sRootCacheDir);
|
||||
return $aInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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[] = apcFile::StoreOneFile($sKey, $value, $ttl);
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
return apcFile::StoreOneFile($key, $var, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key string|array
|
||||
* @return mixed
|
||||
*/
|
||||
function apc_fetch($key)
|
||||
{
|
||||
if (is_array($key))
|
||||
{
|
||||
$aResult = array();
|
||||
foreach($key as $sKey)
|
||||
{
|
||||
$aResult[$sKey] = apcFile::FetchOneFile($sKey);
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
return apcFile::FetchOneFile($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cache_type
|
||||
* @return bool
|
||||
*/
|
||||
function apc_clear_cache($cache_type = '')
|
||||
{
|
||||
apcFile::DeleteEntry(utils::GetCachePath());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return bool|string[]
|
||||
*/
|
||||
function apc_delete($key)
|
||||
{
|
||||
if (empty($key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$bRet1 = apcFile::DeleteEntry(apcFile::GetCacheFileName($key));
|
||||
$bRet2 = apcFile::DeleteEntry(apcFile::GetCacheFileName('-'.$key));
|
||||
return $bRet1 || $bRet2;
|
||||
}
|
||||
|
||||
class apcFile
|
||||
{
|
||||
// Check only once per request
|
||||
static public $aFilesByTime = null;
|
||||
static public $iFileCount = 0;
|
||||
|
||||
/** Get the file name corresponding to the cache entry.
|
||||
* If an empty key is provided, the root of the cache is returned.
|
||||
* @param $sKey
|
||||
* @return string
|
||||
*/
|
||||
static public function GetCacheFileName($sKey = '')
|
||||
{
|
||||
$sPath = str_replace(array(' ', '/', '\\', '.'), '-', $sKey);
|
||||
return utils::GetCachePath().'apc-emul/'.$sPath;
|
||||
}
|
||||
|
||||
/** Get the list of entries from a starting folder.
|
||||
* @param $sEntry string starting folder.
|
||||
* @return array list of entries stored into array of key 'info'
|
||||
*/
|
||||
static public function GetCacheEntries($sEntry)
|
||||
{
|
||||
$aResult = array();
|
||||
if (is_dir($sEntry))
|
||||
{
|
||||
$aFiles = array_diff(scandir($sEntry), array('.', '..'));
|
||||
foreach($aFiles as $sFile)
|
||||
{
|
||||
$sSubFile = $sEntry.'/'.$sFile;
|
||||
$aResult = array_merge($aResult, self::GetCacheEntries($sSubFile));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sKey = basename($sEntry);
|
||||
if (strpos($sKey, '-') === 0)
|
||||
{
|
||||
$sKey = substr($sKey, 1);
|
||||
}
|
||||
$aResult[] = array('info' => $sKey);
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/** Delete one cache entry.
|
||||
* @param $sCache
|
||||
* @return bool true if the entry was deleted false if error occurs (like entry did not exist).
|
||||
*/
|
||||
static public function DeleteEntry($sCache)
|
||||
{
|
||||
if (is_dir($sCache))
|
||||
{
|
||||
$aFiles = array_diff(scandir($sCache), array('.', '..'));
|
||||
foreach($aFiles as $sFile)
|
||||
{
|
||||
$sSubFile = $sCache.'/'.$sFile;
|
||||
if (!self::DeleteEntry($sSubFile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!@rmdir($sCache))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!@unlink($sCache))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self::ResetFileCount();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Get one cache entry content.
|
||||
* @param $sKey
|
||||
* @return bool|mixed
|
||||
*/
|
||||
static public function FetchOneFile($sKey)
|
||||
{
|
||||
// Try the 'TTLed' version
|
||||
$sValue = self::ReadCacheLocked(self::GetCacheFileName('-'.$sKey));
|
||||
if ($sValue === false)
|
||||
{
|
||||
$sValue = self::ReadCacheLocked(self::GetCacheFileName($sKey));
|
||||
if ($sValue === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$oRes = @unserialize($sValue);
|
||||
return $oRes;
|
||||
}
|
||||
|
||||
/** Add one cache entry.
|
||||
* @param string $sKey
|
||||
* @param $value
|
||||
* @param int $iTTL time to live
|
||||
* @return bool
|
||||
*/
|
||||
static public function StoreOneFile($sKey, $value, $iTTL)
|
||||
{
|
||||
if (empty($sKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@unlink(self::GetCacheFileName($sKey));
|
||||
@unlink(self::GetCacheFileName('-'.$sKey));
|
||||
if ($iTTL > 0)
|
||||
{
|
||||
// hint for ttl management
|
||||
$sKey = '-'.$sKey;
|
||||
}
|
||||
|
||||
$sFilename = self::GetCacheFileName($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);
|
||||
self::AddFile($sFilename);
|
||||
return $bRes;
|
||||
}
|
||||
|
||||
/** Manage the cache files when adding a new cache entry:
|
||||
* remove older files if the mamximum is reached.
|
||||
* @param $sNewFilename
|
||||
*/
|
||||
static protected function AddFile($sNewFilename)
|
||||
{
|
||||
if (strpos(basename($sNewFilename), '-') !== 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$iMaxFiles = MetaModel::GetConfig()->Get('apc_cache_emulation.max_entries');
|
||||
if ($iMaxFiles == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!self::$aFilesByTime)
|
||||
{
|
||||
self::ListFilesByTime();
|
||||
self::$iFileCount = count(self::$aFilesByTime);
|
||||
if ($iMaxFiles !== 0)
|
||||
{
|
||||
asort(self::$aFilesByTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$aFilesByTime[$sNewFilename] = time();
|
||||
self::$iFileCount++;
|
||||
}
|
||||
if (self::$iFileCount > $iMaxFiles)
|
||||
{
|
||||
$iFileNbToRemove = self::$iFileCount - $iMaxFiles;
|
||||
foreach(self::$aFilesByTime as $sFileToRemove => $iTime)
|
||||
{
|
||||
@unlink($sFileToRemove);
|
||||
if (--$iFileNbToRemove === 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
self::$aFilesByTime = array_slice(self::$aFilesByTime, self::$iFileCount - $iMaxFiles, null, true);
|
||||
self::$iFileCount = $iMaxFiles;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the list of files with their associated access time
|
||||
* @param string $sCheck Directory to scan
|
||||
*/
|
||||
static protected function ListFilesByTime($sCheck = null)
|
||||
{
|
||||
if (empty($sCheck))
|
||||
{
|
||||
$sCheck = self::GetCacheFileName();
|
||||
}
|
||||
// Garbage collection
|
||||
$aFiles = array_diff(@scandir($sCheck), array('.', '..'));
|
||||
foreach($aFiles as $sFile)
|
||||
{
|
||||
$sSubFile = $sCheck.'/'.$sFile;
|
||||
if (is_dir($sSubFile))
|
||||
{
|
||||
self::ListFilesByTime($sSubFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strpos(basename($sSubFile), '-') === 0)
|
||||
{
|
||||
self::$aFilesByTime[$sSubFile] = @fileatime($sSubFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Read the content of one cache file under lock protection
|
||||
* @param $sFilename
|
||||
* @return bool|string the content of the cache entry or false if error
|
||||
*/
|
||||
static protected function ReadCacheLocked($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;
|
||||
}
|
||||
|
||||
static protected function ResetFileCount()
|
||||
{
|
||||
self::$aFilesByTime = null;
|
||||
self::$iFileCount = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -168,6 +168,7 @@ abstract class AsyncTask extends DBObject
|
||||
$aConfig = $aRetries[get_class($this)];
|
||||
$iMaxRetries = $aConfig['max_retries'];
|
||||
}
|
||||
return $iMaxRetries;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,8 +205,7 @@ abstract class AsyncTask extends DBObject
|
||||
$oEventLog->DBUpdate();
|
||||
}
|
||||
$bRet = true;
|
||||
}
|
||||
catch(Exception $e)
|
||||
} catch (Exception $e)
|
||||
{
|
||||
$this->HandleError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
@@ -215,6 +215,7 @@ abstract class AsyncTask extends DBObject
|
||||
// Already done or being handled by another process... skip...
|
||||
$bRet = false;
|
||||
}
|
||||
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
@@ -238,7 +239,20 @@ abstract class AsyncTask extends DBObject
|
||||
{
|
||||
$iRetryDelay = $this->GetRetryDelay($iErrorCode);
|
||||
IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage.' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s');
|
||||
|
||||
if ($this->Get('event_id') != 0)
|
||||
{
|
||||
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
|
||||
$oEventLog->Set('message', "$sErrorMessage\nFailed to process async task. Remaining retries: '.$iRemaining.'. Next retry in '.$iRetryDelay.'s'");
|
||||
try
|
||||
{
|
||||
$oEventLog->DBUpdate();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$oEventLog->Set('message', "Failed to process async task. Remaining retries: '.$iRemaining.'. Next retry in '.$iRetryDelay.'s', more details in the log");
|
||||
$oEventLog->DBUpdate();
|
||||
}
|
||||
}
|
||||
$this->Set('remaining_retries', $iRemaining - 1);
|
||||
$this->Set('status', 'planned');
|
||||
$this->Set('started', null);
|
||||
@@ -247,7 +261,20 @@ abstract class AsyncTask extends DBObject
|
||||
else
|
||||
{
|
||||
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage);
|
||||
|
||||
if ($this->Get('event_id') != 0)
|
||||
{
|
||||
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
|
||||
$oEventLog->Set('message', "$sErrorMessage\nFailed to process async task.");
|
||||
try
|
||||
{
|
||||
$oEventLog->DBUpdate();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$oEventLog->Set('message', 'Failed to process async task, more details in the log');
|
||||
$oEventLog->DBUpdate();
|
||||
}
|
||||
}
|
||||
$this->Set('status', 'error');
|
||||
$this->Set('started', null);
|
||||
$this->Set('planned', null);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,6 @@ class ObsolescenceDateUpdater implements iBackgroundProcess
|
||||
$oObsoletedToday->AddCondition('obsolescence_date', null, '!=');
|
||||
$iCountReset += MetaModel::BulkUpdate($oObsoletedToday, array('obsolescence_date' => null));
|
||||
}
|
||||
echo "Obsolescence date updated (classes: $iClasses ; set: $iCountSet ; reset: $iCountReset)\n";
|
||||
return "Obsolescence date updated (classes: $iClasses ; set: $iCountSet ; reset: $iCountReset)\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,16 @@
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iProcess
|
||||
{
|
||||
/**
|
||||
* @param int $iUnixTimeLimit
|
||||
*
|
||||
* @return string status message
|
||||
* @throws \ProcessException
|
||||
* @throws \ProcessFatalException
|
||||
* @throws MySQLHasGoneAwayException
|
||||
*/
|
||||
public function Process($iUnixTimeLimit);
|
||||
}
|
||||
|
||||
@@ -37,12 +44,10 @@ interface iProcess
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iBackgroundProcess extends iProcess
|
||||
{
|
||||
/*
|
||||
Gives the repetition rate in seconds
|
||||
@returns integer
|
||||
/**
|
||||
* @return int repetition rate in seconds
|
||||
*/
|
||||
public function GetPeriodicity();
|
||||
}
|
||||
@@ -54,14 +59,30 @@ interface iBackgroundProcess extends iProcess
|
||||
* @copyright Copyright (C) 2013 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iScheduledProcess extends iProcess
|
||||
{
|
||||
/*
|
||||
Gives the exact time at which the process must be run next time
|
||||
@returns DateTime
|
||||
/**
|
||||
* @return DateTime exact time at which the process must be run next time
|
||||
*/
|
||||
public function GetNextOccurrence();
|
||||
}
|
||||
|
||||
?>
|
||||
/**
|
||||
* Class ProcessException
|
||||
* Exception for iProcess implementations.<br>
|
||||
* An error happened during the processing but we can go on with the next implementations.
|
||||
*/
|
||||
class ProcessException extends CoreException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class ProcessFatalException
|
||||
* Exception for iProcess implementations.<br>
|
||||
* A big error occurred, we have to stop the iProcess processing.
|
||||
*/
|
||||
class ProcessFatalException extends CoreException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ class BulkChange
|
||||
// Returns true if the CSV data specifies that the external key must be left undefined
|
||||
protected function IsNullExternalKeySpec($aRowData, $sAttCode)
|
||||
{
|
||||
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
//$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
foreach ($this->m_aExtKeys[$sAttCode] as $sForeignAttCode => $iCol)
|
||||
{
|
||||
// The foreign attribute is one of our reconciliation key
|
||||
@@ -332,6 +332,18 @@ class BulkChange
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObject $oTargetObj
|
||||
* @param array $aRowData
|
||||
* @param array $aErrors
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
protected function PrepareObject(&$oTargetObj, $aRowData, &$aErrors)
|
||||
{
|
||||
$aResults = array();
|
||||
@@ -367,6 +379,7 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
|
||||
$aCacheKeys = array();
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
{
|
||||
@@ -385,7 +398,6 @@ class BulkChange
|
||||
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
|
||||
}
|
||||
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
|
||||
$iCount = 0;
|
||||
$iForeignKey = null;
|
||||
$sOQL = '';
|
||||
// TODO: check if *too long* keys can lead to collisions... and skip the cache in such a case...
|
||||
@@ -468,16 +480,18 @@ class BulkChange
|
||||
//
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
|
||||
// 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);
|
||||
$iFlags = ($oTargetObj->IsNew())
|
||||
? $oTargetObj->GetInitialStateAttributeFlags($sAttCode, $aReasons)
|
||||
: $oTargetObj->GetAttributeFlags($sAttCode, $aReasons);
|
||||
if ( (($iFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY) && ( $oTargetObj->Get($sAttCode) != $aRowData[$iCol]) )
|
||||
{
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-Readonly', $sAttCode, $oTargetObj->Get($sAttCode), $aRowData[$iCol]);
|
||||
@@ -532,13 +546,11 @@ class BulkChange
|
||||
{
|
||||
$sCurValue = $oTargetObj->GetAsHTML($sAttCode, $this->m_bLocalizedValues);
|
||||
$sOrigValue = $oTargetObj->GetOriginalAsHTML($sAttCode, $this->m_bLocalizedValues);
|
||||
$sInput = htmlentities($aRowData[$iCol], ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sCurValue = $oTargetObj->GetAsCSV($sAttCode, $this->m_sReportCsvSep, $this->m_sReportCsvDelimiter, $this->m_bLocalizedValues);
|
||||
$sOrigValue = $oTargetObj->GetOriginalAsCSV($sAttCode, $this->m_sReportCsvSep, $this->m_sReportCsvDelimiter, $this->m_bLocalizedValues);
|
||||
$sInput = $aRowData[$iCol];
|
||||
}
|
||||
if (isset($aErrors[$sAttCode]))
|
||||
{
|
||||
@@ -609,10 +621,6 @@ class BulkChange
|
||||
throw new BulkChangeException('Invalid attribute code', array('class' => get_class($oTargetObj), 'attcode' => $sAttCode));
|
||||
}
|
||||
$oTargetObj->Set($sAttCode, $value);
|
||||
if (!array_key_exists($sAttCode, $this->m_aAttList))
|
||||
{
|
||||
// #@# will be out of the reporting... (counted anyway)
|
||||
}
|
||||
}
|
||||
|
||||
// Reporting on fields
|
||||
@@ -650,6 +658,47 @@ class BulkChange
|
||||
protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null)
|
||||
{
|
||||
$oTargetObj = MetaModel::NewObject($this->m_sClass);
|
||||
|
||||
// Populate the cache for hierarchical keys (only if in verify mode)
|
||||
if (is_null($oChange))
|
||||
{
|
||||
// 1. determine if a hierarchical key exists
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
|
||||
{
|
||||
$oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode);
|
||||
if (!$this->IsNullExternalKeySpec($aRowData, $sAttCode) && MetaModel::IsParentClass(get_class($oTargetObj), $this->m_sClass))
|
||||
{
|
||||
// 2. Populate the cache for further checks
|
||||
$aCacheKeys = array();
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
{
|
||||
// The foreign attribute is one of our reconciliation key
|
||||
if ($sForeignAttCode == 'id')
|
||||
{
|
||||
$value = $aRowData[$iCol];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isset($this->m_aAttList[$sForeignAttCode]) || !isset($aRowData[$this->m_aAttList[$sForeignAttCode]]))
|
||||
{
|
||||
// the key is not in the import
|
||||
break 2;
|
||||
}
|
||||
$value = $aRowData[$this->m_aAttList[$sForeignAttCode]];
|
||||
}
|
||||
$aCacheKeys[] = $value;
|
||||
}
|
||||
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
|
||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey] = array(
|
||||
'c' => 1,
|
||||
'k' => -1,
|
||||
'oql' => '',
|
||||
'h' => 0, // number of hits on this cache entry
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
|
||||
|
||||
if (count($aErrors) > 0)
|
||||
@@ -683,7 +732,7 @@ class BulkChange
|
||||
if ($oChange)
|
||||
{
|
||||
$newID = $oTargetObj->DBInsertTrackedNoReload($oChange);
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($this->m_sClass, $newID);
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj();
|
||||
$aResult[$iRow]["finalclass"] = get_class($oTargetObj);
|
||||
$aResult[$iRow]["id"] = new CellStatus_Void($newID);
|
||||
}
|
||||
@@ -1175,6 +1224,9 @@ EOF
|
||||
|
||||
/**
|
||||
* Display the details of an import
|
||||
* @param iTopWebPage $oPage
|
||||
* @param $iChange
|
||||
* @throws Exception
|
||||
*/
|
||||
static function DisplayImportHistoryDetails(iTopWebPage $oPage, $iChange)
|
||||
{
|
||||
|
||||
@@ -73,8 +73,14 @@ class BulkExportResult extends DBObject
|
||||
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())));
|
||||
MetaModel::Init_AddAttribute(new AttributeBoolean("localize_output", array("allowed_values"=>null, "sql"=>"localize_output", "default_value"=>true, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ComputeValues()
|
||||
{
|
||||
$this->Set('user_id', UserRights::GetUserId());
|
||||
@@ -150,9 +156,12 @@ abstract class BulkExport
|
||||
|
||||
/**
|
||||
* 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 string $sFormatCode 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
|
||||
*
|
||||
* @return BulkExport|null
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
static public function FindExporter($sFormatCode, $oSearch = null)
|
||||
{
|
||||
@@ -177,8 +186,13 @@ abstract class BulkExport
|
||||
|
||||
/**
|
||||
* Find the exporter corresponding to the given persistent token
|
||||
*
|
||||
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
|
||||
* @return iBulkExport|NULL
|
||||
*
|
||||
* @return iBulkExport|null
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreException
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
static public function FindExporterFromToken($iPersistentToken = null)
|
||||
{
|
||||
@@ -196,6 +210,10 @@ abstract class BulkExport
|
||||
$oBulkExporter->SetObjectList($oSearch);
|
||||
$oBulkExporter->SetChunkSize($oInfo->Get('chunk_size'));
|
||||
$oBulkExporter->SetStatusInfo(json_decode($oInfo->Get('status_info'), true));
|
||||
|
||||
$oBulkExporter->SetLocalizeOutput($oInfo->Get('localize_output'));
|
||||
|
||||
|
||||
$oBulkExporter->sTmpFile = $oInfo->Get('temp_file_path');
|
||||
$oBulkExporter->oBulkExportResult = $oInfo;
|
||||
}
|
||||
@@ -203,6 +221,10 @@ abstract class BulkExport
|
||||
return $oBulkExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @throws Exception
|
||||
*/
|
||||
public function AppendToTmpFile($data)
|
||||
{
|
||||
if ($this->sTmpFile == '')
|
||||
@@ -224,7 +246,7 @@ abstract class BulkExport
|
||||
|
||||
/**
|
||||
* Lists all possible export formats. The output is a hash array in the form: 'format_code' => 'localized format label'
|
||||
* @return multitype:string
|
||||
* @return array :string
|
||||
*/
|
||||
static public function FindSupportedFormats()
|
||||
{
|
||||
@@ -251,6 +273,14 @@ abstract class BulkExport
|
||||
$this->iChunkSize = $iChunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $bLocalizeOutput
|
||||
*/
|
||||
public function SetLocalizeOutput($bLocalizeOutput)
|
||||
{
|
||||
$this->bLocalizeOutput = $bLocalizeOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see iBulkExport::SetObjectList()
|
||||
@@ -288,13 +318,21 @@ abstract class BulkExport
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetHeader()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
abstract public function GetNextChunk(&$aStatus);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetFooter()
|
||||
{
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function SaveState()
|
||||
@@ -306,6 +344,7 @@ abstract class BulkExport
|
||||
$this->oBulkExportResult->Set('search', $this->oSearch->serialize());
|
||||
$this->oBulkExportResult->Set('chunk_size', $this->iChunkSize);
|
||||
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
|
||||
$this->oBulkExportResult->Set('localize_output', $this->bLocalizeOutput);
|
||||
}
|
||||
$this->oBulkExportResult->Set('status_info', json_encode($this->GetStatusInfo()));
|
||||
utils::PushArchiveMode(false);
|
||||
@@ -355,13 +394,21 @@ abstract class BulkExport
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetMimeType()
|
||||
{
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetFileExtension()
|
||||
{
|
||||
|
||||
return '';
|
||||
}
|
||||
public function GetCharacterSet()
|
||||
{
|
||||
@@ -388,6 +435,11 @@ abstract class BulkExport
|
||||
return $this->aStatusInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sExtension
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function MakeTmpFile($sExtension)
|
||||
{
|
||||
if(!is_dir(APPROOT."data/bulk_export"))
|
||||
@@ -401,7 +453,6 @@ abstract class BulkExport
|
||||
}
|
||||
|
||||
$iNum = rand();
|
||||
$sFileName = '';
|
||||
do
|
||||
{
|
||||
$iNum++;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* DB Server abstraction
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -29,22 +29,76 @@ require_once(APPROOT.'core/kpi.class.inc.php');
|
||||
|
||||
class MySQLException extends CoreException
|
||||
{
|
||||
public function __construct($sIssue, $aContext, $oException = null)
|
||||
/**
|
||||
* MySQLException constructor.
|
||||
*
|
||||
* @param string $sIssue
|
||||
* @param array $aContext
|
||||
* @param \Exception $oException
|
||||
* @param \mysqli $oMysqli to use when working with a custom mysqli instance
|
||||
*/
|
||||
public function __construct($sIssue, $aContext, $oException = null, $oMysqli = null)
|
||||
{
|
||||
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 if ($oMysqli != null)
|
||||
{
|
||||
$aContext['mysql_errno'] = $oMysqli->errno;
|
||||
$this->code = $oMysqli->errno;
|
||||
$aContext['mysql_error'] = $oMysqli->error;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aContext['mysql_error'] = CMDBSource::GetError();
|
||||
$aContext['mysql_errno'] = CMDBSource::GetErrNo();
|
||||
$this->code = CMDBSource::GetErrNo();
|
||||
$aContext['mysql_error'] = CMDBSource::GetError();
|
||||
}
|
||||
parent::__construct($sIssue, $aContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MySQLQueryHasNoResultException
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
class MySQLQueryHasNoResultException extends MySQLException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MySQLHasGoneAwayException
|
||||
*
|
||||
* @since 2.5
|
||||
* @see itop bug 1195
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/gone-away.html
|
||||
*/
|
||||
class MySQLHasGoneAwayException extends MySQLException
|
||||
{
|
||||
/**
|
||||
* can not be a constant before PHP 5.6 (http://php.net/manual/fr/language.oop5.constants.php)
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public static function getErrorCodes()
|
||||
{
|
||||
return array(
|
||||
2006,
|
||||
2013
|
||||
);
|
||||
}
|
||||
|
||||
public function __construct($sIssue, $aContext)
|
||||
{
|
||||
parent::__construct($sIssue, $aContext, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CMDBSource
|
||||
@@ -58,37 +112,136 @@ class CMDBSource
|
||||
protected static $m_sDBUser;
|
||||
protected static $m_sDBPwd;
|
||||
protected static $m_sDBName;
|
||||
/**
|
||||
* @var boolean
|
||||
* @since 2.5 #1260 MySQL TLS first implementation
|
||||
*/
|
||||
protected static $m_bDBTlsEnabled;
|
||||
/**
|
||||
* @var string
|
||||
* @since 2.5 #1260 MySQL TLS first implementation
|
||||
*/
|
||||
protected static $m_sDBTlsCA;
|
||||
|
||||
/** @var mysqli $m_oMysqli */
|
||||
protected static $m_oMysqli;
|
||||
|
||||
public static function Init($sServer, $sUser, $sPwd, $sSource = '')
|
||||
/**
|
||||
* SQL charset & collation declaration for text columns
|
||||
*
|
||||
* Using a function instead of a constant or attribute to avoid crash in the setup for older PHP versions (cannot
|
||||
* use expression as value)
|
||||
*
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-column.html
|
||||
* @since 2.5 #1001 switch to utf8mb4
|
||||
*/
|
||||
public static function GetSqlStringColumnDefinition()
|
||||
{
|
||||
return ' CHARACTER SET '.DEFAULT_CHARACTER_SET.' COLLATE '.DEFAULT_COLLATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config $oConfig
|
||||
*
|
||||
* @throws \MySQLException
|
||||
* @uses \CMDBSource::Init()
|
||||
* @uses \CMDBSource::SetCharacterSet()
|
||||
*/
|
||||
public static function InitFromConfig($oConfig)
|
||||
{
|
||||
$sServer = $oConfig->Get('db_host');
|
||||
$sUser = $oConfig->Get('db_user');
|
||||
$sPwd = $oConfig->Get('db_pwd');
|
||||
$sSource = $oConfig->Get('db_name');
|
||||
$bTlsEnabled = $oConfig->Get('db_tls.enabled');
|
||||
$sTlsCA = $oConfig->Get('db_tls.ca');
|
||||
|
||||
self::Init($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA);
|
||||
|
||||
$sCharacterSet = DEFAULT_CHARACTER_SET;
|
||||
$sCollation = DEFAULT_COLLATION;
|
||||
self::SetCharacterSet($sCharacterSet, $sCollation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sServer
|
||||
* @param string $sUser
|
||||
* @param string $sPwd
|
||||
* @param string $sSource database to use
|
||||
* @param bool $bTlsEnabled
|
||||
* @param string $sTlsCA
|
||||
*
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public static function Init(
|
||||
$sServer, $sUser, $sPwd, $sSource = '', $bTlsEnabled = false, $sTlsCA = null
|
||||
)
|
||||
{
|
||||
self::$m_sDBHost = $sServer;
|
||||
self::$m_sDBUser = $sUser;
|
||||
self::$m_sDBPwd = $sPwd;
|
||||
self::$m_sDBName = $sSource;
|
||||
self::$m_oMysqli = null;
|
||||
self::$m_bDBTlsEnabled = empty($bTlsEnabled) ? false : $bTlsEnabled;
|
||||
self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA;
|
||||
|
||||
self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sDbHost
|
||||
* @param string $sUser
|
||||
* @param string $sPwd
|
||||
* @param string $sSource database to use
|
||||
* @param bool $bTlsEnabled
|
||||
* @param string $sTlsCa
|
||||
* @param bool $bCheckTlsAfterConnection If true then verify after connection if it is encrypted
|
||||
*
|
||||
* @return \mysqli
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public static function GetMysqliInstance(
|
||||
$sDbHost, $sUser, $sPwd, $sSource = '', $bTlsEnabled = false, $sTlsCa = null, $bCheckTlsAfterConnection = false
|
||||
) {
|
||||
$oMysqli = null;
|
||||
|
||||
$sServer = null;
|
||||
$iPort = null;
|
||||
self::InitServerAndPort($sDbHost, $sServer, $iPort);
|
||||
|
||||
$iFlags = null;
|
||||
|
||||
// *some* errors (like connection errors) will throw mysqli_sql_exception instead of generating warnings printed to the output
|
||||
// but some other errors will still cause the query() method to return false !!!
|
||||
mysqli_report(MYSQLI_REPORT_STRICT);
|
||||
|
||||
mysqli_report(MYSQLI_REPORT_STRICT); // *some* errors (like connection errors) will throw mysqli_sql_exception instead
|
||||
// of generating warnings printed to the output but some other errors will still
|
||||
// cause the query() method to return false !!!
|
||||
try
|
||||
{
|
||||
$aConnectInfo = explode(':', self::$m_sDBHost);
|
||||
if (count($aConnectInfo) > 1)
|
||||
$oMysqli = new mysqli();
|
||||
$oMysqli->init();
|
||||
|
||||
if ($bTlsEnabled)
|
||||
{
|
||||
// Override the default port
|
||||
$sServer = $aConnectInfo[0];
|
||||
$iPort = (int)$aConnectInfo[1];
|
||||
self::$m_oMysqli = new mysqli($sServer, self::$m_sDBUser, self::$m_sDBPwd, '', $iPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_oMysqli = new mysqli(self::$m_sDBHost, self::$m_sDBUser, self::$m_sDBPwd);
|
||||
$iFlags = (empty($sTlsCa))
|
||||
? MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT
|
||||
: MYSQLI_CLIENT_SSL;
|
||||
$sTlsCert = null; // not implemented
|
||||
$sTlsCaPath = null; // not implemented
|
||||
$sTlsCipher = null; // not implemented
|
||||
$oMysqli->ssl_set($bTlsEnabled, $sTlsCert, $sTlsCa, $sTlsCaPath, $sTlsCipher);
|
||||
}
|
||||
$oMysqli->real_connect($sServer, $sUser, $sPwd, '', $iPort, ini_get("mysqli.default_socket"), $iFlags);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
throw new MySQLException('Could not connect to the DB server', array('host'=>self::$m_sDBHost, 'user'=>self::$m_sDBUser), $e);
|
||||
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser), $e);
|
||||
}
|
||||
|
||||
if ($bTlsEnabled
|
||||
&& $bCheckTlsAfterConnection
|
||||
&& !self::IsOpenedDbConnectionUsingTls($oMysqli))
|
||||
{
|
||||
throw new MySQLException("Connection to the database is not encrypted whereas it was opened using TLS parameters",
|
||||
null, null, $oMysqli);
|
||||
}
|
||||
|
||||
if (!empty($sSource))
|
||||
@@ -96,16 +249,104 @@ class CMDBSource
|
||||
try
|
||||
{
|
||||
mysqli_report(MYSQLI_REPORT_STRICT); // Errors, in the next query, will throw mysqli_sql_exception
|
||||
self::$m_oMysqli->query("USE `$sSource`");
|
||||
$oMysqli->query("USE `$sSource`");
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
throw new MySQLException('Could not select DB', array('host'=>self::$m_sDBHost, 'user'=>self::$m_sDBUser, 'db_name'=>self::$m_sDBName), $e);
|
||||
}
|
||||
throw new MySQLException('Could not select DB',
|
||||
array('host' => $sServer, 'user' => $sUser, 'db_name' => $sSource), $e);
|
||||
}
|
||||
}
|
||||
|
||||
public static function SetCharacterSet($sCharset = 'utf8', $sCollation = 'utf8_general_ci')
|
||||
return $oMysqli;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sDbHost initial value ("p:domain:port" syntax)
|
||||
* @param string $sServer server variable to update
|
||||
* @param int $iPort port variable to update
|
||||
*/
|
||||
public static function InitServerAndPort($sDbHost, &$sServer, &$iPort)
|
||||
{
|
||||
$aConnectInfo = explode(':', $sDbHost);
|
||||
|
||||
$bUsePersistentConnection = false;
|
||||
if (strcasecmp($aConnectInfo[0], 'p') == 0)
|
||||
{
|
||||
// we might have "p:" prefix to use persistent connections (see http://php.net/manual/en/mysqli.persistconns.php)
|
||||
$bUsePersistentConnection = true;
|
||||
$sServer = $aConnectInfo[0].':'.$aConnectInfo[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sServer = $aConnectInfo[0];
|
||||
}
|
||||
|
||||
$iConnectInfoCount = count($aConnectInfo);
|
||||
if ($bUsePersistentConnection && ($iConnectInfoCount == 3))
|
||||
{
|
||||
$iPort = $aConnectInfo[2];
|
||||
}
|
||||
else if (!$bUsePersistentConnection && ($iConnectInfoCount == 2))
|
||||
{
|
||||
$iPort = $aConnectInfo[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$iPort = 3306;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A DB connection can be opened transparently (no errors thrown) without being encrypted, whereas the TLS
|
||||
* parameters were used.<br>
|
||||
* This method can be called to ensure that the DB connection really uses TLS.
|
||||
*
|
||||
* <p>We're using this object connection : {@link self::$m_oMysqli}
|
||||
*
|
||||
* @param \mysqli $oMysqli
|
||||
*
|
||||
* @return boolean true if the connection was really established using TLS
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses IsMySqlVarNonEmpty
|
||||
*/
|
||||
private static function IsOpenedDbConnectionUsingTls($oMysqli)
|
||||
{
|
||||
if (self::$m_oMysqli == null)
|
||||
{
|
||||
self::$m_oMysqli = $oMysqli;
|
||||
}
|
||||
|
||||
$bNonEmptySslVersionVar = self::IsMySqlVarNonEmpty('ssl_version');
|
||||
$bNonEmptySslCipherVar = self::IsMySqlVarNonEmpty('ssl_cipher');
|
||||
|
||||
return ($bNonEmptySslVersionVar && $bNonEmptySslCipherVar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sVarName
|
||||
*
|
||||
* @return bool
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses SHOW STATUS queries
|
||||
*/
|
||||
private static function IsMySqlVarNonEmpty($sVarName)
|
||||
{
|
||||
try
|
||||
{
|
||||
$sResult = self::QueryToScalar("SHOW SESSION STATUS LIKE '$sVarName'", 1);
|
||||
}
|
||||
catch (MySQLQueryHasNoResultException $e)
|
||||
{
|
||||
$sResult = null;
|
||||
}
|
||||
|
||||
return (!empty($sResult));
|
||||
}
|
||||
|
||||
public static function SetCharacterSet($sCharset = DEFAULT_CHARACTER_SET, $sCollation = DEFAULT_COLLATION)
|
||||
{
|
||||
if (strlen($sCharset) > 0)
|
||||
{
|
||||
@@ -165,6 +406,11 @@ class CMDBSource
|
||||
return $aVersions[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSource
|
||||
*
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public static function SelectDB($sSource)
|
||||
{
|
||||
if (!((bool)self::$m_oMysqli->query("USE `$sSource`")))
|
||||
@@ -174,9 +420,15 @@ class CMDBSource
|
||||
self::$m_sDBName = $sSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSource
|
||||
*
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
public static function CreateDB($sSource)
|
||||
{
|
||||
self::Query("CREATE DATABASE `$sSource` CHARACTER SET utf8 COLLATE utf8_unicode_ci");
|
||||
self::Query("CREATE DATABASE `$sSource` CHARACTER SET ".DEFAULT_CHARACTER_SET." COLLATE ".DEFAULT_COLLATION);
|
||||
self::SelectDB($sSource);
|
||||
}
|
||||
|
||||
@@ -207,6 +459,14 @@ class CMDBSource
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \mysqli
|
||||
*/
|
||||
public static function GetMysqli()
|
||||
{
|
||||
return self::$m_oMysqli;
|
||||
}
|
||||
|
||||
public static function GetErrNo()
|
||||
{
|
||||
if (self::$m_oMysqli->errno != 0)
|
||||
@@ -236,15 +496,19 @@ class CMDBSource
|
||||
public static function DBPwd() {return self::$m_sDBPwd;}
|
||||
public static function DBName() {return self::$m_sDBName;}
|
||||
|
||||
/**
|
||||
* Quote variable and protect against SQL injection attacks
|
||||
* Code found in the PHP documentation: quote_smart($value)
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param bool $bAlways should be set to true when the purpose is to create a IN clause,
|
||||
* otherwise and if there is a mix of strings and numbers, the clause would always be false
|
||||
* @param string $cQuoteStyle
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public static function Quote($value, $bAlways = false, $cQuoteStyle = "'")
|
||||
{
|
||||
// Quote variable and protect against SQL injection attacks
|
||||
// Code found in the PHP documentation: quote_smart($value)
|
||||
|
||||
// bAlways should be set to true when the purpose is to create a IN clause,
|
||||
// otherwise and if there is a mix of strings and numbers, the clause
|
||||
// would always be false
|
||||
|
||||
if (is_null($value))
|
||||
{
|
||||
return 'NULL';
|
||||
@@ -273,6 +537,13 @@ class CMDBSource
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSQLQuery
|
||||
*
|
||||
* @return \mysqli_result
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
public static function Query($sSQLQuery)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
@@ -287,19 +558,35 @@ class CMDBSource
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSQLQuery);
|
||||
if ($oResult === false)
|
||||
{
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSQLQuery));
|
||||
$aContext = array('query' => $sSQLQuery);
|
||||
|
||||
$iMySqlErrorNo = self::$m_oMysqli->errno;
|
||||
$aMySqlHasGoneAwayErrorCodes = MySQLHasGoneAwayException::getErrorCodes();
|
||||
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes))
|
||||
{
|
||||
throw new MySQLHasGoneAwayException(self::GetError(), $aContext);
|
||||
}
|
||||
|
||||
throw new MySQLException('Failed to issue SQL query', $aContext);
|
||||
}
|
||||
|
||||
return $oResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sTable
|
||||
*
|
||||
* @return int
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
public static function GetNextInsertId($sTable)
|
||||
{
|
||||
$sSQL = "SHOW TABLE STATUS LIKE '$sTable'";
|
||||
$oResult = self::Query($sSQL);
|
||||
$aRow = $oResult->fetch_assoc();
|
||||
$iNextInsertId = $aRow['Auto_increment'];
|
||||
return $iNextInsertId;
|
||||
|
||||
return $aRow['Auto_increment'];
|
||||
}
|
||||
|
||||
public static function GetInsertId()
|
||||
@@ -326,7 +613,15 @@ class CMDBSource
|
||||
self::Query($sSQLQuery);
|
||||
}
|
||||
|
||||
public static function QueryToScalar($sSql)
|
||||
/**
|
||||
* @param string $sSql
|
||||
* @param int $iCol beginning at 0
|
||||
*
|
||||
* @return string corresponding cell content on the first line
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
*/
|
||||
public static function QueryToScalar($sSql, $iCol = 0)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
@@ -346,17 +641,25 @@ class CMDBSource
|
||||
|
||||
if ($aRow = $oResult->fetch_array(MYSQLI_BOTH))
|
||||
{
|
||||
$res = $aRow[0];
|
||||
$res = $aRow[$iCol];
|
||||
}
|
||||
else
|
||||
{
|
||||
$oResult->free();
|
||||
throw new MySQLException('Found no result for query', array('query' => $sSql));
|
||||
throw new MySQLQueryHasNoResultException('Found no result for query', array('query' => $sSql));
|
||||
}
|
||||
$oResult->free();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sSql
|
||||
*
|
||||
* @return array
|
||||
* @throws \MySQLException if query cannot be processed
|
||||
*/
|
||||
public static function QueryToArray($sSql)
|
||||
{
|
||||
$aData = array();
|
||||
@@ -384,6 +687,13 @@ class CMDBSource
|
||||
return $aData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSql
|
||||
* @param int $col
|
||||
*
|
||||
* @return array
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public static function QueryToCol($sSql, $col)
|
||||
{
|
||||
$aColumn = array();
|
||||
@@ -395,6 +705,12 @@ class CMDBSource
|
||||
return $aColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSql
|
||||
*
|
||||
* @return array
|
||||
* @throws \MySQLException if query cannot be processed
|
||||
*/
|
||||
public static function ExplainQuery($sSql)
|
||||
{
|
||||
$aData = array();
|
||||
@@ -411,7 +727,7 @@ class CMDBSource
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
||||
}
|
||||
|
||||
$aNames = self::GetColumns($oResult);
|
||||
$aNames = self::GetColumns($oResult, $sSql);
|
||||
|
||||
$aData[] = $aNames;
|
||||
while ($aRow = $oResult->fetch_array(MYSQLI_ASSOC))
|
||||
@@ -422,6 +738,12 @@ class CMDBSource
|
||||
return $aData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSql
|
||||
*
|
||||
* @return string
|
||||
* @throws \MySQLException if query cannot be processed
|
||||
*/
|
||||
public static function TestQuery($sSql)
|
||||
{
|
||||
try
|
||||
@@ -459,7 +781,14 @@ class CMDBSource
|
||||
return $oResult->fetch_array(MYSQLI_ASSOC);
|
||||
}
|
||||
|
||||
public static function GetColumns($oResult)
|
||||
/**
|
||||
* @param mysqli_result $oResult
|
||||
* @param string $sSql
|
||||
*
|
||||
* @return string[]
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public static function GetColumns($oResult, $sSql)
|
||||
{
|
||||
$aNames = array();
|
||||
for ($i = 0; $i < (($___mysqli_tmp = $oResult->field_count) ? $___mysqli_tmp : 0) ; $i++)
|
||||
@@ -541,17 +870,35 @@ class CMDBSource
|
||||
return ($aFieldData["Type"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sTable
|
||||
* @param string $sField
|
||||
*
|
||||
* @return bool|string
|
||||
* @see \AttributeDefinition::GetSQLColumns()
|
||||
*/
|
||||
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"];
|
||||
|
||||
$sColumnCharset = $aFieldData["Charset"];
|
||||
$sColumnCollation = $aFieldData["Collation"];
|
||||
if (!empty($sColumnCharset))
|
||||
{
|
||||
$sRet .= ' CHARACTER SET '.$sColumnCharset;
|
||||
$sRet .= ' COLLATE '.$sColumnCollation;
|
||||
}
|
||||
|
||||
if ($aFieldData["Null"] == 'NO')
|
||||
{
|
||||
$sRet .= ' NOT NULL';
|
||||
}
|
||||
|
||||
if (is_numeric($aFieldData["Default"]))
|
||||
{
|
||||
if (strtolower(substr($aFieldData["Type"], 0, 5)) == 'enum(')
|
||||
@@ -569,10 +916,11 @@ class CMDBSource
|
||||
{
|
||||
$sRet .= ' DEFAULT '.self::Quote($aFieldData["Default"]);
|
||||
}
|
||||
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public static function HasIndex($sTable, $sIndexId, $aFields = null)
|
||||
public static function HasIndex($sTable, $sIndexId, $aFields = null, $aLength = null)
|
||||
{
|
||||
$aTableInfo = self::GetTableInfo($sTable);
|
||||
if (empty($aTableInfo)) return false;
|
||||
@@ -586,11 +934,26 @@ class CMDBSource
|
||||
|
||||
// Compare the columns
|
||||
$sSearchedIndex = implode(',', $aFields);
|
||||
$sExistingIndex = implode(',', $aTableInfo['Indexes'][$sIndexId]);
|
||||
$aColumnNames = array();
|
||||
$aSubParts = array();
|
||||
foreach($aTableInfo['Indexes'][$sIndexId] as $aIndexDef)
|
||||
{
|
||||
$aColumnNames[] = $aIndexDef['Column_name'];
|
||||
$aSubParts[] = $aIndexDef['Sub_part'];
|
||||
}
|
||||
$sExistingIndex = implode(',', $aColumnNames);
|
||||
|
||||
if (is_null($aLength))
|
||||
{
|
||||
return ($sSearchedIndex == $sExistingIndex);
|
||||
}
|
||||
|
||||
$sSearchedLength = implode(',', $aLength);
|
||||
$sExistingLength = implode(',', $aSubParts);
|
||||
|
||||
return ($sSearchedIndex == $sExistingIndex) && ($sSearchedLength == $sExistingLength);
|
||||
}
|
||||
|
||||
// Returns an array of (fieldname => array of field info)
|
||||
public static function GetTableFieldsList($sTable)
|
||||
{
|
||||
@@ -608,35 +971,49 @@ class CMDBSource
|
||||
{
|
||||
self::$m_aTablesInfo = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sTableName
|
||||
*
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
private static function _TableInfoCacheInit($sTableName)
|
||||
{
|
||||
if (isset(self::$m_aTablesInfo[strtolower($sTableName)])
|
||||
&& (self::$m_aTablesInfo[strtolower($sTableName)] != null)) return;
|
||||
|
||||
try
|
||||
&& (self::$m_aTablesInfo[strtolower($sTableName)] != null))
|
||||
{
|
||||
// Check if the table exists
|
||||
$aFields = self::QueryToArray("SHOW COLUMNS FROM `$sTableName`");
|
||||
// Note: without backticks, you get an error with some table names (e.g. "group")
|
||||
return;
|
||||
}
|
||||
|
||||
// Create array entry, if table does not exist / has no columns
|
||||
self::$m_aTablesInfo[strtolower($sTableName)] = null;
|
||||
|
||||
// Get table informations
|
||||
// We were using SHOW COLUMNS FROM... but this don't return charset and collation info !
|
||||
// so since 2.5 and #1001 (switch to utf8mb4) we're using INFORMATION_SCHEMA !
|
||||
$aMapping = array(
|
||||
"Name" => "COLUMN_NAME",
|
||||
"Type" => "COLUMN_TYPE",
|
||||
"Null" => "IS_NULLABLE",
|
||||
"Key" => "COLUMN_KEY",
|
||||
"Default" => "COLUMN_DEFAULT",
|
||||
"Extra" => "EXTRA",
|
||||
"Charset" => "CHARACTER_SET_NAME",
|
||||
"Collation" => "COLLATION_NAME",
|
||||
"CharMaxLength" => "CHARACTER_MAXIMUM_LENGTH",
|
||||
);
|
||||
$sColumns = implode(', ', $aMapping);
|
||||
$sDBName = self::$m_sDBName;
|
||||
$aFields = self::QueryToArray("SELECT $sColumns FROM information_schema.`COLUMNS` WHERE table_schema = '$sDBName' AND table_name = '$sTableName';");
|
||||
foreach ($aFields as $aFieldData)
|
||||
{
|
||||
$sFieldName = $aFieldData["Field"];
|
||||
self::$m_aTablesInfo[strtolower($sTableName)]["Fields"][$sFieldName] =
|
||||
array
|
||||
(
|
||||
"Name"=>$aFieldData["Field"],
|
||||
"Type"=>$aFieldData["Type"],
|
||||
"Null"=>$aFieldData["Null"],
|
||||
"Key"=>$aFieldData["Key"],
|
||||
"Default"=>$aFieldData["Default"],
|
||||
"Extra"=>$aFieldData["Extra"]
|
||||
);
|
||||
}
|
||||
}
|
||||
catch(MySQLException $e)
|
||||
$aFields = array();
|
||||
foreach($aMapping as $sKey => $sColumn)
|
||||
{
|
||||
// Table does not exist
|
||||
self::$m_aTablesInfo[strtolower($sTableName)] = null;
|
||||
$aFields[$sKey] = $aFieldData[$sColumn];
|
||||
}
|
||||
$sFieldName = $aFieldData["COLUMN_NAME"];
|
||||
self::$m_aTablesInfo[strtolower($sTableName)]["Fields"][$sFieldName] = $aFields;
|
||||
}
|
||||
|
||||
if (!is_null(self::$m_aTablesInfo[strtolower($sTableName)]))
|
||||
@@ -645,32 +1022,57 @@ class CMDBSource
|
||||
$aMyIndexes = array();
|
||||
foreach ($aIndexes as $aIndexColumn)
|
||||
{
|
||||
$aMyIndexes[$aIndexColumn['Key_name']][$aIndexColumn['Seq_in_index']-1] = $aIndexColumn['Column_name'];
|
||||
$aMyIndexes[$aIndexColumn['Key_name']][$aIndexColumn['Seq_in_index']-1] = $aIndexColumn;
|
||||
}
|
||||
self::$m_aTablesInfo[strtolower($sTableName)]["Indexes"] = $aMyIndexes;
|
||||
}
|
||||
}
|
||||
//public static function EnumTables()
|
||||
//{
|
||||
// self::_TablesInfoCacheInit();
|
||||
// return array_keys(self::$m_aTablesInfo);
|
||||
//}
|
||||
|
||||
public static function GetTableInfo($sTable)
|
||||
{
|
||||
self::_TableInfoCacheInit($sTable);
|
||||
|
||||
// perform a case insensitive match because on Windows the table names become lowercase :-(
|
||||
//foreach(self::$m_aTablesInfo as $sTableName => $aInfo)
|
||||
//{
|
||||
// if (strtolower($sTableName) == strtolower($sTable))
|
||||
// {
|
||||
// return $aInfo;
|
||||
// }
|
||||
//}
|
||||
return self::$m_aTablesInfo[strtolower($sTable)];
|
||||
//return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sTableName
|
||||
*
|
||||
* @return string query to upgrade table charset and collation if needed, null if not
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @since 2.5 #1001 switch to utf8mb4
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-table.html
|
||||
*/
|
||||
public static function DBCheckTableCharsetAndCollation($sTableName)
|
||||
{
|
||||
$sDBName = self::DBName();
|
||||
$sTableInfoQuery = "SELECT C.character_set_name, T.table_collation
|
||||
FROM information_schema.`TABLES` T inner join information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` C
|
||||
ON T.table_collation = C.collation_name
|
||||
WHERE T.table_schema = '$sDBName'
|
||||
AND T.table_name = '$sTableName';";
|
||||
$aTableInfo = self::QueryToArray($sTableInfoQuery);
|
||||
$sTableCharset = $aTableInfo[0]['character_set_name'];
|
||||
$sTableCollation = $aTableInfo[0]['table_collation'];
|
||||
|
||||
if ((DEFAULT_CHARACTER_SET == $sTableCharset) && (DEFAULT_COLLATION == $sTableCollation))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return 'ALTER TABLE `'.$sTableName.'` '.self::GetSqlStringColumnDefinition().';';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sTable
|
||||
*
|
||||
* @return array
|
||||
* @throws \MySQLException if query cannot be processed
|
||||
*/
|
||||
public static function DumpTable($sTable)
|
||||
{
|
||||
$sSql = "SELECT * FROM `$sTable`";
|
||||
@@ -726,6 +1128,7 @@ class CMDBSource
|
||||
}
|
||||
catch(MySQLException $e)
|
||||
{
|
||||
$iCode = self::GetErrNo();
|
||||
return "Current user not allowed to see his own privileges (could not access to the database 'mysql' - $iCode)";
|
||||
}
|
||||
|
||||
@@ -784,4 +1187,28 @@ class CMDBSource
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string query to upgrade database charset and collation if needed, null if not
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @since 2.5 #1001 switch to utf8mb4
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-database.html
|
||||
*/
|
||||
public static function DBCheckCharsetAndCollation()
|
||||
{
|
||||
$sDBName = CMDBSource::DBName();
|
||||
$sDBInfoQuery = "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
|
||||
FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$sDBName';";
|
||||
$aDBInfo = CMDBSource::QueryToArray($sDBInfoQuery);
|
||||
$sDBCharset = $aDBInfo[0]['DEFAULT_CHARACTER_SET_NAME'];
|
||||
$sDBCollation = $aDBInfo[0]['DEFAULT_COLLATION_NAME'];
|
||||
|
||||
if ((DEFAULT_CHARACTER_SET == $sDBCharset) && (DEFAULT_COLLATION == $sDBCollation))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'ALTER DATABASE'.CMDBSource::GetSqlStringColumnDefinition().';';
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
// Copyright (C) 2010-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
|
||||
define('ITOP_APPLICATION', 'iTop');
|
||||
define('ITOP_VERSION', '2.4.x');
|
||||
define('ITOP_APPLICATION_SHORT', 'iTop');
|
||||
define('ITOP_VERSION', '2.5.0');
|
||||
define('ITOP_REVISION', 'svn');
|
||||
define('ITOP_BUILD_DATE', '$WCNOW$');
|
||||
|
||||
@@ -30,7 +31,7 @@ define('ACCESS_READONLY', 0);
|
||||
/**
|
||||
* Configuration read/write
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -41,8 +42,12 @@ class ConfigException extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
define ('DEFAULT_CHARACTER_SET', 'utf8');
|
||||
define ('DEFAULT_COLLATION', 'utf8_unicode_ci');
|
||||
// was utf8 but it only supports BMP chars (https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)
|
||||
// so we switched to utf8mb4 in iTop 2.5, adding dependency to MySQL 5.5.3
|
||||
// The config params db_character_set and db_collation were introduced as a temporary workaround and removed in iTop 2.5
|
||||
// now everything uses those fixed value !
|
||||
define('DEFAULT_CHARACTER_SET', 'utf8mb4');
|
||||
define('DEFAULT_COLLATION', 'utf8mb4_unicode_ci');
|
||||
|
||||
define('DEFAULT_LOG_GLOBAL', true);
|
||||
define('DEFAULT_LOG_NOTIFICATION', true);
|
||||
@@ -79,8 +84,12 @@ class Config
|
||||
|
||||
protected $m_aModuleSettings;
|
||||
|
||||
// New way to store the settings !
|
||||
//
|
||||
/**
|
||||
* New way to store the settings !
|
||||
*
|
||||
* @var array
|
||||
* @since 2.5 db* variables
|
||||
*/
|
||||
protected $m_aSettings = array(
|
||||
'app_env_label' => array(
|
||||
'type' => 'string',
|
||||
@@ -106,6 +115,73 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'db_host' => array(
|
||||
'type' => 'string',
|
||||
'default' => null,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'db_user' => array(
|
||||
'type' => 'string',
|
||||
'default' => null,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'db_pwd' => array(
|
||||
'type' => 'string',
|
||||
'default' => null,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'db_name' => array(
|
||||
'type' => 'string',
|
||||
'default' => null,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'db_subname' => array(
|
||||
'type' => 'string',
|
||||
'default' => null,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'db_tls.enabled' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'If true then the connection to the DB will be encrypted',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'db_tls.ca' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Path to certificate authority file for SSL',
|
||||
'default' => null,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'db_character_set' => array( // @deprecated to remove in 2.7 ? #1001 utf8mb4 switch
|
||||
'type' => 'string',
|
||||
'description' => 'Deprecated since iTop 2.5 : now using utf8mb4',
|
||||
'default' => 'DEPRECATED_2.5',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'db_collation' => array( // @deprecated to remove in 2.7 ? #1001 utf8mb4 switch
|
||||
'type' => 'string',
|
||||
'description' => 'Deprecated since iTop 2.5 : now using utf8mb4_unicode_ci',
|
||||
'default' => 'DEPRECATED_2.5',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'skip_check_to_write' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Disable data format and integrity checks to boost up data load (insert or update)',
|
||||
@@ -189,8 +265,8 @@ class Config
|
||||
'min_autocomplete_chars' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'The minimum number of characters to type in order to trigger the "autocomplete" behavior',
|
||||
'default' => 3,
|
||||
'value' => 3,
|
||||
'default' => 2,
|
||||
'value' => 2,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
@@ -419,6 +495,22 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'email_default_sender_address' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Default address provided in the email from header field.',
|
||||
'default' => "",
|
||||
'value' => "",
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'email_default_sender_label' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Default label provided in the email from header field.',
|
||||
'default' => "",
|
||||
'value' => "",
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'apc_cache.enabled' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'If set, the APC cache is allowed (the PHP extension must also be active)',
|
||||
@@ -435,11 +527,27 @@ 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',
|
||||
// examples... not used (nor 'description')
|
||||
'examples' => array('America/Sao_Paulo', 'America/New_York (standing for EDT)', 'America/Los_Angeles (standing for PDT)', 'Asia/Istanbul', 'Asia/Singapore', 'Africa/Casablanca', 'Australia/Sydney'),
|
||||
'examples' => array(
|
||||
'America/Sao_Paulo',
|
||||
'America/New_York (standing for EDT)',
|
||||
'America/Los_Angeles (standing for PDT)',
|
||||
'Asia/Istanbul',
|
||||
'Asia/Singapore',
|
||||
'Africa/Casablanca',
|
||||
'Australia/Sydney'
|
||||
),
|
||||
'default' => 'Europe/Paris',
|
||||
'value' => 'Europe/Paris',
|
||||
'source_of_value' => '',
|
||||
@@ -679,6 +787,38 @@ class Config
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'email_decoration_class' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'CSS class(es) to use as decoration for the HTML rendering of the attribute. eg. "fa fa-envelope" will put a mail icon.',
|
||||
'default' => 'fa fa-envelope',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'phone_number_validation_pattern' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Regular expression to validate/detect the format of a phone number',
|
||||
'default' => "[0-9.\-\ \+\(\)]+",
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'phone_number_url_pattern' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Format for phone number url, use %1$s as a placeholder for the value. eg. "tel:%1$s" for regular phone applications or "callto:%1$s" for Skype. Default is "tel:%1$s".',
|
||||
'default' => 'tel:%1$s',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'phone_number_decoration_class' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'CSS class(es) to use as decoration for the HTML rendering of the attribute. eg. "fa fa-phone" will put a phone icon.',
|
||||
'default' => 'fa fa-phone',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'log_kpi_duration' => array(
|
||||
'type' => 'integer',
|
||||
@@ -913,6 +1053,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'draft_attachments_lifetime' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Lifetime (in seconds) of drafts\' attachments and inline images: after this duration, the garbage collector will delete them.',
|
||||
'default' => 3600,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'date_and_time_format' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'Format for date and time display (per language)',
|
||||
@@ -929,14 +1077,6 @@ class Config
|
||||
'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"',
|
||||
@@ -953,17 +1093,70 @@ class Config
|
||||
'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,
|
||||
),
|
||||
'secure_rest_services' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'When set to true, only the users with the profile "REST Services User" are allowed to use the REST web services.',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'search_manual_submit' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'Force manual submit of search all requests',
|
||||
'default' => false,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'high_cardinality_classes' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'List of classes with high cardinality (Force manual submit of search)',
|
||||
'default' => array(),
|
||||
'value' => array(),
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
);
|
||||
|
||||
public function IsProperty($sPropCode)
|
||||
{
|
||||
return (array_key_exists($sPropCode, $this->m_aSettings));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string identifier that can be used for example to name WebStorage/SessionStorage keys (they
|
||||
* are related to a whole domain, and a domain can host multiple itop)
|
||||
* Beware: do not expose server side information to the client !
|
||||
*/
|
||||
public function GetItopInstanceid()
|
||||
{
|
||||
return md5(utils::GetAbsoluteUrlAppRoot()
|
||||
.'==='.$this->Get('db_host')
|
||||
.'/'.$this->Get('db_name')
|
||||
.'/'.$this->Get('db_subname'));
|
||||
}
|
||||
|
||||
public function GetDescription($sPropCode)
|
||||
{
|
||||
return $this->m_aSettings[$sPropCode];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPropCode
|
||||
* @param mixed $value
|
||||
* @param string $sSourceDesc mandatory for variables with show_in_conf_sample=false
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function Set($sPropCode, $value, $sSourceDesc = 'unknown')
|
||||
{
|
||||
$sType = $this->m_aSettings[$sPropCode]['type'];
|
||||
@@ -996,15 +1189,6 @@ class Config
|
||||
return $this->m_aSettings[$sPropCode]['value'];
|
||||
}
|
||||
|
||||
// Those variables will be deprecated later, when the transition to ...Get('my_setting') will be done
|
||||
protected $m_sDBHost;
|
||||
protected $m_sDBUser;
|
||||
protected $m_sDBPwd;
|
||||
protected $m_sDBName;
|
||||
protected $m_sDBSubname;
|
||||
protected $m_sDBCharacterSet;
|
||||
protected $m_sDBCollation;
|
||||
|
||||
/**
|
||||
* Event log options (see LOG_... definition)
|
||||
*/
|
||||
@@ -1086,13 +1270,6 @@ class Config
|
||||
$this->m_aSettings[$sPropCode]['value'] = $aSettingInfo['default'];
|
||||
}
|
||||
|
||||
$this->m_sDBHost = '';
|
||||
$this->m_sDBUser = '';
|
||||
$this->m_sDBPwd = '';
|
||||
$this->m_sDBName = '';
|
||||
$this->m_sDBSubname = '';
|
||||
$this->m_sDBCharacterSet = DEFAULT_CHARACTER_SET;
|
||||
$this->m_sDBCollation = DEFAULT_COLLATION;
|
||||
$this->m_bLogGlobal = DEFAULT_LOG_GLOBAL;
|
||||
$this->m_bLogNotification = DEFAULT_LOG_NOTIFICATION;
|
||||
$this->m_bLogIssue = DEFAULT_LOG_ISSUE;
|
||||
@@ -1141,16 +1318,31 @@ class Config
|
||||
}
|
||||
if (!is_readable($sFileName))
|
||||
{
|
||||
throw new ConfigException("Could not read $sPurpose file (the file exists but cannot be read). Do you have the rights to access this file?", array('file' => $sFileName));
|
||||
throw new ConfigException("Could not read $sPurpose file (the file exists but cannot be read). Do you have the rights to access this file?",
|
||||
array('file' => $sFileName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sConfigFile
|
||||
*
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected function Load($sConfigFile)
|
||||
{
|
||||
$this->CheckFile('configuration', $sConfigFile);
|
||||
|
||||
$sConfigCode = trim(file_get_contents($sConfigFile));
|
||||
|
||||
// Variables created when doing an eval() on the config file
|
||||
/** @var array $MySettings */
|
||||
$MySettings = null;
|
||||
/** @var array $MyModuleSettings */
|
||||
$MyModuleSettings = null;
|
||||
/** @var array $MyModules */
|
||||
$MyModules = null;
|
||||
|
||||
// This does not work on several lines
|
||||
// preg_match('/^<\\?php(.*)\\?'.'>$/', $sConfigCode, $aMatches)...
|
||||
// So, I've implemented a solution suggested in the PHP doc (search for phpWrapper)
|
||||
@@ -1165,22 +1357,32 @@ class Config
|
||||
{
|
||||
// well, never reach in case of parsing error :-(
|
||||
// will be improved in PHP 6 ?
|
||||
throw new ConfigException('Error in configuration file', array('file' => $sConfigFile, 'error' => $e->getMessage()));
|
||||
throw new ConfigException('Error in configuration file',
|
||||
array('file' => $sConfigFile, 'error' => $e->getMessage()));
|
||||
}
|
||||
catch(Error $e)
|
||||
{
|
||||
// PHP 7
|
||||
throw new ConfigException('Error in configuration file',
|
||||
array('file' => $sConfigFile, 'error' => $e->getMessage().' at line '.$e->getLine()));
|
||||
}
|
||||
if (strlen($sNoise) > 0)
|
||||
{
|
||||
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
|
||||
throw new ConfigException('Syntax error in configuration file', array('file' => $sConfigFile, 'error' => '<tt>'.htmlentities($sNoise, ENT_QUOTES, 'UTF-8').'</tt>'));
|
||||
throw new ConfigException('Syntax error in configuration file',
|
||||
array('file' => $sConfigFile, 'error' => '<tt>'.htmlentities($sNoise, ENT_QUOTES, 'UTF-8').'</tt>'));
|
||||
}
|
||||
|
||||
if (!isset($MySettings) || !is_array($MySettings))
|
||||
{
|
||||
throw new ConfigException('Missing array in configuration file', array('file' => $sConfigFile, 'expected' => '$MySettings'));
|
||||
throw new ConfigException('Missing array in configuration file',
|
||||
array('file' => $sConfigFile, 'expected' => '$MySettings'));
|
||||
}
|
||||
|
||||
if (!array_key_exists('addons', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
|
||||
throw new ConfigException('Missing item in configuration file',
|
||||
array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
|
||||
}
|
||||
if (!array_key_exists('user rights', $MyModules['addons']))
|
||||
{
|
||||
@@ -1206,15 +1408,6 @@ class Config
|
||||
}
|
||||
}
|
||||
|
||||
$this->m_sDBHost = trim($MySettings['db_host']);
|
||||
$this->m_sDBUser = trim($MySettings['db_user']);
|
||||
$this->m_sDBPwd = trim($MySettings['db_pwd']);
|
||||
$this->m_sDBName = trim($MySettings['db_name']);
|
||||
$this->m_sDBSubname = trim($MySettings['db_subname']);
|
||||
|
||||
$this->m_sDBCharacterSet = isset($MySettings['db_character_set']) ? trim($MySettings['db_character_set']) : DEFAULT_CHARACTER_SET;
|
||||
$this->m_sDBCollation = isset($MySettings['db_collation']) ? trim($MySettings['db_collation']) : DEFAULT_COLLATION;
|
||||
|
||||
$this->m_bLogGlobal = isset($MySettings['log_global']) ? (bool)trim($MySettings['log_global']) : DEFAULT_LOG_GLOBAL;
|
||||
$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;
|
||||
@@ -1248,6 +1441,7 @@ class Config
|
||||
{
|
||||
return $this->m_aModuleSettings[$sModule][$sProperty];
|
||||
}
|
||||
|
||||
// Fall back to the predefined XML parameter, if any
|
||||
return $this->GetModuleParameter($sModule, $sProperty, $defaultvalue);
|
||||
}
|
||||
@@ -1263,6 +1457,7 @@ class Config
|
||||
$ret = $aAllParams[$sProperty];
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
@@ -1275,44 +1470,80 @@ class Config
|
||||
{
|
||||
return $this->m_aAddons;
|
||||
}
|
||||
|
||||
public function SetAddons($aAddons)
|
||||
{
|
||||
$this->m_aAddons = $aAddons;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated 2.5 will be removed in 2.6
|
||||
* @see Config::Get() as a replacement
|
||||
*/
|
||||
public function GetDBHost()
|
||||
{
|
||||
return $this->m_sDBHost;
|
||||
return $this->Get('db_host');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated 2.5 will be removed in 2.6
|
||||
* @see Config::Get() as a replacement
|
||||
*/
|
||||
public function GetDBName()
|
||||
{
|
||||
return $this->m_sDBName;
|
||||
return $this->Get('db_name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated 2.5 will be removed in 2.6
|
||||
* @see Config::Get() as a replacement
|
||||
*/
|
||||
public function GetDBSubname()
|
||||
{
|
||||
return $this->m_sDBSubname;
|
||||
return $this->Get('db_subname');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated 2.5 will be removed in 2.6 #1001 utf8mb4 switch
|
||||
* @see Config::DEFAULT_CHARACTER_SET
|
||||
*/
|
||||
public function GetDBCharacterSet()
|
||||
{
|
||||
return $this->m_sDBCharacterSet;
|
||||
return DEFAULT_CHARACTER_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated 2.5 will be removed in 2.6 #1001 utf8mb4 switch
|
||||
* @see Config::DEFAULT_COLLATION
|
||||
*/
|
||||
public function GetDBCollation()
|
||||
{
|
||||
return $this->m_sDBCollation;
|
||||
return DEFAULT_COLLATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated 2.5 will be removed in 2.6
|
||||
* @see Config::Get() as a replacement
|
||||
*/
|
||||
public function GetDBUser()
|
||||
{
|
||||
return $this->m_sDBUser;
|
||||
return $this->Get('db_user');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated 2.5 will be removed in 2.6
|
||||
* @see Config::Get() as a replacement
|
||||
*/
|
||||
public function GetDBPwd()
|
||||
{
|
||||
return $this->m_sDBPwd;
|
||||
return $this->Get('db_pwd');
|
||||
}
|
||||
|
||||
public function GetLogGlobal()
|
||||
@@ -1395,41 +1626,6 @@ class Config
|
||||
return $this->m_aCharsets;
|
||||
}
|
||||
|
||||
public function SetDBHost($sDBHost)
|
||||
{
|
||||
$this->m_sDBHost = $sDBHost;
|
||||
}
|
||||
|
||||
public function SetDBName($sDBName)
|
||||
{
|
||||
$this->m_sDBName = $sDBName;
|
||||
}
|
||||
|
||||
public function SetDBSubname($sDBSubName)
|
||||
{
|
||||
$this->m_sDBSubname = $sDBSubName;
|
||||
}
|
||||
|
||||
public function SetDBCharacterSet($sDBCharacterSet)
|
||||
{
|
||||
$this->m_sDBCharacterSet = $sDBCharacterSet;
|
||||
}
|
||||
|
||||
public function SetDBCollation($sDBCollation)
|
||||
{
|
||||
$this->m_sDBCollation = $sDBCollation;
|
||||
}
|
||||
|
||||
public function SetDBUser($sUser)
|
||||
{
|
||||
$this->m_sDBUser = $sUser;
|
||||
}
|
||||
|
||||
public function SetDBPwd($sPwd)
|
||||
{
|
||||
$this->m_sDBPwd = $sPwd;
|
||||
}
|
||||
|
||||
public function SetLogGlobal($iLogGlobal)
|
||||
{
|
||||
$this->m_iLogGlobal = $iLogGlobal;
|
||||
@@ -1519,7 +1715,8 @@ class Config
|
||||
|
||||
/**
|
||||
* Render the configuration as an associative array
|
||||
* @return boolean True otherwise throws an Exception
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function ToArray()
|
||||
{
|
||||
@@ -1528,13 +1725,6 @@ class Config
|
||||
{
|
||||
$aSettings[$sPropCode] = $aSettingInfo['value'];
|
||||
}
|
||||
$aSettings['db_host'] = $this->m_sDBHost;
|
||||
$aSettings['db_user'] = $this->m_sDBUser;
|
||||
$aSettings['db_pwd'] = $this->m_sDBPwd;
|
||||
$aSettings['db_name'] = $this->m_sDBName;
|
||||
$aSettings['db_subname'] = $this->m_sDBSubname;
|
||||
$aSettings['db_character_set'] = $this->m_sDBCharacterSet;
|
||||
$aSettings['db_collation'] = $this->m_sDBCollation;
|
||||
$aSettings['log_global'] = $this->m_bLogGlobal;
|
||||
$aSettings['log_notification'] = $this->m_bLogNotification;
|
||||
$aSettings['log_issue'] = $this->m_bLogIssue;
|
||||
@@ -1562,13 +1752,16 @@ class Config
|
||||
{
|
||||
$aSettings['addon_list'][] = $sFile;
|
||||
}
|
||||
|
||||
return $aSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the configuration to a file (php format) that can be reloaded later
|
||||
* By default write to the same file that was specified when constructing the object
|
||||
*
|
||||
* @param $sFileName string Name of the file to write to (emtpy to write to the same file)
|
||||
*
|
||||
* @return boolean True otherwise throws an Exception
|
||||
*/
|
||||
public function WriteToFile($sFileName = '')
|
||||
@@ -1585,7 +1778,8 @@ class Config
|
||||
fwrite($hFile, " *\n");
|
||||
fwrite($hFile, " * Configuration file, generated by the ".ITOP_APPLICATION." configuration wizard\n");
|
||||
fwrite($hFile, " *\n");
|
||||
fwrite($hFile, " * The file is used in MetaModel::LoadConfig() which does all the necessary initialization job\n");
|
||||
fwrite($hFile,
|
||||
" * The file is used in MetaModel::LoadConfig() which does all the necessary initialization job\n");
|
||||
fwrite($hFile, " *\n");
|
||||
fwrite($hFile, " */\n");
|
||||
|
||||
@@ -1627,13 +1821,6 @@ class Config
|
||||
|
||||
// Old fashioned remaining values
|
||||
$aOtherValues = array(
|
||||
'db_host' => $this->m_sDBHost,
|
||||
'db_user' => $this->m_sDBUser,
|
||||
'db_pwd' => $this->m_sDBPwd,
|
||||
'db_name' => $this->m_sDBName,
|
||||
'db_subname' => $this->m_sDBSubname,
|
||||
'db_character_set' => $this->m_sDBCharacterSet,
|
||||
'db_collation' => $this->m_sDBCollation,
|
||||
'default_language' => $this->m_sDefaultLanguage,
|
||||
'allowed_login_types' => $this->m_sAllowedLoginTypes,
|
||||
'ext_auth_variable' => $this->m_sExtAuthVariable,
|
||||
@@ -1677,7 +1864,8 @@ class Config
|
||||
{
|
||||
$default = $default ? 'true' : 'false';
|
||||
}
|
||||
fwrite($hFile, "\t//\tdefault: ".self::PrettyVarExport($aSettingInfo['default'],"\t//\t\t", true)."\n");
|
||||
fwrite($hFile,
|
||||
"\t//\tdefault: ".self::PrettyVarExport($aSettingInfo['default'], "\t//\t\t", true)."\n");
|
||||
}
|
||||
fwrite($hFile, "\t'$sPropCode' => $sSeenAs,\n");
|
||||
}
|
||||
@@ -1713,6 +1901,7 @@ class Config
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, ");\n");
|
||||
fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting !
|
||||
|
||||
return fclose($hFile);
|
||||
}
|
||||
else
|
||||
@@ -1743,17 +1932,38 @@ class Config
|
||||
}
|
||||
if (isset($aParamValues['db_server']))
|
||||
{
|
||||
$this->SetDBHost($aParamValues['db_server']);
|
||||
$this->SetDBUser($aParamValues['db_user']);
|
||||
$this->SetDBPwd($aParamValues['db_pwd']);
|
||||
$this->Set('db_host', $aParamValues['db_server']);
|
||||
$this->Set('db_user', $aParamValues['db_user']);
|
||||
$this->Set('db_pwd', $aParamValues['db_pwd']);
|
||||
$sDBName = $aParamValues['db_name'];
|
||||
if ($sDBName == '')
|
||||
{
|
||||
// Todo - obsolete after the transition to the new setup (2.0) is complete (WARNING: used by the designer)
|
||||
if (isset($aParamValues['new_db_name']))
|
||||
{
|
||||
$sDBName = $aParamValues['new_db_name'];
|
||||
}
|
||||
$this->SetDBName($sDBName);
|
||||
$this->SetDBSubname($aParamValues['db_prefix']);
|
||||
}
|
||||
$this->Set('db_name', $sDBName);
|
||||
$this->Set('db_subname', $aParamValues['db_prefix']);
|
||||
|
||||
$bDbTlsEnabled = (bool) $aParamValues['db_tls_enabled'];
|
||||
if ($bDbTlsEnabled)
|
||||
{
|
||||
$this->Set('db_tls.enabled', $bDbTlsEnabled, 'UpdateFromParams');
|
||||
}
|
||||
else
|
||||
{
|
||||
// disabled : we don't want parameter in the file
|
||||
$this->Set('db_tls.enabled', $bDbTlsEnabled, null);
|
||||
}
|
||||
$sDbTlsCa = $bDbTlsEnabled ? $aParamValues['db_tls_ca'] : null;
|
||||
if (isset($sDbTlsCa) && !empty($sDbTlsCa)) {
|
||||
$this->Set('db_tls.ca', $sDbTlsCa, 'UpdateFromParams');
|
||||
} else {
|
||||
// empty parameter : we don't want it in the file
|
||||
$this->Set('db_tls.ca', null, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($aParamValues['selected_modules']))
|
||||
@@ -1765,12 +1975,22 @@ class Config
|
||||
$aSelectedModules = null;
|
||||
}
|
||||
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
|
||||
|
||||
if (isset($aParamValues['source_dir']))
|
||||
{
|
||||
$this->Set('source_dir', $aParamValues['source_dir']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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)
|
||||
@@ -1847,9 +2067,12 @@ class Config
|
||||
|
||||
/**
|
||||
* Pretty format a var_export'ed value so that (if possible) the identation is preserved on every line
|
||||
*
|
||||
* @param mixed $value The value to export
|
||||
* @param string $sIndentation The string to use to indent the text
|
||||
* @param bool $bForceIndentation Forces the identation (enven if it breaks/changes an eval, for example to ouput a value inside a comment)
|
||||
* @param bool $bForceIndentation Forces the identation (enven if it breaks/changes an eval, for example to ouput a
|
||||
* value inside a comment)
|
||||
*
|
||||
* @return string The indented export string
|
||||
*/
|
||||
protected static function PrettyVarExport($value, $sIndentation, $bForceIndentation = false)
|
||||
@@ -1858,6 +2081,8 @@ class Config
|
||||
$sNiceExport = str_replace(array("\r\n", "\n", "\r"), "\n".$sIndentation, trim($sExport));
|
||||
if (!$bForceIndentation)
|
||||
{
|
||||
/** @var array $aImported */
|
||||
$aImported = null;
|
||||
eval('$aImported='.$sNiceExport.';');
|
||||
// Check if adding the identations at the beginning of each line
|
||||
// did not modify the values (in case of a string containing a line break)
|
||||
@@ -1866,8 +2091,10 @@ class Config
|
||||
$sNiceExport = $sExport;
|
||||
}
|
||||
}
|
||||
|
||||
return $sNiceExport;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -69,6 +69,13 @@ class CoreException extends Exception
|
||||
parent::__construct($sMessage, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string code and message for log purposes
|
||||
*/
|
||||
public function getInfoLog()
|
||||
{
|
||||
return 'error_code='.$this->getCode().', message="'.$this->getMessage().'"';
|
||||
}
|
||||
public function getHtmlDesc($sHighlightHtmlBegin = '<b>', $sHighlightHtmlEnd = '</b>')
|
||||
{
|
||||
return $this->getMessage();
|
||||
@@ -112,4 +119,11 @@ 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
|
||||
{
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ class cmdbDataGenerator
|
||||
*/
|
||||
protected function CleanForEmail($sText)
|
||||
{
|
||||
return str_replace(array("'", "é", "è", "ê", "ç", "à", "â", "ñ", "ö", "ä"), array("", "e", "e", "e", "c", "a", "a", "n", "oe", "ae"), $sText);
|
||||
return str_replace(array("'", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>"), array("", "e", "e", "e", "c", "a", "a", "n", "oe", "ae"), $sText);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,258 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
|
||||
<user_rights>
|
||||
<profiles>
|
||||
<profile id="1024" _delta="define">
|
||||
<name>REST Services User</name>
|
||||
<description>Only users having this profile are allowed to use the REST Web Services (unless 'secure_rest_services' is set to false in the configuration file).</description>
|
||||
<groups />
|
||||
</profile>
|
||||
</profiles>
|
||||
</user_rights>
|
||||
<meta>
|
||||
<classes>
|
||||
<class id="User" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>core,grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="contactid" xsi:type="AttributeExternalKey">
|
||||
<target_class>Person</target_class>
|
||||
</field>
|
||||
<field id="last_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="first_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="email" xsi:type="AttributeExternalField"/>
|
||||
<field id="org_id" xsi:type="AttributeExternalField"/>
|
||||
<field id="login" xsi:type="AttributeString"/>
|
||||
<field id="language" xsi:type="AttributeApplicationLanguage"/>
|
||||
<field id="status" xsi:type="AttributeEnum"/>
|
||||
<field id="profile_list" xsi:type="AttributeLinkedSetIndirect"/>
|
||||
<field id="allowed_org_list" xsi:type="AttributeLinkedSetIndirect"/>
|
||||
<field id="finalclass" xsi:type="AttributeFinalClass"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
<field id="contactid_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="contactid_obsolescence_flag" xsi:type="AttributeExternalField"/>
|
||||
<field id="org_id_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="org_id_obsolescence_flag" xsi:type="AttributeExternalField"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="URP_Profiles" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>addon/userrights,grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString"/>
|
||||
<field id="description" xsi:type="AttributeString"/>
|
||||
<field id="user_list" xsi:type="AttributeLinkedSetIndirect"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="URP_UserProfile" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>addon/userrights,grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="userid" xsi:type="AttributeExternalKey">
|
||||
<target_class>User</target_class>
|
||||
</field>
|
||||
<field id="userlogin" xsi:type="AttributeExternalField"/>
|
||||
<field id="profileid" xsi:type="AttributeExternalKey">
|
||||
<target_class>URP_Profiles</target_class>
|
||||
</field>
|
||||
<field id="profile" xsi:type="AttributeExternalField"/>
|
||||
<field id="reason" xsi:type="AttributeString"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
<field id="userid_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="userid_finalclass_recall" xsi:type="AttributeExternalField"/>
|
||||
<field id="profileid_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="URP_UserOrg" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>addon/userrights,grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="userid" xsi:type="AttributeExternalKey">
|
||||
<target_class>User</target_class>
|
||||
</field>
|
||||
<field id="userlogin" xsi:type="AttributeExternalField"/>
|
||||
<field id="allowed_org_id" xsi:type="AttributeExternalKey">
|
||||
<target_class>Organization</target_class>
|
||||
</field>
|
||||
<field id="allowed_org_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="reason" xsi:type="AttributeString"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
<field id="userid_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="userid_finalclass_recall" xsi:type="AttributeExternalField"/>
|
||||
<field id="allowed_org_id_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="allowed_org_id_obsolescence_flag" xsi:type="AttributeExternalField"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="Action" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>grant_by_profile,core/cmdb</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString"/>
|
||||
<field id="description" xsi:type="AttributeString"/>
|
||||
<field id="status" xsi:type="AttributeEnum"/>
|
||||
<field id="trigger_list" xsi:type="AttributeLinkedSetIndirect"/>
|
||||
<field id="finalclass" xsi:type="AttributeFinalClass"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="Trigger" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>grant_by_profile,core/cmdb</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="description" xsi:type="AttributeString"/>
|
||||
<field id="action_list" xsi:type="AttributeLinkedSetIndirect"/>
|
||||
<field id="finalclass" xsi:type="AttributeFinalClass"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="SynchroDataSource" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,view_in_gui,grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString"/>
|
||||
<field id="description" xsi:type="AttributeText"/>
|
||||
<field id="status" xsi:type="AttributeEnum"/>
|
||||
<field id="user_id" xsi:type="AttributeExternalKey">
|
||||
<target_class>User</target_class>
|
||||
</field>
|
||||
<field id="notify_contact_id" xsi:type="AttributeExternalKey">
|
||||
<target_class>Contact</target_class>
|
||||
</field>
|
||||
<field id="scope_class" xsi:type="AttributeClass"/>
|
||||
<field id="database_table_name" xsi:type="AttributeString"/>
|
||||
<field id="scope_restriction" xsi:type="AttributeString"/>
|
||||
<field id="full_load_periodicity" xsi:type="AttributeDuration"/>
|
||||
<field id="reconciliation_policy" xsi:type="AttributeEnum"/>
|
||||
<field id="action_on_zero" xsi:type="AttributeEnum"/>
|
||||
<field id="action_on_one" xsi:type="AttributeEnum"/>
|
||||
<field id="action_on_multiple" xsi:type="AttributeEnum"/>
|
||||
<field id="delete_policy" xsi:type="AttributeEnum"/>
|
||||
<field id="delete_policy_update" xsi:type="AttributeString"/>
|
||||
<field id="delete_policy_retention" xsi:type="AttributeDuration"/>
|
||||
<field id="attribute_list" xsi:type="AttributeLinkedSet"/>
|
||||
<field id="user_delete_policy" xsi:type="AttributeEnum"/>
|
||||
<field id="url_icon" xsi:type="AttributeURL"/>
|
||||
<field id="url_application" xsi:type="AttributeString"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
<field id="user_id_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="user_id_finalclass_recall" xsi:type="AttributeExternalField"/>
|
||||
<field id="notify_contact_id_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="notify_contact_id_finalclass_recall" xsi:type="AttributeExternalField"/>
|
||||
<field id="notify_contact_id_obsolescence_flag" xsi:type="AttributeExternalField"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="SynchroAttribute" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,view_in_gui,grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="sync_source_id" xsi:type="AttributeExternalKey">
|
||||
<target_class>SynchroDataSource</target_class>
|
||||
</field>
|
||||
<field id="sync_source_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="attcode" xsi:type="AttributeString"/>
|
||||
<field id="update" xsi:type="AttributeBoolean"/>
|
||||
<field id="reconcile" xsi:type="AttributeBoolean"/>
|
||||
<field id="update_policy" xsi:type="AttributeEnum"/>
|
||||
<field id="finalclass" xsi:type="AttributeFinalClass"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
<field id="sync_source_id_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="AuditRule" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>application, grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString"/>
|
||||
<field id="description" xsi:type="AttributeString"/>
|
||||
<field id="query" xsi:type="AttributeOQL"/>
|
||||
<field id="valid_flag" xsi:type="AttributeEnum"/>
|
||||
<field id="category_id" xsi:type="AttributeExternalKey">
|
||||
<target_class>AuditCategory</target_class>
|
||||
</field>
|
||||
<field id="category_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
<field id="category_id_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="AuditCategory" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>application, grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString"/>
|
||||
<field id="description" xsi:type="AttributeString"/>
|
||||
<field id="definition_set" xsi:type="AttributeOQL"/>
|
||||
<field id="rules_list" xsi:type="AttributeLinkedSet"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="Query" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,view_in_gui,application,grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString"/>
|
||||
<field id="description" xsi:type="AttributeText"/>
|
||||
<field id="fields" xsi:type="AttributeText"/>
|
||||
<field id="finalclass" xsi:type="AttributeFinalClass"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="lnkTriggerAction" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>grant_by_profile,core/cmdb,application</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="action_id" xsi:type="AttributeExternalKey">
|
||||
<target_class>Action</target_class>
|
||||
</field>
|
||||
<field id="action_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="trigger_id" xsi:type="AttributeExternalKey">
|
||||
<target_class>Trigger</target_class>
|
||||
</field>
|
||||
<field id="trigger_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="order" xsi:type="AttributeInteger"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
<field id="action_id_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="action_id_finalclass_recall" xsi:type="AttributeExternalField"/>
|
||||
<field id="trigger_id_friendlyname" xsi:type="AttributeExternalField"/>
|
||||
<field id="trigger_id_finalclass_recall" xsi:type="AttributeExternalField"/>
|
||||
</fields>
|
||||
</class>
|
||||
</classes>
|
||||
</meta>
|
||||
</itop_design>
|
||||
@@ -74,8 +74,8 @@ abstract class DBObject implements iDisplay
|
||||
private static $m_aBulkInsertCols = array(); // class => array of ('table' => array of <sql_column>)
|
||||
private static $m_bBulkInsert = false;
|
||||
|
||||
private $m_bIsInDB = false; // true IIF the object is mapped to a DB record
|
||||
private $m_iKey = null;
|
||||
protected $m_bIsInDB = false; // true IIF the object is mapped to a DB record
|
||||
protected $m_iKey = null;
|
||||
private $m_aCurrValues = array();
|
||||
protected $m_aOrigValues = array();
|
||||
|
||||
@@ -163,13 +163,8 @@ abstract class DBObject implements iDisplay
|
||||
$sClass = get_class($this);
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
$iPKey = $this->GetKey();
|
||||
$sRet .= "<b title=\"$sRootClass\">$sClass</b>::$iPKey<br/>\n";
|
||||
$sRet .= "<ul class=\"treeview\">\n";
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
|
||||
{
|
||||
$sRet .= "<li>".$oAttDef->GetLabel()." = ".$this->GetAsHtml($sAttCode)."</li>\n";
|
||||
}
|
||||
$sRet .= "</ul>";
|
||||
$sFriendlyname = $this->Get('friendlyname');
|
||||
$sRet .= "<b title=\"$sRootClass\">$sClass</b>::$iPKey ($sFriendlyname)<br/>\n";
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
@@ -573,7 +568,8 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this));
|
||||
}
|
||||
return $this->m_aOrigValues[$sAttCode];
|
||||
$aOrigValues = $this->m_aOrigValues;
|
||||
return isset($aOrigValues[$sAttCode]) ? $aOrigValues[$sAttCode] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1053,25 +1049,25 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
|
||||
* for the given attribute in the current state of the object
|
||||
* @param $sAttCode string $sAttCode The code of the attribute
|
||||
* @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas)
|
||||
* @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used
|
||||
* @return integer Flags: the binary combination of the flags applicable to this attribute
|
||||
*
|
||||
* @param string $sAttCode $sAttCode The code of the attribute
|
||||
* @param array $aReasons To store the reasons why the attribute is read-only (info about the synchro replicas)
|
||||
* @param string $sTargetState The target state in which to evalutate the flags, if empty the current state will be
|
||||
* used
|
||||
*
|
||||
* @return integer the binary combination of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...) for the
|
||||
* given attribute in the given state of the object
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
|
||||
{
|
||||
$iFlags = 0; // By default (if no life cycle) no flag at all
|
||||
|
||||
$aReadOnlyAtts = $this->GetReadOnlyAttributes();
|
||||
if ($aReadOnlyAtts != null)
|
||||
{
|
||||
if (in_array($sAttCode, $aReadOnlyAtts))
|
||||
if (($aReadOnlyAtts != null) && (in_array($sAttCode, $aReadOnlyAtts)))
|
||||
{
|
||||
return OPT_ATT_READONLY;
|
||||
}
|
||||
}
|
||||
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
|
||||
if (!empty($sStateAttCode))
|
||||
@@ -1094,6 +1090,19 @@ abstract class DBObject implements iDisplay
|
||||
return $iFlags | $iSynchroFlags; // Combine both sets of flags
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAttCode
|
||||
* @param array $aReasons To store the reasons why the attribute is read-only (info about the synchro replicas)
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function IsAttributeReadOnlyForCurrentState($sAttCode, &$aReasons = array())
|
||||
{
|
||||
$iAttFlags = $this->GetAttributeFlags($sAttCode, $aReasons);
|
||||
|
||||
return ($iAttFlags & OPT_ATT_READONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
|
||||
* for the given attribute in a transition
|
||||
@@ -1216,16 +1225,17 @@ abstract class DBObject implements iDisplay
|
||||
if ($oAtt->IsHierarchicalKey())
|
||||
{
|
||||
// This check cannot be deactivated since otherwise the user may break things by a CSV import of a bulk modify
|
||||
if ($toCheck == $this->GetKey())
|
||||
$aValues = $oAtt->GetAllowedValues(array('this' => $this));
|
||||
if (!array_key_exists($toCheck, $aValues))
|
||||
{
|
||||
return "An object can not be its own parent in a hierarchy (".$oAtt->Getlabel()." = $toCheck)";
|
||||
return "Value not allowed [$toCheck]";
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($oAtt->IsScalar())
|
||||
{
|
||||
$aValues = $oAtt->GetAllowedValues($this->ToArgsForQuery());
|
||||
if (count($aValues) > 0)
|
||||
if (is_array($aValues) && (count($aValues) > 0))
|
||||
{
|
||||
if (!array_key_exists($toCheck, $aValues))
|
||||
{
|
||||
@@ -1731,6 +1741,14 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
$this->m_bIsInDB = true;
|
||||
$this->m_bDirty = false;
|
||||
foreach ($this->m_aCurrValues as $sAttCode => $value)
|
||||
{
|
||||
if (is_object($value))
|
||||
{
|
||||
$value = clone $value;
|
||||
}
|
||||
$this->m_aOrigValues[$sAttCode] = $value;
|
||||
}
|
||||
|
||||
$this->AfterInsert();
|
||||
|
||||
@@ -2922,7 +2940,7 @@ abstract class DBObject implements iDisplay
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
$oSet = new CMDBObjectSet($oSearch);
|
||||
if ($oSet->Count() > 0)
|
||||
if ($oSet->CountExceeds(0))
|
||||
{
|
||||
$aDependentObjects[$sRemoteClass][$sExtKeyAttCode] = array(
|
||||
'attribute' => $oExtKeyAttDef,
|
||||
@@ -3640,5 +3658,76 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_aCurrValues['archive_date'] = null;
|
||||
$this->m_aOrigValues['archive_date'] = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sClass Needs to be an instanciable class
|
||||
* @returns $oObj
|
||||
**/
|
||||
public static function MakeDefaultInstance($sClass)
|
||||
{
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
|
||||
$oObj = MetaModel::NewObject($sClass);
|
||||
if (!empty($sStateAttCode))
|
||||
{
|
||||
$sTargetState = MetaModel::GetDefaultState($sClass);
|
||||
$oObj->Set($sStateAttCode, $sTargetState);
|
||||
}
|
||||
return $oObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a new object with data from context
|
||||
* @param array $aContextParam Context used for creation form prefilling
|
||||
*
|
||||
*/
|
||||
public function PrefillCreationForm(&$aContextParam)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete an object after a state transition with data from context
|
||||
* @param array $aContextParam Context used for creation form prefilling
|
||||
*
|
||||
*/
|
||||
public function PrefillTransitionForm(&$aContextParam)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a filter ($aContextParam['filter']) data from context
|
||||
* (Called on source object)
|
||||
* @param array $aContextParam Context used for creation form prefilling
|
||||
*
|
||||
*/
|
||||
public function PrefillSearchForm(&$aContextParam)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefill a creation / stimulus change / search form according to context, current state of an object, stimulus.. $sOperation
|
||||
* @param string $sOperation Operation identifier
|
||||
* @param array $aContextParam Context used for creation form prefilling
|
||||
*
|
||||
*/
|
||||
public function PrefillForm($sOperation, &$aContextParam)
|
||||
{
|
||||
switch($sOperation){
|
||||
case 'creation_from_0':
|
||||
case 'creation_from_extkey':
|
||||
case 'creation_from_editinplace':
|
||||
$this->PrefillCreationForm($aContextParam);
|
||||
break;
|
||||
case 'state_change':
|
||||
$this->PrefillTransitionForm($aContextParam);
|
||||
break;
|
||||
case 'search':
|
||||
$this->PrefillSearchForm($aContextParam);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
// Copyright (c) 2010-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -15,14 +15,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Define filters for a given class of objects (formerly named "filter")
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
//
|
||||
|
||||
// Dev hack for disabling the some query build optimizations (Folding/Merging)
|
||||
define('ENABLE_OPT', true);
|
||||
@@ -33,7 +26,6 @@ class DBObjectSearch extends DBSearch
|
||||
private $m_aSelectedClasses; // selected for the output (alias => class name)
|
||||
private $m_oSearchCondition;
|
||||
private $m_aParams;
|
||||
private $m_aFullText;
|
||||
private $m_aPointingTo;
|
||||
private $m_aReferencedBy;
|
||||
|
||||
@@ -54,7 +46,6 @@ class DBObjectSearch extends DBSearch
|
||||
$this->m_aClasses = array($sClassAlias => $sClass);
|
||||
$this->m_oSearchCondition = new TrueExpression;
|
||||
$this->m_aParams = array();
|
||||
$this->m_aFullText = array();
|
||||
$this->m_aPointingTo = array();
|
||||
$this->m_aReferencedBy = array();
|
||||
}
|
||||
@@ -110,6 +101,11 @@ class DBObjectSearch extends DBSearch
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param $sNewClass
|
||||
* @param null $sAlias
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function ChangeClass($sNewClass, $sAlias = null)
|
||||
{
|
||||
@@ -195,7 +191,9 @@ class DBObjectSearch extends DBSearch
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
*
|
||||
* @return bool True if the alias has been found and changed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function RenameAlias($sOldName, $sNewName)
|
||||
{
|
||||
@@ -285,7 +283,6 @@ class DBObjectSearch extends DBSearch
|
||||
public function IsAny()
|
||||
{
|
||||
if (!$this->m_oSearchCondition->IsTrue()) return false;
|
||||
if (count($this->m_aFullText) > 0) return false;
|
||||
if (count($this->m_aPointingTo) > 0) return false;
|
||||
if (count($this->m_aReferencedBy) > 0) return false;
|
||||
return true;
|
||||
@@ -367,10 +364,19 @@ class DBObjectSearch extends DBSearch
|
||||
$this->AddConditionExpression($oNewCondition);
|
||||
}
|
||||
|
||||
public function AddCondition($sFilterCode, $value, $sOpCode = null, $bParseSeachString = false)
|
||||
/**
|
||||
* @param string $sFilterCode
|
||||
* @param mixed $value
|
||||
* @param string $sOpCode operator to use : 'IN', 'NOT IN', 'Contains',' Begins with', 'Finishes with', ...
|
||||
* @param bool $bParseSearchString
|
||||
*
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @see AddConditionForInOperatorUsingParam for IN/NOT IN queries with lots of params
|
||||
*/
|
||||
public function AddCondition($sFilterCode, $value, $sOpCode = null, $bParseSearchString = false)
|
||||
{
|
||||
MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass()));
|
||||
$oFilterDef = MetaModel::GetClassFilterDef($this->GetClass(), $sFilterCode);
|
||||
|
||||
$oField = new FieldExpression($sFilterCode, $this->GetClassAlias());
|
||||
if (empty($sOpCode))
|
||||
@@ -382,15 +388,15 @@ class DBObjectSearch extends DBSearch
|
||||
else
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode);
|
||||
$oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams, $bParseSeachString);
|
||||
$oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams);
|
||||
$this->AddConditionExpression($oNewCondition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Parse search strings if needed and if the filter code corresponds to a valid attcode
|
||||
if($bParseSeachString && MetaModel::IsValidAttCode($this->GetClass(), $sFilterCode))
|
||||
if($bParseSearchString && MetaModel::IsValidAttCode($this->GetClass(), $sFilterCode))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sFilterCode);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode);
|
||||
$value = $oAttDef->ParseSearchString($value);
|
||||
}
|
||||
|
||||
@@ -408,14 +414,14 @@ class DBObjectSearch extends DBSearch
|
||||
throw new CoreException('Deprecated operator, please consider using OQL (SQL) expressions like "(TO_DAYS(NOW()) - TO_DAYS(x)) AS AgeDays"', array('operator' => $sOpCode));
|
||||
break;
|
||||
|
||||
case "IN":
|
||||
case 'IN':
|
||||
if (!is_array($value)) $value = array($value);
|
||||
if (count($value) === 0) throw new Exception('AddCondition '.$sOpCode.': Value cannot be an empty array.');
|
||||
$sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
|
||||
$sOQLCondition = $oField->Render()." IN $sListExpr";
|
||||
break;
|
||||
|
||||
case "NOTIN":
|
||||
case 'NOTIN':
|
||||
if (!is_array($value)) $value = array($value);
|
||||
if (count($value) === 0) throw new Exception('AddCondition '.$sOpCode.': Value cannot be an empty array.');
|
||||
$sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
|
||||
@@ -469,6 +475,8 @@ class DBObjectSearch extends DBSearch
|
||||
break;
|
||||
case "IN":
|
||||
case "NOTIN":
|
||||
// this will parse all of the values... Can take forever if there are lots of them !
|
||||
// In this case using a parameter is far better : WHERE ... IN (:my_param)
|
||||
$oNewCondition = Expression::FromOQL($sOQLCondition);
|
||||
break;
|
||||
|
||||
@@ -483,12 +491,39 @@ class DBObjectSearch extends DBSearch
|
||||
$this->AddConditionExpression($oNewCondition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sFilterCode attribute code to use
|
||||
* @param array $aValues
|
||||
* @param bool $bPositiveMatch if true will add a IN filter, else a NOT IN
|
||||
*
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @since 2.5 N°1418
|
||||
*/
|
||||
public function AddConditionForInOperatorUsingParam($sFilterCode, $aValues, $bPositiveMatch = true)
|
||||
{
|
||||
$oFieldExpression = new FieldExpression($sFilterCode, $this->GetClassAlias());
|
||||
|
||||
$sOperator = $bPositiveMatch ? 'IN' : 'NOT IN';
|
||||
|
||||
$sInParamName = $this->GenerateUniqueParamName();
|
||||
$oParamExpression = new VariableExpression($sInParamName);
|
||||
$this->GetInternalParamsByRef()[$sInParamName] = $aValues;
|
||||
|
||||
$oListExpression = new ListExpression(array($oParamExpression));
|
||||
|
||||
$oInCondition = new BinaryExpression($oFieldExpression, $sOperator, $oListExpression);
|
||||
$this->AddConditionExpression($oInCondition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param string $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...)
|
||||
* @param $value
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
* @throws \CoreWarning
|
||||
*/
|
||||
public function AddConditionAdvanced($sAttSpec, $value)
|
||||
{
|
||||
@@ -549,9 +584,24 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition_FullText($sFullText)
|
||||
public function AddCondition_FullText($sNeedle)
|
||||
{
|
||||
$this->m_aFullText[] = $sFullText;
|
||||
// Transform the full text condition into additional condition expression
|
||||
$aFullTextFields = array();
|
||||
foreach (MetaModel::ListAttributeDefs($this->GetClass()) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (!$oAttDef->IsScalar()) continue;
|
||||
if ($oAttDef->IsExternalKey()) continue;
|
||||
$aFullTextFields[] = new FieldExpression($sAttCode, $this->GetClassAlias());
|
||||
}
|
||||
$oTextFields = new CharConcatWSExpression(' ', $aFullTextFields);
|
||||
|
||||
$sQueryParam = 'needle';
|
||||
$oFlexNeedle = new CharConcatExpression(array(new ScalarExpression('%'), new VariableExpression($sQueryParam), new ScalarExpression('%')));
|
||||
|
||||
$oNewCond = new BinaryExpression($oTextFields, 'LIKE', $oFlexNeedle);
|
||||
$this->AddConditionExpression($oNewCond);
|
||||
$this->m_aParams[$sQueryParam] = $sNeedle;
|
||||
}
|
||||
|
||||
protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation, $bTranslateMainAlias = true)
|
||||
@@ -670,9 +720,9 @@ class DBObjectSearch extends DBSearch
|
||||
* - convert a translation table (format optimized for the translation in an expression tree) into simple hash
|
||||
* - compile over an eventually existing map
|
||||
*
|
||||
* @param $aRealiasingMap Map to update
|
||||
* @param $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace
|
||||
* @return array of <old-alias> => <new-alias>
|
||||
* @param array $aRealiasingMap Map to update
|
||||
* @param array $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace
|
||||
* @return void of <old-alias> => <new-alias>
|
||||
*/
|
||||
protected function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation)
|
||||
{
|
||||
@@ -702,7 +752,7 @@ class DBObjectSearch extends DBSearch
|
||||
* This a workaround to handle some cases in which the list of classes is not correctly updated.
|
||||
* This code should disappear as soon as DBObjectSearch get split between a container search class and a Node class
|
||||
*
|
||||
* @param $aClasses List to be completed
|
||||
* @param array $aClasses List to be completed
|
||||
*/
|
||||
protected function RecomputeClassList(&$aClasses)
|
||||
{
|
||||
@@ -822,6 +872,8 @@ class DBObjectSearch extends DBSearch
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
@@ -844,7 +896,7 @@ class DBObjectSearch extends DBSearch
|
||||
// NO: $oFilter = $oFilter->DeepClone();
|
||||
// See also: Trac #639, and self::AddCondition_PointingTo()
|
||||
$aAliasTranslation = array();
|
||||
$res = $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
|
||||
$this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
|
||||
$this->TransferConditionExpression($oFilter, $aAliasTranslation);
|
||||
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
|
||||
|
||||
@@ -868,7 +920,6 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
$this->RecomputeClassList($this->m_aClasses);
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function AddCondition_ReferencedBy_InNameSpace(DBSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
|
||||
@@ -927,7 +978,10 @@ class DBObjectSearch extends DBSearch
|
||||
$oRightFilter = $oRightFilter->DeepClone();
|
||||
|
||||
$bAllowAllData = ($oLeftFilter->IsAllDataAllowed() && $oRightFilter->IsAllDataAllowed());
|
||||
$oLeftFilter->AllowAllData($bAllowAllData);
|
||||
if ($bAllowAllData)
|
||||
{
|
||||
$oLeftFilter->AllowAllData();
|
||||
}
|
||||
|
||||
if ($oLeftFilter->GetClass() != $oRightFilter->GetClass())
|
||||
{
|
||||
@@ -973,8 +1027,6 @@ class DBObjectSearch extends DBSearch
|
||||
// Translate search condition into our aliasing scheme
|
||||
$aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetClassAlias();
|
||||
|
||||
$this->m_aFullText = array_merge($this->m_aFullText, $oFilter->m_aFullText);
|
||||
|
||||
foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
|
||||
{
|
||||
foreach($aPointingTo as $iOperatorCode => $aFilter)
|
||||
@@ -1001,8 +1053,8 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
|
||||
public function GetCriteria() {return $this->m_oSearchCondition;}
|
||||
public function GetCriteria_FullText() {return $this->m_aFullText;}
|
||||
protected function GetCriteria_PointingTo($sKeyAttCode = "")
|
||||
public function GetCriteria_FullText() {throw new Exception("Removed GetCriteria_FullText");}
|
||||
public function GetCriteria_PointingTo($sKeyAttCode = "")
|
||||
{
|
||||
if (empty($sKeyAttCode))
|
||||
{
|
||||
@@ -1021,11 +1073,45 @@ class DBObjectSearch extends DBSearch
|
||||
return $this->m_aParams = $aParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array <strong>warning</strong> : array returned by value
|
||||
* @see self::GetInternalParamsByRef to get the attribute by reference
|
||||
*/
|
||||
public function GetInternalParams()
|
||||
{
|
||||
return $this->m_aParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @see http://php.net/manual/en/language.references.return.php
|
||||
* @since 2.5.1 N°1582
|
||||
*/
|
||||
public function &GetInternalParamsByRef()
|
||||
{
|
||||
return $this->m_aParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sKey
|
||||
* @param mixed $value
|
||||
* @param bool $bDoNotOverride
|
||||
*
|
||||
* @throws \CoreUnexpectedValue if $bDoNotOverride and $sKey already exists
|
||||
*/
|
||||
public function AddInternalParam($sKey, $value, $bDoNotOverride = false)
|
||||
{
|
||||
if ($bDoNotOverride)
|
||||
{
|
||||
if (array_key_exists($sKey, $this->m_aParams))
|
||||
{
|
||||
throw new CoreUnexpectedValue("The key $sKey already exists with value : ".$this->m_aParams[$sKey]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->m_aParams[$sKey] = $value;
|
||||
}
|
||||
|
||||
public function GetQueryParams($bExcludeMagicParams = true)
|
||||
{
|
||||
$aParams = array();
|
||||
@@ -1078,10 +1164,11 @@ class DBObjectSearch extends DBSearch
|
||||
/**
|
||||
* Turn the parameters (:xxx) into scalar values in order to easily
|
||||
* serialize a search
|
||||
* @param $aArgs
|
||||
*/
|
||||
public function ApplyParameters($aArgs)
|
||||
{
|
||||
return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
|
||||
$this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
|
||||
}
|
||||
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
|
||||
@@ -1119,11 +1206,6 @@ class DBObjectSearch extends DBSearch
|
||||
$sRes .= $this->ToOQL_Joins();
|
||||
$sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams);
|
||||
|
||||
// Temporary: add more info about other conditions, necessary to avoid strange behaviors with the cache
|
||||
foreach($this->m_aFullText as $sFullText)
|
||||
{
|
||||
$sRes .= " AND MATCHES '$sFullText'";
|
||||
}
|
||||
if ($bWithAllowAllFlag && $this->m_bAllowAllData)
|
||||
{
|
||||
$sRes .= " ALLOW ALL DATA";
|
||||
@@ -1436,7 +1518,7 @@ class DBObjectSearch extends DBSearch
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
{
|
||||
// Hide objects that are not visible to the current user
|
||||
//
|
||||
@@ -1455,11 +1537,6 @@ class DBObjectSearch extends DBSearch
|
||||
$oSearch = $this->Intersect($oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
else
|
||||
{
|
||||
// should be true at this point, meaning that no additional filtering
|
||||
// is required
|
||||
}
|
||||
}
|
||||
|
||||
// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
|
||||
@@ -1468,10 +1545,32 @@ class DBObjectSearch extends DBSearch
|
||||
|
||||
// Create a unique cache id
|
||||
//
|
||||
$aContextData = array();
|
||||
$bCanCache = true;
|
||||
if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries)
|
||||
{
|
||||
if (isset($_SERVER['REQUEST_URI']))
|
||||
{
|
||||
$aContextData['sRequestUri'] = $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
else if (isset($_SERVER['SCRIPT_NAME']))
|
||||
{
|
||||
$aContextData['sRequestUri'] = $_SERVER['SCRIPT_NAME'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$aContextData['sRequestUri'] = '';
|
||||
}
|
||||
|
||||
// Need to identify the query
|
||||
$sOqlQuery = $oSearch->ToOql(false, null, true);
|
||||
if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false))
|
||||
{
|
||||
// Requests containing "id IN" are not worth caching
|
||||
$bCanCache = false;
|
||||
}
|
||||
|
||||
$aContextData['sOqlQuery'] = $sOqlQuery;
|
||||
|
||||
if (count($aModifierProperties))
|
||||
{
|
||||
@@ -1482,12 +1581,14 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
$sModifierProperties = '';
|
||||
}
|
||||
$aContextData['sModifierProperties'] = $sModifierProperties;
|
||||
|
||||
$sRawId = $sOqlQuery.$sModifierProperties;
|
||||
if (!is_null($aAttToLoad))
|
||||
{
|
||||
$sRawId .= json_encode($aAttToLoad);
|
||||
}
|
||||
$aContextData['aAttToLoad'] = $aAttToLoad;
|
||||
if (!is_null($aGroupByExpr))
|
||||
{
|
||||
foreach($aGroupByExpr as $sAlias => $oExpr)
|
||||
@@ -1495,13 +1596,28 @@ class DBObjectSearch extends DBSearch
|
||||
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render();
|
||||
}
|
||||
}
|
||||
if (!is_null($aSelectExpr))
|
||||
{
|
||||
foreach($aSelectExpr as $sAlias => $oExpr)
|
||||
{
|
||||
$sRawId .= 'se:'.$sAlias.'!'.$oExpr->Render();
|
||||
}
|
||||
}
|
||||
$aContextData['aGroupByExpr'] = $aGroupByExpr;
|
||||
$aContextData['aSelectExpr'] = $aSelectExpr;
|
||||
$sRawId .= $bGetCount;
|
||||
$aContextData['bGetCount'] = $bGetCount;
|
||||
if (is_array($aSelectedClasses))
|
||||
{
|
||||
$sRawId .= implode(',', $aSelectedClasses); // Unions may alter the list of selected columns
|
||||
}
|
||||
$sRawId .= $oSearch->GetArchiveMode() ? '--arch' : '';
|
||||
$sRawId .= $oSearch->GetShowObsoleteData() ? '--obso' : '';
|
||||
$aContextData['aSelectedClasses'] = $aSelectedClasses;
|
||||
$bIsArchiveMode = $oSearch->GetArchiveMode();
|
||||
$sRawId .= $bIsArchiveMode ? '--arch' : '';
|
||||
$bShowObsoleteData = $oSearch->GetShowObsoleteData();
|
||||
$sRawId .= $bShowObsoleteData ? '--obso' : '';
|
||||
$aContextData['bIsArchiveMode'] = $bIsArchiveMode;
|
||||
$aContextData['bShowObsoleteData'] = $bShowObsoleteData;
|
||||
$sOqlId = md5($sRawId);
|
||||
}
|
||||
else
|
||||
@@ -1513,6 +1629,7 @@ class DBObjectSearch extends DBSearch
|
||||
|
||||
// Query caching
|
||||
//
|
||||
$sOqlAPCCacheId = null;
|
||||
if (self::$m_bQueryCacheEnabled)
|
||||
{
|
||||
// Warning: using directly the query string as the key to the hash array can FAIL if the string
|
||||
@@ -1552,13 +1669,14 @@ class DBObjectSearch extends DBSearch
|
||||
if (!isset($oSQLQuery))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses);
|
||||
$oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr);
|
||||
$oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery);
|
||||
|
||||
if (self::$m_bQueryCacheEnabled)
|
||||
{
|
||||
if (self::$m_bUseAPCCache)
|
||||
if ($bCanCache && self::$m_bUseAPCCache)
|
||||
{
|
||||
$oSQLQuery->m_aContextData = $aContextData;
|
||||
$oKPI = new ExecutionKPI();
|
||||
apc_store($sOqlAPCCacheId, $oSQLQuery, self::$m_iQueryCacheTTL);
|
||||
$oKPI->ComputeStats('Query APC (store)', $sOqlQuery);
|
||||
@@ -1570,13 +1688,24 @@ class DBObjectSearch extends DBSearch
|
||||
return $oSQLQuery;
|
||||
}
|
||||
|
||||
protected function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
/**
|
||||
* @param array $aAttToLoad
|
||||
* @param bool $bGetCount
|
||||
* @param array $aModifierProperties
|
||||
* @param array $aGroupByExpr
|
||||
* @param array $aSelectedClasses
|
||||
* @param array $aSelectExpr
|
||||
*
|
||||
* @return null|SQLObjectQuery
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
{
|
||||
$oBuild = new QueryBuilderContext($this, $aModifierProperties, $aGroupByExpr, $aSelectedClasses);
|
||||
$oBuild = new QueryBuilderContext($this, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr);
|
||||
|
||||
$oSQLQuery = $this->MakeSQLObjectQuery($oBuild, $aAttToLoad, array());
|
||||
$oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition());
|
||||
if ($aGroupByExpr)
|
||||
if (is_array($aGroupByExpr))
|
||||
{
|
||||
$aCols = $oBuild->m_oQBExpressions->GetGroupBy();
|
||||
$oSQLQuery->SetGroupBy($aCols);
|
||||
@@ -1586,6 +1715,17 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
$oSQLQuery->SetSelect($oBuild->m_oQBExpressions->GetSelect());
|
||||
}
|
||||
if ($aSelectExpr)
|
||||
{
|
||||
// Get the fields corresponding to the select expressions
|
||||
foreach($oBuild->m_oQBExpressions->GetSelect() as $sAlias => $oExpr)
|
||||
{
|
||||
if (key_exists($sAlias, $aSelectExpr))
|
||||
{
|
||||
$oSQLQuery->AddSelect($sAlias, $oExpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aMandatoryTables = null;
|
||||
if (self::$m_bOptimizeQueries)
|
||||
@@ -1614,6 +1754,13 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $oBuild
|
||||
* @param null $aAttToLoad
|
||||
* @param array $aValues
|
||||
* @return null|SQLObjectQuery
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected function MakeSQLObjectQuery(&$oBuild, $aAttToLoad = null, $aValues = array())
|
||||
{
|
||||
// Note: query class might be different than the class of the filter
|
||||
@@ -1623,9 +1770,9 @@ class DBObjectSearch extends DBSearch
|
||||
|
||||
$bIsOnQueriedClass = array_key_exists($sClassAlias, $oBuild->GetRootFilter()->GetSelectedClasses());
|
||||
|
||||
self::DbgTrace("Entering: ".$this->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY"));
|
||||
//self::DbgTrace("Entering: ".$this->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY"));
|
||||
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
//$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
$sKeyField = MetaModel::DBGetKey($sClass);
|
||||
|
||||
if ($bIsOnQueriedClass)
|
||||
@@ -1658,26 +1805,6 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the full text condition into additional condition expression
|
||||
$aFullText = $this->GetCriteria_FullText();
|
||||
if (count($aFullText) > 0)
|
||||
{
|
||||
$aFullTextFields = array();
|
||||
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (!$oAttDef->IsScalar()) continue;
|
||||
if ($oAttDef->IsExternalKey()) continue;
|
||||
$aFullTextFields[] = new FieldExpression($sAttCode, $sClassAlias);
|
||||
}
|
||||
$oTextFields = new CharConcatWSExpression(' ', $aFullTextFields);
|
||||
|
||||
foreach($aFullText as $sFTNeedle)
|
||||
{
|
||||
$oNewCond = new BinaryExpression($oTextFields, 'LIKE', new ScalarExpression("%$sFTNeedle%"));
|
||||
$oBuild->m_oQBExpressions->AddCondition($oNewCond);
|
||||
}
|
||||
}
|
||||
}
|
||||
//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oBuild->m_oQBExpressions, true)."</pre></p>\n";
|
||||
$aExpectedAtts = array(); // array of (attcode => fieldexpression)
|
||||
@@ -1798,37 +1925,46 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
// First query built upon on the leaf (ie current) class
|
||||
// First query built from the root, adding all tables including the leaf
|
||||
// Before N.1065 we were joining from the leaf first, but this wasn't a good choice :
|
||||
// most of the time (obsolescence, friendlyname, ...) we want to get a root attribute !
|
||||
//
|
||||
self::DbgTrace("Main (=leaf) class, call MakeSQLObjectQuerySingleTable()");
|
||||
if (MetaModel::HasTable($sClass))
|
||||
{
|
||||
$oSelectBase = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sClass, $aExtKeys, $aValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oSelectBase = null;
|
||||
|
||||
// As the join will not filter on the expected classes, we have to specify it explicitely
|
||||
$sExpectedClasses = implode("', '", MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL));
|
||||
$oFinalClassRestriction = Expression::FromOQL("`$sClassAlias`.finalclass IN ('$sExpectedClasses')");
|
||||
$oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction);
|
||||
$aClassHierarchy = MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, true);
|
||||
$bIsClassStandaloneClass = (count($aClassHierarchy) == 1);
|
||||
foreach($aClassHierarchy as $sSomeClass)
|
||||
{
|
||||
if (!MetaModel::HasTable($sSomeClass))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Then we join the queries of the eventual parent classes (compound model)
|
||||
foreach(MetaModel::EnumParentClasses($sClass) as $sParentClass)
|
||||
{
|
||||
if (!MetaModel::HasTable($sParentClass)) continue;
|
||||
|
||||
self::DbgTrace("Parent class: $sParentClass... let's call MakeSQLObjectQuerySingleTable()");
|
||||
$oSelectParentTable = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sParentClass, $aExtKeys, $aValues);
|
||||
self::DbgTrace("Adding join from root to leaf: $sSomeClass... let's call MakeSQLObjectQuerySingleTable()");
|
||||
$oSelectParentTable = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sSomeClass, $aExtKeys, $aValues);
|
||||
if (is_null($oSelectBase))
|
||||
{
|
||||
$oSelectBase = $oSelectParentTable;
|
||||
if (!$bIsClassStandaloneClass && (MetaModel::IsRootClass($sSomeClass)))
|
||||
{
|
||||
// As we're linking to root class first, we're adding a where clause on the finalClass attribute :
|
||||
// COALESCE($sRootClassFinalClass IN ('$sExpectedClasses'), 1)
|
||||
// If we don't, the child classes can be removed in the query optimisation phase, including the leaf that was queried
|
||||
// So we still need to filter records to only those corresponding to the child classes !
|
||||
// The coalesce is mandatory if we have a polymorphic query (left join)
|
||||
$oClassListExpr = ListExpression::FromScalars(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL));
|
||||
$sFinalClassSqlColumnName = MetaModel::DBGetClassField($sSomeClass);
|
||||
$oClassExpr = new FieldExpression($sFinalClassSqlColumnName, $oSelectBase->GetTableAlias());
|
||||
$oInExpression = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr);
|
||||
$oTrueExpression = new TrueExpression();
|
||||
$aCoalesceAttr = array($oInExpression, $oTrueExpression);
|
||||
$oFinalClassRestriction = new FunctionExpression("COALESCE", $aCoalesceAttr);
|
||||
|
||||
$oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, MetaModel::DBGetKey($sParentClass));
|
||||
$oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, MetaModel::DBGetKey($sSomeClass));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2104,7 +2240,7 @@ class DBObjectSearch extends DBSearch
|
||||
self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField");
|
||||
if ($oKeyAttDef->IsNullAllowed())
|
||||
{
|
||||
$oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField, $sExternalKeyTable);
|
||||
$oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2158,9 +2294,19 @@ class DBObjectSearch extends DBSearch
|
||||
/**
|
||||
* Get the expression for the class and its subclasses (if finalclass = 'subclass' ...)
|
||||
* Simplifies the final expression by grouping classes having the same expression
|
||||
* @param $sClass
|
||||
* @param $sAttCode
|
||||
* @return \FunctionExpression|mixed|null
|
||||
* @throws \CoreException
|
||||
*/
|
||||
static protected function GetPolymorphicExpression($sClass, $sAttCode)
|
||||
static public function GetPolymorphicExpression($sClass, $sAttCode)
|
||||
{
|
||||
$oExpression = ExpressionCache::GetCachedExpression($sClass, $sAttCode);
|
||||
if (!empty($oExpression))
|
||||
{
|
||||
return $oExpression;
|
||||
}
|
||||
|
||||
// 1st step - get all of the required expressions (instantiable classes)
|
||||
// and group them using their OQL representation
|
||||
//
|
||||
|
||||
@@ -69,7 +69,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
* @var int
|
||||
*/
|
||||
protected $m_iCurrRow;
|
||||
/*
|
||||
/**
|
||||
* @var DBSearch
|
||||
*/
|
||||
protected $m_oFilter;
|
||||
@@ -77,18 +77,20 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
* @var mysqli_result
|
||||
*/
|
||||
protected $m_oSQLResult;
|
||||
protected $m_bSort;
|
||||
|
||||
/**
|
||||
* Create a new set based on a Search definition.
|
||||
*
|
||||
* @param DBSearch $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 hash $aExtendedDataSpec
|
||||
* @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 array $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(DBSearch $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();
|
||||
@@ -98,6 +100,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
$this->m_aExtendedDataSpec = $aExtendedDataSpec;
|
||||
$this->m_iLimitCount = $iLimitCount;
|
||||
$this->m_iLimitStart = $iLimitStart;
|
||||
$this->m_bSort = $bSort;
|
||||
|
||||
$this->m_iNumTotalDBRows = null;
|
||||
$this->m_iNumLoadedDBRows = 0;
|
||||
@@ -171,7 +174,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
/**
|
||||
* Specify the subset of attributes to load (for each class of objects) before performing the SQL query for retrieving the rows from the DB
|
||||
*
|
||||
* @param hash $aAttToLoad Format: alias => array of attribute_codes
|
||||
* @param array $aAttToLoad Format: alias => array of attribute_codes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -195,11 +198,25 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,6 +224,20 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
$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]))
|
||||
{
|
||||
@@ -312,7 +343,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: After calling this method, the set cursor will be at the end of the set. You might to rewind it.
|
||||
* 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
|
||||
@@ -386,7 +417,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: After calling this method, the set cursor will be at the end of the set. You might to rewind it.
|
||||
* 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
|
||||
@@ -539,7 +570,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
$aAttributes = array();
|
||||
foreach ($aAliases as $sAlias => $bClassDirection)
|
||||
{
|
||||
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClass($sAlias)) as $sAttCode => $bAttributeDirection)
|
||||
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClassName($sAlias)) as $sAttCode => $bAttributeDirection)
|
||||
{
|
||||
$bDirection = $bClassDirection ? $bAttributeDirection : !$bAttributeDirection;
|
||||
$aAttributes[$sAlias.'.'.$sAttCode] = $bDirection;
|
||||
@@ -573,10 +604,15 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
*
|
||||
* 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))
|
||||
@@ -598,14 +634,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
// Note: it is mandatory to set this value now, to protect against reentrance
|
||||
$this->m_bLoaded = true;
|
||||
|
||||
if ($this->m_iLimitCount > 0)
|
||||
{
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
|
||||
}
|
||||
$sSQL = $this->_makeSelectQuery($this->m_aAttToLoad);
|
||||
|
||||
if (is_object($this->m_oSQLResult))
|
||||
{
|
||||
@@ -613,22 +642,73 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
$this->m_oSQLResult->free();
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
$this->m_iNumTotalDBRows = null;
|
||||
|
||||
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))
|
||||
if ((($this->m_iLimitCount == 0) || ($this->m_iLimitCount > $this->m_oSQLResult->num_rows)) && ($this->m_iLimitStart == 0))
|
||||
{
|
||||
$this->m_iNumTotalDBRows = $this->m_oSQLResult->num_rows;
|
||||
}
|
||||
|
||||
$this->m_iNumLoadedDBRows = $this->m_oSQLResult->num_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param string[] $aAttToLoad
|
||||
*
|
||||
* May actually perform the SQL query SELECT COUNT... if the set was not previously loaded, or loaded with a SetLimit
|
||||
* @return string SQL query
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return int The total number of rows for this set.
|
||||
*/
|
||||
@@ -642,11 +722,80 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
$this->m_iNumTotalDBRows = $aRow['COUNT'];
|
||||
$this->m_iNumTotalDBRows = intval($aRow['COUNT']);
|
||||
}
|
||||
|
||||
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
|
||||
}
|
||||
|
||||
/** Check if the count exceeds a given limit
|
||||
* @param $iLimit
|
||||
*
|
||||
* @return bool
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
public function CountExceeds($iLimit)
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
if ($resQuery)
|
||||
{
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
$iCount = intval($aRow['COUNT']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$iCount = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iCount = $this->m_iNumTotalDBRows;
|
||||
}
|
||||
|
||||
return ($iCount > $iLimit);
|
||||
}
|
||||
|
||||
/** Count only up to the given limit
|
||||
* @param $iLimit
|
||||
*
|
||||
* @return int
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
public function CountWithLimit($iLimit)
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
if ($resQuery)
|
||||
{
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
$iCount = intval($aRow['COUNT']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$iCount = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iCount = $this->m_iNumTotalDBRows;
|
||||
}
|
||||
|
||||
return $iCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of rows available in memory (loaded from DB + added in memory)
|
||||
*
|
||||
|
||||
@@ -49,11 +49,20 @@ abstract class DBSearch
|
||||
|
||||
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)
|
||||
*
|
||||
* @return \DBSearch
|
||||
**/
|
||||
public function DeepClone()
|
||||
{
|
||||
@@ -236,12 +245,17 @@ abstract class DBSearch
|
||||
public function serialize($bDevelopParams = false, $aContextParams = null)
|
||||
{
|
||||
$sOql = $this->ToOql($bDevelopParams, $aContextParams);
|
||||
return base64_encode(serialize(array($sOql, $this->GetInternalParams(), $this->m_aModifierProperties)));
|
||||
return rawurlencode(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));
|
||||
$aData = unserialize(base64_decode(rawurldecode($sValue)));
|
||||
$sOql = $aData[0];
|
||||
$aParams = $aData[1];
|
||||
// We've tried to use gzcompress/gzuncompress, but for some specific queries
|
||||
@@ -284,12 +298,15 @@ abstract class DBSearch
|
||||
/**
|
||||
* @param string $sQuery
|
||||
* @param array $aParams
|
||||
* @return DBSearch
|
||||
* @return self
|
||||
* @throws OQLException
|
||||
*/
|
||||
static public function FromOQL($sQuery, $aParams = null)
|
||||
{
|
||||
if (empty($sQuery)) return null;
|
||||
if (empty($sQuery))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Query caching
|
||||
$sQueryId = md5($sQuery);
|
||||
@@ -349,6 +366,10 @@ abstract class DBSearch
|
||||
{
|
||||
$oResultFilter->SetInternalParams($aParams);
|
||||
}
|
||||
|
||||
// Set the default fields
|
||||
$oResultFilter->Init();
|
||||
|
||||
return $oResultFilter;
|
||||
}
|
||||
|
||||
@@ -361,7 +382,10 @@ abstract class DBSearch
|
||||
{
|
||||
$sSQL = $this->MakeSelectQuery($aOrderBy, $aArgs);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
if (!$resQuery) return;
|
||||
if (!$resQuery)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($aColumns) == 0)
|
||||
{
|
||||
@@ -370,7 +394,7 @@ abstract class DBSearch
|
||||
array_unshift($aColumns, 'id');
|
||||
}
|
||||
|
||||
$aQueryCols = CMDBSource::GetColumns($resQuery);
|
||||
$aQueryCols = CMDBSource::GetColumns($resQuery, $sSQL);
|
||||
|
||||
$sClassAlias = $this->GetClassAlias();
|
||||
$aColMap = array();
|
||||
@@ -405,8 +429,53 @@ abstract class DBSearch
|
||||
protected static $m_aQueryStructCache = array();
|
||||
|
||||
|
||||
public function MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues = false)
|
||||
/** Generate a Group By SQL request from a search
|
||||
* @param array $aArgs
|
||||
* @param array $aGroupByExpr array('alias' => Expression)
|
||||
* @param bool $bExcludeNullValues
|
||||
* @param array $aSelectExpr array('alias' => Expression) Additional expressions added to the request
|
||||
* @param array $aOrderBy array('alias' => bool) true = ASC false = DESC
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @return string SQL query generated
|
||||
* @throws Exception
|
||||
*/
|
||||
public function MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues = false, $aSelectExpr = array(), $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0)
|
||||
{
|
||||
// Sanity check
|
||||
foreach($aGroupByExpr as $sAlias => $oExpr)
|
||||
{
|
||||
if (!($oExpr instanceof Expression))
|
||||
{
|
||||
throw new CoreException("Wrong parameter for 'Group By' for [$sAlias] (an array('alias' => Expression) is awaited)");
|
||||
}
|
||||
}
|
||||
foreach($aSelectExpr as $sAlias => $oExpr)
|
||||
{
|
||||
if (array_key_exists($sAlias, $aGroupByExpr))
|
||||
{
|
||||
throw new CoreException("Alias collision between 'Group By' and 'Select Expressions' [$sAlias]");
|
||||
}
|
||||
if (!($oExpr instanceof Expression))
|
||||
{
|
||||
throw new CoreException("Wrong parameter for 'Select Expressions' for [$sAlias] (an array('alias' => Expression) is awaited)");
|
||||
}
|
||||
}
|
||||
foreach($aOrderBy as $sAlias => $bAscending)
|
||||
{
|
||||
if (!array_key_exists($sAlias, $aGroupByExpr) && !array_key_exists($sAlias, $aSelectExpr) && ($sAlias != '_itop_count_'))
|
||||
{
|
||||
$aAllowedAliases = array_keys($aSelectExpr);
|
||||
$aAllowedAliases = array_merge($aAllowedAliases, array_keys($aGroupByExpr));
|
||||
$aAllowedAliases[] = '_itop_count_';
|
||||
throw new CoreException("Wrong alias [$sAlias] for 'Order By'. Allowed values are: ", null, implode(", ", $aAllowedAliases));
|
||||
}
|
||||
if (!is_bool($bAscending))
|
||||
{
|
||||
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value for '$sAlias''");
|
||||
}
|
||||
}
|
||||
|
||||
if ($bExcludeNullValues)
|
||||
{
|
||||
// Null values are not handled (though external keys set to 0 are allowed)
|
||||
@@ -424,15 +493,15 @@ abstract class DBSearch
|
||||
}
|
||||
|
||||
$aAttToLoad = array();
|
||||
$oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr);
|
||||
$oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr, $aSelectExpr);
|
||||
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
|
||||
try
|
||||
{
|
||||
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
$sRes = $oSQLQuery->RenderGroupBy($aScalarArgs, $bBeautifulSQL);
|
||||
$sRes = $oSQLQuery->RenderGroupBy($aScalarArgs, $bBeautifulSQL, $aOrderBy, $iLimitCount, $iLimitStart);
|
||||
}
|
||||
catch (MissingQueryArgument $e)
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Add some information...
|
||||
$e->addInfo('OQL', $this->ToOQL());
|
||||
@@ -538,11 +607,30 @@ abstract class DBSearch
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
protected abstract function IsDataFiltered();
|
||||
protected abstract function SetDataFiltered();
|
||||
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null)
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null)
|
||||
{
|
||||
$oSQLQuery = $this->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
|
||||
$oSQLQuery->SetSourceOQL($this->ToOQL());
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
|
||||
{
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false)
|
||||
{
|
||||
// Make sure this is a valid search object, saying NO for all
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass());
|
||||
}
|
||||
if (is_object($oVisibleObjects))
|
||||
{
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $this->Intersect($oVisibleObjects);
|
||||
/** @var DBSearch $oSearch */
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
$oSQLQuery = $oSearch->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, null, $aSelectExpr);
|
||||
$oSQLQuery->SetSourceOQL($oSearch->ToOQL());
|
||||
|
||||
// Join to an additional table, if required...
|
||||
//
|
||||
@@ -562,6 +650,24 @@ abstract class DBSearch
|
||||
return $oSQLQuery;
|
||||
}
|
||||
|
||||
public abstract function GetSQLQueryStructure(
|
||||
$aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null
|
||||
);
|
||||
|
||||
/**
|
||||
* @return \Expression
|
||||
*/
|
||||
public abstract function GetCriteria();
|
||||
|
||||
public abstract function AddConditionForInOperatorUsingParam($sFilterCode, $aValues, $bPositiveMatch = true);
|
||||
|
||||
/**
|
||||
* @return string a unique param name
|
||||
*/
|
||||
protected function GenerateUniqueParamName() {
|
||||
return str_replace('.', '', 'param_'.microtime(true).rand(0,100));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Cache/Trace/Log queries
|
||||
@@ -673,7 +779,10 @@ abstract class DBSearch
|
||||
|
||||
public static function RecordQueryTrace()
|
||||
{
|
||||
if (!self::$m_bTraceQueries) return;
|
||||
if (!self::$m_bTraceQueries)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$iOqlCount = count(self::$m_aQueriesLog);
|
||||
$iSqlCount = 0;
|
||||
@@ -711,6 +820,7 @@ abstract class DBSearch
|
||||
{
|
||||
// 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))
|
||||
@@ -729,7 +839,10 @@ abstract class DBSearch
|
||||
|
||||
protected static function DbgTrace($value)
|
||||
{
|
||||
if (!self::$m_bDebugQuery) return;
|
||||
if (!self::$m_bDebugQuery)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$aBacktrace = debug_backtrace();
|
||||
$iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery;
|
||||
$sIndent = "";
|
||||
@@ -745,11 +858,7 @@ abstract class DBSearch
|
||||
$sCallers = "Callstack: ".implode(', ', $aCallers);
|
||||
$sFunction = "<b title=\"$sCallers\">".$aBacktrace[1]["function"]."</b>";
|
||||
|
||||
if (is_string($value))
|
||||
{
|
||||
echo "$sIndent$sFunction: $value<br/>\n";
|
||||
}
|
||||
else if (is_object($value))
|
||||
if (is_object($value))
|
||||
{
|
||||
echo "$sIndent$sFunction:\n<pre>\n";
|
||||
print_r($value);
|
||||
@@ -805,7 +914,10 @@ abstract class DBSearch
|
||||
$aUpdates = array();
|
||||
foreach (MetaModel::EnumParentClasses($sFinalClass, ENUM_PARENT_CLASSES_ALL) as $sParentClass)
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode($sParentClass, 'archive_flag')) continue;
|
||||
if (!MetaModel::IsValidAttCode($sParentClass, 'archive_flag'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$sTable = MetaModel::DBGetTable($sParentClass);
|
||||
$aUpdates[] = "`$sTable`.`archive_flag` = $iFlag";
|
||||
@@ -835,4 +947,9 @@ abstract class DBSearch
|
||||
CMDBSource::Query($sUpdateQuery);
|
||||
}
|
||||
}
|
||||
|
||||
public function UpdateContextFromUser()
|
||||
{
|
||||
$this->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,9 +312,9 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param String 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...)
|
||||
* @param Object value The value to match (can be an array => IN(val1, val2...)
|
||||
* @return void
|
||||
*/
|
||||
public function AddConditionAdvanced($sAttSpec, $value)
|
||||
@@ -474,15 +474,17 @@ class DBUnionSearch extends DBSearch
|
||||
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
|
||||
}
|
||||
|
||||
protected function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
{
|
||||
if (count($this->aSearches) == 1)
|
||||
{
|
||||
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
|
||||
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, $aSelectExpr);
|
||||
}
|
||||
|
||||
$aSQLQueries = array();
|
||||
$aAliases = array_keys($this->aSelectedClasses);
|
||||
$aQueryAttToLoad = null;
|
||||
$aUnionQuerySelectExpr = array();
|
||||
foreach ($this->aSearches as $iSearch => $oSearch)
|
||||
{
|
||||
$aSearchAliases = array_keys($oSearch->GetSelectedClasses());
|
||||
@@ -496,6 +498,16 @@ class DBUnionSearch extends DBSearch
|
||||
$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;
|
||||
@@ -511,6 +523,7 @@ class DBUnionSearch extends DBSearch
|
||||
$aQueryAttToLoad[$sQueryAlias] = $aAttributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($aGroupByExpr))
|
||||
{
|
||||
@@ -533,7 +546,43 @@ class DBUnionSearch extends DBSearch
|
||||
$aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
|
||||
}
|
||||
}
|
||||
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses);
|
||||
|
||||
if (is_null($aSelectExpr))
|
||||
{
|
||||
$aQuerySelectExpr = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aQuerySelectExpr = array();
|
||||
$aTranslationData = array();
|
||||
$aQueryColumns = array_keys($oSearch->GetSelectedClasses());
|
||||
foreach($aAliases as $iColumn => $sAlias)
|
||||
{
|
||||
$sQueryAlias = $aQueryColumns[$iColumn];
|
||||
$aTranslationData[$sAlias]['*'] = $sQueryAlias;
|
||||
}
|
||||
foreach($aSelectExpr as $sExpressionAlias => $oExpression)
|
||||
{
|
||||
$oExpression->Browse(function ($oNode) use (&$aQuerySelectExpr, &$aTranslationData)
|
||||
{
|
||||
if ($oNode instanceof FieldExpression)
|
||||
{
|
||||
$sAlias = $oNode->GetParent()."__".$oNode->GetName();
|
||||
if (!key_exists($sAlias, $aQuerySelectExpr))
|
||||
{
|
||||
$aQuerySelectExpr[$sAlias] = $oNode->Translate($aTranslationData, false, false);
|
||||
}
|
||||
$aTranslationData[$oNode->GetParent()][$oNode->GetName()] = new FieldExpression($sAlias);
|
||||
}
|
||||
});
|
||||
// Only done for the first select as aliases are named after the first query
|
||||
if (!array_key_exists($sExpressionAlias, $aUnionQuerySelectExpr))
|
||||
{
|
||||
$aUnionQuerySelectExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses, $aQuerySelectExpr);
|
||||
if (count($aSearchAliases) > 1)
|
||||
{
|
||||
// Necessary to make sure that selected columns will match throughout all the queries
|
||||
@@ -543,10 +592,60 @@ class DBUnionSearch extends DBSearch
|
||||
$aSQLQueries[] = $oSubQuery;
|
||||
}
|
||||
|
||||
$oSQLQuery = new SQLUnionQuery($aSQLQueries, $aGroupByExpr);
|
||||
$oSQLQuery = new SQLUnionQuery($aSQLQueries, $aGroupByExpr, $aUnionQuerySelectExpr);
|
||||
//MyHelpers::var_dump_html($oSQLQuery, true);
|
||||
//MyHelpers::var_dump_html($oSQLQuery->RenderSelect(), true);
|
||||
if (self::$m_bDebugQuery) $oSQLQuery->DisplayHtml();
|
||||
return $oSQLQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Expression
|
||||
*/
|
||||
public function GetCriteria()
|
||||
{
|
||||
// We're at the limit here
|
||||
$oSearch = reset($this->aSearches);
|
||||
return $oSearch->GetCriteria();
|
||||
}
|
||||
|
||||
protected function IsDataFiltered()
|
||||
{
|
||||
$bIsAllDataFiltered = true;
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
if (!$oSearch->IsDataFiltered())
|
||||
{
|
||||
$bIsAllDataFiltered = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $bIsAllDataFiltered;
|
||||
}
|
||||
|
||||
protected function SetDataFiltered()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
|
||||
public function AddConditionForInOperatorUsingParam($sFilterCode, $aValues, $bPositiveMatch = true)
|
||||
{
|
||||
$sInParamName = $this->GenerateUniqueParamName();
|
||||
foreach ($this->aSearches as $iSearchIndex => $oSearch)
|
||||
{
|
||||
$oFieldExpression = new FieldExpression($sFilterCode, $oSearch->GetClassAlias());
|
||||
|
||||
$sOperator = $bPositiveMatch ? 'IN' : 'NOT IN';
|
||||
|
||||
$oParamExpression = new VariableExpression($sInParamName);
|
||||
$oSearch->GetInternalParamsByRef()[$sInParamName] = $aValues;
|
||||
|
||||
$oListExpression = new ListExpression(array($oParamExpression));
|
||||
$oInCondition = new BinaryExpression($oFieldExpression, $sOperator, $oListExpression);
|
||||
$oSearch->AddConditionExpression($oInCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<?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/>
|
||||
/**
|
||||
* Copyright (c) 2010-2018 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
|
||||
@@ -57,6 +60,9 @@ class DesignDocument extends DOMDocument
|
||||
|
||||
/**
|
||||
* Overload of the standard API
|
||||
*
|
||||
* @param $filename
|
||||
* @param int $options
|
||||
*/
|
||||
public function load($filename, $options = 0)
|
||||
{
|
||||
@@ -65,6 +71,11 @@ class DesignDocument extends DOMDocument
|
||||
|
||||
/**
|
||||
* Overload of the standard API
|
||||
*
|
||||
* @param $filename
|
||||
* @param int $options
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function save($filename, $options = 0)
|
||||
{
|
||||
@@ -84,18 +95,18 @@ class DesignDocument extends DOMDocument
|
||||
{
|
||||
return $sXml;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
echo "<pre>\n";
|
||||
echo htmlentities($sXml);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param string $sValue The value to be quoted
|
||||
* @return string to be used within an XPath
|
||||
*/
|
||||
public static function XPathQuote($sValue)
|
||||
@@ -115,7 +126,7 @@ class DesignDocument extends DOMDocument
|
||||
/**
|
||||
* Extracts some nodes from the DOM
|
||||
* @param string $sXPath A XPath expression
|
||||
* @param DesignNode|null $oContextNode The node to start the search from
|
||||
* @param DesignElement $oContextNode The node to start the search from
|
||||
* @return \DOMNodeList
|
||||
*/
|
||||
public function GetNodes($sXPath, $oContextNode = null)
|
||||
@@ -134,7 +145,7 @@ class DesignDocument extends DOMDocument
|
||||
|
||||
/**
|
||||
* An alternative to getNodePath, that gives the id of nodes instead of the position within the children
|
||||
* @param $oNode The node to describe
|
||||
* @param DesignElement $oNode The node to describe
|
||||
* @return string
|
||||
*/
|
||||
public static function GetItopNodePath($oNode)
|
||||
@@ -166,8 +177,11 @@ class DesignElement extends \DOMElement
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function Dump($bReturnRes = false)
|
||||
{
|
||||
@@ -180,19 +194,16 @@ class DesignElement extends \DOMElement
|
||||
{
|
||||
return $sXml;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "<pre>\n";
|
||||
echo htmlentities($sXml);
|
||||
echo "</pre>\n";
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node directly under the given node
|
||||
* @param $sTagName
|
||||
* @param bool|true $bMustExist
|
||||
* @return MFElement
|
||||
* @return \MFElement
|
||||
* @throws DOMFormatException
|
||||
*/
|
||||
public function GetUniqueElement($sTagName, $bMustExist = true)
|
||||
@@ -216,7 +227,7 @@ class DesignElement extends \DOMElement
|
||||
/**
|
||||
* Returns the node directly under the current node, or null if missing
|
||||
* @param $sTagName
|
||||
* @return MFElement
|
||||
* @return \MFElement
|
||||
* @throws DOMFormatException
|
||||
*/
|
||||
public function GetOptionalElement($sTagName)
|
||||
@@ -252,9 +263,12 @@ class DesignElement extends \DOMElement
|
||||
|
||||
/**
|
||||
* Get the TEXT value from a child node
|
||||
*
|
||||
* @param string $sTagName
|
||||
* @param string|null $sDefault
|
||||
*
|
||||
* @return string
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
public function GetChildText($sTagName, $sDefault = null)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* Class Dict
|
||||
* Management of localizable strings
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -65,6 +65,11 @@ class Dict
|
||||
protected static $m_aData = array();
|
||||
protected static $m_sApplicationPrefix = null;
|
||||
|
||||
/**
|
||||
* @param $sLanguageCode
|
||||
*
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
*/
|
||||
public static function SetDefaultLanguage($sLanguageCode)
|
||||
{
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
@@ -74,6 +79,11 @@ class Dict
|
||||
self::$m_sDefaultLanguage = $sLanguageCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sLanguageCode
|
||||
*
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
*/
|
||||
public static function SetUserLanguage($sLanguageCode)
|
||||
{
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
@@ -108,11 +118,12 @@ class Dict
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
{
|
||||
@@ -122,7 +133,7 @@ class Dict
|
||||
|
||||
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
|
||||
{
|
||||
// It may happen, when something happens before the dictionnaries get loaded
|
||||
// It may happen, when something happens before the dictionaries get loaded
|
||||
return $sStringCode;
|
||||
}
|
||||
$aCurrentDictionary = self::$m_aData[self::GetUserLanguage()];
|
||||
@@ -153,25 +164,12 @@ class Dict
|
||||
}
|
||||
// Could not find the string...
|
||||
//
|
||||
switch (self::$m_iErrorMode)
|
||||
{
|
||||
case DICT_ERR_STRING:
|
||||
if (is_null($sDefault))
|
||||
{
|
||||
return $sStringCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sDefault;
|
||||
}
|
||||
break;
|
||||
|
||||
case DICT_ERR_EXCEPTION:
|
||||
default:
|
||||
throw new DictExceptionMissingString(self::$m_sCurrentLanguage, $sStringCode);
|
||||
break;
|
||||
}
|
||||
return 'bug!';
|
||||
return $sDefault;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,6 +281,9 @@ class Dict
|
||||
|
||||
/**
|
||||
* Clone a string in every language (if it exists in that language)
|
||||
*
|
||||
* @param $sSourceCode
|
||||
* @param $sDestCode
|
||||
*/
|
||||
public static function CloneString($sSourceCode, $sDestCode)
|
||||
{
|
||||
@@ -355,5 +356,38 @@ class Dict
|
||||
// No need to actually load the strings since it's only used to know the list of languages
|
||||
// at setup time !!
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all the dictionary entries - of the given language - whose code matches the given prefix
|
||||
* missing entries in the current language will be replaced by entries in the default language
|
||||
* @param string $sStartingWith
|
||||
* @return string[]
|
||||
*/
|
||||
public static function ExportEntries($sStartingWith)
|
||||
{
|
||||
self::InitLangIfNeeded(self::GetUserLanguage());
|
||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||
$aEntries = array();
|
||||
$iLength = strlen($sStartingWith);
|
||||
|
||||
// First prefill the array with entries from the default language
|
||||
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
|
||||
{
|
||||
if (substr($sCode, 0, $iLength) == $sStartingWith)
|
||||
{
|
||||
$aEntries[$sCode] = $sEntry;
|
||||
}
|
||||
}
|
||||
|
||||
// Now put (overwrite) the entries for the user language
|
||||
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
|
||||
{
|
||||
if (substr($sCode, 0, $iLength) == $sStartingWith)
|
||||
{
|
||||
$aEntries[$sCode] = $sEntry;
|
||||
}
|
||||
}
|
||||
return $aEntries;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1270,6 +1270,7 @@ class DisplayableGraph extends SimpleGraph
|
||||
|
||||
$oPdf->SetAutoPageBreak(true, $fBreakMargin);
|
||||
$oPdf->SetAlpha(1);
|
||||
$oPdf->SetTextColor(0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1438,22 +1439,22 @@ class DisplayableGraph extends SimpleGraph
|
||||
}
|
||||
$aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
|
||||
}
|
||||
$sSftShort = Dict::S('UI:ElementsDisplayed');
|
||||
$sSearchToggle = Dict::S('UI:Search:Toggle');
|
||||
$oP->add("<div class=\"not-printable\">\n");
|
||||
$oP->add("<div id=\"ds_flash\" class=\"SearchDrawer\" style=\"display:none;\">\n");
|
||||
if (!$oP->IsPrintableVersion())
|
||||
{
|
||||
$oP->add_ready_script(
|
||||
$oP->add(
|
||||
<<<EOF
|
||||
$( "#tabbedContent_0" ).tabs({ heightStyle: "fill" });
|
||||
<div id="ds_flash" class="search_box">
|
||||
<form id="dh_flash" class="search_form_handler closed">
|
||||
<h2 class="sf_title"><span class="sft_long">$sSftShort</span><span class="sft_short">$sSftShort</span><span class="sft_toggler fa fa-caret-down pull-right" title="$sSearchToggle"></span></h2>
|
||||
<div id="dh_flash_criterion_outer" class="sf_criterion_area"><div class="sf_criterion_row">
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$("#dh_flash").click( function() {
|
||||
$("#ds_flash").slideToggle('normal', function() { $("#ds_flash").parent().resize(); $("#dh_flash").trigger('toggle_complete'); } );
|
||||
$("#dh_flash").toggleClass('open');
|
||||
$("#dh_flash > .sf_title").click( function() {
|
||||
$("#dh_flash").toggleClass('closed');
|
||||
});
|
||||
$('#ReloadMovieBtn').button().button('disable');
|
||||
EOF
|
||||
@@ -1476,9 +1477,8 @@ EOF
|
||||
$idx++;
|
||||
}
|
||||
$oP->add("<p style=\"text-align:right\"><button type=\"button\" id=\"ReloadMovieBtn\" onClick=\"DoReload()\">".Dict::S('UI:Button:Refresh')."</button></p>");
|
||||
$oP->add("</div></div></form>");
|
||||
$oP->add("</div>\n");
|
||||
$oP->add("<div class=\"HRDrawer\"></div>\n");
|
||||
$oP->add("<div id=\"dh_flash\" class=\"DrawerHandle\">".Dict::S('UI:ElementsDisplayed')."</div>\n");
|
||||
$oP->add("</div>\n"); // class="not-printable"
|
||||
|
||||
$aAdditionalContexts = array();
|
||||
|
||||
@@ -57,6 +57,7 @@ class EMail
|
||||
{
|
||||
$this->m_aData = array();
|
||||
$this->m_oMessage = Swift_Message::newInstance();
|
||||
$this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,20 +196,31 @@ class EMail
|
||||
|
||||
$aFailedRecipients = array();
|
||||
$this->m_oMessage->setMaxLineLength(0);
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients);
|
||||
if ($iSent === 0)
|
||||
{
|
||||
// Beware: it seems that $aFailedRecipients sometimes contains the recipients that actually received the message !!!
|
||||
IssueLog::Warning('Email sending failed: Some recipients were invalid, aFailedRecipients contains: '.implode(', ', $aFailedRecipients));
|
||||
$aIssues = array('Some recipients were invalid.');
|
||||
$oKPI->ComputeStats('Email Sent', 'Error received');
|
||||
return EMAIL_SEND_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aIssues = array();
|
||||
$oKPI->ComputeStats('Email Sent', 'Succeded');
|
||||
return EMAIL_SEND_OK;
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$oKPI->ComputeStats('Email Sent', 'Error received');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reprocess the body of the message (if it is an HTML message)
|
||||
@@ -224,17 +236,26 @@ class EMail
|
||||
@$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]";
|
||||
$sXPath = '//img[@'.InlineImage::DOM_ATTR_ID.']';
|
||||
$oImagesList = $oXPath->query($sXPath);
|
||||
|
||||
if ($oImagesList->length != 0)
|
||||
{
|
||||
foreach($oImagesList as $oImg)
|
||||
{
|
||||
$iAttId = $oImg->getAttribute('data-img-id');
|
||||
$iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID);
|
||||
$oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */);
|
||||
if ($oAttachment)
|
||||
{
|
||||
$sImageSecret = $oImg->getAttribute('data-img-secret');
|
||||
$sAttachmentSecret = $oAttachment->Get('secret');
|
||||
if ($sImageSecret !== $sAttachmentSecret)
|
||||
{
|
||||
// @see N°1921
|
||||
// If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret
|
||||
continue;
|
||||
}
|
||||
|
||||
$oDoc = $oAttachment->Get('contents');
|
||||
$oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType());
|
||||
$sCid = $this->m_oMessage->embed($oSwiftImage);
|
||||
@@ -249,6 +270,11 @@ class EMail
|
||||
|
||||
public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
|
||||
{
|
||||
//select a default sender if none is provided.
|
||||
if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){
|
||||
$this->SetRecipientFrom($this->m_aData['to']);
|
||||
}
|
||||
|
||||
if ($bForceSynchronous)
|
||||
{
|
||||
return $this->SendSynchronous($aIssues, $oLog);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -408,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,10 +188,10 @@ EOF
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
|
||||
}
|
||||
else if ($value instanceOf ormCustomFieldsValue)
|
||||
else if ($value instanceOf ormDocument)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$sRet = $oAttDef->GetAsCSV($value, "\n", '', $oObj);
|
||||
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
111
core/expressioncache.class.inc.php
Normal file
111
core/expressioncache.class.inc.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?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;
|
||||
|
||||
SetupUtils::builddir(dirname($sFilePath));
|
||||
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';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -151,11 +151,17 @@ class HTMLPurifierSanitizer extends HTMLSanitizer
|
||||
class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
{
|
||||
protected $oDoc;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @see https://www.itophub.io/wiki/page?id=2_5_0%3Aadmin%3Arich_text_limitations
|
||||
*/
|
||||
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'),
|
||||
@@ -178,16 +184,15 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'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'),
|
||||
'tr' => array('style', 'colspan', 'rowspan'),
|
||||
'td' => array('style', 'colspan', 'rowspan'),
|
||||
'th' => array('style', 'colspan', 'rowspan'),
|
||||
'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(),
|
||||
@@ -198,23 +203,49 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'q' => array(),
|
||||
'hr' => array('style'),
|
||||
'pre' => array(),
|
||||
'center' => array(),
|
||||
'caption' => array(),
|
||||
);
|
||||
|
||||
protected static $aAttrsWhiteList = array(
|
||||
'src' => '/^(http:|https:|data:)/i',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @see https://www.itophub.io/wiki/page?id=2_5_0%3Aadmin%3Arich_text_limitations
|
||||
*/
|
||||
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',
|
||||
'background-color',
|
||||
'border',
|
||||
'border-collapse',
|
||||
'bordercolor',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'color',
|
||||
'float',
|
||||
'font',
|
||||
'font-family',
|
||||
'font-size',
|
||||
'font-style',
|
||||
'height',
|
||||
'margin',
|
||||
'padding',
|
||||
'text-align',
|
||||
'width',
|
||||
);
|
||||
|
||||
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))
|
||||
{
|
||||
$sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i';
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -315,7 +346,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
$this->CleanNode($oNode);
|
||||
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img'))
|
||||
{
|
||||
$this->ProcessImage($oNode);
|
||||
InlineImage::ProcessImageTag($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,23 +358,6 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
@@ -27,6 +27,11 @@ define('INLINEIMAGE_DOWNLOAD_URL', 'pages/ajax.document.php?operation=download_i
|
||||
|
||||
class InlineImage extends DBObject
|
||||
{
|
||||
/** @var string attribute to be added to IMG tags to contain ID */
|
||||
const DOM_ATTR_ID = 'data-img-id';
|
||||
/** @var string attribute to be added to IMG tags to contain secret */
|
||||
const DOM_ATTR_SECRET = 'data-img-secret';
|
||||
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
@@ -208,7 +213,8 @@ class InlineImage extends DBObject
|
||||
$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))
|
||||
if (preg_match_all('/<img ([^>]*)'.self::DOM_ATTR_ID.'="([0-9]+)"([^>]*)>/i',
|
||||
$sHtml, $aMatches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
|
||||
{
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL;
|
||||
foreach($aMatches as $aImgInfo)
|
||||
@@ -230,6 +236,42 @@ class InlineImage extends DBObject
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 \DOMElement $oElement
|
||||
*/
|
||||
public static function ProcessImageTag(DOMElement $oElement)
|
||||
{
|
||||
$sSrc = $oElement->getAttribute('src');
|
||||
$sDownloadUrl = str_replace(array('.', '?'), array('\.', '\?'), INLINEIMAGE_DOWNLOAD_URL); // Escape . and ?
|
||||
$sUrlPattern = '|'.$sDownloadUrl.'([0-9]+)&s=([0-9a-f]+)|';
|
||||
$bIsInlineImage = preg_match($sUrlPattern, $sSrc, $aMatches);
|
||||
if (!$bIsInlineImage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$iInlineImageId = $aMatches[1];
|
||||
$sInlineIMageSecret = $aMatches[2];
|
||||
|
||||
$sAppRoot = utils::GetAbsoluteUrlAppRoot();
|
||||
$sAppRootPattern = '/^'.preg_quote($sAppRoot, '/').'/';
|
||||
$bIsSameItop = preg_match($sAppRootPattern, $sSrc);
|
||||
if (!$bIsSameItop)
|
||||
{
|
||||
// @see N°1921
|
||||
// image from another iTop should be treated as external images
|
||||
$oElement->removeAttribute(self::DOM_ATTR_ID);
|
||||
$oElement->removeAttribute(self::DOM_ATTR_SECRET);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$oElement->setAttribute(self::DOM_ATTR_ID, $iInlineImageId);
|
||||
$oElement->setAttribute(self::DOM_ATTR_SECRET, $sInlineIMageSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the javascript fragment - to be added to "on document ready" - to adjust (on the fly) the width on Inline Images
|
||||
*/
|
||||
@@ -499,7 +541,7 @@ class InlineImageGC implements iBackgroundProcess
|
||||
{
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return 3600; // Runs every 3600 seconds
|
||||
return 3600; // Runs every hour
|
||||
}
|
||||
|
||||
public function Process($iTimeLimit)
|
||||
|
||||
@@ -107,7 +107,7 @@ class ExecutionKPI
|
||||
self::Report("<div style=\"background-color: grey; padding: 10px;\">");
|
||||
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
|
||||
self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
|
||||
self::Report("<p>log_kpi_user_id: ".MetaModel::GetConfig()->Get('log_kpi_user_id')."</p>");
|
||||
self::Report("<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>");
|
||||
self::Report("<div>");
|
||||
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
|
||||
self::Report("<thead>");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2013-2017 Combodo SARL
|
||||
// Copyright (C) 2013-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -24,17 +24,29 @@
|
||||
* Relies on MySQL locks because the API sem_get is not always present in the
|
||||
* installed PHP.
|
||||
*
|
||||
* @copyright Copyright (C) 2013-2017 Combodo SARL
|
||||
* @copyright Copyright (C) 2013-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
class iTopMutex
|
||||
{
|
||||
protected $sName;
|
||||
protected $hDBLink;
|
||||
/** @var bool */
|
||||
protected $bLocked; // Whether or not this instance of the Mutex is locked
|
||||
|
||||
/** @var \mysqli */
|
||||
protected $hDBLink;
|
||||
protected $sDBHost;
|
||||
protected $sDBUser;
|
||||
protected $sDBPwd;
|
||||
protected $sDBName;
|
||||
protected $sDBSubname;
|
||||
protected $bDBTlsEnabled;
|
||||
protected $sDBTlsCA;
|
||||
static protected $aAcquiredLocks = array(); // Number of instances of the Mutex, having the lock, in this page
|
||||
|
||||
public function __construct($sName, $sDBHost = null, $sDBUser = null, $sDBPwd = null)
|
||||
public function __construct(
|
||||
$sName, $sDBHost = null, $sDBUser = null, $sDBPwd = null, $bDBTlsEnabled = false, $sDBTlsCA = null
|
||||
)
|
||||
{
|
||||
// Compute the name of a lock for mysql
|
||||
// Note: names are server-wide!!! So let's make the name specific to this iTop instance
|
||||
@@ -43,17 +55,27 @@ class iTopMutex
|
||||
{
|
||||
$oConfig = utils::GetConfig(); // Will return an empty config when called during the setup
|
||||
}
|
||||
$sDBName = $oConfig->GetDBName();
|
||||
$sDBSubname = $oConfig->GetDBSubname();
|
||||
$this->sName = 'itop.'.$sName;
|
||||
if (substr($sName, -strlen($sDBName.$sDBSubname)) != $sDBName.$sDBSubname)
|
||||
$this->sDBHost = is_null($sDBHost) ? $oConfig->Get('db_host') : $sDBHost;
|
||||
$this->sDBUser = is_null($sDBUser) ? $oConfig->Get('db_user') : $sDBUser;
|
||||
$this->sDBPwd = is_null($sDBPwd) ? $oConfig->Get('db_pwd') : $sDBPwd;
|
||||
$this->sDBName = $oConfig->Get('db_name');
|
||||
$sDBSubname = $oConfig->Get('db_subname');
|
||||
|
||||
$this->bDBTlsEnabled = is_null($bDBTlsEnabled) ? $oConfig->Get('db_tls.enabled') : $bDBTlsEnabled;
|
||||
$this->sDBTlsCA = is_null($sDBTlsCA) ? $oConfig->Get('db_tls.ca') : $sDBTlsCA;
|
||||
|
||||
$this->sName = $sName;
|
||||
if (substr($sName, -strlen($this->sDBName.$sDBSubname)) != $this->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;
|
||||
$this->sName .= $this->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))
|
||||
@@ -61,12 +83,9 @@ class iTopMutex
|
||||
self::$aAcquiredLocks[$this->sName] = 0;
|
||||
}
|
||||
|
||||
// It is a MUST to create a dedicated session each time a lock is required, because
|
||||
// It is MANDATORY 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)
|
||||
$sDBHost = is_null($sDBHost) ? $oConfig->GetDBHost() : $sDBHost;
|
||||
$sDBUser = is_null($sDBUser) ? $oConfig->GetDBUser() : $sDBUser;
|
||||
$sDBPwd = is_null($sDBPwd) ? $oConfig->GetDBPwd() : $sDBPwd;
|
||||
$this->InitMySQLSession($sDBHost, $sDBUser, $sDBPwd);
|
||||
$this->InitMySQLSession();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
@@ -79,7 +98,9 @@ class iTopMutex
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire the mutex
|
||||
* Acquire the mutex. Uses a MySQL lock. <b>Warn</b> : can have an abnormal behavior on MySQL clusters (see R-016204)
|
||||
*
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
|
||||
*/
|
||||
public function Lock()
|
||||
{
|
||||
@@ -197,26 +218,26 @@ class iTopMutex
|
||||
self::$aAcquiredLocks[$this->sName]--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database connection. Mandatory attributes must be already set !
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function InitMySQLSession()
|
||||
{
|
||||
$sServer = $this->sDBHost;
|
||||
$sUser = $this->sDBUser;
|
||||
$sPwd = $this->sDBPwd;
|
||||
$sSource = $this->sDBName;
|
||||
$bTlsEnabled = $this->bDBTlsEnabled;
|
||||
$sTlsCA = $this->sDBTlsCA;
|
||||
|
||||
|
||||
public function InitMySQLSession($sHost, $sUser, $sPwd)
|
||||
{
|
||||
$aConnectInfo = explode(':', $sHost);
|
||||
if (count($aConnectInfo) > 1)
|
||||
{
|
||||
// Override the default port
|
||||
$sServer = $aConnectInfo[0];
|
||||
$iPort = $aConnectInfo[1];
|
||||
$this->hDBLink = @mysqli_connect($sServer, $sUser, $sPwd, '', $iPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->hDBLink = @mysqli_connect($sHost, $sUser, $sPwd);
|
||||
}
|
||||
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
|
||||
|
||||
if (!$this->hDBLink)
|
||||
{
|
||||
throw new Exception("Could not connect to the DB server (host=$sHost, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
||||
throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
* echo "Ok, '$sOQL' is a valid query";
|
||||
* }
|
||||
*/
|
||||
if (!class_exists('CoreException', false))
|
||||
{
|
||||
class CoreException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
//if (!class_exists('CoreException', false))
|
||||
//{
|
||||
// class CoreException extends Exception
|
||||
// {
|
||||
//
|
||||
// }
|
||||
//}
|
||||
|
||||
require_once(__DIR__.'/expression.class.inc.php');
|
||||
require_once(__DIR__.'/oqlquery.class.inc.php');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,7 @@ class OqlInterpreter
|
||||
|
||||
/**
|
||||
* @return OqlQuery
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function ParseQuery()
|
||||
{
|
||||
|
||||
@@ -285,7 +285,7 @@ abstract class OqlQuery
|
||||
* and the query in which it is used
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @throws OqlNormalizeException
|
||||
* @param string $sSourceQuery
|
||||
*/
|
||||
abstract public function Check(ModelReflection $oModelReflection, $sSourceQuery);
|
||||
|
||||
|
||||
@@ -191,9 +191,16 @@ class ormCaseLog {
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if($this->IsEmpty()) return '';
|
||||
|
||||
return $this->m_sLog;
|
||||
}
|
||||
|
||||
public function IsEmpty()
|
||||
{
|
||||
return ($this->m_sLog === null);
|
||||
}
|
||||
|
||||
public function ClearModifiedFlag()
|
||||
{
|
||||
$this->m_bModified = false;
|
||||
|
||||
@@ -51,6 +51,8 @@ class ormDocument
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if($this->IsEmpty()) return '';
|
||||
|
||||
return MyHelpers::beautifulstr($this->m_data, 100, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
* Object from the original set, minus the removed objects
|
||||
* @var DBObject[] array of iObjectId => DBObject
|
||||
*/
|
||||
protected $aPreserved;
|
||||
protected $aPreserved = array();
|
||||
|
||||
/**
|
||||
* @var DBObject[] New items
|
||||
@@ -74,6 +74,14 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
*/
|
||||
protected $iCursor = 0;
|
||||
|
||||
/**
|
||||
* __toString magical function overload.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* ormLinkSet constructor.
|
||||
* @param $sHostClass
|
||||
@@ -123,19 +131,18 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
{
|
||||
assert($oLink instanceof $this->sClass);
|
||||
// No impact on the iteration algorithm
|
||||
$this->aAdded[] = $oLink;
|
||||
$iObjectId = $oLink->GetKey();
|
||||
$this->aAdded[$iObjectId] = $oLink;
|
||||
$this->bHasDelta = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObject $oObject
|
||||
* @param string $sClassAlias
|
||||
* @deprecated
|
||||
* @deprecated Since iTop 2.4, use ormLinkset->AddItem() instead.
|
||||
*/
|
||||
public function AddObject(DBObject $oObject, $sClassAlias = '')
|
||||
{
|
||||
trigger_error('iTop: ormLinkSet::AddObject() is deprecated use ormLinkSet::AddItem() instead.', E_USER_DEPRECATED);
|
||||
|
||||
$this->AddItem($oObject);
|
||||
}
|
||||
|
||||
@@ -150,6 +157,13 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$this->aRemoved[$iObjectId] = $iObjectId;
|
||||
$this->bHasDelta = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (array_key_exists($iObjectId, $this->aAdded))
|
||||
{
|
||||
unset($this->aAdded[$iObjectId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,10 +172,15 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
public function ModifyItem(DBObject $oLink)
|
||||
{
|
||||
assert($oLink instanceof $this->sClass);
|
||||
|
||||
$iObjectId = $oLink->GetKey();
|
||||
if (array_key_exists($iObjectId, $this->aPreserved))
|
||||
{
|
||||
unset($this->aPreserved[$iObjectId]);
|
||||
$this->aModified[$iObjectId] = $oLink;
|
||||
$this->bHasDelta = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function LoadOriginalIds()
|
||||
{
|
||||
@@ -169,9 +188,16 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
{
|
||||
if ($this->oOriginalSet)
|
||||
{
|
||||
$this->aOriginalObjects = $this->oOriginalSet->ToArray();
|
||||
$this->aOriginalObjects = $this->GetArrayOfIndex();
|
||||
$this->aPreserved = $this->aOriginalObjects; // Copy (not effective until aPreserved gets modified)
|
||||
foreach ($this->aRemoved as $iObjectId)
|
||||
{
|
||||
if (array_key_exists($iObjectId, $this->aPreserved))
|
||||
{
|
||||
unset($this->aPreserved[$iObjectId]);
|
||||
}
|
||||
}
|
||||
foreach ($this->aModified as $iObjectId => $oLink)
|
||||
{
|
||||
if (array_key_exists($iObjectId, $this->aPreserved))
|
||||
{
|
||||
@@ -190,16 +216,28 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: After calling this method, the set cursor will be at the end of the set. You might to rewind it.
|
||||
*
|
||||
* Note: After calling this method, the set cursor will be at the end of the set. You might want to rewind it.
|
||||
* @return array
|
||||
*/
|
||||
protected function GetArrayOfIndex()
|
||||
{
|
||||
$aRet = array();
|
||||
$this->oOriginalSet->Rewind();
|
||||
$iRow = 0;
|
||||
while ($oObject = $this->oOriginalSet->Fetch())
|
||||
{
|
||||
$aRet[$oObject->GetKey()] = $iRow++;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bWithId
|
||||
* @return array
|
||||
* @deprecated
|
||||
* @deprecated Since iTop 2.4, use foreach($this as $oItem){} instead
|
||||
*/
|
||||
public function ToArray($bWithId = true)
|
||||
{
|
||||
trigger_error('iTop: ormLinkSet::ToArray() is deprecated use foreach instead.', E_USER_DEPRECATED);
|
||||
|
||||
$aRet = array();
|
||||
foreach($this as $oItem)
|
||||
{
|
||||
@@ -215,6 +253,28 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAttCode
|
||||
* @param bool $bWithId
|
||||
* @return array
|
||||
*/
|
||||
public function GetColumnAsArray($sAttCode, $bWithId = true)
|
||||
{
|
||||
$aRet = array();
|
||||
foreach($this as $oItem)
|
||||
{
|
||||
if ($bWithId)
|
||||
{
|
||||
$aRet[$oItem->GetKey()] = $oItem->Get($sAttCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRet[] = $oItem->Get($sAttCode);
|
||||
}
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* The class of the objects of the collection (at least a common ancestor)
|
||||
*
|
||||
@@ -233,7 +293,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
public function Count()
|
||||
{
|
||||
$this->LoadOriginalIds();
|
||||
$iRet = count($this->aPreserved) + count($this->aAdded);
|
||||
$iRet = count($this->aPreserved) + count($this->aAdded) + count($this->aModified);
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
@@ -290,11 +350,21 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$iPreservedCount = count($this->aPreserved);
|
||||
if ($this->iCursor < $iPreservedCount)
|
||||
{
|
||||
$oRet = current($this->aPreserved);
|
||||
$iRet = current($this->aPreserved);
|
||||
$this->oOriginalSet->Seek($iRet);
|
||||
$oRet = $this->oOriginalSet->Fetch();
|
||||
}
|
||||
else
|
||||
{
|
||||
$iModifiedCount = count($this->aModified);
|
||||
if($this->iCursor < $iPreservedCount + $iModifiedCount)
|
||||
{
|
||||
$oRet = current($this->aModified);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oRet = current($this->aAdded);
|
||||
}
|
||||
}
|
||||
return $oRet;
|
||||
}
|
||||
@@ -314,10 +384,18 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
next($this->aPreserved);
|
||||
}
|
||||
else
|
||||
{
|
||||
$iModifiedCount = count($this->aModified);
|
||||
if($this->iCursor < $iPreservedCount + $iModifiedCount)
|
||||
{
|
||||
next($this->aModified);
|
||||
}
|
||||
else
|
||||
{
|
||||
next($this->aAdded);
|
||||
}
|
||||
// Increment AFTER moving the internal cursors because when starting aAdded, we must leave it intact
|
||||
}
|
||||
// Increment AFTER moving the internal cursors because when starting aModified / aAdded, we must leave it intact
|
||||
$this->iCursor++;
|
||||
}
|
||||
|
||||
@@ -358,6 +436,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$this->iCursor = 0;
|
||||
reset($this->aPreserved);
|
||||
reset($this->aAdded);
|
||||
reset($this->aModified);
|
||||
}
|
||||
|
||||
public function HasDelta()
|
||||
@@ -429,7 +508,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$this->aAdded = array();
|
||||
$this->aRemoved = array();
|
||||
$this->aModified = array();
|
||||
$this->aPreserved = $this->aOriginalObjects;
|
||||
$this->aPreserved = ($this->aOriginalObjects === null) ? array() : $this->aOriginalObjects;
|
||||
$this->bHasDelta = false;
|
||||
|
||||
/** @var AttributeLinkedSet $oAttDef */
|
||||
@@ -460,6 +539,27 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of all modified (added, modified and removed) links
|
||||
*
|
||||
* @return array of link objects
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ListModifiedLinks()
|
||||
{
|
||||
$aAdded = $this->aAdded;
|
||||
$aModified = $this->aModified;
|
||||
$aRemoved = array();
|
||||
if (count($this->aRemoved) > 0)
|
||||
{
|
||||
$oSearch = new DBObjectSearch($this->sClass);
|
||||
$oSearch->AddCondition('id', $this->aRemoved, 'IN');
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$aRemoved = $oSet->ToArray();
|
||||
}
|
||||
return array_merge($aAdded, $aModified, $aRemoved);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObject $oHostObject
|
||||
*/
|
||||
@@ -504,12 +604,15 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
|
||||
// Check for the existing links
|
||||
//
|
||||
/** @var DBObject[] $aExistingLinks */
|
||||
$aExistingLinks = array();
|
||||
/** @var Int[] $aExistingRemote */
|
||||
$aExistingRemote = array();
|
||||
if (count($aCheckLinks) > 0)
|
||||
{
|
||||
$oSearch = new DBObjectSearch($this->sClass);
|
||||
$oSearch->AddCondition('id', $aCheckLinks, 'IN');
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
/** @var DBObject[] $aExistingLinks */
|
||||
$aExistingLinks = $oSet->ToArray();
|
||||
}
|
||||
|
||||
@@ -521,8 +624,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$oSearch->AddCondition($sExtKeyToMe, $oHostObject->GetKey(), '=');
|
||||
$oSearch->AddCondition($sExtKeyToRemote, $aCheckRemote, 'IN');
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
/** @var Int[] $aExistingRemote */
|
||||
$aExistingRemote = $oSet->GetColumnAsArray($sExtKeyToRemote);
|
||||
$aExistingRemote = $oSet->GetColumnAsArray($sExtKeyToRemote, true);
|
||||
}
|
||||
|
||||
// Write the links according to the existing links
|
||||
@@ -536,9 +638,26 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
{
|
||||
if (count($aCheckRemote) > 0)
|
||||
{
|
||||
if (in_array($oLink->Get($sExtKeyToRemote), $aExistingRemote))
|
||||
$bIsDuplicate = false;
|
||||
foreach($aExistingRemote as $sLinkKey => $sExtKey)
|
||||
{
|
||||
if ($sExtKey == $oLink->Get($sExtKeyToRemote))
|
||||
{
|
||||
// Do not create a duplicate
|
||||
// + In the case of a remove action followed by an add action
|
||||
// of an existing link,
|
||||
// the final state to consider is add action,
|
||||
// so suppress the entry in the removed list.
|
||||
if (array_key_exists($sLinkKey, $this->aRemoved))
|
||||
{
|
||||
unset($this->aRemoved[$sLinkKey]);
|
||||
}
|
||||
$bIsDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($bIsDuplicate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -598,4 +717,34 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
//
|
||||
$oMtx->Unlock();
|
||||
}
|
||||
|
||||
public function ToDBObjectSet($bShowObsolete = true)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->sHostClass, $this->sAttCode);
|
||||
$oLinkSearch = $this->GetFilter();
|
||||
if ($oAttDef->IsIndirect())
|
||||
{
|
||||
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToRemote);
|
||||
$sTargetClass = $oLinkingAttDef->GetTargetClass();
|
||||
if (!$bShowObsolete && MetaModel::IsObsoletable($sTargetClass))
|
||||
{
|
||||
$oNotObsolete = new BinaryExpression(
|
||||
new FieldExpression('obsolescence_flag', $sTargetClass),
|
||||
'=',
|
||||
new ScalarExpression(0)
|
||||
);
|
||||
$oNotObsoleteRemote = new DBObjectSearch($sTargetClass);
|
||||
$oNotObsoleteRemote->AddConditionExpression($oNotObsolete);
|
||||
$oLinkSearch->AddCondition_PointingTo($oNotObsoleteRemote, $sExtKeyToRemote);
|
||||
}
|
||||
}
|
||||
$oLinkSet = new DBObjectSet($oLinkSearch);
|
||||
$oLinkSet->SetShowObsoleteData($bShowObsolete);
|
||||
if ($this->HasDelta())
|
||||
{
|
||||
$oLinkSet->AddObjectArray($this->aAdded);
|
||||
}
|
||||
return $oLinkSet;
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ EOF
|
||||
|
||||
$sUrl = 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
|
||||
}
|
||||
$sRet = '<img src="' . $sUrl . '" style="width: ' . $iNewWidth . 'px; height: ' . $iNewHeight . 'px">';
|
||||
$sRet = ($sUrl !== null) ? '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px">' : '';
|
||||
$sRet = '<div class="view-image">'.$sRet.'</div>';
|
||||
}
|
||||
else
|
||||
|
||||
@@ -34,10 +34,10 @@ class QueryBuilderContext
|
||||
|
||||
public $m_oQBExpressions;
|
||||
|
||||
public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
{
|
||||
$this->m_oRootFilter = $oFilter;
|
||||
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter, $aGroupByExpr);
|
||||
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter, $aGroupByExpr, $aSelectExpr);
|
||||
|
||||
$this->m_aClassAliases = $oFilter->GetJoinedClasses();
|
||||
$this->m_aTableAliases = array();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
// Copyright (C) 2015-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -15,10 +15,12 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Data structures (i.e. PHP classes) to build and use relation graphs
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
@@ -39,6 +41,10 @@ class RelationObjectNode extends GraphNode
|
||||
|
||||
/**
|
||||
* Make a normalized ID to ensure the uniqueness of such a node
|
||||
*
|
||||
* @param string $oObject
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function MakeId($oObject)
|
||||
{
|
||||
@@ -47,6 +53,10 @@ class RelationObjectNode extends GraphNode
|
||||
|
||||
/**
|
||||
* Formatting for GraphViz
|
||||
*
|
||||
* @param bool $bNoLabel
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetDotAttributes($bNoLabel = false)
|
||||
{
|
||||
@@ -76,6 +86,9 @@ class RelationObjectNode extends GraphNode
|
||||
|
||||
/**
|
||||
* Recursively mark the objects nodes as reached, unless we get stopped by a redundancy node or a 'not allowed' node
|
||||
*
|
||||
* @param string $sProperty
|
||||
* @param $value
|
||||
*/
|
||||
public function ReachDown($sProperty, $value)
|
||||
{
|
||||
@@ -105,6 +118,13 @@ class RelationRedundancyNode extends GraphNode
|
||||
|
||||
/**
|
||||
* Make a normalized ID to ensure the uniqueness of such a node
|
||||
*
|
||||
* @param string $sRelCode
|
||||
* @param string $sNeighbourId
|
||||
* @param $oSourceObject
|
||||
* @param \DBObject $oSinkObject
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function MakeId($sRelCode, $sNeighbourId, $oSourceObject, $oSinkObject)
|
||||
{
|
||||
@@ -113,6 +133,10 @@ class RelationRedundancyNode extends GraphNode
|
||||
|
||||
/**
|
||||
* Formatting for GraphViz
|
||||
*
|
||||
* @param bool $bNoLabel
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetDotAttributes($bNoLabel = false)
|
||||
{
|
||||
@@ -123,6 +147,9 @@ class RelationRedundancyNode extends GraphNode
|
||||
|
||||
/**
|
||||
* Recursively mark the objects nodes as reached, unless we get stopped by a redundancy node
|
||||
*
|
||||
* @param string $sProperty
|
||||
* @param $value
|
||||
*/
|
||||
public function ReachDown($sProperty, $value)
|
||||
{
|
||||
@@ -145,6 +172,16 @@ class RelationRedundancyNode extends GraphNode
|
||||
*/
|
||||
class RelationEdge extends GraphEdge
|
||||
{
|
||||
/**
|
||||
* RelationEdge constructor.
|
||||
*
|
||||
* @param \SimpleGraph $oGraph
|
||||
* @param \GraphNode $oSourceNode
|
||||
* @param \GraphNode $oSinkNode
|
||||
* @param bool $bMustBeUnique
|
||||
*
|
||||
* @throws \SimpleGraphException
|
||||
*/
|
||||
public function __construct(SimpleGraph $oGraph, GraphNode $oSourceNode, GraphNode $oSinkNode, $bMustBeUnique = false)
|
||||
{
|
||||
$sId = $oSourceNode->GetId().'-to-'.$oSinkNode->GetId();
|
||||
@@ -182,6 +219,8 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Add an object that will be the starting point for building the relations downstream
|
||||
*
|
||||
* @param \DBObject $oObject
|
||||
*/
|
||||
public function AddSourceObject(DBObject $oObject)
|
||||
{
|
||||
@@ -192,6 +231,8 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Add an object that will be the starting point for building the relations uptream
|
||||
*
|
||||
* @param \DBObject $oObject
|
||||
*/
|
||||
public function AddSinkObject(DBObject$oObject)
|
||||
{
|
||||
@@ -203,11 +244,15 @@ class RelationGraph extends SimpleGraph
|
||||
/**
|
||||
* Add a 'context' OQL query, specifying extra objects to be marked as 'is_reached'
|
||||
* even though they are not part of the sources.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $sOQL The OQL query defining the context objects
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function AddContextQuery($key, $sOQL)
|
||||
{
|
||||
if ($sOQL === '') return;
|
||||
if ($sOQL === '') { return;}
|
||||
|
||||
$oSearch = static::MakeSearch($sOQL);
|
||||
$aAliases = $oSearch->GetSelectedClasses();
|
||||
@@ -217,7 +262,6 @@ class RelationGraph extends SimpleGraph
|
||||
throw new Exception("Invalid context query '$sOQL'. A context query must contain at least two columns. Columns: ".implode(', ', $aAliases).'. ');
|
||||
}
|
||||
$aAliasNames = array_keys($aAliases);
|
||||
$sClassAlias = $oSearch->GetClassAlias();
|
||||
$oCondition = new BinaryExpression(new FieldExpression('id', $aAliasNames[0]), '=', new VariableExpression('id'));
|
||||
$oSearch->AddConditionExpression($oCondition);
|
||||
|
||||
@@ -231,8 +275,11 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Determines if the given DBObject is part of a 'context'
|
||||
*
|
||||
* @param DBObject $oObj
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function IsPartOfContext(DBObject $oObj, &$aRootCauses)
|
||||
{
|
||||
@@ -271,6 +318,14 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Build the graph downstream, and mark the nodes that can be reached from the source node
|
||||
*
|
||||
* @param string $sRelCode
|
||||
* @param int $iMaxDepth
|
||||
* @param bool $bEnableRedundancy
|
||||
* @param array $aUnreachableObjects
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy, $aUnreachableObjects = array())
|
||||
{
|
||||
@@ -316,6 +371,13 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Build the graph upstream
|
||||
*
|
||||
* @param string $sRelCode
|
||||
* @param int $iMaxDepth
|
||||
* @param bool $bEnableRedundancy
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ComputeRelatedObjectsUp($sRelCode, $iMaxDepth, $bEnableRedundancy)
|
||||
{
|
||||
@@ -347,11 +409,11 @@ class RelationGraph extends SimpleGraph
|
||||
*
|
||||
* @param string $sRelCode The code of the relation to use for the computation
|
||||
* @param boolean $bDown The direction: downstream or upstream
|
||||
* @param array $oObjectNode The node from which to compute the neighbours
|
||||
* @param \GraphElement $oObjectNode The node from which to compute the neighbours
|
||||
* @param int $iMaxDepth
|
||||
* @param boolean $bEnableReduncancy
|
||||
* @param boolean $bEnableRedundancy
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function AddRelatedObjects($sRelCode, $bDown, $oObjectNode, $iMaxDepth, $bEnableRedundancy)
|
||||
{
|
||||
@@ -371,15 +433,19 @@ class RelationGraph extends SimpleGraph
|
||||
}
|
||||
elseif ($oObjectNode->GetProperty('developped', false))
|
||||
{
|
||||
// No need to execute the queries again... just dig into the nodes down/up to iMaxDepth
|
||||
//
|
||||
$aRelatedEdges = $bDown ? $oObjectNode->GetOutgoingEdges() : $oObjectNode->GetIncomingEdges();
|
||||
foreach ($aRelatedEdges as $oRelatedEdge)
|
||||
{
|
||||
$oRelatedNode = $bDown ? $oRelatedEdge->GetSinkNode() : $oRelatedEdge->GetSourceNode();
|
||||
// Recurse (decrement the depth)
|
||||
$this->AddRelatedObjects($sRelCode, $bDown, $oRelatedNode, $iMaxDepth - 1, $bEnableRedundancy);
|
||||
}
|
||||
// No need to explore the underlying graph at all. We can stop here since the node has already been developped.
|
||||
// Otherwise in case of "loops" in the graph we would recurse up to the max depth limit
|
||||
// without producing any difference in the resulting graph... but potentially taking a LOOOONG time.
|
||||
return;
|
||||
|
||||
// Former code was
|
||||
//$aRelatedEdges = $bDown ? $oObjectNode->GetOutgoingEdges() : $oObjectNode->GetIncomingEdges();
|
||||
//foreach ($aRelatedEdges as $oRelatedEdge)
|
||||
//{
|
||||
// $oRelatedNode = $bDown ? $oRelatedEdge->GetSinkNode() : $oRelatedEdge->GetSourceNode();
|
||||
// // Recurse (decrement the depth)
|
||||
// $this->AddRelatedObjects($sRelCode, $bDown, $oRelatedNode, $iMaxDepth - 1, $bEnableRedundancy);
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -442,6 +508,14 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Determine if there is a redundancy (or use the existing one) and add the corresponding nodes/edges
|
||||
*
|
||||
* @param string $sRelCode
|
||||
* @param array $aQueryInfo
|
||||
* @param GraphElement $oFromNode
|
||||
* @param GraphElement $oToNode
|
||||
*
|
||||
* @return \GraphNode|NULL|\RelationRedundancyNode
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function ComputeRedundancy($sRelCode, $aQueryInfo, $oFromNode, $oToNode)
|
||||
{
|
||||
@@ -494,6 +568,12 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Helper to determine the redundancy setting on a given relation
|
||||
*
|
||||
* @param string $sRelCode
|
||||
* @param array $aQueryInfo
|
||||
* @param GraphElement $oToNode
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function IsRedundancyEnabled($sRelCode, $aQueryInfo, $oToNode)
|
||||
{
|
||||
@@ -510,6 +590,13 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Helper to determine the redundancy threshold, given the count of objects upstream
|
||||
*
|
||||
* @param string $sRelCode
|
||||
* @param array $aQueryInfo
|
||||
* @param GraphElement $oToNode
|
||||
* @param int $iUpstreamObjects
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function GetRedundancyMinUp($sRelCode, $aQueryInfo, $oToNode, $iUpstreamObjects)
|
||||
{
|
||||
@@ -534,6 +621,12 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Helper to search for the redundancy attribute
|
||||
*
|
||||
* @param string $sRelCode
|
||||
* @param array $aQueryInfo
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return \AttributeDefinition|\AttributeRedundancySettings|null
|
||||
*/
|
||||
protected function FindRedundancyAttribute($sRelCode, $aQueryInfo, $sClass)
|
||||
{
|
||||
@@ -560,7 +653,7 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Get the objects referenced by the graph as a hash array: 'class' => array of objects
|
||||
* @return Ambigous <multitype:multitype: , unknown>
|
||||
* @return array Ambigous <multitype:multitype: , unknown>
|
||||
*/
|
||||
public function GetObjectsByClass()
|
||||
{
|
||||
@@ -582,6 +675,13 @@ class RelationGraph extends SimpleGraph
|
||||
return $aResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sOQL
|
||||
*
|
||||
* @return \DBSearch
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
protected static function MakeSearch($sOQL)
|
||||
{
|
||||
$oSearch = DBSearch::FromOQL($sOQL);
|
||||
|
||||
@@ -257,13 +257,14 @@ class CoreServices implements iRestServiceProvider
|
||||
*/
|
||||
public function ListOperations($sVersion)
|
||||
{
|
||||
// 1.4 - iTop 2.5.2, 2.6.1, 2.7.0, Verb 'core/get': added pagination parameters limit and page
|
||||
// 1.3 - iTop 2.2.0, Verb 'get_related': added the options 'redundancy' and 'direction' to take into account the redundancy in the impact analysis
|
||||
// 1.2 - was documented in the wiki but never released ! Same as 1.3
|
||||
// 1.1 - In the reply, objects have a 'key' entry so that it is no more necessary to split class::key programmaticaly
|
||||
// 1.0 - Initial implementation in iTop 2.0.1
|
||||
//
|
||||
$aOps = array();
|
||||
if (in_array($sVersion, array('1.0', '1.1', '1.2', '1.3')))
|
||||
if (in_array($sVersion, array('1.0', '1.1', '1.2', '1.3', '1.4')))
|
||||
{
|
||||
$aOps[] = array(
|
||||
'verb' => 'core/create',
|
||||
@@ -299,9 +300,16 @@ class CoreServices implements iRestServiceProvider
|
||||
|
||||
/**
|
||||
* Enumerate services delivered by this class
|
||||
*
|
||||
* @param string $sVersion The version (e.g. 1.0) supported by the services
|
||||
* @param string $sVerb
|
||||
* @param $aParams
|
||||
*
|
||||
* @return RestResult The standardized result structure (at least a message)
|
||||
* @throws Exception in case of internal failure.
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \SimpleGraphException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ExecOperation($sVersion, $sVerb, $aParams)
|
||||
{
|
||||
@@ -436,8 +444,10 @@ class CoreServices implements iRestServiceProvider
|
||||
$key = RestUtils::GetMandatoryParam($aParams, 'key');
|
||||
$aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
|
||||
$bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+');
|
||||
$iLimit = (int)RestUtils::GetOptionalParam($aParams, 'limit', 0);
|
||||
$iPage = (int)RestUtils::GetOptionalParam($aParams, 'page', 1);
|
||||
|
||||
$oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key);
|
||||
$oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key, $iLimit, self::getOffsetFromLimitAndPage($iLimit, $iPage));
|
||||
$sTargetClass = $oObjectSet->GetFilter()->GetClass();
|
||||
|
||||
if (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ) != UR_ALLOWED_YES)
|
||||
@@ -449,6 +459,11 @@ class CoreServices implements iRestServiceProvider
|
||||
{
|
||||
$oResult->code = RestResult::UNAUTHORIZED;
|
||||
$oResult->message = "The current user does not have enough permissions for exporting data of class $sTargetClass";
|
||||
}
|
||||
elseif ($iPage < 1)
|
||||
{
|
||||
$oResult->code = RestResult::INVALID_PAGE;
|
||||
$oResult->message = "The request page number is not valid. It must be an integer greater than 0";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -774,4 +789,15 @@ class CoreServices implements iRestServiceProvider
|
||||
$oResult->message = $sRes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $iLimit
|
||||
* @param int $iPage
|
||||
*
|
||||
* @return int Offset for a given page number
|
||||
*/
|
||||
protected static function getOffsetFromLimitAndPage($iLimit, $iPage)
|
||||
{
|
||||
return $iLimit * max(0, $iPage - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,8 @@ EOF
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeCustomFields)
|
||||
{
|
||||
$sRet = $oObj->GetAsHTML($sAttCode);
|
||||
// Stick to the weird implementation made in GetNextChunk
|
||||
$sRet = utils::TextToHtml($oObj->GetEditValue($sAttCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -233,7 +234,6 @@ EOF
|
||||
}
|
||||
}
|
||||
$sData = '';
|
||||
$sData .= '<style>table br {mso-data-placement:same-cell;}</style>'; // Trick for Excel: keep line breaks inside the same cell !
|
||||
$sData .= "<table border=\"1\">\n";
|
||||
$sData .= "<tr>\n";
|
||||
foreach($aData as $sLabel)
|
||||
@@ -326,6 +326,12 @@ EOF
|
||||
}
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeCustomFields)
|
||||
{
|
||||
// GetAsHTML returns a table that would not fit
|
||||
$sField = utils::TextToHtml($oObj->GetEditValue($sAttCode));
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else if($oAttDef instanceof AttributeString)
|
||||
{
|
||||
$sField = $oObj->GetAsHTML($sAttCode, $this->bLocalizeOutput);
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
|
||||
class SQLObjectQuery extends SQLQuery
|
||||
{
|
||||
private $m_SourceOQL = '';
|
||||
public $m_aContextData = null;
|
||||
public $m_iOriginalTableCount = 0;
|
||||
private $m_sTable = '';
|
||||
private $m_sTableAlias = '';
|
||||
private $m_aFields = array();
|
||||
@@ -46,7 +47,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
private $m_aValues = array(); // Values to set in case of an update query
|
||||
private $m_oSelectedIdField = null;
|
||||
private $m_aJoinSelects = array();
|
||||
private $m_bBeautifulQuery = false;
|
||||
protected $m_bBeautifulQuery = false;
|
||||
|
||||
// Data set by PrepareRendering()
|
||||
private $__aFrom;
|
||||
@@ -244,6 +245,12 @@ class SQLObjectQuery extends SQLQuery
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
|
||||
/**
|
||||
* @param array $aArgs
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function RenderDelete($aArgs = array())
|
||||
{
|
||||
$this->PrepareRendering();
|
||||
@@ -269,6 +276,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
$sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs);
|
||||
return "DELETE $sDelete FROM $sFrom WHERE $sWhere";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,15 +291,24 @@ class SQLObjectQuery extends SQLQuery
|
||||
|
||||
/**
|
||||
* Needed for the unions
|
||||
* @param $aOrderBy
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function RenderOrderByClause($aOrderBy)
|
||||
{
|
||||
$this->PrepareRendering();
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy);
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy, $this->__aFields);
|
||||
return $sOrderBy;
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
|
||||
/**
|
||||
* @param array $aArgs
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function RenderUpdate($aArgs = array())
|
||||
{
|
||||
$this->PrepareRendering();
|
||||
@@ -302,6 +319,17 @@ class SQLObjectQuery extends SQLQuery
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
|
||||
/**
|
||||
* @param array $aOrderBy
|
||||
* @param array $aArgs
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @param bool $bGetCount
|
||||
* @param bool $bBeautifulQuery
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function RenderSelect($aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulQuery = false)
|
||||
{
|
||||
$this->m_bBeautifulQuery = $bBeautifulQuery;
|
||||
@@ -311,6 +339,14 @@ class SQLObjectQuery extends SQLQuery
|
||||
$this->PrepareRendering();
|
||||
$sFrom = self::ClauseFrom($this->__aFrom, $sIndent);
|
||||
$sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs);
|
||||
if ($iLimitCount > 0)
|
||||
{
|
||||
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLimit = '';
|
||||
}
|
||||
if ($bGetCount)
|
||||
{
|
||||
if (count($this->__aSelectedIdFields) > 0)
|
||||
@@ -321,17 +357,57 @@ class SQLObjectQuery extends SQLQuery
|
||||
$aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count
|
||||
}
|
||||
$sCountFields = implode(', ', $aCountFields);
|
||||
$sSQL = "SELECT$sLineSep COUNT(DISTINCT $sCountFields) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere";
|
||||
// Count can be limited for performance reason, in this case the total amount is not important,
|
||||
// we only need to know if the number of entries is greater than a certain amount.
|
||||
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep DISTINCT $sCountFields $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _tatooine_";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere";
|
||||
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep 1 $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _tatooine_";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSelect = self::ClauseSelect($this->__aFields, $sLineSep);
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy, $this->__aFields);
|
||||
if (!empty($sOrderBy))
|
||||
{
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
|
||||
}
|
||||
|
||||
$sSQL = "SELECT$sLineSep DISTINCT $sSelect$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sOrderBy $sLimit";
|
||||
}
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
|
||||
/**
|
||||
* @param array $aArgs
|
||||
* @param bool $bBeautifulQuery
|
||||
* @param array $aOrderBy
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false, $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0)
|
||||
{
|
||||
$this->m_bBeautifulQuery = $bBeautifulQuery;
|
||||
$sLineSep = $this->m_bBeautifulQuery ? "\n" : '';
|
||||
$sIndent = $this->m_bBeautifulQuery ? " " : null;
|
||||
|
||||
$this->PrepareRendering();
|
||||
|
||||
$sSelect = self::ClauseSelect($this->__aFields);
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy);
|
||||
$sFrom = self::ClauseFrom($this->__aFrom, $sIndent);
|
||||
$sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs);
|
||||
$sGroupBy = self::ClauseGroupBy($this->__aGroupBy);
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy, $this->__aFields);
|
||||
if (!empty($sGroupBy))
|
||||
{
|
||||
$sGroupBy = "GROUP BY $sGroupBy$sLineSep";
|
||||
}
|
||||
if (!empty($sOrderBy))
|
||||
{
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
|
||||
@@ -344,24 +420,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
{
|
||||
$sLimit = '';
|
||||
}
|
||||
$sSQL = "SELECT$sLineSep DISTINCT $sSelect$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sOrderBy $sLimit";
|
||||
}
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false)
|
||||
{
|
||||
$this->m_bBeautifulQuery = $bBeautifulQuery;
|
||||
$sLineSep = $this->m_bBeautifulQuery ? "\n" : '';
|
||||
$sIndent = $this->m_bBeautifulQuery ? " " : null;
|
||||
|
||||
$this->PrepareRendering();
|
||||
$sSelect = self::ClauseSelect($this->__aFields);
|
||||
$sFrom = self::ClauseFrom($this->__aFrom, $sIndent);
|
||||
$sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs);
|
||||
$sGroupBy = self::ClauseGroupBy($this->__aGroupBy);
|
||||
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep GROUP BY $sGroupBy";
|
||||
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sGroupBy $sOrderBy$sLineSep $sLimit";
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
@@ -384,6 +443,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
private function PrepareSingleTable(SQLObjectQuery $oRootQuery, &$aFrom, $sCallerAlias = '', $aJoinData)
|
||||
{
|
||||
$aTranslationTable[$this->m_sTable]['*'] = $this->m_sTableAlias;
|
||||
$sJoinCond = '';
|
||||
|
||||
// Handle the various kinds of join (or first table in the list)
|
||||
//
|
||||
@@ -501,7 +561,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
{
|
||||
$oRightSelect = $aJoinData["select"];
|
||||
|
||||
$sJoinTableAlias = $oRightSelect->PrepareSingleTable($oRootQuery, $aTempFrom, $this->m_sTableAlias, $aJoinData);
|
||||
$oRightSelect->PrepareSingleTable($oRootQuery, $aTempFrom, $this->m_sTableAlias, $aJoinData);
|
||||
}
|
||||
$aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom;
|
||||
|
||||
@@ -510,6 +570,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
|
||||
public function OptimizeJoins($aUsedTables, $bTopCall = true)
|
||||
{
|
||||
$this->m_iOriginalTableCount = $this->CountTables();
|
||||
if ($bTopCall)
|
||||
{
|
||||
// Top call: complete the list of tables absolutely required to perform the right query
|
||||
@@ -545,7 +606,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
protected function CollectUsedTables(&$aTables)
|
||||
public function CollectUsedTables(&$aTables)
|
||||
{
|
||||
$this->m_oConditionExpr->CollectUsedParents($aTables);
|
||||
foreach($this->m_aFields as $sFieldAlias => $oField)
|
||||
@@ -576,7 +637,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
}
|
||||
if (isset($aJoinInfo["on_expression"]))
|
||||
{
|
||||
$sJoinCond = $aJoinInfo["on_expression"]->CollectUsedParents($aTables);
|
||||
$aJoinInfo["on_expression"]->CollectUsedParents($aTables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -604,7 +665,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
}
|
||||
if (isset($aJoinInfo["on_expression"]))
|
||||
{
|
||||
$sJoinCond = $aJoinInfo["on_expression"]->CollectUsedParents($aTables);
|
||||
$aJoinInfo["on_expression"]->CollectUsedParents($aTables);
|
||||
}
|
||||
$bResult = true;
|
||||
}
|
||||
@@ -612,4 +673,5 @@ class SQLObjectQuery extends SQLQuery
|
||||
// None of the tables is in the list of required tables
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ require_once('cmdbsource.class.inc.php');
|
||||
abstract class SQLQuery
|
||||
{
|
||||
private $m_SourceOQL = '';
|
||||
private $m_bBeautifulQuery = false;
|
||||
protected $m_bBeautifulQuery = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -69,18 +69,18 @@ abstract class SQLQuery
|
||||
abstract public function RenderDelete($aArgs = array());
|
||||
abstract public function RenderUpdate($aArgs = array());
|
||||
abstract public function RenderSelect($aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulQuery = false);
|
||||
abstract public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false);
|
||||
abstract public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false, $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0);
|
||||
|
||||
abstract public function OptimizeJoins($aUsedTables, $bTopCall = true);
|
||||
|
||||
protected static function ClauseSelect($aFields)
|
||||
protected static function ClauseSelect($aFields, $sLineSep = '')
|
||||
{
|
||||
$aSelect = array();
|
||||
foreach ($aFields as $sFieldAlias => $sSQLExpr)
|
||||
{
|
||||
$aSelect[] = "$sSQLExpr AS $sFieldAlias";
|
||||
}
|
||||
$sSelect = implode(', ', $aSelect);
|
||||
$sSelect = implode(",$sLineSep ", $aSelect);
|
||||
return $sSelect;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,13 @@ abstract class SQLQuery
|
||||
return $sDelTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aFrom
|
||||
* @param null $sIndent
|
||||
* @param int $iIndentLevel
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected static function ClauseFrom($aFrom, $sIndent = null, $iIndentLevel = 0)
|
||||
{
|
||||
$sLineBreakLong = $sIndent ? "\n".str_repeat($sIndent, $iIndentLevel + 1) : '';
|
||||
@@ -174,7 +181,13 @@ abstract class SQLQuery
|
||||
}
|
||||
}
|
||||
|
||||
protected static function ClauseOrderBy($aOrderBy)
|
||||
/**
|
||||
* @param array $aOrderBy
|
||||
* @param array $aExistingFields
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected static function ClauseOrderBy($aOrderBy, $aExistingFields)
|
||||
{
|
||||
$aOrderBySpec = array();
|
||||
foreach($aOrderBy as $sFieldAlias => $bAscending)
|
||||
|
||||
@@ -38,8 +38,9 @@ class SQLUnionQuery extends SQLQuery
|
||||
{
|
||||
protected $aQueries;
|
||||
protected $aGroupBy;
|
||||
protected $aSelectExpr;
|
||||
|
||||
public function __construct($aQueries, $aGroupBy)
|
||||
public function __construct($aQueries, $aGroupBy, $aSelectExpr = array())
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
@@ -49,6 +50,7 @@ class SQLUnionQuery extends SQLQuery
|
||||
$this->aQueries[] = $oSQLQuery->DeepClone();
|
||||
}
|
||||
$this->aGroupBy = $aGroupBy;
|
||||
$this->aSelectExpr = $aSelectExpr;
|
||||
}
|
||||
|
||||
public function DisplayHtml()
|
||||
@@ -58,7 +60,7 @@ class SQLUnionQuery extends SQLQuery
|
||||
{
|
||||
$aQueriesHtml[] = '<p>'.$oSQLQuery->DisplayHtml().'</p>';
|
||||
}
|
||||
echo implode('UNION', $aQueries);
|
||||
echo implode('UNION', $aQueriesHtml);
|
||||
}
|
||||
|
||||
public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRightTable = '')
|
||||
@@ -69,12 +71,21 @@ class SQLUnionQuery extends SQLQuery
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aArgs
|
||||
* @throws Exception
|
||||
*/
|
||||
public function RenderDelete($aArgs = array())
|
||||
{
|
||||
throw new Exception(__class__.'::'.__function__.'Not implemented !');
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
|
||||
/**
|
||||
* @param array $aArgs
|
||||
* @throws Exception
|
||||
*/
|
||||
public function RenderUpdate($aArgs = array())
|
||||
{
|
||||
throw new Exception(__class__.'::'.__function__.'Not implemented !');
|
||||
@@ -85,7 +96,59 @@ class SQLUnionQuery extends SQLQuery
|
||||
{
|
||||
$this->m_bBeautifulQuery = $bBeautifulQuery;
|
||||
$sLineSep = $this->m_bBeautifulQuery ? "\n" : '';
|
||||
$sIndent = $this->m_bBeautifulQuery ? " " : null;
|
||||
|
||||
$aSelects = array();
|
||||
foreach ($this->aQueries as $oSQLQuery)
|
||||
{
|
||||
// Render SELECTS without orderby/limit/count
|
||||
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
|
||||
}
|
||||
if ($iLimitCount > 0)
|
||||
{
|
||||
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLimit = '';
|
||||
}
|
||||
|
||||
if ($bGetCount)
|
||||
{
|
||||
$sSelects = '('.implode(" $sLimit)$sLineSep UNION$sLineSep(", $aSelects)." $sLimit)";
|
||||
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
|
||||
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep 1 $sLineSep FROM $sFrom$sLineSep) AS _union_tatooine_";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sOrderBy = $this->aQueries[0]->RenderOrderByClause($aOrderBy);
|
||||
if (!empty($sOrderBy))
|
||||
{
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep $sLimit";
|
||||
$sSQL = '('.implode(")$sLineSep UNION$sLineSep (", $aSelects).')'.$sLineSep.$sOrderBy;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQL = '('.implode(" $sLimit)$sLineSep UNION$sLineSep (", $aSelects)." $sLimit)";
|
||||
}
|
||||
}
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
|
||||
/**
|
||||
* @param array $aArgs
|
||||
* @param bool $bBeautifulQuery
|
||||
* @param array $aOrderBy
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false, $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0)
|
||||
{
|
||||
$this->m_bBeautifulQuery = $bBeautifulQuery;
|
||||
$sLineSep = $this->m_bBeautifulQuery ? "\n" : '';
|
||||
|
||||
$aSelects = array();
|
||||
foreach ($this->aQueries as $oSQLQuery)
|
||||
@@ -94,22 +157,28 @@ class SQLUnionQuery extends SQLQuery
|
||||
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
|
||||
}
|
||||
$sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')';
|
||||
|
||||
if ($bGetCount)
|
||||
{
|
||||
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
|
||||
$sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep";
|
||||
}
|
||||
else
|
||||
|
||||
$aSelectAliases = array();
|
||||
$aGroupAliases = array();
|
||||
foreach ($this->aGroupBy as $sGroupAlias => $trash)
|
||||
{
|
||||
$aSelects = array();
|
||||
foreach ($this->aQueries as $oSQLQuery)
|
||||
{
|
||||
// Render SELECT without orderby/limit/count
|
||||
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
|
||||
$aSelectAliases[$sGroupAlias] = "`$sGroupAlias`";
|
||||
$aGroupAliases[] = "`$sGroupAlias`";
|
||||
}
|
||||
foreach($this->aSelectExpr as $sSelectAlias => $oExpr)
|
||||
{
|
||||
$aSelectAliases[$sSelectAlias] = $oExpr->Render()." AS `$sSelectAlias`";
|
||||
}
|
||||
|
||||
$sSelect = implode(",$sLineSep ", $aSelectAliases);
|
||||
$sGroupBy = implode(', ', $aGroupAliases);
|
||||
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy, $aSelectAliases);
|
||||
if (!empty($sGroupBy))
|
||||
{
|
||||
$sGroupBy = "GROUP BY $sGroupBy$sLineSep";
|
||||
}
|
||||
$sSelect = $this->aQueries[0]->RenderSelectClause();
|
||||
$sOrderBy = $this->aQueries[0]->RenderOrderByClause($aOrderBy);
|
||||
if (!empty($sOrderBy))
|
||||
{
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
|
||||
@@ -122,36 +191,9 @@ class SQLUnionQuery extends SQLQuery
|
||||
{
|
||||
$sLimit = '';
|
||||
}
|
||||
$sSQL = $sSelects.$sLineSep.$sOrderBy.' '.$sLimit;
|
||||
}
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false)
|
||||
{
|
||||
$this->m_bBeautifulQuery = $bBeautifulQuery;
|
||||
$sLineSep = $this->m_bBeautifulQuery ? "\n" : '';
|
||||
$sIndent = $this->m_bBeautifulQuery ? " " : null;
|
||||
|
||||
$aSelects = array();
|
||||
foreach ($this->aQueries as $oSQLQuery)
|
||||
{
|
||||
// Render SELECTS without orderby/limit/count
|
||||
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
|
||||
}
|
||||
$sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')';
|
||||
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
|
||||
|
||||
$aAliases = array();
|
||||
foreach ($this->aGroupBy as $sGroupAlias => $trash)
|
||||
{
|
||||
$aAliases[] = "`$sGroupAlias`";
|
||||
}
|
||||
$sSelect = implode(', ', $aAliases);
|
||||
$sGroupBy = implode(', ', $aAliases);
|
||||
|
||||
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep GROUP BY $sGroupBy";
|
||||
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep $sGroupBy $sOrderBy$sLineSep $sLimit";
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ abstract class TabularBulkExport extends BulkExport
|
||||
$aAuthorizedClasses = array();
|
||||
foreach($aSelectedClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) != UR_ALLOWED_NO)
|
||||
{
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ abstract class Trigger extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"category" => "grant_by_profile,core/cmdb",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "description",
|
||||
"state_attcode" => "",
|
||||
@@ -97,7 +97,7 @@ abstract class TriggerOnObject extends Trigger
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"category" => "grant_by_profile,core/cmdb",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "description",
|
||||
"state_attcode" => "",
|
||||
@@ -116,8 +116,8 @@ abstract class TriggerOnObject extends Trigger
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'description')); // 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
|
||||
MetaModel::Init_SetZListItems('default_search', array('description', 'target_class')); // Default criteria of the search banner
|
||||
// MetaModel::Init_SetZListItems('standard_search', array('name', 'target_class', 'description')); // Criteria of the search form
|
||||
}
|
||||
|
||||
public function DoCheckToWrite()
|
||||
@@ -194,7 +194,7 @@ class TriggerOnPortalUpdate extends TriggerOnObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,application",
|
||||
"category" => "grant_by_profile,core/cmdb,application",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "description",
|
||||
"state_attcode" => "",
|
||||
@@ -220,7 +220,7 @@ abstract class TriggerOnStateChange extends TriggerOnObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"category" => "grant_by_profile,core/cmdb",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "description",
|
||||
"state_attcode" => "",
|
||||
@@ -249,7 +249,7 @@ class TriggerOnStateEnter extends TriggerOnStateChange
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,application",
|
||||
"category" => "grant_by_profile,core/cmdb,application",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "description",
|
||||
"state_attcode" => "",
|
||||
@@ -277,7 +277,7 @@ class TriggerOnStateLeave extends TriggerOnStateChange
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,application",
|
||||
"category" => "grant_by_profile,core/cmdb,application",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "description",
|
||||
"state_attcode" => "",
|
||||
@@ -305,7 +305,7 @@ class TriggerOnObjectCreate extends TriggerOnObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,application",
|
||||
"category" => "grant_by_profile,core/cmdb,application",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "description",
|
||||
"state_attcode" => "",
|
||||
@@ -333,7 +333,7 @@ class lnkTriggerAction extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,application",
|
||||
"category" => "grant_by_profile,core/cmdb,application",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
@@ -366,7 +366,7 @@ class TriggerOnThresholdReached extends TriggerOnObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,application",
|
||||
"category" => "grant_by_profile,core/cmdb,application",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "description",
|
||||
"state_attcode" => "",
|
||||
|
||||
@@ -168,7 +168,7 @@ abstract class User extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core",
|
||||
"category" => "core,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "login",
|
||||
"state_attcode" => "",
|
||||
@@ -185,6 +185,7 @@ abstract class User extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("last_name", array("allowed_values"=>null, "extkey_attcode"=> 'contactid', "target_attcode"=>"name")));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("first_name", array("allowed_values"=>null, "extkey_attcode"=> 'contactid', "target_attcode"=>"first_name")));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("email", array("allowed_values"=>null, "extkey_attcode"=> 'contactid', "target_attcode"=>"email")));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("org_id", array("allowed_values"=>null, "extkey_attcode"=> 'contactid', "target_attcode"=>"org_id")));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeString("login", array("allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
@@ -195,11 +196,11 @@ abstract class User extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("allowed_org_list", array("linked_class"=>"URP_UserOrg", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"allowed_org_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'status', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'login', 'status')); // Attributes to be displayed for a list
|
||||
MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'language', 'status', 'profile_list', 'allowed_org_list')); // Unused as it's an abstract class !
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'status')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('login', 'contactid')); // Criteria of the advanced search form
|
||||
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'email', 'language', 'status', 'org_id')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array('login', 'contactid', 'org_id')); // Default criteria of the search banner
|
||||
}
|
||||
|
||||
abstract public function CheckCredentials($sPassword);
|
||||
@@ -240,7 +241,7 @@ abstract class User extends cmdbAbstractObject
|
||||
protected $oContactObject;
|
||||
|
||||
/**
|
||||
* Fetch and memoize the associated contact (if any)
|
||||
* Fetch and memorize the associated contact (if any)
|
||||
*/
|
||||
public function GetContactObject()
|
||||
{
|
||||
@@ -254,8 +255,10 @@ abstract class User extends cmdbAbstractObject
|
||||
return $this->oContactObject;
|
||||
}
|
||||
|
||||
/*
|
||||
* Overload the standard behavior
|
||||
/**
|
||||
* Overload the standard behavior.
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function DoCheckToWrite()
|
||||
{
|
||||
@@ -280,13 +283,59 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check that this user has at least one profile assigned
|
||||
// Check that this user has at least one profile assigned when profiles have changed
|
||||
if (array_key_exists('profile_list', $aChanges))
|
||||
{
|
||||
$oSet = $this->Get('profile_list');
|
||||
if ($oSet->Count() == 0)
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneProfileIsNeeded');
|
||||
}
|
||||
}
|
||||
// Only administrators can manage administrators
|
||||
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator())
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::Format('UI:Login:Error:AccessRestricted');
|
||||
}
|
||||
|
||||
if (!UserRights::IsAdministrator())
|
||||
{
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$oAddon = UserRights::GetModuleInstance();
|
||||
if (!is_null($oUser) && method_exists($oAddon, 'GetUserOrgs'))
|
||||
{
|
||||
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
|
||||
if (count($aOrgs) > 0)
|
||||
{
|
||||
// Check that the modified User belongs to one of our organization
|
||||
if (!in_array($this->GetOriginal('org_id'), $aOrgs) && !in_array($this->Get('org_id'), $aOrgs))
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:UserOrganizationNotAllowed');
|
||||
}
|
||||
// Check users with restricted organizations when allowed organizations have changed
|
||||
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges))
|
||||
{
|
||||
$oSet = $this->get('allowed_org_list');
|
||||
if ($oSet->Count() == 0)
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneOrganizationIsNeeded');
|
||||
}
|
||||
else
|
||||
{
|
||||
$aModifiedLinks = $oSet->ListModifiedLinks();
|
||||
foreach($aModifiedLinks as $oLink)
|
||||
{
|
||||
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs))
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:OrganizationNotAllowed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function GetGrantAsHtml($sClass, $iAction)
|
||||
{
|
||||
@@ -338,6 +387,8 @@ abstract class User extends cmdbAbstractObject
|
||||
'bulkread' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_READ),
|
||||
'write' => $this->GetGrantAsHtml($sClass, UR_ACTION_MODIFY),
|
||||
'bulkwrite' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_MODIFY),
|
||||
'delete' => $this->GetGrantAsHtml($sClass, UR_ACTION_DELETE),
|
||||
'bulkdelete' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_DELETE),
|
||||
'stimuli' => $sStimuli,
|
||||
);
|
||||
}
|
||||
@@ -350,6 +401,8 @@ abstract class User extends cmdbAbstractObject
|
||||
$aDisplayConfig['bulkread'] = array('label' => Dict::S('UI:UserManagement:Action:BulkRead'), 'description' => Dict::S('UI:UserManagement:Action:BulkRead+'));
|
||||
$aDisplayConfig['write'] = array('label' => Dict::S('UI:UserManagement:Action:Modify'), 'description' => Dict::S('UI:UserManagement:Action:Modify+'));
|
||||
$aDisplayConfig['bulkwrite'] = array('label' => Dict::S('UI:UserManagement:Action:BulkModify'), 'description' => Dict::S('UI:UserManagement:Action:BulkModify+'));
|
||||
$aDisplayConfig['delete'] = array('label' => Dict::S('UI:UserManagement:Action:Delete'), 'description' => Dict::S('UI:UserManagement:Action:Delete+'));
|
||||
$aDisplayConfig['bulkdelete'] = array('label' => Dict::S('UI:UserManagement:Action:BulkDelete'), 'description' => Dict::S('UI:UserManagement:Action:BulkDelete+'));
|
||||
$aDisplayConfig['stimuli'] = array('label' => Dict::S('UI:UserManagement:Action:Stimuli'), 'description' => Dict::S('UI:UserManagement:Action:Stimuli+'));
|
||||
$oPage->table($aDisplayConfig, $aDisplayData);
|
||||
}
|
||||
@@ -360,7 +413,7 @@ abstract class User extends cmdbAbstractObject
|
||||
if (!$bEditMode)
|
||||
{
|
||||
$oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix'));
|
||||
$this->DoShowGrantSumary($oPage, 'bizmodel');
|
||||
$this->DoShowGrantSumary($oPage, 'bizmodel,grant_by_profile');
|
||||
|
||||
// debug
|
||||
if (false)
|
||||
@@ -418,7 +471,7 @@ abstract class UserInternal extends User
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core",
|
||||
"category" => "core,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "login",
|
||||
"state_attcode" => "",
|
||||
@@ -434,11 +487,10 @@ abstract class UserInternal extends User
|
||||
MetaModel::Init_AddAttribute(new AttributeOneWayPassword("reset_pwd_token", array("allowed_values"=>null, "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'status', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'login', 'status')); // Attributes to be displayed for a list
|
||||
MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'status', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'status')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('login', 'contactid')); // Criteria of the advanced search form
|
||||
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'status', 'org_id')); // Criteria of the std search form
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,6 +571,7 @@ interface iSelfRegister
|
||||
*/
|
||||
class UserRights
|
||||
{
|
||||
/** @var UserRightsAddOnAPI $m_oAddOn */
|
||||
protected static $m_oAddOn;
|
||||
protected static $m_oUser;
|
||||
protected static $m_oRealUser;
|
||||
@@ -564,7 +617,7 @@ class UserRights
|
||||
return $bRes;
|
||||
}
|
||||
|
||||
protected static function IsLoggedIn()
|
||||
public static function IsLoggedIn()
|
||||
{
|
||||
if (self::$m_oUser == null)
|
||||
{
|
||||
@@ -706,6 +759,7 @@ class UserRights
|
||||
}
|
||||
else
|
||||
{
|
||||
$oUser->AllowWrite(true);
|
||||
return $oUser->ChangePassword($sOldPassword, $sNewPassword);
|
||||
}
|
||||
}
|
||||
@@ -918,13 +972,24 @@ class UserRights
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional filter for organization silos to all the requests.
|
||||
*
|
||||
* @param $sClass
|
||||
* @param array $aSettings
|
||||
*
|
||||
* @return bool|\Expression
|
||||
*/
|
||||
public static function GetSelectFilter($sClass, $aSettings = array())
|
||||
{
|
||||
// When initializing, we need to let everything pass trough
|
||||
if (!self::CheckLogin()) return true;
|
||||
if (!self::CheckLogin()) {return true;}
|
||||
|
||||
if (self::IsAdministrator()) return true;
|
||||
if (self::IsAdministrator()) {return true;}
|
||||
|
||||
try
|
||||
{
|
||||
// Check Bug 1436 for details
|
||||
if (MetaModel::HasCategory($sClass, 'bizmodel'))
|
||||
{
|
||||
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);
|
||||
@@ -933,20 +998,31 @@ class UserRights
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param int $iActionCode
|
||||
* @param DBObjectSet $oInstanceSet
|
||||
* @param User $oUser
|
||||
* @return int (UR_ALLOWED_YES|UR_ALLOWED_NO|UR_ALLOWED_DEPENDS)
|
||||
*/
|
||||
public static function IsActionAllowed($sClass, $iActionCode, /*dbObjectSet*/$oInstanceSet = null, $oUser = null)
|
||||
{
|
||||
// When initializing, we need to let everything pass trough
|
||||
if (!self::CheckLogin()) return true;
|
||||
if (!self::CheckLogin()) return UR_ALLOWED_YES;
|
||||
|
||||
if (MetaModel::DBIsReadOnly())
|
||||
{
|
||||
if ($iActionCode == UR_ACTION_CREATE) return false;
|
||||
if ($iActionCode == UR_ACTION_MODIFY) return false;
|
||||
if ($iActionCode == UR_ACTION_BULK_MODIFY) return false;
|
||||
if ($iActionCode == UR_ACTION_DELETE) return false;
|
||||
if ($iActionCode == UR_ACTION_BULK_DELETE) return false;
|
||||
if ($iActionCode == UR_ACTION_CREATE) return UR_ALLOWED_NO;
|
||||
if ($iActionCode == UR_ACTION_MODIFY) return UR_ALLOWED_NO;
|
||||
if ($iActionCode == UR_ACTION_BULK_MODIFY) return UR_ALLOWED_NO;
|
||||
if ($iActionCode == UR_ACTION_DELETE) return UR_ALLOWED_NO;
|
||||
if ($iActionCode == UR_ACTION_BULK_DELETE) return UR_ALLOWED_NO;
|
||||
}
|
||||
|
||||
$aPredefinedObjects = call_user_func(array($sClass, 'GetPredefinedObjects'));
|
||||
@@ -955,14 +1031,14 @@ class UserRights
|
||||
// As opposed to the read-only DB, modifying an object is allowed
|
||||
// (the constant columns will be marked as read-only)
|
||||
//
|
||||
if ($iActionCode == UR_ACTION_CREATE) return false;
|
||||
if ($iActionCode == UR_ACTION_DELETE) return false;
|
||||
if ($iActionCode == UR_ACTION_BULK_DELETE) return false;
|
||||
if ($iActionCode == UR_ACTION_CREATE) return UR_ALLOWED_NO;
|
||||
if ($iActionCode == UR_ACTION_DELETE) return UR_ALLOWED_NO;
|
||||
if ($iActionCode == UR_ACTION_BULK_DELETE) return UR_ALLOWED_NO;
|
||||
}
|
||||
|
||||
if (self::IsAdministrator($oUser)) return true;
|
||||
if (self::IsAdministrator($oUser)) return UR_ALLOWED_YES;
|
||||
|
||||
if (MetaModel::HasCategory($sClass, 'bizmodel'))
|
||||
if (MetaModel::HasCategory($sClass, 'bizmodel') || MetaModel::HasCategory($sClass, 'grant_by_profile'))
|
||||
{
|
||||
if (is_null($oUser))
|
||||
{
|
||||
@@ -978,12 +1054,12 @@ class UserRights
|
||||
}
|
||||
elseif(($iActionCode == UR_ACTION_READ) && MetaModel::HasCategory($sClass, 'view_in_gui'))
|
||||
{
|
||||
return true;
|
||||
return UR_ALLOWED_YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other classes could be edited/listed by the administrators
|
||||
return false;
|
||||
return UR_ALLOWED_NO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,27 +1090,31 @@ class UserRights
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param int $iActionCode
|
||||
* @param DBObjectSet $oInstanceSet
|
||||
* @param User $oUser
|
||||
* @return int (UR_ALLOWED_YES|UR_ALLOWED_NO)
|
||||
*/
|
||||
public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, /*dbObjectSet*/$oInstanceSet = null, $oUser = null)
|
||||
{
|
||||
// When initializing, we need to let everything pass trough
|
||||
if (!self::CheckLogin()) return true;
|
||||
if (!self::CheckLogin()) return UR_ALLOWED_YES;
|
||||
|
||||
if (MetaModel::DBIsReadOnly())
|
||||
{
|
||||
if ($iActionCode == UR_ACTION_MODIFY) return false;
|
||||
if ($iActionCode == UR_ACTION_DELETE) return false;
|
||||
if ($iActionCode == UR_ACTION_BULK_MODIFY) return false;
|
||||
if ($iActionCode == UR_ACTION_BULK_DELETE) return false;
|
||||
if ($iActionCode == UR_ACTION_MODIFY) return UR_ALLOWED_NO;
|
||||
if ($iActionCode == UR_ACTION_DELETE) return UR_ALLOWED_NO;
|
||||
if ($iActionCode == UR_ACTION_BULK_MODIFY) return falUR_ALLOWED_NOse;
|
||||
if ($iActionCode == UR_ACTION_BULK_DELETE) return UR_ALLOWED_NO;
|
||||
}
|
||||
|
||||
if (self::IsAdministrator($oUser)) return true;
|
||||
|
||||
// this module is forbidden for non admins
|
||||
if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false;
|
||||
|
||||
// the rest is allowed (#@# to be improved)
|
||||
if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true;
|
||||
if (self::IsAdministrator($oUser)) return UR_ALLOWED_YES;
|
||||
|
||||
if (MetaModel::HasCategory($sClass, 'bizmodel') || MetaModel::HasCategory($sClass, 'grant_by_profile'))
|
||||
{
|
||||
if (is_null($oUser))
|
||||
{
|
||||
$oUser = self::$m_oUser;
|
||||
@@ -1042,6 +1122,15 @@ class UserRights
|
||||
return self::$m_oAddOn->IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet);
|
||||
}
|
||||
|
||||
// this module is forbidden for non admins
|
||||
if (MetaModel::HasCategory($sClass, 'addon/userrights')) return UR_ALLOWED_NO;
|
||||
|
||||
// the rest is allowed
|
||||
return UR_ALLOWED_YES;
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected static $m_aAdmins = array();
|
||||
public static function IsAdministrator($oUser = null)
|
||||
{
|
||||
@@ -1153,7 +1242,7 @@ class UserRights
|
||||
self::$m_aAdmins = array();
|
||||
self::$m_aPortalUsers = array();
|
||||
}
|
||||
if (!isset($_SESSION))
|
||||
if (!isset($_SESSION) && !utils::IsModeCLI())
|
||||
{
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
session_start();
|
||||
|
||||
@@ -52,7 +52,7 @@ abstract class ValueSetDefinition
|
||||
}
|
||||
|
||||
|
||||
public function GetValues($aArgs, $sContains = '')
|
||||
public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
|
||||
{
|
||||
if (!$this->m_bIsLoaded)
|
||||
{
|
||||
@@ -93,12 +93,16 @@ abstract class ValueSetDefinition
|
||||
class ValueSetObjects extends ValueSetDefinition
|
||||
{
|
||||
protected $m_sContains;
|
||||
protected $m_sOperation;
|
||||
protected $m_sFilterExpr; // in OQL
|
||||
protected $m_sValueAttCode;
|
||||
protected $m_aOrderBy;
|
||||
protected $m_aExtraConditions;
|
||||
private $m_bAllowAllData;
|
||||
private $m_aModifierProperties;
|
||||
private $m_bSort;
|
||||
private $m_iLimit;
|
||||
|
||||
|
||||
/**
|
||||
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
@@ -106,12 +110,15 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
|
||||
{
|
||||
$this->m_sContains = '';
|
||||
$this->m_sOperation = '';
|
||||
$this->m_sFilterExpr = $sFilterExp;
|
||||
$this->m_sValueAttCode = $sValueAttCode;
|
||||
$this->m_aOrderBy = $aOrderBy;
|
||||
$this->m_bAllowAllData = $bAllowAllData;
|
||||
$this->m_aModifierProperties = $aModifierProperties;
|
||||
$this->m_aExtraConditions = array();
|
||||
$this->m_bSort = true;
|
||||
$this->m_iLimit = 0;
|
||||
}
|
||||
|
||||
public function SetModifierProperty($sPluginClass, $sProperty, $value)
|
||||
@@ -163,11 +170,20 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
|
||||
}
|
||||
|
||||
public function GetValues($aArgs, $sContains = '')
|
||||
/**
|
||||
* @param $aArgs
|
||||
* @param string $sContains
|
||||
* @param string $sOperation for the values @see self::LoadValues()
|
||||
*
|
||||
* @return array
|
||||
* @throws CoreException
|
||||
* @throws OQLException
|
||||
*/
|
||||
public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
|
||||
{
|
||||
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains))
|
||||
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation))
|
||||
{
|
||||
$this->LoadValues($aArgs, $sContains);
|
||||
$this->LoadValues($aArgs, $sContains, $sOperation);
|
||||
$this->m_bIsLoaded = true;
|
||||
}
|
||||
// The results are already filtered and sorted (on friendly name)
|
||||
@@ -175,9 +191,19 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
protected function LoadValues($aArgs, $sContains = '')
|
||||
/**
|
||||
* @param $aArgs
|
||||
* @param string $sContains
|
||||
* @param string $sOperation 'contains' or 'equals_start_with'
|
||||
*
|
||||
* @return bool
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
protected function LoadValues($aArgs, $sContains = '', $sOperation = 'contains')
|
||||
{
|
||||
$this->m_sContains = $sContains;
|
||||
$this->m_sOperation = $sOperation;
|
||||
|
||||
$this->m_aValues = array();
|
||||
|
||||
@@ -202,12 +228,72 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
}
|
||||
}
|
||||
|
||||
$oExpression = DBObjectSearch::GetPolymorphicExpression($oFilter->GetClass(), 'friendlyname');
|
||||
$aFields = $oExpression->ListRequiredFields();
|
||||
$sClass = $oFilter->GetClass();
|
||||
foreach($aFields as $sField)
|
||||
{
|
||||
$aFieldItems = explode('.', $sField);
|
||||
if ($aFieldItems[0] != $sClass)
|
||||
{
|
||||
$sOperation = 'contains';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($sOperation)
|
||||
{
|
||||
case 'equals':
|
||||
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
|
||||
$sClassAlias = $oFilter->GetClassAlias();
|
||||
$aFilters = array();
|
||||
$oValueExpr = new ScalarExpression($sContains);
|
||||
foreach($aAttributes as $sAttribute)
|
||||
{
|
||||
$oNewFilter = $oFilter->DeepClone();
|
||||
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
|
||||
$oCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr);
|
||||
$oNewFilter->AddConditionExpression($oCondition);
|
||||
$aFilters[] = $oNewFilter;
|
||||
}
|
||||
// Unions are much faster than OR conditions
|
||||
$oFilter = new DBUnionSearch($aFilters);
|
||||
break;
|
||||
case 'start_with':
|
||||
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
|
||||
$sClassAlias = $oFilter->GetClassAlias();
|
||||
$aFilters = array();
|
||||
$oValueExpr = new ScalarExpression($sContains.'%');
|
||||
foreach($aAttributes as $sAttribute)
|
||||
{
|
||||
$oNewFilter = $oFilter->DeepClone();
|
||||
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
|
||||
$oCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
|
||||
$oNewFilter->AddConditionExpression($oCondition);
|
||||
$aFilters[] = $oNewFilter;
|
||||
}
|
||||
// Unions are much faster than OR conditions
|
||||
$oFilter = new DBUnionSearch($aFilters);
|
||||
break;
|
||||
|
||||
default:
|
||||
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
|
||||
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
|
||||
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
|
||||
$oFilter->AddConditionExpression($oNewCondition);
|
||||
break;
|
||||
}
|
||||
|
||||
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
|
||||
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
|
||||
if (empty($this->m_sValueAttCode))
|
||||
{
|
||||
$aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode));
|
||||
}
|
||||
$oObjects->OptimizeColumnLoad($aAttToLoad);
|
||||
while ($oObject = $oObjects->Fetch())
|
||||
{
|
||||
if (empty($this->m_sValueAttCode))
|
||||
@@ -231,6 +317,22 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
{
|
||||
return $this->m_sFilterExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $iLimit
|
||||
*/
|
||||
public function SetLimit($iLimit)
|
||||
{
|
||||
$this->m_iLimit = $iLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $bSort
|
||||
*/
|
||||
public function SetSort($bSort)
|
||||
{
|
||||
$this->m_bSort = $bSort;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ class XMLBulkExport extends BulkExport
|
||||
$aClass2Attributes = array();
|
||||
foreach($aClasses as $sAlias => $sClassName)
|
||||
{
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
|
||||
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) != UR_ALLOWED_NO)
|
||||
{
|
||||
$aAuthorizedClasses[$sAlias] = $sClassName;
|
||||
$aAttributes = array();
|
||||
|
||||
@@ -1,8 +1,53 @@
|
||||
$highlight-color: #E87C1E;
|
||||
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
|
||||
$version: "v2.5.2";
|
||||
|
||||
// Base colors
|
||||
$gray-base: #000 !default;
|
||||
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
|
||||
$gray-dark: #444 !default;
|
||||
$gray: #777 !default;
|
||||
$gray-light: #808080 !default;
|
||||
$gray-lighter: #ddd !default;
|
||||
$gray-extra-light: #F1F1F1 !default;
|
||||
|
||||
$white: #FFFFFF !default;
|
||||
|
||||
$combodo-orange: #EA7D1E !default;
|
||||
$combodo-dark-gray: #585653 !default;
|
||||
|
||||
$combodo-orange-dark: darken($combodo-orange, 13.8%) !default;
|
||||
$combodo-orange-darker: darken($combodo-orange, 18%) !default;
|
||||
$combodo-dark-gray-dark: darken($combodo-dark-gray, 13.5%) !default;
|
||||
$combodo-dark-gray-darker: darken($combodo-dark-gray, 18%) !default;
|
||||
|
||||
// Vars
|
||||
$highlight-color: $combodo-orange;
|
||||
$grey-color: #555555;
|
||||
$complement-color: #1c94c4;
|
||||
$complement-light: #d6e8ef;
|
||||
$frame-background-color: #F1F1F1;
|
||||
$frame-background-color: $gray-extra-light;
|
||||
$text-color: #000;
|
||||
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
|
||||
$version: "v2.4.0-beta";
|
||||
$box-radius: 0px;
|
||||
$box-shadow-regular: 0 1px 1px rgba(0, 0, 0, 0.15);
|
||||
// - Boxes
|
||||
//$search-criteria-box-color: #2D2D2D;
|
||||
//$search-criteria-box-bg-color: #f0f3f5;
|
||||
//$search-criteria-box-border-color: #3f7294;
|
||||
//$search-criteria-box-border: 1px solid $search-criteria-box-border-color;
|
||||
//$search-criteria-box-radius: 1px;
|
||||
//
|
||||
$search-criteria-box-color: #2D2D2D;
|
||||
$search-criteria-box-picto-color: #E87C1E;
|
||||
$search-criteria-box-bg-color: #EEEEEE;
|
||||
$search-criteria-box-hover-color: $white;
|
||||
$search-criteria-box-border-color: #CCCCCC;
|
||||
$search-criteria-box-border: 1px solid $search-criteria-box-border-color;
|
||||
$search-criteria-box-radius: 1px;
|
||||
//
|
||||
$search-add-criteria-box-color: $search-criteria-box-color;
|
||||
$search-add-criteria-box-bg-color: $white;
|
||||
$search-add-criteria-box-hover-color: $gray-extra-light;
|
||||
//
|
||||
$search-button-box-color: $combodo-orange;
|
||||
$search-button-box-bg-color: $white;
|
||||
$search-button-box-bg-hover-color: $gray-extra-light;
|
||||
|
||||
BIN
css/font-combodo/combodo-webfont.ttf
Normal file
BIN
css/font-combodo/combodo-webfont.ttf
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user