mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-14 07:54:10 +01:00
Compare commits
1249 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eadb11ce39 | ||
|
|
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 | ||
|
|
48f6635917 | ||
|
|
d6707743a9 | ||
|
|
6f474686ad | ||
|
|
affd8ea8a6 | ||
|
|
abd2748a09 | ||
|
|
6a1c9b9f26 | ||
|
|
ac04aa3359 | ||
|
|
051d68c902 | ||
|
|
ce81f7abb6 | ||
|
|
03fbde3403 | ||
|
|
59ddab9f94 | ||
|
|
225cbb450e | ||
|
|
7cace73844 | ||
|
|
2562d33997 | ||
|
|
a1e5b32e49 | ||
|
|
0c5df83a95 | ||
|
|
cf79456ff3 | ||
|
|
a0a378a91a | ||
|
|
9cf42f6cc8 | ||
|
|
c022d12a0a | ||
|
|
3d72060bf5 | ||
|
|
43709576c0 | ||
|
|
8ca6610e75 | ||
|
|
b02d347541 | ||
|
|
a39789cf89 | ||
|
|
58534dca39 | ||
|
|
538e9c11e8 | ||
|
|
cba6e8d8a3 | ||
|
|
fd9fe85ef3 | ||
|
|
7ed3e10d0f | ||
|
|
fe98b850d1 | ||
|
|
b1494d0dd9 | ||
|
|
0655904683 | ||
|
|
bad5aff764 | ||
|
|
440f2639b5 | ||
|
|
5f2245595f | ||
|
|
03db4e7a14 | ||
|
|
04030e07e3 | ||
|
|
80832877eb | ||
|
|
aa63847502 | ||
|
|
4171c5c35f | ||
|
|
8e06a95502 | ||
|
|
1c84934777 | ||
|
|
0294f50339 | ||
|
|
33098943a1 | ||
|
|
b953a6ab3e | ||
|
|
7287cab6f4 | ||
|
|
df4cad3ff1 | ||
|
|
66b6206daf | ||
|
|
6f40459215 | ||
|
|
24430e630f | ||
|
|
3420db26a5 | ||
|
|
c27cafb02f | ||
|
|
09a2dbc185 | ||
|
|
015c5b7a96 | ||
|
|
4710ae6eb7 | ||
|
|
c6b98dd5c1 | ||
|
|
61d3a4e48f | ||
|
|
be4c1e2c1c | ||
|
|
6f499dd36e | ||
|
|
03661158f1 | ||
|
|
b9866008b2 | ||
|
|
2557d6e7d5 | ||
|
|
79582b55a4 | ||
|
|
46b5293867 | ||
|
|
54ca6ad3d9 | ||
|
|
2ab8161182 | ||
|
|
3c7f5db5e7 | ||
|
|
e72bc65df8 | ||
|
|
c039414adc | ||
|
|
d45cbb36f7 | ||
|
|
ba5dd94240 | ||
|
|
fe84cbd978 | ||
|
|
f8ddcc7b70 | ||
|
|
c0d7737872 | ||
|
|
97e6c53add | ||
|
|
68ac5d7300 | ||
|
|
e0eebc0b4e | ||
|
|
fb28de971c | ||
|
|
ae637c7c96 | ||
|
|
6bc24b6064 | ||
|
|
db60dfb707 | ||
|
|
ee2aa67959 | ||
|
|
eff42f5d3b | ||
|
|
3a1810bc42 | ||
|
|
1f1ab2b10c | ||
|
|
d0d9b1ce50 | ||
|
|
307145502c | ||
|
|
5c84703cf0 | ||
|
|
cb2745be24 | ||
|
|
b613b9b302 | ||
|
|
624f6bcfc5 | ||
|
|
ce2f1edaac | ||
|
|
140efb4240 | ||
|
|
e3847ac24b | ||
|
|
b583bd2edc | ||
|
|
c654a2067d | ||
|
|
3531d5c5b7 | ||
|
|
e31fa066fc | ||
|
|
bc476295cb | ||
|
|
98781fac6d | ||
|
|
efeee395d1 | ||
|
|
b17505f86e | ||
|
|
f89dd77dbc | ||
|
|
f8b6fb51c2 | ||
|
|
d80c2293dc | ||
|
|
3cecdeff1c | ||
|
|
3f819eaa19 | ||
|
|
01de060093 | ||
|
|
7c39a8baf1 | ||
|
|
ebcaaa089a | ||
|
|
121635d636 | ||
|
|
68885496dd | ||
|
|
9a56c3acfd | ||
|
|
a0259636b1 | ||
|
|
0844beca79 | ||
|
|
dbe3e94d5c | ||
|
|
9a43083b3b | ||
|
|
c6e472b98b | ||
|
|
0834e36b38 | ||
|
|
f7cf825975 | ||
|
|
189c03dfea | ||
|
|
990be7a105 | ||
|
|
813c80499c | ||
|
|
495cedc04f | ||
|
|
cbf2919dcd | ||
|
|
7cbdfaa5d4 | ||
|
|
374ce20d0c | ||
|
|
1dbc2051cc | ||
|
|
7e38d4be50 | ||
|
|
8451ffdfb4 | ||
|
|
c2b9716e28 | ||
|
|
b15621639b | ||
|
|
4ca998ce91 | ||
|
|
71ec3da4ac | ||
|
|
43b8522b85 | ||
|
|
8b820ce403 | ||
|
|
4301a5d39d | ||
|
|
51519a3659 | ||
|
|
2693e81bad | ||
|
|
eca746f558 | ||
|
|
01865ed7f9 | ||
|
|
14dfe04714 | ||
|
|
befa0b4429 | ||
|
|
68ff589f9c | ||
|
|
04e1f32860 | ||
|
|
636356f479 | ||
|
|
313ea72017 | ||
|
|
98b4a0178f | ||
|
|
602be73d0b | ||
|
|
9bfc9a0a76 | ||
|
|
7e6a040983 | ||
|
|
905d47cab5 | ||
|
|
2118a5da71 | ||
|
|
c2c0221535 | ||
|
|
ba0a9709f4 | ||
|
|
80121b89c3 | ||
|
|
3b48428897 | ||
|
|
018d6a98e9 | ||
|
|
522108dc69 | ||
|
|
5451ced894 | ||
|
|
944c24e18f | ||
|
|
248f8d6fd4 | ||
|
|
ebe467b77a | ||
|
|
e9b7ccd475 | ||
|
|
b213f2baea | ||
|
|
50970810d2 | ||
|
|
17fbc504e2 | ||
|
|
237980097d | ||
|
|
44b53e40a0 | ||
|
|
6c6ad0a45b | ||
|
|
623fa8ec63 | ||
|
|
02c79fd0a2 | ||
|
|
05bb797768 | ||
|
|
096cfdc529 | ||
|
|
5354b0b32b | ||
|
|
88ec528071 | ||
|
|
5584d42813 | ||
|
|
b696b140b2 | ||
|
|
1de6ac1765 | ||
|
|
d2d895fdf5 | ||
|
|
a8ad3004ea | ||
|
|
b707db9364 | ||
|
|
36c53249a0 | ||
|
|
f9ed88a084 | ||
|
|
294964b227 | ||
|
|
8a6fba1981 | ||
|
|
1fe0ce5640 | ||
|
|
7bbe907edc | ||
|
|
6f7d364826 | ||
|
|
ea2681e08c | ||
|
|
c66728e478 | ||
|
|
ba9c6bd8b3 | ||
|
|
b23b468c0f | ||
|
|
f5144c2bb1 | ||
|
|
ee95dd2480 | ||
|
|
c1805ce47f | ||
|
|
23556e3a43 | ||
|
|
e70e1de75e | ||
|
|
091b989715 | ||
|
|
b238283104 | ||
|
|
de2eed5187 | ||
|
|
8e046cafda | ||
|
|
f02df401ff | ||
|
|
28eba2512a | ||
|
|
e3931274ae | ||
|
|
a55245d203 | ||
|
|
7b0acbdad3 | ||
|
|
9cc583c47b | ||
|
|
fa2fd6dcdf | ||
|
|
9d242e1623 | ||
|
|
ec6a8537e9 | ||
|
|
402e95b9e2 | ||
|
|
2fcf50bb88 | ||
|
|
2e8c629195 | ||
|
|
9c5b3818eb | ||
|
|
c9bb27a3ff | ||
|
|
f96a2092e7 | ||
|
|
5dafb92dd3 | ||
|
|
63f0a0ef11 | ||
|
|
bb9236deae | ||
|
|
1a66a6491a | ||
|
|
1528d85f5f | ||
|
|
3773a88cc1 | ||
|
|
856a73fb87 | ||
|
|
d0bc9a0bb8 | ||
|
|
751b7c8560 | ||
|
|
4fccae157b | ||
|
|
85dc7c9452 | ||
|
|
1cf25e49ae | ||
|
|
ab6cd49fcb | ||
|
|
5411e60c81 | ||
|
|
15da430459 | ||
|
|
5adb0fd556 | ||
|
|
2876d7e08c | ||
|
|
59c3f565a3 | ||
|
|
86191b36d8 | ||
|
|
b737252076 | ||
|
|
e688d04511 | ||
|
|
76fd5c0b5f | ||
|
|
d944f386ce | ||
|
|
9f9fd8550f | ||
|
|
772954711a | ||
|
|
3644553886 | ||
|
|
c073611597 | ||
|
|
802d20d554 | ||
|
|
38c12104b3 | ||
|
|
399662ef99 | ||
|
|
3daf4c62db | ||
|
|
9937f62f9d | ||
|
|
cf17e197ce | ||
|
|
2f8b5e5eeb | ||
|
|
d22fb83443 | ||
|
|
af093ba86a | ||
|
|
020f47ab2c | ||
|
|
8f0095f751 | ||
|
|
b7fc2e4012 | ||
|
|
1dcf830141 | ||
|
|
a10cdaba74 | ||
|
|
435a177283 | ||
|
|
d22d657886 | ||
|
|
1da5618204 | ||
|
|
318c069f3d | ||
|
|
d0267f60ae | ||
|
|
ea3c7703c4 | ||
|
|
95e04178ea | ||
|
|
cf0792cd64 | ||
|
|
e41a8833e2 | ||
|
|
0b2ce4289d | ||
|
|
a13270ef91 | ||
|
|
64ca121f5b | ||
|
|
0a5708272d | ||
|
|
fec15ffe66 | ||
|
|
0b0340bf21 | ||
|
|
cf716ce2a3 | ||
|
|
5ebcb41224 | ||
|
|
2d9c0e16b9 | ||
|
|
20e0ab3d6e | ||
|
|
d94ccc441a | ||
|
|
fdff9e048b | ||
|
|
64e1d7e276 | ||
|
|
5f82c78dc7 | ||
|
|
b3a0c6119e | ||
|
|
342ac0444c | ||
|
|
d9469360fe | ||
|
|
0df420cd17 | ||
|
|
ab44522016 | ||
|
|
bc875653d2 | ||
|
|
ee41204d6c | ||
|
|
c4d1113bb8 | ||
|
|
6582569150 | ||
|
|
69d0ceaf5b | ||
|
|
60165d5216 | ||
|
|
76ca7dc9e8 | ||
|
|
fcc5342775 | ||
|
|
b4534f455a | ||
|
|
0d3203476c | ||
|
|
9fa6157c37 | ||
|
|
2ee674f11b | ||
|
|
820d7108e4 | ||
|
|
9a6a562ed2 | ||
|
|
f3f9e98aa4 | ||
|
|
fc375da128 | ||
|
|
172ceba464 | ||
|
|
ecae7ea983 | ||
|
|
a204a786c0 | ||
|
|
79a2ab8abd | ||
|
|
af13b42eab | ||
|
|
75721091f2 | ||
|
|
6e327e245b | ||
|
|
6bfead405d | ||
|
|
a4f5620076 | ||
|
|
134a427901 | ||
|
|
27c3facb2b | ||
|
|
fa9848d1be | ||
|
|
1afd7d2ae4 | ||
|
|
c622efe95c | ||
|
|
c0949421ad | ||
|
|
0e21edcc77 | ||
|
|
df85186407 | ||
|
|
403ecf7fba | ||
|
|
d143f0880b | ||
|
|
c185e1fc4f | ||
|
|
68c180f7eb | ||
|
|
c903fc2246 | ||
|
|
7d5898f302 | ||
|
|
a4bdd3aaf4 | ||
|
|
5bae9deecc | ||
|
|
ffbd666aca | ||
|
|
4ec3aeec92 | ||
|
|
c5a00a1bf1 | ||
|
|
1b1e91f0dc | ||
|
|
21fc272674 | ||
|
|
5716c11450 | ||
|
|
f7e77e9fa6 | ||
|
|
ad8c3db6b4 | ||
|
|
d5c3b8d8e2 | ||
|
|
6ac6aea29f | ||
|
|
7ce06c0797 | ||
|
|
53bf1f424a | ||
|
|
b793cded34 | ||
|
|
47ec6d4917 | ||
|
|
e586ba8d6e | ||
|
|
8a913a18cf | ||
|
|
a09e579451 | ||
|
|
79c5dc2ce2 | ||
|
|
95d7bc5319 | ||
|
|
d3f5d05063 | ||
|
|
b30a35ceb5 | ||
|
|
fac22c9729 | ||
|
|
44e329c38d | ||
|
|
e48ad8cb61 | ||
|
|
359dc73526 | ||
|
|
8169e81e9a | ||
|
|
f62f087bcc | ||
|
|
4eb0b3086d | ||
|
|
1c81650572 | ||
|
|
59e3367da8 | ||
|
|
2b4b0fed83 | ||
|
|
fe6ae6f2eb | ||
|
|
79d994acf7 | ||
|
|
9dbbb8e012 | ||
|
|
17fafbf85b | ||
|
|
f3a6455ed8 | ||
|
|
5336d9e965 | ||
|
|
97d11ba910 | ||
|
|
2fa68a3abc | ||
|
|
a88365ca49 | ||
|
|
f3053c39c2 | ||
|
|
c7ac39b86a | ||
|
|
88eece7188 | ||
|
|
b7d101cdfb | ||
|
|
56ade2b44c | ||
|
|
2a38eb757d | ||
|
|
954ba60611 | ||
|
|
f9a984f3e4 | ||
|
|
8fd9acb2ce | ||
|
|
915a88afd4 | ||
|
|
c755b66d8c | ||
|
|
2f8dc0fa0f | ||
|
|
a34747f893 | ||
|
|
4dd6c813b1 | ||
|
|
8e96094977 | ||
|
|
dd41ebf861 | ||
|
|
f732df751c | ||
|
|
acfab8fb63 | ||
|
|
23193153c6 | ||
|
|
a908dcd752 | ||
|
|
b8a80cb267 | ||
|
|
ad1412c7e2 | ||
|
|
4bf51515be | ||
|
|
05f97dd75f | ||
|
|
90cab29a3c | ||
|
|
445b488603 | ||
|
|
7141e0f4b0 | ||
|
|
ffc756e4a4 | ||
|
|
8bed267feb | ||
|
|
13933e488c | ||
|
|
d48f76e965 | ||
|
|
a025c95054 | ||
|
|
b2fa9c468f | ||
|
|
8feef7fd8a | ||
|
|
8330f3d498 | ||
|
|
88e7bd225c | ||
|
|
1b0f818c4b | ||
|
|
32dc1225ca | ||
|
|
2b35cf5047 | ||
|
|
df66e28545 | ||
|
|
0ab060c958 | ||
|
|
dfceef4ca6 | ||
|
|
49f82e6377 | ||
|
|
c5957f1146 | ||
|
|
b357e7d7d6 | ||
|
|
e7342b0eb8 | ||
|
|
f33f4e3406 | ||
|
|
3d57c720e0 | ||
|
|
ef5d156b98 | ||
|
|
8722447f2f | ||
|
|
e6047dcbf5 | ||
|
|
df1d10f1cb | ||
|
|
d1dd60f928 | ||
|
|
7bea59fea1 | ||
|
|
17703ce572 | ||
|
|
6d556249aa | ||
|
|
16b5db448b | ||
|
|
8ef7f073b3 | ||
|
|
22c647e3f8 | ||
|
|
bf94dfa894 | ||
|
|
43d3cfefb5 | ||
|
|
e0855093ea | ||
|
|
c147062aaa | ||
|
|
480c2fab04 | ||
|
|
53fb619da1 | ||
|
|
c0a7bbaa72 | ||
|
|
12b27778f5 | ||
|
|
babcd6a92a | ||
|
|
d80d24c348 | ||
|
|
2c78a91a00 | ||
|
|
109e5dfe2c | ||
|
|
93ff327b54 | ||
|
|
359c188089 | ||
|
|
6682eafb4d |
@@ -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)
|
||||
{
|
||||
@@ -183,7 +180,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
if (!$bEditMode)
|
||||
{
|
||||
$oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix'));
|
||||
$this->DoShowGrantSumary($oPage);
|
||||
$this->DoShowGrantSumary($oPage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -412,11 +504,15 @@ 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;
|
||||
@@ -613,11 +613,15 @@ 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)
|
||||
|
||||
@@ -79,7 +79,7 @@ class Html2Text {
|
||||
// replace with spaces
|
||||
|
||||
$html = str_replace(" ", " ", $html);
|
||||
$html = mb_str_replace("\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
|
||||
$html = mb_str_replace("\xc2\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
|
||||
|
||||
$html = static::fixNewlines($html);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
* Simple web page with no includes, header or fancy formatting, useful to
|
||||
* generate HTML fragments when called by an AJAX method
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -53,7 +53,9 @@ class ajax_page extends WebPage implements iTabbedPage
|
||||
$this->sContentType = 'text/html';
|
||||
$this->sContentDisposition = 'inline';
|
||||
$this->m_sMenu = "";
|
||||
}
|
||||
|
||||
utils::InitArchiveMode();
|
||||
}
|
||||
|
||||
public function AddTabContainer($sTabContainer, $sPrefix = '')
|
||||
{
|
||||
@@ -202,32 +204,16 @@ EOF
|
||||
// Render the tabs in the page (if any)
|
||||
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
|
||||
|
||||
// Additional UI widgets to be activated inside the ajax fragment ??
|
||||
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
|
||||
// Additional UI widgets to be activated inside the ajax fragment
|
||||
// Important: Testing the content type is not enough because some ajax handlers have not correctly positionned the flag (e.g json response corrupted by the script)
|
||||
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
|
||||
{
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
PrepareWidgets();
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
$s_captured_output = $this->ob_get_clean_safe();
|
||||
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
|
||||
{
|
||||
@@ -258,9 +244,18 @@ EOF
|
||||
//echo $this->s_deferred_content;
|
||||
if (count($this->a_scripts) > 0)
|
||||
{
|
||||
echo "<script type=\"text/javascript\">\n";
|
||||
echo implode("\n", $this->a_scripts);
|
||||
echo "\n</script>\n";
|
||||
echo "<script type=\"text/javascript\">\n";
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
require_once(APPROOT.'/application/applicationcontext.class.inc.php');
|
||||
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
require_once(APPROOT.'/application/sqlblock.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.category.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.rule.class.inc.php');
|
||||
require_once(APPROOT.'/application/query.class.inc.php');
|
||||
|
||||
@@ -1,362 +1,362 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Class ApplicationContext
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT."/application/utils.inc.php");
|
||||
|
||||
/**
|
||||
* Interface for directing end-users to the relevant application
|
||||
*/
|
||||
interface iDBObjectURLMaker
|
||||
{
|
||||
public static function MakeObjectURL($sClass, $iId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct end-users to the standard iTop application: UI.php
|
||||
*/
|
||||
class iTopStandardURLMaker implements iDBObjectURLMaker
|
||||
{
|
||||
public static function MakeObjectURL($sClass, $iId)
|
||||
{
|
||||
$sPage = DBObject::ComputeStandardUIPage($sClass);
|
||||
$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$sUrl = "{$sAbsoluteUrl}pages/$sPage?operation=details&class=$sClass&id=$iId";
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct end-users to the standard Portal application
|
||||
*/
|
||||
class PortalURLMaker implements iDBObjectURLMaker
|
||||
{
|
||||
public static function MakeObjectURL($sClass, $iId)
|
||||
{
|
||||
$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$sUrl = "{$sAbsoluteUrl}portal/index.php?operation=details&class=$sClass&id=$iId";
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper class to store and manipulate the parameters that make the application's context
|
||||
*
|
||||
* Usage:
|
||||
* 1) Build the application's context by constructing the object
|
||||
* (the object will read some of the page's parameters)
|
||||
*
|
||||
* 2) Add these parameters to hyperlinks or to forms using the helper, functions
|
||||
* GetForLink(), GetForForm() or GetAsHash()
|
||||
*/
|
||||
class ApplicationContext
|
||||
{
|
||||
protected $aNames;
|
||||
protected $aValues;
|
||||
protected static $aDefaultValues; // Cache shared among all instances
|
||||
|
||||
public function __construct($bReadContext = true)
|
||||
{
|
||||
$this->aNames = array(
|
||||
'org_id', 'menu'
|
||||
);
|
||||
if ($bReadContext)
|
||||
{
|
||||
$this->ReadContext();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the context directly in the PHP parameters (either POST or GET)
|
||||
* return nothing
|
||||
*/
|
||||
protected function ReadContext()
|
||||
{
|
||||
if (!isset(self::$aDefaultValues))
|
||||
{
|
||||
self::$aDefaultValues = array();
|
||||
$aContext = utils::ReadParam('c', array(), false, 'context_param');
|
||||
foreach($this->aNames as $sName)
|
||||
{
|
||||
$sValue = isset($aContext[$sName]) ? $aContext[$sName] : '';
|
||||
// TO DO: check if some of the context parameters are mandatory (or have default values)
|
||||
if (!empty($sValue))
|
||||
{
|
||||
self::$aDefaultValues[$sName] = $sValue;
|
||||
}
|
||||
// Hmm, there must be a better (more generic) way to handle the case below:
|
||||
// When there is only one possible (allowed) organization, the context must be
|
||||
// fixed to this org
|
||||
if ($sName == 'org_id')
|
||||
{
|
||||
if (MetaModel::IsValidClass('Organization'))
|
||||
{
|
||||
$oSearchFilter = new DBObjectSearch('Organization');
|
||||
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
|
||||
$oSet = new CMDBObjectSet($oSearchFilter);
|
||||
$iCount = $oSet->Count();
|
||||
if ($iCount == 1)
|
||||
{
|
||||
// Only one possible value for org_id, set it in the context
|
||||
$oOrg = $oSet->Fetch();
|
||||
self::$aDefaultValues[$sName] = $oOrg->GetKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->aValues = self::$aDefaultValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value for the given parameter
|
||||
* @param string $sParamName Name of the parameter to read
|
||||
* @return mixed The value for this parameter
|
||||
*/
|
||||
public function GetCurrentValue($sParamName, $defaultValue = '')
|
||||
{
|
||||
if (isset($this->aValues[$sParamName]))
|
||||
{
|
||||
return $this->aValues[$sParamName];
|
||||
}
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context as string with the format name1=value1&name2=value2....
|
||||
* return string The context as a string to be appended to an href property
|
||||
*/
|
||||
public function GetForLink()
|
||||
{
|
||||
$aParams = array();
|
||||
foreach($this->aValues as $sName => $sValue)
|
||||
{
|
||||
$aParams[] = "c[$sName]".'='.urlencode($sValue);
|
||||
}
|
||||
return implode("&", $aParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context as sequence of input tags to be inserted inside a <form> tag
|
||||
* return string The context as a sequence of <input type="hidden" /> tags
|
||||
*/
|
||||
public function GetForForm()
|
||||
{
|
||||
$sContext = "";
|
||||
foreach($this->aValues as $sName => $sValue)
|
||||
{
|
||||
$sContext .= "<input type=\"hidden\" name=\"c[$sName]\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
|
||||
}
|
||||
return $sContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context as a hash array 'parameter_name' => value
|
||||
* return array The context information
|
||||
*/
|
||||
public function GetAsHash()
|
||||
{
|
||||
$aReturn = array();
|
||||
foreach($this->aValues as $sName => $sValue)
|
||||
{
|
||||
$aReturn["c[$sName]"] = $sValue;
|
||||
}
|
||||
return $aReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the context parameters NAMEs
|
||||
* @return array The list of context parameters
|
||||
*/
|
||||
public function GetNames()
|
||||
{
|
||||
return $this->aNames;
|
||||
}
|
||||
/**
|
||||
* Removes the specified parameter from the context, for example when the same parameter
|
||||
* is already a search parameter
|
||||
* @param string $sParamName Name of the parameter to remove
|
||||
* @return none
|
||||
*/
|
||||
public function Reset($sParamName)
|
||||
{
|
||||
if (isset($this->aValues[$sParamName]))
|
||||
{
|
||||
unset($this->aValues[$sParamName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the given object with the default values provided by the context
|
||||
*/
|
||||
public function InitObjectFromContext(DBObject &$oObj)
|
||||
{
|
||||
$sClass = get_class($oObj);
|
||||
foreach($this->GetNames() as $key)
|
||||
{
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
|
||||
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef->IsWritable())
|
||||
{
|
||||
$value = $this->GetCurrentValue($key, null);
|
||||
if (!is_null($value))
|
||||
{
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static $m_sUrlMakerClass = null;
|
||||
|
||||
/**
|
||||
* Set the current application url provider
|
||||
* @param sClass string Class implementing iDBObjectURLMaker
|
||||
* @return void
|
||||
*/
|
||||
public static function SetUrlMakerClass($sClass = 'iTopStandardURLMaker')
|
||||
{
|
||||
$sPrevious = self::GetUrlMakerClass();
|
||||
|
||||
self::$m_sUrlMakerClass = $sClass;
|
||||
$_SESSION['UrlMakerClass'] = $sClass;
|
||||
|
||||
return $sPrevious;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current application url provider
|
||||
* @return string the name of the class
|
||||
*/
|
||||
public static function GetUrlMakerClass()
|
||||
{
|
||||
if (is_null(self::$m_sUrlMakerClass))
|
||||
{
|
||||
if (isset($_SESSION['UrlMakerClass']))
|
||||
{
|
||||
self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass'];
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_sUrlMakerClass = 'iTopStandardURLMaker';
|
||||
}
|
||||
}
|
||||
return self::$m_sUrlMakerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current application url provider
|
||||
* @return string the name of the class
|
||||
*/
|
||||
public static function MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass = null, $bWithNavigationContext = true)
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
if (is_null($sUrlMakerClass))
|
||||
{
|
||||
$sUrlMakerClass = self::GetUrlMakerClass();
|
||||
}
|
||||
$sUrl = call_user_func(array($sUrlMakerClass, 'MakeObjectUrl'), $sObjClass, $sObjKey);
|
||||
if (strlen($sUrl) > 0)
|
||||
{
|
||||
if ($bWithNavigationContext)
|
||||
{
|
||||
return $sUrl."&".$oAppContext->GetForLink();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
protected static $m_aPluginProperties = null;
|
||||
|
||||
/**
|
||||
* Load plugin properties for the current session
|
||||
* @return void
|
||||
*/
|
||||
protected static function LoadPluginProperties()
|
||||
{
|
||||
if (isset($_SESSION['PluginProperties']))
|
||||
{
|
||||
self::$m_aPluginProperties = $_SESSION['PluginProperties'];
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_aPluginProperties = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set plugin properties
|
||||
* @param sPluginClass string Class implementing any plugin interface
|
||||
* @param sProperty string Name of the property
|
||||
* @param value scalar Value (numeric or string)
|
||||
* @return void
|
||||
*/
|
||||
public static function SetPluginProperty($sPluginClass, $sProperty, $value)
|
||||
{
|
||||
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
|
||||
|
||||
self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
|
||||
$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin properties
|
||||
* @param sPluginClass string Class implementing any plugin interface
|
||||
* @return array of sProperty=>value pairs
|
||||
*/
|
||||
public static function GetPluginProperties($sPluginClass)
|
||||
{
|
||||
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
|
||||
|
||||
if (array_key_exists($sPluginClass, self::$m_aPluginProperties))
|
||||
{
|
||||
return self::$m_aPluginProperties[$sPluginClass];
|
||||
}
|
||||
else
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Class ApplicationContext
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT."/application/utils.inc.php");
|
||||
|
||||
/**
|
||||
* Interface for directing end-users to the relevant application
|
||||
*/
|
||||
interface iDBObjectURLMaker
|
||||
{
|
||||
public static function MakeObjectURL($sClass, $iId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct end-users to the standard iTop application: UI.php
|
||||
*/
|
||||
class iTopStandardURLMaker implements iDBObjectURLMaker
|
||||
{
|
||||
public static function MakeObjectURL($sClass, $iId)
|
||||
{
|
||||
$sPage = DBObject::ComputeStandardUIPage($sClass);
|
||||
$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$sUrl = "{$sAbsoluteUrl}pages/$sPage?operation=details&class=$sClass&id=$iId";
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct end-users to the standard Portal application
|
||||
*/
|
||||
class PortalURLMaker implements iDBObjectURLMaker
|
||||
{
|
||||
public static function MakeObjectURL($sClass, $iId)
|
||||
{
|
||||
$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$sUrl = "{$sAbsoluteUrl}portal/index.php?operation=details&class=$sClass&id=$iId";
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper class to store and manipulate the parameters that make the application's context
|
||||
*
|
||||
* Usage:
|
||||
* 1) Build the application's context by constructing the object
|
||||
* (the object will read some of the page's parameters)
|
||||
*
|
||||
* 2) Add these parameters to hyperlinks or to forms using the helper, functions
|
||||
* GetForLink(), GetForForm() or GetAsHash()
|
||||
*/
|
||||
class ApplicationContext
|
||||
{
|
||||
protected $aNames;
|
||||
protected $aValues;
|
||||
protected static $aDefaultValues; // Cache shared among all instances
|
||||
|
||||
public function __construct($bReadContext = true)
|
||||
{
|
||||
$this->aNames = array(
|
||||
'org_id', 'menu'
|
||||
);
|
||||
if ($bReadContext)
|
||||
{
|
||||
$this->ReadContext();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the context directly in the PHP parameters (either POST or GET)
|
||||
* return nothing
|
||||
*/
|
||||
protected function ReadContext()
|
||||
{
|
||||
if (!isset(self::$aDefaultValues))
|
||||
{
|
||||
self::$aDefaultValues = array();
|
||||
$aContext = utils::ReadParam('c', array(), false, 'context_param');
|
||||
foreach($this->aNames as $sName)
|
||||
{
|
||||
$sValue = isset($aContext[$sName]) ? $aContext[$sName] : '';
|
||||
// TO DO: check if some of the context parameters are mandatory (or have default values)
|
||||
if (!empty($sValue))
|
||||
{
|
||||
self::$aDefaultValues[$sName] = $sValue;
|
||||
}
|
||||
// Hmm, there must be a better (more generic) way to handle the case below:
|
||||
// When there is only one possible (allowed) organization, the context must be
|
||||
// fixed to this org
|
||||
if ($sName == 'org_id')
|
||||
{
|
||||
if (MetaModel::IsValidClass('Organization'))
|
||||
{
|
||||
$oSearchFilter = new DBObjectSearch('Organization');
|
||||
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
|
||||
$oSet = new CMDBObjectSet($oSearchFilter);
|
||||
$iCount = $oSet->CountWithLimit(2);
|
||||
if ($iCount == 1)
|
||||
{
|
||||
// Only one possible value for org_id, set it in the context
|
||||
$oOrg = $oSet->Fetch();
|
||||
self::$aDefaultValues[$sName] = $oOrg->GetKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->aValues = self::$aDefaultValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value for the given parameter
|
||||
* @param string $sParamName Name of the parameter to read
|
||||
* @return mixed The value for this parameter
|
||||
*/
|
||||
public function GetCurrentValue($sParamName, $defaultValue = '')
|
||||
{
|
||||
if (isset($this->aValues[$sParamName]))
|
||||
{
|
||||
return $this->aValues[$sParamName];
|
||||
}
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context as string with the format name1=value1&name2=value2....
|
||||
* return string The context as a string to be appended to an href property
|
||||
*/
|
||||
public function GetForLink()
|
||||
{
|
||||
$aParams = array();
|
||||
foreach($this->aValues as $sName => $sValue)
|
||||
{
|
||||
$aParams[] = "c[$sName]".'='.urlencode($sValue);
|
||||
}
|
||||
return implode("&", $aParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context as sequence of input tags to be inserted inside a <form> tag
|
||||
* return string The context as a sequence of <input type="hidden" /> tags
|
||||
*/
|
||||
public function GetForForm()
|
||||
{
|
||||
$sContext = "";
|
||||
foreach($this->aValues as $sName => $sValue)
|
||||
{
|
||||
$sContext .= "<input type=\"hidden\" name=\"c[$sName]\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
|
||||
}
|
||||
return $sContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context as a hash array 'parameter_name' => value
|
||||
* return array The context information
|
||||
*/
|
||||
public function GetAsHash()
|
||||
{
|
||||
$aReturn = array();
|
||||
foreach($this->aValues as $sName => $sValue)
|
||||
{
|
||||
$aReturn["c[$sName]"] = $sValue;
|
||||
}
|
||||
return $aReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the context parameters NAMEs
|
||||
* @return array The list of context parameters
|
||||
*/
|
||||
public function GetNames()
|
||||
{
|
||||
return $this->aNames;
|
||||
}
|
||||
/**
|
||||
* Removes the specified parameter from the context, for example when the same parameter
|
||||
* is already a search parameter
|
||||
* @param string $sParamName Name of the parameter to remove
|
||||
* @return none
|
||||
*/
|
||||
public function Reset($sParamName)
|
||||
{
|
||||
if (isset($this->aValues[$sParamName]))
|
||||
{
|
||||
unset($this->aValues[$sParamName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the given object with the default values provided by the context
|
||||
*/
|
||||
public function InitObjectFromContext(DBObject &$oObj)
|
||||
{
|
||||
$sClass = get_class($oObj);
|
||||
foreach($this->GetNames() as $key)
|
||||
{
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
|
||||
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef->IsWritable())
|
||||
{
|
||||
$value = $this->GetCurrentValue($key, null);
|
||||
if (!is_null($value))
|
||||
{
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static $m_sUrlMakerClass = null;
|
||||
|
||||
/**
|
||||
* Set the current application url provider
|
||||
* @param sClass string Class implementing iDBObjectURLMaker
|
||||
* @return void
|
||||
*/
|
||||
public static function SetUrlMakerClass($sClass = 'iTopStandardURLMaker')
|
||||
{
|
||||
$sPrevious = self::GetUrlMakerClass();
|
||||
|
||||
self::$m_sUrlMakerClass = $sClass;
|
||||
$_SESSION['UrlMakerClass'] = $sClass;
|
||||
|
||||
return $sPrevious;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current application url provider
|
||||
* @return string the name of the class
|
||||
*/
|
||||
public static function GetUrlMakerClass()
|
||||
{
|
||||
if (is_null(self::$m_sUrlMakerClass))
|
||||
{
|
||||
if (isset($_SESSION['UrlMakerClass']))
|
||||
{
|
||||
self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass'];
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_sUrlMakerClass = 'iTopStandardURLMaker';
|
||||
}
|
||||
}
|
||||
return self::$m_sUrlMakerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current application url provider
|
||||
* @return string the name of the class
|
||||
*/
|
||||
public static function MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass = null, $bWithNavigationContext = true)
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
if (is_null($sUrlMakerClass))
|
||||
{
|
||||
$sUrlMakerClass = self::GetUrlMakerClass();
|
||||
}
|
||||
$sUrl = call_user_func(array($sUrlMakerClass, 'MakeObjectUrl'), $sObjClass, $sObjKey);
|
||||
if (strlen($sUrl) > 0)
|
||||
{
|
||||
if ($bWithNavigationContext)
|
||||
{
|
||||
return $sUrl."&".$oAppContext->GetForLink();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
protected static $m_aPluginProperties = null;
|
||||
|
||||
/**
|
||||
* Load plugin properties for the current session
|
||||
* @return void
|
||||
*/
|
||||
protected static function LoadPluginProperties()
|
||||
{
|
||||
if (isset($_SESSION['PluginProperties']))
|
||||
{
|
||||
self::$m_aPluginProperties = $_SESSION['PluginProperties'];
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_aPluginProperties = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set plugin properties
|
||||
* @param sPluginClass string Class implementing any plugin interface
|
||||
* @param sProperty string Name of the property
|
||||
* @param value scalar Value (numeric or string)
|
||||
* @return void
|
||||
*/
|
||||
public static function SetPluginProperty($sPluginClass, $sProperty, $value)
|
||||
{
|
||||
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
|
||||
|
||||
self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
|
||||
$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin properties
|
||||
* @param sPluginClass string Class implementing any plugin interface
|
||||
* @return array of sProperty=>value pairs
|
||||
*/
|
||||
public static function GetPluginProperties($sPluginClass)
|
||||
{
|
||||
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
|
||||
|
||||
if (array_key_exists($sPluginClass, self::$m_aPluginProperties))
|
||||
{
|
||||
return self::$m_aPluginProperties[$sPluginClass];
|
||||
}
|
||||
else
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -308,6 +308,43 @@ interface iPopupMenuExtension
|
||||
* $param is null
|
||||
*/
|
||||
const MENU_USER_ACTIONS = 5;
|
||||
/**
|
||||
* Insert an item into the Action menu on an object item in an objects list in the portal
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on the current line)
|
||||
*/
|
||||
const PORTAL_OBJLISTITEM_ACTIONS = 7;
|
||||
/**
|
||||
* Insert an item into the Action menu on an object details page in the portal
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object currently displayed)
|
||||
*/
|
||||
const PORTAL_OBJDETAILS_ACTIONS = 8;
|
||||
|
||||
/**
|
||||
* Insert an item into the Actions menu of a list in the portal
|
||||
* Note: This is not implemented yet !
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object_set' => $oSet) containing DBObjectSet containing the list of objects
|
||||
* @todo
|
||||
*/
|
||||
const PORTAL_OBJLIST_ACTIONS = 6;
|
||||
/**
|
||||
* Insert an item into the user menu of the portal
|
||||
* Note: This is not implemented yet !
|
||||
*
|
||||
* $param is the portal id
|
||||
* @todo
|
||||
*/
|
||||
const PORTAL_USER_ACTIONS = 9;
|
||||
/**
|
||||
* Insert an item into the navigation menu of the portal
|
||||
* Note: This is not implemented yet !
|
||||
*
|
||||
* $param is the portal id
|
||||
* @todo
|
||||
*/
|
||||
const PORTAL_MENU_ACTIONS = 10;
|
||||
|
||||
/**
|
||||
* Get the list of items to be added to a menu.
|
||||
@@ -334,17 +371,21 @@ abstract class ApplicationPopupMenuItem
|
||||
protected $sUID;
|
||||
/** @ignore */
|
||||
protected $sLabel;
|
||||
/** @ignore */
|
||||
protected $aCssClasses;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
|
||||
* @param string $sLabel The display label of the menu (must be localized)
|
||||
*/
|
||||
* @param string $sLabel The display label of the menu (must be localized)
|
||||
* @param array $aCssClasses The CSS classes to add to the menu
|
||||
*/
|
||||
public function __construct($sUID, $sLabel)
|
||||
{
|
||||
$this->sUID = $sUID;
|
||||
$this->sLabel = $sLabel;
|
||||
$this->aCssClasses = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,6 +409,35 @@ abstract class ApplicationPopupMenuItem
|
||||
{
|
||||
return $this->sLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSS classes
|
||||
*
|
||||
* @return array
|
||||
* @ignore
|
||||
*/
|
||||
public function GetCssClasses()
|
||||
{
|
||||
return $this->aCssClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aCssClasses
|
||||
*/
|
||||
public function SetCssClasses($aCssClasses)
|
||||
{
|
||||
$this->aCssClasses = $aCssClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a CSS class to the CSS classes that will be put on the menu item
|
||||
*
|
||||
* @param $sCssClass
|
||||
*/
|
||||
public function AddCssClass($sCssClass)
|
||||
{
|
||||
$this->aCssClasses[] = $sCssClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the components to create a popup menu item in HTML
|
||||
@@ -415,7 +485,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/** @ignore */
|
||||
public function GetMenuItem()
|
||||
{
|
||||
return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget);
|
||||
return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget, 'css_classes' => $this->aCssClasses);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,7 +521,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
public function GetMenuItem()
|
||||
{
|
||||
// Note: the semicolumn is a must here!
|
||||
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#');
|
||||
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#', 'css_classes' => $this->aCssClasses);
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
@@ -483,10 +553,34 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/** @ignore */
|
||||
public function GetMenuItem()
|
||||
{
|
||||
return array ('label' => '<hr class="menu-separator">', 'url' => '');
|
||||
return array ('label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for adding an item as a button that browses to the given URL
|
||||
*
|
||||
* @package Extensibility
|
||||
* @api
|
||||
* @since 2.0
|
||||
*/
|
||||
class URLButtonItem extends URLPopupMenuItem
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for adding an item as a button that runs some JS code
|
||||
*
|
||||
* @package Extensibility
|
||||
* @api
|
||||
* @since 2.0
|
||||
*/
|
||||
class JSButtonItem extends JSPopupMenuItem
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add content to any iTopWebPage
|
||||
*
|
||||
@@ -528,6 +622,128 @@ interface iPageUIExtension
|
||||
public function GetBannerHtml(iTopWebPage $oPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add content to any enhanced portal page
|
||||
*
|
||||
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
|
||||
*
|
||||
* @package Extensibility
|
||||
* @api
|
||||
* @since 2.4
|
||||
*/
|
||||
interface iPortalUIExtension
|
||||
{
|
||||
const ENUM_PORTAL_EXT_UI_BODY = 'Body';
|
||||
const ENUM_PORTAL_EXT_UI_NAVIGATION_MENU = 'NavigationMenu';
|
||||
const ENUM_PORTAL_EXT_UI_MAIN_CONTENT = 'MainContent';
|
||||
|
||||
/**
|
||||
* Returns an array of CSS file urls
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return array
|
||||
*/
|
||||
public function GetCSSFiles(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns inline (raw) CSS
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetCSSInline(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns an array of JS file urls
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return array
|
||||
*/
|
||||
public function GetJSFiles(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns raw JS code
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetJSInline(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the <body> tag
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetBodyHTML(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the #main-wrapper element
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetMainContentHTML(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetNavigationMenuHTML(\Silex\Application $oApp);
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
|
||||
*/
|
||||
abstract class AbstractPortalUIExtension implements iPortalUIExtension
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetCSSFiles(\Silex\Application $oApp)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetCSSInline(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetJSFiles(\Silex\Application $oApp)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetJSInline(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetBodyHTML(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetMainContentHTML(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetNavigationMenuHTML(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add new operations to the REST/JSON web service
|
||||
*
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -23,7 +23,7 @@ require_once(APPROOT.'core/modelreflection.class.inc.php');
|
||||
/**
|
||||
* A user editable dashboard page
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
abstract class Dashboard
|
||||
@@ -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,24 +429,11 @@ 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('<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>');
|
||||
|
||||
@@ -465,6 +467,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()
|
||||
{
|
||||
@@ -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
|
||||
@@ -515,8 +558,6 @@ class RuntimeDashboard extends Dashboard
|
||||
// Assuming there is at most one couple {user, menu}!
|
||||
$oUserDashboard = $oUDSet->Fetch();
|
||||
$oUserDashboard->Set('contents', $sXml);
|
||||
|
||||
$oUserDashboard->DBUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -525,9 +566,10 @@ class RuntimeDashboard extends Dashboard
|
||||
$oUserDashboard->Set('user_id', UserRights::GetUserId());
|
||||
$oUserDashboard->Set('menu_code', $this->sId);
|
||||
$oUserDashboard->Set('contents', $sXml);
|
||||
|
||||
$oUserDashboard->DBInsert();
|
||||
}
|
||||
}
|
||||
utils::PushArchiveMode(false);
|
||||
$oUserDashboard->DBWrite();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
|
||||
public function Revert()
|
||||
@@ -540,7 +582,9 @@ class RuntimeDashboard extends Dashboard
|
||||
{
|
||||
// Assuming there is at most one couple {user, menu}!
|
||||
$oUserDashboard = $oUDSet->Fetch();
|
||||
utils::PushArchiveMode(false);
|
||||
$oUserDashboard->DBDelete();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,33 +798,65 @@ EOF
|
||||
|
||||
public static function GetDashletCreationForm($sOQL = null)
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContextMenuId = $oAppContext->GetCurrentValue('menu', null);
|
||||
|
||||
$oForm = new DesignerForm();
|
||||
|
||||
// Get the list of all 'dashboard' menus in which we can insert a dashlet
|
||||
$aAllMenus = ApplicationMenu::ReflectionMenuNodes();
|
||||
$sRootMenuId = ApplicationMenu::GetRootMenuId($sContextMenuId);
|
||||
$aAllowedDashboards = array();
|
||||
foreach($aAllMenus as $idx => $aMenu)
|
||||
$sDefaultDashboard = null;
|
||||
|
||||
// Store the parent menus for acces check
|
||||
$aParentMenus = array();
|
||||
foreach($aAllMenus as $idx => $aMenu)
|
||||
{
|
||||
/** @var MenuNode $oMenu */
|
||||
$oMenu = $aMenu['node'];
|
||||
if (count(ApplicationMenu::GetChildren($oMenu->GetIndex())) > 0)
|
||||
{
|
||||
$aParentMenus[$oMenu->GetMenuId()] = $aMenu;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($aAllMenus as $idx => $aMenu)
|
||||
{
|
||||
$oMenu = $aMenu['node'];
|
||||
$sParentId = $aMenu['parent'];
|
||||
if ($oMenu instanceof DashboardMenuNode)
|
||||
{
|
||||
$sMenuLabel = $oMenu->GetTitle();
|
||||
$sParentLabel = Dict::S('Menu:'.$sParentId);
|
||||
if ($sParentLabel != $sMenuLabel)
|
||||
{
|
||||
$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
|
||||
}
|
||||
}
|
||||
if ($oMenu instanceof DashboardMenuNode)
|
||||
{
|
||||
// Get the root parent for access check
|
||||
$sParentId = $aMenu['parent'];
|
||||
$aParentMenu = $aParentMenus[$sParentId];
|
||||
while (isset($aParentMenus[$aParentMenu['parent']]))
|
||||
{
|
||||
// grand parent exists
|
||||
$sParentId = $aParentMenu['parent'];
|
||||
$aParentMenu = $aParentMenus[$sParentId];
|
||||
}
|
||||
$oParentMenu = $aParentMenu['node'];
|
||||
if ($oMenu->IsEnabled() && $oParentMenu->IsEnabled())
|
||||
{
|
||||
$sMenuLabel = $oMenu->GetTitle();
|
||||
$sParentLabel = Dict::S('Menu:'.$sParentId);
|
||||
if ($sParentLabel != $sMenuLabel)
|
||||
{
|
||||
$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
|
||||
}
|
||||
if (empty($sDefaultDashboard) && ($sRootMenuId == ApplicationMenu::GetRootMenuId($oMenu->GetMenuId())))
|
||||
{
|
||||
$sDefaultDashboard = $oMenu->GetMenuId();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
asort($aAllowedDashboards);
|
||||
|
||||
$aKeys = array_keys($aAllowedDashboards); // Select the first one by default
|
||||
$sDefaultDashboard = $aKeys[0];
|
||||
$oField = new DesignerComboField('menu_id', Dict::S('UI:DashletCreation:Dashboard'), $sDefaultDashboard);
|
||||
$oField->SetAllowedValues($aAllowedDashboards);
|
||||
$oField->SetMandatory(true);
|
||||
@@ -841,7 +917,7 @@ EOF
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#dashlet_creation_dlg').dialog({
|
||||
width: 400,
|
||||
width: 600,
|
||||
modal: true,
|
||||
title: '$sDialogTitle',
|
||||
buttons: [
|
||||
|
||||
@@ -104,7 +104,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
|
||||
// Trim the list of cells to remove the invisible/empty ones at the end of the array
|
||||
$aCells = $this->TrimCellsArray($aCells);
|
||||
|
||||
$oPage->add('<table style="width:100%"><tbody>');
|
||||
$oPage->add('<table style="width:100%;table-layout:fixed;"><tbody>');
|
||||
$iCellIdx = 0;
|
||||
$fColSize = 100 / $this->iNbCols;
|
||||
$sStyle = $bEditMode ? 'border: 1px #ccc dashed; width:'.$fColSize.'%;' : 'width: '.$fColSize.'%;';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -18,7 +18,7 @@
|
||||
/**
|
||||
* Data Table to display a set of objects in a tabular manner in HTML
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,8 @@ class DataTable
|
||||
protected $iNbObjects; // Total number of objects inthe set
|
||||
protected $bUseCustomSettings; // Whether or not the current display uses custom settings
|
||||
protected $oDefaultSettings; // the default settings for displaying such a list
|
||||
|
||||
protected $bShowObsoleteData;
|
||||
|
||||
/**
|
||||
* @param $iListId mixed Unique ID for this div/table in the page
|
||||
* @param $oSet DBObjectSet The set of data to display
|
||||
@@ -47,6 +48,7 @@ class DataTable
|
||||
$this->iNbObjects = $oSet->Count();
|
||||
$this->bUseCustomSettings = false;
|
||||
$this->oDefaultSettings = null;
|
||||
$this->bShowObsoleteData = $oSet->GetShowObsoleteData();
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage, DataTableSettings $oSettings, $bActionsMenu, $sSelectMode, $bViewLink, $aExtraParams)
|
||||
@@ -145,7 +147,9 @@ class DataTable
|
||||
$sHtml .= "<tr><td class=\"datacontents\">$sDataTable</td></tr>";
|
||||
$sHtml .= "</table>\n";
|
||||
$oPage->add_at_the_end($sConfigDlg);
|
||||
|
||||
|
||||
$aExtraParams['show_obsolete_data'] = $this->bShowObsoleteData;
|
||||
|
||||
$aOptions = array(
|
||||
'sPersistentId' => '',
|
||||
'sFilter' => $this->oSet->GetFilter()->serialize(),
|
||||
@@ -170,6 +174,7 @@ class DataTable
|
||||
}
|
||||
$sJSOptions = json_encode($aOptions);
|
||||
$oPage->add_ready_script("$('#datatable_{$this->iListId}').datatable($sJSOptions);");
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -293,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(
|
||||
@@ -486,6 +491,7 @@ EOF;
|
||||
{
|
||||
$aExtraParams['query_params'][$sName] = $sValue;
|
||||
}
|
||||
$aExtraParams['show_obsolete_data'] = $this->bShowObsoleteData;
|
||||
|
||||
$sHtml .= "<tr><td>";
|
||||
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
|
||||
@@ -566,33 +572,6 @@ EOF
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
@@ -601,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()");
|
||||
@@ -928,8 +907,15 @@ class DataTableSettings implements Serializable
|
||||
}
|
||||
else if ($oAttDef->IsExternalField())
|
||||
{
|
||||
$oExtAttDef = $oAttDef->GetExtAttDef();
|
||||
$sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
|
||||
if ($oAttDef->IsFriendlyName())
|
||||
{
|
||||
$sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel());
|
||||
}
|
||||
else
|
||||
{
|
||||
$oExtAttDef = $oAttDef->GetExtAttDef();
|
||||
$sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeFriendlyName)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ class DesignerForm
|
||||
|
||||
public function RenderAsPropertySheet($oP, $bReturnHTML = false, $sNotifyParentSelector = null)
|
||||
{
|
||||
$sReturn = '';
|
||||
$sReturn = '';
|
||||
$sActionUrl = addslashes($this->sSubmitTo);
|
||||
$sJSSubmitParams = json_encode($this->aSubmitParams);
|
||||
$sFormId = $this->GetFormId();
|
||||
@@ -360,6 +360,7 @@ EOF
|
||||
<<<EOF
|
||||
$('#$sDialogId').dialog({
|
||||
height: 'auto',
|
||||
maxHeight: $(window).height() - 8,
|
||||
width: $iDialogWidth,
|
||||
modal: true,
|
||||
autoOpen: $sAutoOpen,
|
||||
@@ -531,7 +532,7 @@ EOF
|
||||
|
||||
public function GetFieldId($sCode)
|
||||
{
|
||||
return $this->GetPrefix().'attr_'.$sCode;
|
||||
return $this->GetPrefix().'attr_'.utils::GetSafeId($sCode.$this->GetSuffix());
|
||||
}
|
||||
|
||||
public function GetFieldName($sCode)
|
||||
@@ -881,7 +882,7 @@ class DesignerTextField extends DesignerFormField
|
||||
$this->sValidationPattern = $sValidationPattern;
|
||||
}
|
||||
|
||||
public function SetForbiddenValues($aValues, $sExplain)
|
||||
public function SetForbiddenValues($aValues, $sExplain, $bCaseSensitive = true)
|
||||
{
|
||||
$aForbiddenValues = $aValues;
|
||||
|
||||
@@ -893,7 +894,7 @@ class DesignerTextField extends DesignerFormField
|
||||
|
||||
}
|
||||
|
||||
$this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain);
|
||||
$this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain, 'case_sensitive' => $bCaseSensitive);
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
@@ -1367,6 +1368,36 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
}
|
||||
|
||||
static protected function FindIconsOnDisk($sBaseDir, $sDir = '')
|
||||
{
|
||||
$aFiles = null;
|
||||
$sKey = $sBaseDir.'/'.$sDir;
|
||||
$sShortKey = abs(crc32($sKey));
|
||||
$sCacheFile = utils::GetCachePath().'available-icons-'.$sShortKey.'.php';
|
||||
$sCacheClass = 'AvailableIcons_'.$sShortKey;
|
||||
if (file_exists($sCacheFile))
|
||||
{
|
||||
require_once($sCacheFile);
|
||||
if ($sCacheClass::$sKey === $sKey) // crc32 collision detection
|
||||
{
|
||||
$aFiles = $sCacheClass::$aIconFiles;
|
||||
}
|
||||
}
|
||||
if ($aFiles === null)
|
||||
{
|
||||
$aFiles = self::_FindIconsOnDisk($sBaseDir, $sDir);
|
||||
$sAvailableIcons = '<?php'.PHP_EOL;
|
||||
$sAvailableIcons .= '// Generated and used by '.__METHOD__.PHP_EOL;
|
||||
$sAvailableIcons .= 'class '.$sCacheClass.PHP_EOL;
|
||||
$sAvailableIcons .= '{'.PHP_EOL;
|
||||
$sAvailableIcons .= ' static $sKey = '.var_export($sKey, true).';'.PHP_EOL;
|
||||
$sAvailableIcons .= ' static $aIconFiles = '.var_export($aFiles, true).';'.PHP_EOL;
|
||||
$sAvailableIcons .= '}'.PHP_EOL;
|
||||
file_put_contents($sCacheFile, $sAvailableIcons, LOCK_EX);
|
||||
}
|
||||
return $aFiles;
|
||||
}
|
||||
|
||||
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '')
|
||||
{
|
||||
$aResult = array();
|
||||
// Populate automatically the list of icon files
|
||||
@@ -1378,7 +1409,7 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
|
||||
{
|
||||
$sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
|
||||
$aResult = array_merge($aResult, self::FindIconsOnDisk($sBaseDir, $sDirSubPath));
|
||||
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath));
|
||||
}
|
||||
if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
|
||||
{
|
||||
@@ -1408,8 +1439,12 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
|
||||
public function GetDefaultValue($sClass = 'Contact')
|
||||
{
|
||||
$sIconPath = MetaModel::GetClassIcon($sClass, false);
|
||||
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
|
||||
$sIcon = '';
|
||||
if (MetaModel::IsValidClass($sClass))
|
||||
{
|
||||
$sIconPath = MetaModel::GetClassIcon($sClass, false);
|
||||
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
|
||||
}
|
||||
return $sIcon;
|
||||
}
|
||||
}
|
||||
@@ -1490,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)
|
||||
{
|
||||
@@ -1504,7 +1538,7 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
|
||||
|
||||
$this->aCSSClasses[] = 'formSelector';
|
||||
|
||||
$sCSSClasses = '';
|
||||
@@ -1520,8 +1554,6 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
$aSelected = array();
|
||||
$aHiddenValues = array();
|
||||
$sDisplayValue = '';
|
||||
$sHiddenValue = '';
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
@@ -1537,8 +1569,6 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
{
|
||||
@@ -1554,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);
|
||||
@@ -1580,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
|
||||
{
|
||||
@@ -1646,7 +1657,6 @@ EOF
|
||||
if ($selectedValue == $aFormData['value'])
|
||||
{
|
||||
$this->defaultValue =$iKey;
|
||||
$aDefaultValues = $this->oForm->GetDefaultValues();
|
||||
$oSubForm = $aFormData['form'];
|
||||
$oSubForm->SetDefaultValues($aAllDefaultValues);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class LoginWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
@@ -56,8 +57,13 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
protected static $m_sLoginFailedMessage = '';
|
||||
|
||||
public function __construct($sTitle = 'iTop Login')
|
||||
public function __construct($sTitle = null)
|
||||
{
|
||||
if($sTitle === null)
|
||||
{
|
||||
$sTitle = Dict::S('UI:Login:Title');
|
||||
}
|
||||
|
||||
parent::__construct($sTitle);
|
||||
$this->SetStyleSheet();
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
@@ -90,12 +96,12 @@ class LoginWebPage extends NiceWebPage
|
||||
$sLogo = 'itop-logo-external.png';
|
||||
$sBrandingLogo = 'login-logo.png';
|
||||
}
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?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");
|
||||
}
|
||||
@@ -112,7 +118,7 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
case 'basic':
|
||||
case 'url':
|
||||
$this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
$this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
|
||||
$this->add_header('HTTP/1.0 401 Unauthorized');
|
||||
$this->add_header('Content-type: text/html; charset=iso-8859-1');
|
||||
// Note: displayed when the user will click on Cancel
|
||||
@@ -189,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;
|
||||
}
|
||||
@@ -253,10 +259,6 @@ class LoginWebPage extends NiceWebPage
|
||||
$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);
|
||||
@@ -430,6 +432,8 @@ EOF
|
||||
// Unset all of the session variables.
|
||||
unset($_SESSION['auth_user']);
|
||||
unset($_SESSION['login_mode']);
|
||||
unset($_SESSION['archive_mode']);
|
||||
unset($_SESSION['impersonate_user']);
|
||||
UserRights::_ResetSessionCache();
|
||||
// If it's desired to kill the session, also delete the session cookie.
|
||||
// Note: This will destroy the session, and not just the session data!
|
||||
@@ -572,96 +576,95 @@ EOF
|
||||
break;
|
||||
}
|
||||
$index++;
|
||||
|
||||
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
|
||||
if ($sLoginMode == '')
|
||||
}
|
||||
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
|
||||
if ($sLoginMode == '')
|
||||
{
|
||||
// First connection
|
||||
$sDesiredLoginMode = utils::ReadParam('login_mode');
|
||||
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
|
||||
{
|
||||
// First connection
|
||||
$sDesiredLoginMode = utils::ReadParam('login_mode');
|
||||
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
|
||||
$sLoginMode = $sDesiredLoginMode;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
|
||||
}
|
||||
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
|
||||
{
|
||||
// X-Combodo-Ajax is a special header automatically added to all ajax requests
|
||||
// Let's reply that we're currently logged-out
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
exit;
|
||||
}
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
if (($sAuthUser !== '') && ($sAuthPwd === null))
|
||||
{
|
||||
$sLoginMode = $sDesiredLoginMode;
|
||||
return self::EXIT_CODE_MISSINGPASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
|
||||
}
|
||||
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
|
||||
{
|
||||
// X-Combodo-Ajax is a special header automatically added to all ajax requests
|
||||
// Let's reply that we're currently logged-out
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
exit;
|
||||
return self::EXIT_CODE_MISSINGLOGIN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
|
||||
{
|
||||
//echo "Check Credentials returned false for user $sAuthUser!";
|
||||
self::ResetSession();
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
if (($sAuthUser !== '') && ($sAuthPwd === null))
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGPASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGLOGIN;
|
||||
}
|
||||
return self::EXIT_CODE_WRONGCREDENTIALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
|
||||
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
|
||||
// User is Ok, let's save it in the session and proceed with normal login
|
||||
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
|
||||
|
||||
if (MetaModel::GetConfig()->Get('log_usage'))
|
||||
{
|
||||
//echo "Check Credentials returned false for user $sAuthUser!";
|
||||
self::ResetSession();
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
return self::EXIT_CODE_WRONGCREDENTIALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// User is Ok, let's save it in the session and proceed with normal login
|
||||
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
|
||||
|
||||
if (MetaModel::GetConfig()->Get('log_usage'))
|
||||
{
|
||||
$oLog = new EventLoginUsage();
|
||||
$oLog->Set('userinfo', UserRights::GetUser());
|
||||
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
|
||||
$oLog->Set('message', 'Successful login');
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
UserRights::_InitSessionCache();
|
||||
$oLog = new EventLoginUsage();
|
||||
$oLog->Set('userinfo', UserRights::GetUser());
|
||||
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
|
||||
$oLog->Set('message', 'Successful login');
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
UserRights::_InitSessionCache();
|
||||
}
|
||||
}
|
||||
return self::EXIT_CODE_OK;
|
||||
@@ -697,26 +700,36 @@ 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)
|
||||
{
|
||||
$sRequestedPortalId = $bIsAllowedToPortalUsers ? 'legacy_portal' : 'backoffice';
|
||||
return self::DoLoginEx($sRequestedPortalId, $bMustBeAdmin, $iOnExit);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the user is already authentified, if yes, then performs some additional validations to redirect towards the desired "portal"
|
||||
* 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)
|
||||
{
|
||||
|
||||
@@ -61,11 +61,23 @@ 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()
|
||||
{
|
||||
if (!self::$bAdditionalMenusLoaded)
|
||||
@@ -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)
|
||||
{
|
||||
@@ -111,11 +123,34 @@ 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;
|
||||
}
|
||||
|
||||
@@ -159,9 +195,12 @@ class ApplicationMenu
|
||||
self::LoadAdditionalMenus();
|
||||
return self::$aMenusIndex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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,34 +211,65 @@ 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>'.$oMenuNode->GetTitle().'</h3>');
|
||||
$oPage->AddToMenu('<h3 id="'.utils::GetSafeId('AccordionMenu_'.$oMenuNode->GetMenuID()).'">'.$oMenuNode->GetTitle().'</h3>');
|
||||
$oPage->AddToMenu('<div>');
|
||||
$oPage->AddToMenu('<ul>');
|
||||
$aChildren = self::GetChildren($aMenu['index']);
|
||||
if (count($aChildren) > 0)
|
||||
$bActive = self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu);
|
||||
$oPage->AddToMenu('</ul>');
|
||||
if ($bActive)
|
||||
{
|
||||
$oPage->AddToMenu('<ul>');
|
||||
$bActive = self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu);
|
||||
$oPage->AddToMenu('</ul>');
|
||||
if ($bActive)
|
||||
{
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
// Accordion Menu
|
||||
$("#accordion").css({display:'block'}).accordion({ header: "h3", navigation: true, heightStyle: "content", collapsible: true, active: $iAccordion, icons: false, animate:true }); // collapsible will be enabled once the item will be selected
|
||||
// Accordion Menu
|
||||
$("#accordion").css({display:'block'}).accordion({ header: "h3", navigation: true, heightStyle: "content", collapsible: true, active: $iAccordion, icons: false, animate:true }); // collapsible will be enabled once the item will be selected
|
||||
EOF
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
$oPage->AddToMenu('</div>');
|
||||
$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'.$sCSSClass.'><a href="'.$oMenu->GetHyperlink($aExtraParams).'">'.$oMenu->GetTitle().'</a></li>');
|
||||
$oPage->AddToMenu('<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'" '.$sCSSClass.'><a href="'.$oMenu->GetHyperlink($aExtraParams).'">'.$oMenu->GetTitle().'</a></li>');
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage->AddToMenu('<li'.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
|
||||
$oPage->AddToMenu('<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'" '.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
|
||||
}
|
||||
$aCurrentMenu = self::$aMenusIndex[$index];
|
||||
if ($iActiveMenu == $index)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
@@ -254,17 +327,21 @@ EOF
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to retrieve the MenuNodeObject based on its ID
|
||||
* Helper function to retrieve the MenuNode Object based on its ID
|
||||
* @param int $index
|
||||
* @return MenuNode|null
|
||||
*/
|
||||
static public function GetMenuNode($index)
|
||||
{
|
||||
return isset(self::$aMenusIndex[$index]) ? self::$aMenusIndex[$index]['node'] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to get the list of child(ren) of a menu
|
||||
* @param int $index
|
||||
* @return array
|
||||
*/
|
||||
static public function GetChildren($index)
|
||||
{
|
||||
@@ -303,8 +380,11 @@ EOF
|
||||
$sMenuId = self::GetDefaultMenuId();
|
||||
}
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -378,7 +486,7 @@ abstract class MenuNode
|
||||
* Stimulus to check: if the user can 'apply' this stimulus, then she/he can see this menu
|
||||
*/
|
||||
protected $m_aEnableStimuli;
|
||||
|
||||
|
||||
/**
|
||||
* Create a menu item, sets the condition to have it displayed 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)
|
||||
@@ -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+", "");
|
||||
@@ -442,7 +571,10 @@ 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;
|
||||
@@ -515,9 +654,19 @@ 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,17 +785,29 @@ 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;
|
||||
|
||||
/**
|
||||
* Extra parameters to be passed to the display block to fine tune its appearence
|
||||
*/
|
||||
protected $m_aParams;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
@@ -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->bSearchFormOpen = $bSearchFormOpen;
|
||||
$this->m_aParams = array();
|
||||
$this->aReflectionProperties['oql'] = $sOQL;
|
||||
$this->aReflectionProperties['do_search'] = $bSearch;
|
||||
@@ -674,9 +847,18 @@ class OQLMenuNode extends MenuNode
|
||||
$this->aReflectionProperties[$sKey] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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,8 +977,11 @@ class SearchMenuNode extends MenuNode
|
||||
*/
|
||||
class WebPageMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sHyperlink;
|
||||
|
||||
|
||||
/**
|
||||
* Create a menu item that points to any web page (not only UI.php)
|
||||
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
|
||||
@@ -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,8 +1028,11 @@ class WebPageMenuNode extends MenuNode
|
||||
*/
|
||||
class NewObjectMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sClass;
|
||||
|
||||
|
||||
/**
|
||||
* Create a menu item that points to the URL for creating a new object, the menu will be added only if the current user has enough
|
||||
* rights to create such an object (or an object of a child class)
|
||||
@@ -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()
|
||||
{
|
||||
@@ -854,7 +1085,12 @@ 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)
|
||||
{
|
||||
@@ -999,7 +1254,12 @@ EOF
|
||||
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function RenderEditor(WebPage $oPage)
|
||||
{
|
||||
$oDashboard = $this->GetDashboard();
|
||||
@@ -1012,7 +1272,11 @@ EOF
|
||||
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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,8 +1347,11 @@ require_once(APPROOT.'application/shortcut.class.inc.php');
|
||||
*/
|
||||
class ShortcutMenuNode extends MenuNode
|
||||
{
|
||||
/**
|
||||
* @var Shortcut
|
||||
*/
|
||||
protected $oShortcut;
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
@@ -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,7 +51,25 @@ 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_ready_script(
|
||||
$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
|
||||
$.tablesorter.addWidget({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Class PortalWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -96,15 +96,88 @@ class PortalWebPage extends NiceWebPage
|
||||
$this->add_linked_script("../js/ckeditor/ckeditor.js");
|
||||
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
|
||||
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
|
||||
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
|
||||
|
||||
$sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage'));
|
||||
$sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle'));
|
||||
$sJSLoginAgain = json_encode(Dict::S('UI:LoginAgain'));
|
||||
$sJSStayOnThePage = json_encode(Dict::S('UI:StayOnThePage'));
|
||||
$sJSDaysMin = json_encode(array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min')));
|
||||
$sJSMonthsShort = json_encode(array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short')));
|
||||
$iFirstDayOfWeek = (int) Dict::S('Calendar-FirstDayOfWeek');
|
||||
$aDaysMin = array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min'));
|
||||
$aMonthsShort = array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short'));
|
||||
$sTimeFormat = AttributeDateTime::GetFormat()->ToTimeFormat();
|
||||
$oTimeFormat = new DateTimeFormat($sTimeFormat);
|
||||
$sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2)));
|
||||
|
||||
// Date picker options
|
||||
$aPickerOptions = array(
|
||||
'showOn' => 'button',
|
||||
'buttonImage' => '../images/calendar.png',
|
||||
'buttonImageOnly' => true,
|
||||
'dateFormat' => AttributeDate::GetFormat()->ToDatePicker(),
|
||||
'constrainInput' => false,
|
||||
'changeMonth' => true,
|
||||
'changeYear' => true,
|
||||
'dayNamesMin' => $aDaysMin,
|
||||
'monthNamesShort' => $aMonthsShort,
|
||||
'firstDay' => (int) Dict::S('Calendar-FirstDayOfWeek'),
|
||||
);
|
||||
$sJSDatePickerOptions = json_encode($aPickerOptions);
|
||||
|
||||
// Time picker additional options
|
||||
$aPickerOptions['showOn'] = '';
|
||||
$aPickerOptions['buttonImage'] = null;
|
||||
$aPickerOptions['timeFormat'] = $oTimeFormat->ToDatePicker();
|
||||
$aPickerOptions['controlType'] = 'select';
|
||||
$aPickerOptions['closeText'] = Dict::S('UI:Button:Ok');
|
||||
$sJSDateTimePickerOptions = json_encode($aPickerOptions);
|
||||
if ($sJSLangShort != '"en"')
|
||||
{
|
||||
// More options that cannot be passed via json_encode since they must be evaluated client-side
|
||||
$aMoreJSOptions = ",
|
||||
'timeText': $.timepicker.regional[$sJSLangShort].timeText,
|
||||
'hourText': $.timepicker.regional[$sJSLangShort].hourText,
|
||||
'minuteText': $.timepicker.regional[$sJSLangShort].minuteText,
|
||||
'secondText': $.timepicker.regional[$sJSLangShort].secondText,
|
||||
'currentText': $.timepicker.regional[$sJSLangShort].currentText
|
||||
}";
|
||||
$sJSDateTimePickerOptions = substr($sJSDateTimePickerOptions, 0, -1).$aMoreJSOptions;
|
||||
}
|
||||
$this->add_script(
|
||||
<<< EOF
|
||||
function PrepareWidgets()
|
||||
{
|
||||
// note: each action implemented here must be idempotent,
|
||||
// because this helper function might be called several times on a given page
|
||||
|
||||
$(".date-pick").datepicker($sJSDatePickerOptions);
|
||||
|
||||
// Hack for the date and time picker addon issue on Chrome (see #1305)
|
||||
// The workaround is to instantiate the widget on demand
|
||||
// It relies on the same markup, thus reverting to the original implementation should be straightforward
|
||||
$(".datetime-pick:not(.is-widget-ready)").each(function(){
|
||||
var oInput = this;
|
||||
$(oInput).addClass('is-widget-ready');
|
||||
$('<img class="datetime-pick-button" src="../images/calendar.png">')
|
||||
.insertAfter($(this))
|
||||
.on('click', function(){
|
||||
$(oInput)
|
||||
.datetimepicker($sJSDateTimePickerOptions)
|
||||
.datetimepicker('show')
|
||||
.datetimepicker('option', 'onClose', function(dateText,inst){
|
||||
$(oInput).datetimepicker('destroy');
|
||||
})
|
||||
.on('click keypress', function(){
|
||||
$(oInput).datetimepicker('hide');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
@@ -146,34 +219,10 @@ try
|
||||
}
|
||||
});
|
||||
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dayNamesMin: $sJSDaysMin,
|
||||
monthNamesShort: $sJSMonthsShort,
|
||||
firstDay: $iFirstDayOfWeek
|
||||
});
|
||||
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dayNamesMin: $sJSDaysMin,
|
||||
monthNamesShort: $sJSMonthsShort,
|
||||
firstDay: $iFirstDayOfWeek
|
||||
});
|
||||
PrepareWidgets();
|
||||
|
||||
//$('.resizable').resizable(); // Make resizable everything that claims to be resizable !
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry_html').toggle(); });
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
|
||||
|
||||
$(document).ajaxSend(function(event, jqxhr, options) {
|
||||
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');
|
||||
@@ -338,7 +387,7 @@ EOF
|
||||
{
|
||||
$sReadOnly = Dict::S('UI:AccessRO-Users');
|
||||
$sAdminMessage = trim(MetaModel::GetConfig()->Get('access_message'));
|
||||
$sApplicationBanner .= '<div id="admin-banner">';
|
||||
$sApplicationBanner .= '<div class="app-message">';
|
||||
$sApplicationBanner .= '<img src="../images/locked.png" style="vertical-align:middle;">';
|
||||
$sApplicationBanner .= ' <b>'.$sReadOnly.'</b>';
|
||||
if (strlen($sAdminMessage) > 0)
|
||||
@@ -767,7 +816,7 @@ EOF
|
||||
$sClass = get_class($oObj);
|
||||
|
||||
$sStimulus = trim(utils::ReadPostedParam('apply_stimulus', ''));
|
||||
$sTargetState = '';
|
||||
$aExpectedAttributes = array();
|
||||
if (!empty($sStimulus))
|
||||
{
|
||||
// Compute the target state
|
||||
@@ -777,10 +826,10 @@ EOF
|
||||
{
|
||||
throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel()));
|
||||
}
|
||||
$sTargetState = $aTransitions[$sStimulus]['target_state'];
|
||||
}
|
||||
$aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/);
|
||||
}
|
||||
|
||||
$oObj->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList, $sTargetState);
|
||||
$oObj->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList, $aExpectedAttributes);
|
||||
|
||||
// Optional: apply a stimulus
|
||||
//
|
||||
@@ -903,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,7 +54,8 @@ 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('advanced_search', array('name')); // Criteria of the advanced 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);
|
||||
|
||||
@@ -1,531 +0,0 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* SqlBlock - display tables or charts, given an SQL query - use cautiously!
|
||||
*
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
require_once(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
|
||||
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
|
||||
|
||||
/**
|
||||
* Helper class to design optimized dashboards, based on an SQL query
|
||||
*
|
||||
*/
|
||||
class SqlBlock
|
||||
{
|
||||
protected $m_sQuery;
|
||||
protected $m_aColumns;
|
||||
protected $m_sTitle;
|
||||
protected $m_sType;
|
||||
protected $m_aParams;
|
||||
|
||||
public function __construct($sQuery, $aColumns, $sTitle, $sType, $aParams = array())
|
||||
{
|
||||
$this->m_sQuery = $sQuery;
|
||||
$this->m_aColumns = $aColumns;
|
||||
$this->m_sTitle = $sTitle;
|
||||
$this->m_sType = $sType;
|
||||
$this->m_aParams = $aParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SqlBlock object from an XML template
|
||||
/*
|
||||
*
|
||||
* <sqlblock>
|
||||
* <sql>SELECT date_format(start_date, '%d') AS Date, count(*) AS Count FROM ticket WHERE DATE_SUB(NOW(), INTERVAL 15 DAY) < start_date AND finalclass = 'UserIssue' GROUP BY date_format(start_date, '%d') AND $CONDITION(param1, ticket.org_id)$</sql>
|
||||
* <type>table</type>
|
||||
* <title>UserRequest:Overview-Title</title>
|
||||
* <parameter>
|
||||
* <name>param1</name>
|
||||
* <type>context</type>
|
||||
* <mapping>org_id</mapping>
|
||||
* </parameter>
|
||||
* <column>
|
||||
* <name>Date</name>
|
||||
* <label>UserRequest:Overview-Date</label>
|
||||
* <drilldown></drilldown>
|
||||
* </column>
|
||||
* <column>
|
||||
* <name>Count</name>
|
||||
* <label>UserRequest:Overview-Count</label>
|
||||
* <drilldown>SELECT UserIssue WHERE date_format(start_date, '%d') = :Date</drilldown>
|
||||
* </column>
|
||||
* </sqlblock>
|
||||
*
|
||||
* Tags
|
||||
* - sql: a (My)SQL query. Do not forget to use html entities (e.g. < for <)
|
||||
* - type: table (default), bars or pie. If bars or pie is selected only the two first columns are taken into account.
|
||||
* - title: optional title, typed in clear or given as a dictionnary entry
|
||||
* - parameter: specifies how to map the context parameters (namely org_id) to a given named parameter in the query.
|
||||
* The expression $CONDITION(<param_name>, <sql_column_name>) will be automatically replaced by:
|
||||
* either the string "1" if there is no restriction on the organisation in iTop
|
||||
* or the string "(<sql_column_name>=<value_of_org_id>)" if there is a limitation to one organizations in iTop
|
||||
* or the string "(<sql_column_name> IN (<values_of_org_id>))" if there is a limitation to a given set of organizations in iTop
|
||||
* - column: specification of a column (not displayed if omitted)
|
||||
* - column / name: name of the column in the SQL query (use aliases)
|
||||
* - column / label: label, typed in clear or given as a dictionnary entry
|
||||
* - column / drilldown: NOT IMPLEMENTED YET - OQL with parameters corresponding to column names (in the query)
|
||||
*
|
||||
* @param $sTemplate string The XML template
|
||||
* @return DisplayBlock The DisplayBlock object, or null if the template is invalid
|
||||
*/
|
||||
public static function FromTemplate($sTemplate)
|
||||
{
|
||||
$oXml = simplexml_load_string('<root>'.$sTemplate.'</root>', 'SimpleXMLElement', LIBXML_NOCDATA);
|
||||
if (false)
|
||||
{
|
||||
// Debug
|
||||
echo "<pre>\n";
|
||||
print_r($oXml);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
|
||||
if (isset($oXml->title))
|
||||
{
|
||||
$sTitle = (string)$oXml->title;
|
||||
}
|
||||
if (isset($oXml->type))
|
||||
{
|
||||
$sType = (string)$oXml->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sType = 'table';
|
||||
}
|
||||
if (!isset($oXml->sql))
|
||||
{
|
||||
throw new Exception('Missing tag "sql" in sqlblock');
|
||||
}
|
||||
$sQuery = (string)$oXml->sql;
|
||||
|
||||
$aColumns = array();
|
||||
if (isset($oXml->column))
|
||||
{
|
||||
foreach ($oXml->column AS $oColumnData)
|
||||
{
|
||||
if (!isset($oColumnData->name))
|
||||
{
|
||||
throw new Exception("Missing tag 'name' in sqlblock/column");
|
||||
}
|
||||
$sName = (string) $oColumnData->name;
|
||||
if (strlen($sName) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'name' in sqlblock/column");
|
||||
}
|
||||
|
||||
$aColumns[$sName] = array();
|
||||
if (isset($oColumnData->label))
|
||||
{
|
||||
$sLabel = (string)$oColumnData->label;
|
||||
if (strlen($sLabel) > 0)
|
||||
{
|
||||
$aColumns[$sName]['label'] = Dict::S($sLabel);
|
||||
}
|
||||
}
|
||||
if (isset($oColumnData->drilldown))
|
||||
{
|
||||
$sDrillDown = (string)$oColumnData->drilldown;
|
||||
if (strlen($sDrillDown) > 0)
|
||||
{
|
||||
$aColumns[$sName]['drilldown'] = $sDrillDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$aParams = array();
|
||||
if (isset($oXml->parameter))
|
||||
{
|
||||
foreach ($oXml->parameter AS $oParamData)
|
||||
{
|
||||
if (!isset($oParamData->name))
|
||||
{
|
||||
throw new Exception("Missing tag 'name' for parameter in sqlblock/column");
|
||||
}
|
||||
$sName = (string) $oParamData->name;
|
||||
if (strlen($sName) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'name' for parameter in sqlblock/column");
|
||||
}
|
||||
if (!isset($oParamData->mapping))
|
||||
{
|
||||
throw new Exception("Missing tag 'mapping' for parameter in sqlblock/column");
|
||||
}
|
||||
$sMapping = (string) $oParamData->mapping;
|
||||
if (strlen($sMapping) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'mapping' for parameter in sqlblock/column");
|
||||
}
|
||||
|
||||
if (isset($oParamData->type))
|
||||
{
|
||||
$sParamType = $oParamData->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sParamType = 'context';
|
||||
}
|
||||
$aParams[$sName] = array('mapping' => $sMapping, 'type' => $sParamType);
|
||||
}
|
||||
}
|
||||
|
||||
return new SqlBlock($sQuery, $aColumns, $sTitle, $sType, $aParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the defined parameters into the SQL query
|
||||
* @return string the SQL query to execute
|
||||
*/
|
||||
public function BuildQuery()
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sQuery = $this->m_sQuery;
|
||||
$sQuery = str_replace('$DB_PREFIX$', MetaModel::GetConfig()->GetDBSubname(), $sQuery); // put the tables DB prefix (if any)
|
||||
foreach($this->m_aParams as $sName => $aParam)
|
||||
{
|
||||
if ($aParam['type'] == 'context')
|
||||
{
|
||||
$sSearchPattern = '/\$CONDITION\('.$sName.',([^\)]+)\)\$/';
|
||||
$value = $oAppContext->GetCurrentValue($aParam['mapping']);
|
||||
if (empty($value))
|
||||
{
|
||||
$sSQLExpr = '(1)';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Special case for managing the hierarchy of organizations
|
||||
if (($aParam['mapping'] == 'org_id') && ( MetaModel::IsValidClass('Organization')))
|
||||
{
|
||||
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
|
||||
if ($sHierarchicalKeyCode != false)
|
||||
{
|
||||
// organizations are in hierarchy... gather all the orgs below the given one...
|
||||
$sOQL = "SELECT Organization AS node JOIN Organization AS root ON node.$sHierarchicalKeyCode BELOW root.id WHERE root.id = :value";
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('value' => $value));
|
||||
$aOrgIds = array();
|
||||
while($oOrg = $oSet->Fetch())
|
||||
{
|
||||
$aOrgIds[]= $oOrg->GetKey();
|
||||
}
|
||||
$sSQLExpr = '($1 IN('.implode(',', $aOrgIds).'))';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
|
||||
}
|
||||
}
|
||||
$sQuery = preg_replace($sSearchPattern, $sSQLExpr, $sQuery);
|
||||
}
|
||||
}
|
||||
return $sQuery;
|
||||
}
|
||||
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
if (empty($aExtraParams['currentId']))
|
||||
{
|
||||
$sId = 'sqlblock_'.$oPage->GetUniqueId(); // Works only if the page is not an Ajax one !
|
||||
}
|
||||
else
|
||||
{
|
||||
$sId = $aExtraParams['currentId'];
|
||||
}
|
||||
// $oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId));
|
||||
|
||||
$sQuery = $this->BuildQuery();
|
||||
$res = CMDBSource::Query($sQuery);
|
||||
$aQueryCols = CMDBSource::GetColumns($res);
|
||||
|
||||
// Prepare column definitions (check + give default values)
|
||||
//
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
if (!in_array($sName, $aQueryCols))
|
||||
{
|
||||
throw new Exception("Unknown column name '$sName' in sqlblock column");
|
||||
}
|
||||
if (!isset($aColumnData['label']))
|
||||
{
|
||||
$this->m_aColumns[$sName]['label'] = $sName;
|
||||
}
|
||||
if (isset($aColumnData['drilldown']) && !empty($aColumnData['drilldown']))
|
||||
{
|
||||
// Check if the OQL is valid
|
||||
try
|
||||
{
|
||||
$this->m_aColumns[$sName]['filter'] = DBObjectSearch::FromOQL($aColumnData['drilldown']);
|
||||
}
|
||||
catch(OQLException $e)
|
||||
{
|
||||
unset($aColumnData['drilldown']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($this->m_sTitle) > 0)
|
||||
{
|
||||
$oPage->add("<h2>".Dict::S($this->m_sTitle)."</h2>\n");
|
||||
}
|
||||
|
||||
switch ($this->m_sType)
|
||||
{
|
||||
case 'bars':
|
||||
case 'pie':
|
||||
$aColNames = array_keys($this->m_aColumns);
|
||||
$sXColName = $aColNames[0];
|
||||
$sYColName = $aColNames[1];
|
||||
$aData = array();
|
||||
$aRows = array();
|
||||
while($aRow = CMDBSource::FetchArray($res))
|
||||
{
|
||||
$aData[$aRow[$sXColName]] = $aRow[$sYColName];
|
||||
$aRows[$aRow[$sXColName]] = $aRow;
|
||||
}
|
||||
$this->RenderChart($oPage, $sId, $aData, $this->m_aColumns[$sYColName]['drilldown'], $aRows);
|
||||
break;
|
||||
|
||||
default:
|
||||
case 'table':
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
if (!empty($sContext))
|
||||
{
|
||||
$sContext = '&'.$sContext;
|
||||
}
|
||||
$aDisplayConfig = array();
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
$aDisplayConfig[$sName] = array('label' => $aColumnData['label'], 'description' => '');
|
||||
}
|
||||
|
||||
$aDisplayData = array();
|
||||
while($aRow = CMDBSource::FetchArray($res))
|
||||
{
|
||||
$aSQLColNames = array_keys($aRow);
|
||||
$aDisplayRow = array();
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
if (isset($aColumnData['filter']))
|
||||
{
|
||||
$sFilter = $aColumnData['drilldown'];
|
||||
$sClass = $aColumnData['filter']->GetClass();
|
||||
$sFilter = str_replace('SELECT '.$sClass, '', $sFilter);
|
||||
foreach($aSQLColNames as $sColName)
|
||||
{
|
||||
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRow[$sColName] )."'", $sFilter);
|
||||
}
|
||||
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&oql_clause='.urlencode($sFilter).'&format=html'.$sContext;
|
||||
$aDisplayRow[$sName] = '<a href="'.$sURL.'">'.$aRow[$sName]."</a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$aDisplayRow[$sName] = $aRow[$sName];
|
||||
}
|
||||
}
|
||||
$aDisplayData[] = $aDisplayRow;
|
||||
}
|
||||
$oPage->table($aDisplayConfig, $aDisplayData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
protected function RenderChart($oPage, $sId, $aValues, $sDrillDown = '', $aRows = array())
|
||||
{
|
||||
// 1- Compute Open Flash Chart data
|
||||
//
|
||||
$aValueKeys = array();
|
||||
$index = 0;
|
||||
if ((count($aValues) > 0) && ($sDrillDown != ''))
|
||||
{
|
||||
$oFilter = DBObjectSearch::FromOQL($sDrillDown);
|
||||
$sClass = $oFilter->GetClass();
|
||||
$sOQLClause = str_replace('SELECT '.$sClass, '', $sDrillDown);
|
||||
$aSQLColNames = array_keys(current($aRows)); // Read the list of columns from the current (i.e. first) element of the array
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&format=html&'.$oAppContext->GetForLink().'&oql_clause=';
|
||||
}
|
||||
$aURLs = array();
|
||||
foreach($aValues as $key => $value)
|
||||
{
|
||||
// Make sure that values are integers (so that max() will work....)
|
||||
// and build an array of STRING with the keys (numeric keys are transformed into string by PHP :-(
|
||||
$aValues[$key] = (int)$value;
|
||||
$aValueKeys[] = (string)$key;
|
||||
|
||||
// Build the custom query for the 'drill down' on each element
|
||||
if ($sDrillDown != '')
|
||||
{
|
||||
$sFilter = $sOQLClause;
|
||||
foreach($aSQLColNames as $sColName)
|
||||
{
|
||||
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRows[$key][$sColName] )."'", $sFilter);
|
||||
$aURLs[$index] = $sURL.urlencode($sFilter);
|
||||
}
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
|
||||
$oChart = new open_flash_chart();
|
||||
|
||||
if ($this->m_sType == 'bars')
|
||||
{
|
||||
$oChartElement = new bar_glass();
|
||||
|
||||
if (count($aValues) > 0)
|
||||
{
|
||||
$maxValue = max($aValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
$maxValue = 1;
|
||||
}
|
||||
$oYAxis = new y_axis();
|
||||
$aMagicValues = array(1,2,5,10);
|
||||
$iMultiplier = 1;
|
||||
$index = 0;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
while($maxValue > $iTop)
|
||||
{
|
||||
$index++;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
if (($index % count($aMagicValues)) == 0)
|
||||
{
|
||||
$iMultiplier = $iMultiplier * 10;
|
||||
}
|
||||
}
|
||||
//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
|
||||
$oYAxis->set_range(0, $iTop, $iMultiplier);
|
||||
$oChart->set_y_axis( $oYAxis );
|
||||
$aBarValues = array();
|
||||
foreach($aValues as $iValue)
|
||||
{
|
||||
$oBarValue = new bar_value($iValue);
|
||||
$oBarValue->on_click("ofc_drilldown_{$sId}");
|
||||
$aBarValues[] = $oBarValue;
|
||||
}
|
||||
$oChartElement->set_values($aBarValues);
|
||||
//$oChartElement->set_values(array_values($aValues));
|
||||
$oXAxis = new x_axis();
|
||||
$oXLabels = new x_axis_labels();
|
||||
// set them vertical
|
||||
$oXLabels->set_vertical();
|
||||
// set the label text
|
||||
$oXLabels->set_labels($aValueKeys);
|
||||
// Add the X Axis Labels to the X Axis
|
||||
$oXAxis->set_labels( $oXLabels );
|
||||
$oChart->set_x_axis( $oXAxis );
|
||||
}
|
||||
else
|
||||
{
|
||||
$oChartElement = new pie();
|
||||
$oChartElement->set_start_angle( 35 );
|
||||
$oChartElement->set_animate( true );
|
||||
$oChartElement->set_tooltip( '#label# - #val# (#percent#)' );
|
||||
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
|
||||
|
||||
$aData = array();
|
||||
foreach($aValues as $sValue => $iValue)
|
||||
{
|
||||
$oPieValue = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!!
|
||||
$oPieValue->on_click("ofc_drilldown_{$sId}");
|
||||
$aData[] = $oPieValue;
|
||||
}
|
||||
|
||||
$oChartElement->set_values( $aData );
|
||||
$oChart->x_axis = null;
|
||||
}
|
||||
|
||||
// Title given in HTML
|
||||
//$oTitle = new title($this->m_sTitle);
|
||||
//$oChart->set_title($oTitle);
|
||||
$oChart->set_bg_colour('#FFFFFF');
|
||||
$oChart->add_element( $oChartElement );
|
||||
|
||||
$sData = $oChart->toPrettyString();
|
||||
$sData = json_encode($sData);
|
||||
|
||||
// 2- Declare the Javascript function that will render the chart data\
|
||||
//
|
||||
$oPage->add_script(
|
||||
<<< EOF
|
||||
function ofc_get_data_{$sId}()
|
||||
{
|
||||
return $sData;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
if (count($aURLs) > 0)
|
||||
{
|
||||
$sURLList = '';
|
||||
foreach($aURLs as $index => $sURL)
|
||||
{
|
||||
$sURLList .= "\taURLs[$index] = '".addslashes($sURL)."';\n";
|
||||
}
|
||||
|
||||
$oPage->add_script(
|
||||
<<< EOF
|
||||
function ofc_drilldown_{$sId}(index)
|
||||
{
|
||||
var aURLs = new Array();
|
||||
{$sURLList}
|
||||
var sURL = aURLs[index];
|
||||
|
||||
window.location.href = sURL; // Navigate !
|
||||
}
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
// 3- Insert the Open Flash chart
|
||||
//
|
||||
$oPage->add("<div id=\"$sId\"><div>\n");
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
swfobject.embedSWF( "../images/open-flash-chart.swf",
|
||||
"{$sId}",
|
||||
"100%", "300","9.0.0",
|
||||
"expressInstall.swf",
|
||||
{"get-data":"ofc_get_data_{$sId}", "id":"{$sId}"},
|
||||
{'wmode': 'transparent'}
|
||||
);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class DisplayTemplate
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -353,11 +353,15 @@ class ObjectDetailsTemplate extends DisplayTemplate
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$iSynchroFlags = $this->m_oObj->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sSynchroIcon = " <img id=\"synchro_$iInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
|
||||
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
|
||||
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
|
||||
$sTip .= "<div class='synchro-source'>";
|
||||
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
|
||||
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
|
||||
}
|
||||
$oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,15 +19,15 @@
|
||||
* Class UIExtKeyWidget
|
||||
* UI wdiget for displaying and editing external keys when
|
||||
* A simple drop-down list is not enough...
|
||||
*
|
||||
*
|
||||
* The layout is the following
|
||||
*
|
||||
*
|
||||
* +-- #label_<id> (input)-------+ +-----------+
|
||||
* | | | Browse... |
|
||||
* +-----------------------------+ +-----------+
|
||||
*
|
||||
*
|
||||
* And the popup dialog has the following layout:
|
||||
*
|
||||
*
|
||||
* +------------------- ac_dlg_<id> (div)-----------+
|
||||
* + +--- ds_<id> (div)---------------------------+ |
|
||||
* | | +------------- fs_<id> (form)------------+ | |
|
||||
@@ -54,20 +54,23 @@
|
||||
* | | +--------+ +-----+ | |
|
||||
* | +--------------------------------------------+ |
|
||||
* +------------------------------------------------+
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
|
||||
class UIExtKeyWidget
|
||||
class UIExtKeyWidget
|
||||
{
|
||||
const ENUM_OUTPUT_FORMAT_CSV = 'csv';
|
||||
const ENUM_OUTPUT_FORMAT_JSON = 'json';
|
||||
|
||||
protected $iId;
|
||||
protected $sTargetClass;
|
||||
protected $sAttCode;
|
||||
protected $bSearchMode;
|
||||
|
||||
|
||||
//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
|
||||
static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false)
|
||||
{
|
||||
@@ -94,36 +97,36 @@ class UIExtKeyWidget
|
||||
$this->sAttCode = $sAttCode;
|
||||
$this->bSearchMode = $bSearchMode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
|
||||
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
|
||||
{
|
||||
if (!is_null($bSearchMode))
|
||||
{
|
||||
$this->bSearchMode = $bSearchMode;
|
||||
}
|
||||
$sTitle = addslashes($sTitle);
|
||||
$sTitle = addslashes($sTitle);
|
||||
$oPage->add_linked_script('../js/extkeywidget.js');
|
||||
$oPage->add_linked_script('../js/forms-json-utils.js');
|
||||
|
||||
$bCreate = (!$this->bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
||||
|
||||
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
||||
$bExtensions = true;
|
||||
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
||||
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
|
||||
|
||||
$sHTMLValue = "<span style=\"white-space:nowrap\">"; // no wrap
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">";
|
||||
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
|
||||
if($this->bSearchMode)
|
||||
{
|
||||
$sWizHelper = 'null';
|
||||
$sWizHelperJSON = "''";
|
||||
$sJSSearchMode = 'true';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isset($aArgs['wizHelper']))
|
||||
@@ -141,16 +144,22 @@ class UIExtKeyWidget
|
||||
{
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
}
|
||||
elseif ($oAllowedValues->Count() < $iMaxComboLength)
|
||||
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
// Don't automatically launch the search if the table is huge
|
||||
$bDoSearch = !utils::IsHighCardinality($this->sTargetClass);
|
||||
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
||||
|
||||
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
|
||||
if (!$oAllowedValues->CountExceeds($iMaxComboLength))
|
||||
{
|
||||
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
||||
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
||||
switch($sDisplayStyle)
|
||||
{
|
||||
case 'radio':
|
||||
case 'radio_horizontal':
|
||||
case 'radio_vertical':
|
||||
$sValidationField = "<span id=\"v_{$this->iId}\"></span><span id=\"fstatus_{$this->iId}\"></span>";
|
||||
$sHTMLValue = '';
|
||||
$sValidationField = null;
|
||||
|
||||
$bVertical = ($sDisplayStyle != 'radio_horizontal');
|
||||
$bExtensions = false;
|
||||
$oAllowedValues->Rewind();
|
||||
@@ -158,54 +167,57 @@ class UIExtKeyWidget
|
||||
while($oObj = $oAllowedValues->Fetch())
|
||||
{
|
||||
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
|
||||
}
|
||||
$sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", $bMandatory, $bVertical, $sValidationField);
|
||||
}
|
||||
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
|
||||
$aEventsList[] ='change';
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
case 'list':
|
||||
default:
|
||||
$sSelectMode = 'true';
|
||||
|
||||
|
||||
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
|
||||
|
||||
$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
|
||||
|
||||
if ($this->bSearchMode)
|
||||
{
|
||||
if ($bSearchMultiple)
|
||||
{
|
||||
$sHTMLValue = "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
|
||||
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
|
||||
}
|
||||
|
||||
$oAllowedValues->Rewind();
|
||||
while($oObj = $oAllowedValues->Fetch())
|
||||
{
|
||||
$key = $oObj->GetKey();
|
||||
$display_value = $oObj->GetName();
|
||||
|
||||
|
||||
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') )
|
||||
{
|
||||
// When there is only once choice, select it by default
|
||||
$sSelected = ' selected';
|
||||
$sSelected = 'selected';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSelected = (is_array($value) && in_array($key, $value)) || ($value == $key) ? ' selected' : '';
|
||||
$sSelected = (is_array($value) && in_array($key, $value)) || ($value == $key) ? 'selected' : '';
|
||||
}
|
||||
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
|
||||
$sHTMLValue .= "<option value=\"$key\" $sSelected>$display_value</option>\n";
|
||||
}
|
||||
$sHTMLValue .= "</select>\n";
|
||||
$sHTMLValue .= "</div>\n";
|
||||
|
||||
if (($this->bSearchMode) && $bSearchMultiple)
|
||||
{
|
||||
$aOptions = array(
|
||||
@@ -221,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') } );
|
||||
@@ -233,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);
|
||||
@@ -253,20 +263,19 @@ 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 count=\"".$oAllowedValues->Count()."\" type=\"text\" id=\"label_$this->iId\" size=\"$iFieldSize\" value=\"$sDisplayValue\"/> ";
|
||||
$sHTMLValue .= "<img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.Search();\"/>";
|
||||
|
||||
$sHTMLValue .= "<input class=\"field_autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
|
||||
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?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";
|
||||
|
||||
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
||||
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
||||
// Scripts to start the autocomplete and bind some events to it
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
|
||||
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
||||
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
||||
$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter',bSearchMode:$JSSearchMode, json: function() { return $sWizHelperJSON; } }});
|
||||
$('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
|
||||
@@ -281,7 +290,7 @@ EOF
|
||||
}
|
||||
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
|
||||
{
|
||||
$sHTMLValue .= "<img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/> ";
|
||||
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/></span>";
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
if ($('#ac_tree_{$this->iId}').length == 0)
|
||||
@@ -293,7 +302,9 @@ EOF
|
||||
}
|
||||
if ($bCreate && $bExtensions)
|
||||
{
|
||||
$sHTMLValue .= "<img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.CreateObject();\"/> ";
|
||||
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
|
||||
|
||||
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"/></span>";
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
if ($('#ajax_{$this->iId}').length == 0)
|
||||
@@ -303,14 +314,17 @@ EOF
|
||||
EOF
|
||||
);
|
||||
}
|
||||
if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
|
||||
{
|
||||
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span><span id=\"fstatus_{$this->iId}\"></span>";
|
||||
}
|
||||
$sHTMLValue .= "</span>"; // end of no wrap
|
||||
$sHTMLValue .= "</div>";
|
||||
|
||||
// Note: This test is no longer necessary as we changed the markup to extract validation decoration in the standard .field_input_xxx container
|
||||
//if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
|
||||
//{
|
||||
$sHTMLValue .= "<span class=\"form_validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
|
||||
//}
|
||||
|
||||
return $sHTMLValue;
|
||||
}
|
||||
|
||||
|
||||
public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null)
|
||||
{
|
||||
$sHTML = '<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">';
|
||||
@@ -328,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";
|
||||
@@ -355,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)
|
||||
{
|
||||
@@ -365,39 +391,113 @@ EOF
|
||||
{
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
}
|
||||
|
||||
|
||||
$oFilter = DBObjectSearch::FromOQL($sFilter);
|
||||
if (strlen($sRemoteClass) > 0)
|
||||
{
|
||||
$oFilter->ChangeClass($sRemoteClass);
|
||||
}
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj)));
|
||||
|
||||
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
|
||||
$iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode);
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId)));
|
||||
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains)
|
||||
|
||||
/**
|
||||
* 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, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null)
|
||||
{
|
||||
if (is_null($sFilter))
|
||||
{
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
}
|
||||
|
||||
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
|
||||
$iCurrentExtKeyId = (is_null($oObj) || $this->sAttCode === '') ? 0 : $oObj->Get($this->sAttCode);
|
||||
|
||||
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
|
||||
$iMax = 150;
|
||||
$oValuesSet->SetLimit($iMax);
|
||||
$oValuesSet->SetSort(false);
|
||||
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$aValues = $oValuesSet->GetValues(array('this' => $oObj), $sContains);
|
||||
foreach($aValues as $sKey => $sFriendlyName)
|
||||
|
||||
if (empty($sOperation) || 'equals_start_with' == $sOperation)
|
||||
{
|
||||
$aValues = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals');
|
||||
asort($aValues);
|
||||
|
||||
$iMax -= count($aValues);
|
||||
if ($iMax > 0 ) {
|
||||
$oValuesSet->SetLimit($iMax);
|
||||
$aValuesStartWith = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with');
|
||||
asort($aValuesStartWith);
|
||||
foreach ($aValuesStartWith as $sKey => $sFriendlyName) {
|
||||
if (!isset($aValues[$sKey])) {
|
||||
$aValues[$sKey] = $sFriendlyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aValues = array();
|
||||
}
|
||||
|
||||
$iMax -= count($aValues);
|
||||
if ($iMax > 0 && (empty($sOperation) || 'contains' == $sOperation))
|
||||
{
|
||||
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
|
||||
$oValuesSet->SetLimit($iMax);
|
||||
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
|
||||
asort($aValuesContains);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the display name of the selected object, to fill back the autocomplete
|
||||
*/
|
||||
@@ -416,11 +516,56 @@ EOF
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the form to select a leaf class from the $this->sTargetClass (that should be abstract)
|
||||
* Note: Inspired from UILinksWidgetDirect::GetObjectCreationDialog()
|
||||
*
|
||||
* @param WebPage $oPage
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*/
|
||||
public function GetClassSelectionForm(WebPage $oPage)
|
||||
{
|
||||
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
|
||||
// and that the current user is allowed to create objects of this class
|
||||
$aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass);
|
||||
$aPossibleClasses = array();
|
||||
foreach($aSubClasses as $sCandidateClass)
|
||||
{
|
||||
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
|
||||
{
|
||||
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
|
||||
}
|
||||
}
|
||||
|
||||
$sDialogTitle = '';
|
||||
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
|
||||
$oPage->add('<form>');
|
||||
|
||||
$sClassLabel = MetaModel::GetName($this->sTargetClass);
|
||||
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
|
||||
$oPage->add('<nobr><select name="class">');
|
||||
asort($aPossibleClasses);
|
||||
foreach($aPossibleClasses as $sClassName => $sClassLabel)
|
||||
{
|
||||
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
|
||||
}
|
||||
$oPage->add('</select>');
|
||||
$oPage->add(' <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>');
|
||||
|
||||
$oPage->add('</form>');
|
||||
$oPage->add('</div></div></div>');
|
||||
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
|
||||
$oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form to create a new object of the 'target' class
|
||||
*/
|
||||
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);
|
||||
@@ -428,7 +573,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 != ''))
|
||||
{
|
||||
@@ -445,7 +590,7 @@ EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 3rd - set values from the page argument 'default'
|
||||
$oNewObj->UpdateObjectFromArg('default');
|
||||
|
||||
@@ -462,7 +607,7 @@ EOF
|
||||
$aFieldsComments[$sAttCode] = ' <img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
|
||||
}
|
||||
}
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
|
||||
$oPage->add('</div></div></div>');
|
||||
// $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
@@ -482,21 +627,12 @@ 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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
$oFilter = DBObjectSearch::FromOQL($sFilter);
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
|
||||
|
||||
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
|
||||
$sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);
|
||||
$this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue);
|
||||
@@ -505,7 +641,7 @@ EOF
|
||||
$oPage->add('</div>');
|
||||
$oPage->add("<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_tree_{$this->iId}').dialog('close');\"> ");
|
||||
$oPage->add("<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoHKOk();\">");
|
||||
|
||||
|
||||
$oPage->add('</div></div>');
|
||||
$oPage->add_ready_script("\$('#tree_$this->iId ul').treeview();\n");
|
||||
$oPage->add_ready_script("\$('#dlg_tree_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: true, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.OnHKResize, close: oACWidget_{$this->iId}.OnHKClose });\n");
|
||||
@@ -527,7 +663,7 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
return array('error' => implode(' ', $aErrors), 'id' => 0);
|
||||
return array('error' => implode(' ', $aErrors), 'id' => 0);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
@@ -550,7 +686,7 @@ EOF
|
||||
$aTree[$iParentId][$oObj->GetKey()] = $oObj->GetName();
|
||||
$aNodes[$oObj->GetKey()] = $oObj;
|
||||
}
|
||||
|
||||
|
||||
$aParents = array_keys($aTree);
|
||||
$aRoots = array();
|
||||
foreach($aParents as $id)
|
||||
@@ -565,7 +701,7 @@ EOF
|
||||
$this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue)
|
||||
{
|
||||
$bSelect = true;
|
||||
@@ -600,4 +736,3 @@ EOF
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,9 +20,8 @@
|
||||
* Class UIHTMLEditorWidget
|
||||
* UI wdiget for displaying and editing one-way encrypted passwords
|
||||
*
|
||||
* @author Phil Eddies
|
||||
* @author Romain Quetiez
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -65,7 +64,7 @@ class UIHTMLEditorWidget
|
||||
$sHelpText = $this->m_sHelpText;
|
||||
$sValidationField = $this->m_sValidationField;
|
||||
|
||||
$sHtmlValue = "<table><tr><td><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"10\" cols=\"10\" id=\"$iId\">$sValue</textarea></td><td>$sValidationField</td></tr></table>";
|
||||
$sHtmlValue = "<div class=\"field_input_zone field_input_html\"><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"10\" cols=\"10\" id=\"$iId\">$sValue</textarea></div>$sValidationField";
|
||||
|
||||
// Replace the text area with CKEditor
|
||||
// To change the default settings of the editor,
|
||||
@@ -99,9 +98,26 @@ class UIHTMLEditorWidget
|
||||
|
||||
// Could also be bound to 'instanceReady.ckeditor'
|
||||
$oPage->add_ready_script("$('#$iId').bind('validate', function(evt, sFormId) { return ValidateCKEditField('$iId', '', {$this->m_sMandatory}, sFormId, '') } );\n");
|
||||
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); $(this).data('ckeditorInstance').setReadOnly($(this).prop('disabled')); } );\n");
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$iId').bind('update', function(evt){
|
||||
BlockField('cke_$iId', $('#$iId').attr('disabled'));
|
||||
//Delayed execution - ckeditor must be properly initialized before setting readonly
|
||||
var retryCount = 0;
|
||||
var oMe = $('#$iId');
|
||||
var delayedSetReadOnly = function () {
|
||||
if (oMe.data('ckeditorInstance').editable() == undefined && retryCount++ < 10) {
|
||||
setTimeout(delayedSetReadOnly, retryCount * 100); //Wait a while longer each iteration
|
||||
}
|
||||
else
|
||||
{
|
||||
oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled'));
|
||||
}
|
||||
};
|
||||
setTimeout(delayedSetReadOnly, 50);
|
||||
});
|
||||
EOF
|
||||
);
|
||||
return $sHtmlValue;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Class UILinksWidgetDirect
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,14 @@ class UILinksWidgetDirect
|
||||
protected $sInputid;
|
||||
protected $sNameSuffix;
|
||||
protected $sLinkedClass;
|
||||
|
||||
|
||||
/**
|
||||
* UILinksWidgetDirect constructor.
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param string $sInputId
|
||||
* @param string $sNameSuffix
|
||||
*/
|
||||
public function __construct($sClass, $sAttCode, $sInputId, $sNameSuffix = '')
|
||||
{
|
||||
$this->sClass = $sClass;
|
||||
@@ -71,8 +78,15 @@ class UILinksWidgetDirect
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet|ormLinkSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
*/
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
switch($oLinksetDef->GetEditMode())
|
||||
@@ -115,8 +129,16 @@ class UILinksWidgetDirect
|
||||
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/);
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayAsBlock(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param bool $bDisplayMenu
|
||||
*/
|
||||
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sTargetClass = $oLinksetDef->GetLinkedClass();
|
||||
@@ -143,6 +165,7 @@ class UILinksWidgetDirect
|
||||
'target_attr' => $oLinksetDef->GetExtKeyToMe(),
|
||||
'object_id' => $oCurrentObj ? $oCurrentObj->GetKey() : null,
|
||||
'menu' => $bDisplayMenu,
|
||||
'menu_actions_target' => '_blank',
|
||||
'default' => $aDefaults,
|
||||
'table_id' => $this->sClass.'_'.$this->sAttCode,
|
||||
);
|
||||
@@ -151,46 +174,12 @@ class UILinksWidgetDirect
|
||||
$oBlock->Display($oPage, $this->sInputid, $aParams);
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
|
||||
$oValue->Rewind();
|
||||
$oPage->add('<table class="listContainer" id="'.$this->sInputid.'"><tr><td>');
|
||||
|
||||
$aData = array();
|
||||
while($oLinkObj = $oValue->Fetch())
|
||||
{
|
||||
$aRow = array();
|
||||
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
|
||||
foreach($this->aZlist as $sLinkedAttCode)
|
||||
{
|
||||
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
|
||||
}
|
||||
$aData[] = $aRow;
|
||||
}
|
||||
$oPage->table($aAttribs, $aData);
|
||||
$oPage->add('</td></tr></table>'); //listcontainer
|
||||
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
|
||||
$aLabels = array(
|
||||
'delete' => Dict::S('UI:Button:Delete'),
|
||||
// 'modify' => 'Modify...' ,
|
||||
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
|
||||
'remove' => Dict::S('UI:Button:Remove'),
|
||||
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
);
|
||||
$oContext = new ApplicationContext();
|
||||
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
|
||||
$sJSONLabels = json_encode($aLabels);
|
||||
$sJSONButtons = json_encode($aButtons);
|
||||
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
||||
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper });");
|
||||
}
|
||||
|
||||
public function GetObjectCreationDlg(WebPage $oPage, $sProposedRealClass = '')
|
||||
/**
|
||||
* @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
|
||||
@@ -215,14 +204,17 @@ class UILinksWidgetDirect
|
||||
$aKeys = array_keys($aPossibleClasses);
|
||||
$sRealClass = $aKeys[0];
|
||||
}
|
||||
|
||||
|
||||
if ($sRealClass != '')
|
||||
{
|
||||
$oPage->add("<h1>".MetaModel::GetClassIcon($sRealClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($sRealClass))."</h1>\n");
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
|
||||
$aFieldFlags = array( $sExtKeyToMe => OPT_ATT_HIDDEN);
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, null, array(), array('formPrefix' => $this->sInputid, 'noRelations' => true, 'fieldsFlags' => $aFieldFlags));
|
||||
$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
|
||||
{
|
||||
@@ -239,13 +231,89 @@ class UILinksWidgetDirect
|
||||
}
|
||||
$oPage->add('</div></div>');
|
||||
}
|
||||
|
||||
public function GetObjectsSelectionDlg($oPage, $oCurrentObj)
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param array $aButtons
|
||||
*/
|
||||
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
|
||||
$oValue->Rewind();
|
||||
$oPage->add('<table class="listContainer" id="'.$this->sInputid.'"><tr><td>');
|
||||
|
||||
$aData = array();
|
||||
while($oLinkObj = $oValue->Fetch())
|
||||
{
|
||||
$aRow = array();
|
||||
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
|
||||
foreach($this->aZlist as $sLinkedAttCode)
|
||||
{
|
||||
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
|
||||
}
|
||||
$aData[] = $aRow;
|
||||
}
|
||||
$oPage->table($aAttribs, $aData);
|
||||
$oPage->add('</td></tr></table>'); //listcontainer
|
||||
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
|
||||
$aLabels = array(
|
||||
'delete' => Dict::S('UI:Button:Delete'),
|
||||
// 'modify' => 'Modify...' ,
|
||||
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
|
||||
'remove' => Dict::S('UI:Button:Remove'),
|
||||
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
);
|
||||
$oContext = new ApplicationContext();
|
||||
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
|
||||
$sJSONLabels = json_encode($aLabels);
|
||||
$sJSONButtons = json_encode($aButtons);
|
||||
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
||||
// Don't automatically launch the search if the table is huge
|
||||
$bDoSearch = !utils::IsHighCardinality($this->sLinkedClass);
|
||||
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
||||
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper, do_search: $sJSDoSearch});");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
* @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);
|
||||
@@ -258,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";
|
||||
@@ -274,16 +355,15 @@ class UILinksWidgetDirect
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "</form>\n";
|
||||
$oPage->add($sHtml);
|
||||
//$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->sInputId}.SearchObjectsToAdd);");
|
||||
//$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix}').resize(oWidget{$this->siInputId}.UpdateSizes);");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for objects to be linked to the current object (i.e "remote" objects)
|
||||
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
||||
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of $this->sLinkedClass
|
||||
* @param array $aAlreadyLinked Array of indentifiers of objects which are already linke to the current object (or about to be linked)
|
||||
* @param DBObject $oCurrentObj The object currently being edited... if known...
|
||||
* @throws Exception
|
||||
*/
|
||||
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinked = array(), $oCurrentObj = null)
|
||||
{
|
||||
@@ -291,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);
|
||||
@@ -309,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(), '!=');
|
||||
@@ -321,6 +401,8 @@ class UILinksWidgetDirect
|
||||
}
|
||||
if ($oCurrentObj != null)
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
|
||||
$aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams());
|
||||
$oFilter->SetInternalParams($aArgs);
|
||||
}
|
||||
@@ -328,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);
|
||||
@@ -355,7 +441,14 @@ class UILinksWidgetDirect
|
||||
}
|
||||
return $aAttribs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string $sRealClass
|
||||
* @param array $aValues
|
||||
* @param int $iTempId
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetRow($oPage, $sRealClass, $aValues, $iTempId)
|
||||
{
|
||||
if ($sRealClass == '')
|
||||
@@ -367,7 +460,13 @@ class UILinksWidgetDirect
|
||||
|
||||
return $this->GetObjectRow($oPage, $oLinkObj, $iTempId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param $oLinkObj
|
||||
* @param int $iTempId
|
||||
* @return mixed
|
||||
*/
|
||||
protected function GetObjectRow($oPage, $oLinkObj, $iTempId)
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
@@ -383,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)
|
||||
{
|
||||
@@ -402,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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,12 +20,12 @@
|
||||
/**
|
||||
* Class UILinksWidget
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @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;
|
||||
@@ -48,12 +63,15 @@ class UILinksWidget
|
||||
$this->m_iInputId = $iInputId;
|
||||
$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();
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote);
|
||||
|
||||
/** @var AttributeExternalKey $oLinkingAttDef */
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote);
|
||||
$this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass();
|
||||
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass);
|
||||
@@ -92,16 +110,25 @@ class UILinksWidget
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A one-row form for editing a link record
|
||||
*
|
||||
* @param WebPage $oP Web page used for the ouput
|
||||
* @param DBObject $oLinkedObj The object to which all the elements of the linked set refer to
|
||||
* @param DBObject $oLinkedObj Remote object
|
||||
* @param mixed $linkObjOrId Either the object linked or a unique number for new link records to add
|
||||
* @param Hash $aArgs Extra context arguments
|
||||
* @return string The HTML fragment of the one-row form
|
||||
* @param array $aArgs Extra context arguments
|
||||
* @param DBObject $oCurrentObj The object to which all the elements of the linked set refer to
|
||||
* @param int $iUniqueId A unique identifier of new links
|
||||
* @param boolean $bReadOnly Display link as editable or read-only. Default is false (editable)
|
||||
*
|
||||
* @return array The HTML fragment of the one-row form
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId = null, $aArgs = array(), $oCurrentObj )
|
||||
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false)
|
||||
{
|
||||
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
|
||||
$aRow = array();
|
||||
@@ -115,16 +142,33 @@ class UILinksWidget
|
||||
$aArgs['prefix'] = $sPrefix;
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$key}";
|
||||
$aArgs['this'] = $linkObjOrId;
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$key\">";
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"$key\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
|
||||
$sSafeId = utils::GetSafeId($sFieldId);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $sSafeId, $sNameSuffix, 0, $aArgs);
|
||||
$aFieldsMap[$sFieldCode] = $sSafeId;
|
||||
}
|
||||
|
||||
if($bReadOnly)
|
||||
{
|
||||
$aRow['form::checkbox'] = "";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
|
||||
$aRow[$sFieldCode] = $sDisplayValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"$key\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$key\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
|
||||
$sSafeId = utils::GetSafeId($sFieldId);
|
||||
$sValue = $linkObjOrId->Get($sFieldCode);
|
||||
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'.
|
||||
cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId, $sNameSuffix, 0, $aArgs).
|
||||
'</div></div></div>';
|
||||
$aFieldsMap[$sFieldCode] = $sSafeId;
|
||||
}
|
||||
}
|
||||
|
||||
$sState = $linkObjOrId->GetState();
|
||||
}
|
||||
else
|
||||
@@ -135,78 +179,60 @@ class UILinksWidget
|
||||
// New link existing only in memory
|
||||
$oNewLinkObj = $linkObjOrId;
|
||||
$iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote);
|
||||
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
$linkObjOrId = -$iRemoteObjKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
$iRemoteObjKey = -$linkObjOrId;
|
||||
$iRemoteObjKey = $linkObjOrId;
|
||||
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
|
||||
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, -$linkObjOrId);
|
||||
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
}
|
||||
$sPrefix .= "[$linkObjOrId][";
|
||||
$sPrefix .= "[-$iUniqueId][";
|
||||
$sNameSuffix = "]"; // To make a tabular form
|
||||
$aArgs['prefix'] = $sPrefix;
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".(-$linkObjOrId);
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".($iUniqueId < 0 ? -$iUniqueId : $iUniqueId);
|
||||
$aArgs['this'] = $oNewLinkObj;
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$linkObjOrId\">";
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"\">";
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"-$iUniqueId\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId.']';
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.-$iUniqueId.']';
|
||||
$sSafeId = utils::GetSafeId($sFieldId);
|
||||
$sValue = $oNewLinkObj->Get($sFieldCode);
|
||||
$sDisplayValue = $oNewLinkObj->GetEditValue($sFieldCode);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $oNewLinkObj->Get($sFieldCode) /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, $sSafeId /* id */, $sNameSuffix, 0, $aArgs);
|
||||
$aRow[$sFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'.
|
||||
cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId /* id */, $sNameSuffix, 0, $aArgs).
|
||||
'</div></div></div>';
|
||||
$aFieldsMap[$sFieldCode] = $sSafeId;
|
||||
}
|
||||
$sState = '';
|
||||
$sJSDaysMin = json_encode(array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min')));
|
||||
$sJSMonthsShort = json_encode(array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short')));
|
||||
$iFirstDayOfWeek = (int) Dict::S('Calendar-FirstDayOfWeek');
|
||||
|
||||
$oP->add_script(
|
||||
<<<EOF
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dayNamesMin: $sJSDaysMin,
|
||||
monthNamesShort: $sJSMonthsShort,
|
||||
firstDay: $iFirstDayOfWeek
|
||||
});
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dayNamesMin: $sJSDaysMin,
|
||||
monthNamesShort: $sJSMonthsShort,
|
||||
firstDay: $iFirstDayOfWeek
|
||||
});
|
||||
// Rows created with ajax call need OnLinkAdded call.
|
||||
// Rows added before loading the form cannot call OnLinkAdded.
|
||||
if ($iUniqueId > 0)
|
||||
{
|
||||
$oP->add_script(
|
||||
<<<EOF
|
||||
PrepareWidgets();
|
||||
oWidget{$this->m_iInputId}.OnLinkAdded($iUniqueId, $iRemoteObjKey);
|
||||
EOF
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
|
||||
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
|
||||
|
||||
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
|
||||
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
|
||||
|
||||
if(!$bReadOnly)
|
||||
{
|
||||
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
|
||||
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
|
||||
|
||||
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
|
||||
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
|
||||
}
|
||||
|
||||
$iFieldsCount = count($aFieldsMap);
|
||||
$sJsonFieldsMap = json_encode($aFieldsMap);
|
||||
|
||||
@@ -227,7 +253,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)
|
||||
{
|
||||
@@ -245,8 +275,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)
|
||||
@@ -283,48 +313,65 @@ EOF
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the HTML fragment corresponding to the linkset editing widget
|
||||
* @param WebPage $oP The web page used for all the output
|
||||
* @param DBObjectSet The initial value of the linked set
|
||||
* @param Hash $aArgs Extra context arguments
|
||||
*
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject|ormLinkSet $oValue
|
||||
* @param array $aArgs Extra context arguments
|
||||
* @param string $sFormPrefix prefix of the fields in the current form
|
||||
* @param DBObject $oCurrentObj the current object to which the linkset is related
|
||||
*
|
||||
* @return string The HTML fragment to be inserted into the page
|
||||
* @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
|
||||
while($oCurrentLink = $oValue->Fetch())
|
||||
{
|
||||
$aRow = array();
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote));
|
||||
if ($oCurrentLink->IsNew())
|
||||
{
|
||||
$key = -$oLinkedObj->GetKey();
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
$key = $oCurrentLink->GetKey();
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
|
||||
}
|
||||
// We try to retrieve the remote object as usual
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */);
|
||||
// If successful, it means that we can edit its link
|
||||
if($oLinkedObj !== null)
|
||||
{
|
||||
$bReadOnly = false;
|
||||
}
|
||||
// Else we retrieve it without restrictions (silos) and will display its link as readonly
|
||||
else
|
||||
{
|
||||
$bReadOnly = true;
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */, true);
|
||||
}
|
||||
|
||||
if ($oCurrentLink->IsNew())
|
||||
{
|
||||
$key = -($iAddedId++);
|
||||
}
|
||||
else
|
||||
{
|
||||
$key = $oCurrentLink->GetKey();
|
||||
}
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly);
|
||||
}
|
||||
$sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm);
|
||||
$sDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
|
||||
// Don't automatically launch the search if the table is huge
|
||||
$bDoSearch = !utils::IsHighCardinality($this->m_sRemoteClass);
|
||||
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
||||
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
||||
$oPage->add_ready_script(<<<EOF
|
||||
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}');
|
||||
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}', $sJSDoSearch);
|
||||
oWidget{$this->m_iInputId}.Init();
|
||||
$('#{$this->m_iInputId}').bind('update_value', function() { $(this).val(oWidget{$this->m_iInputId}.GetUpdatedValue()); })
|
||||
EOF
|
||||
);
|
||||
$sHtmlValue .= "<span style=\"float:left;\"> <img src=\"../images/tv-item-last.gif\"> <input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >";
|
||||
@@ -332,16 +379,27 @@ EOF
|
||||
$sHtmlValue .= "<span style=\"clear:both;\"><p> </p></span>\n";
|
||||
$sHtmlValue .= "</div>\n";
|
||||
$oPage->add_at_the_end("<div id=\"dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}\"></div>"); // To prevent adding forms inside the main form
|
||||
return $sHtmlValue;
|
||||
return $sHtmlValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
*
|
||||
* @return string
|
||||
* @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;
|
||||
@@ -353,21 +411,60 @@ 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);
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
|
||||
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);
|
||||
@@ -379,11 +476,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 != '')
|
||||
{
|
||||
@@ -397,51 +500,35 @@ EOF
|
||||
}
|
||||
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0)
|
||||
{
|
||||
// Positive IDs correspond to existing link records
|
||||
// negative IDs correspond to "remote" objects to be linked
|
||||
$aLinkIds = array();
|
||||
$aRemoteObjIds = array();
|
||||
foreach($aAlreadyLinkedIds as $iId)
|
||||
{
|
||||
if ($iId > 0)
|
||||
{
|
||||
$aLinkIds[] = $iId;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRemoteObjIds[] = -$iId;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($aLinkIds) >0)
|
||||
{
|
||||
// Search for the links to find to which "remote" object they are linked
|
||||
$oLinkFilter = new DBObjectSearch($this->m_sLinkedClass);
|
||||
$oLinkFilter->AddCondition('id', $aLinkIds, 'IN');
|
||||
$oLinkSet = new CMDBObjectSet($oLinkFilter);
|
||||
while($oLink = $oLinkSet->Fetch())
|
||||
{
|
||||
$aRemoteObjIds[] = $oLink->Get($this->m_sExtKeyToRemote);
|
||||
}
|
||||
}
|
||||
$oFilter->AddCondition('id', $aRemoteObjIds, 'NOTIN');
|
||||
$oFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
|
||||
}
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
$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
|
||||
}
|
||||
|
||||
public function DoAddObjects(WebPage $oP, $oFullSetFilter, $oCurrentObj)
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
$iAdditionId = $iMaxAddedId + 1;
|
||||
foreach($aLinkedObjectIds as $iObjectId)
|
||||
{
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId);
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
|
||||
if (is_object($oLinkedObj))
|
||||
{
|
||||
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId, array(), $oCurrentObj ); // Not yet created link get negative Ids
|
||||
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId));
|
||||
$aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
|
||||
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iAdditionId));
|
||||
$iAdditionId++;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -449,11 +536,15 @@ 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)
|
||||
{
|
||||
@@ -472,7 +563,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
|
||||
@@ -486,10 +576,33 @@ EOF
|
||||
|
||||
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue))
|
||||
{
|
||||
$oSearch->AddCondition($sAttCode, $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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -58,9 +58,15 @@ class UIPasswordWidget
|
||||
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
|
||||
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
|
||||
$sHtmlValue = '';
|
||||
$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/> <span class="form_validation" id="v_'.$this->iId.'"></span><span id="fstatus_'.$this->iId.'"></span><br/>';
|
||||
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/> '.Dict::S('UI:PasswordConfirm').' <input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
|
||||
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword">';
|
||||
$sHtmlValue .= '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
|
||||
$sHtmlValue .= '<span>'.Dict::S('UI:PasswordConfirm').'</span>';
|
||||
$sHtmlValue .= '<input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
|
||||
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
|
||||
$sHtmlValue .= '</div>';
|
||||
|
||||
$sHtmlValue .= '<span class="form_validation" id="v_'.$this->iId.'"></span><span class="field_status" id="fstatus_'.$this->iId.'"></span>';
|
||||
|
||||
$oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate
|
||||
$oPage->add_ready_script("$('#$this->iId').bind('keyup change validate', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate
|
||||
|
||||
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,423 +0,0 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Class UILinksWizard
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class UILinksWizard
|
||||
{
|
||||
protected $m_sClass;
|
||||
protected $m_sLinkageAttr;
|
||||
protected $m_iObjectId;
|
||||
protected $m_sLinkedClass;
|
||||
protected $m_sLinkingAttCode;
|
||||
protected $m_aEditableFields;
|
||||
protected $m_aTableConfig;
|
||||
|
||||
public function __construct($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass = '')
|
||||
{
|
||||
$this->m_sClass = $sClass;
|
||||
$this->m_sLinkageAttr = $sLinkageAttr;
|
||||
$this->m_iObjectId = $iObjectId;
|
||||
$this->m_sLinkedClass = $sLinkedClass; // Will try to guess below, if it's empty
|
||||
$this->m_sLinkingAttCode = ''; // Will be filled once we've found the attribute corresponding to the linked class
|
||||
|
||||
$this->m_aEditableFields = array();
|
||||
$this->m_aTableConfig = array();
|
||||
$this->m_aTableConfig['form::checkbox'] = array( 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onChange=\"var value = this.checked; $('.selection').each( function() { this.checked = value; } );OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+'));
|
||||
foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef->IsExternalKey() && ($sAttCode != $this->m_sLinkageAttr))
|
||||
{
|
||||
if (empty($this->m_sLinkedClass))
|
||||
{
|
||||
// This is a class of objects we can manage !
|
||||
// Since nothing was specify, any class will do !
|
||||
$this->m_sLinkedClass = $oAttDef->GetTargetClass();
|
||||
$this->m_sLinkingAttCode = $sAttCode;
|
||||
}
|
||||
else if ($this->m_sLinkedClass == $oAttDef->GetTargetClass())
|
||||
{
|
||||
// This is the class of objects we want to manage !
|
||||
$this->m_sLinkingAttCode = $sAttCode;
|
||||
}
|
||||
}
|
||||
else if ( (!$oAttDef->IsExternalKey()) && (!$oAttDef->IsExternalField()))
|
||||
{
|
||||
$this->m_aEditableFields[] = $sAttCode;
|
||||
$this->m_aTableConfig[$sAttCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
|
||||
}
|
||||
}
|
||||
if (empty($this->m_sLinkedClass))
|
||||
{
|
||||
throw( new Exception(Dict::Format('UI:Error:IncorrectLinkDefinition_LinkedClass_Class', $sLinkedClass, $sClass)));
|
||||
}
|
||||
foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$this->m_aTableConfig['static::'.$sFieldCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
public function Display(WebPage $oP, $aExtraParams = array())
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId);
|
||||
|
||||
$oP->set_title("iTop - ".MetaModel::GetName($this->m_sLinkedClass)." objects linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetRawName());
|
||||
$oP->add("<div class=\"wizContainer\">\n");
|
||||
$oP->add("<form method=\"post\">\n");
|
||||
$oP->add("<div class=\"page_header\">\n");
|
||||
$oP->add("<input type=\"hidden\" id=\"linksToRemove\" name=\"linksToRemove\" value=\"\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"do_modify_links\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"class\" value=\"{$this->m_sClass}\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"linkage\" value=\"{$this->m_sLinkageAttr}\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"object_id\" value=\"{$this->m_iObjectId}\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"linking_attcode\" value=\"{$this->m_sLinkingAttCode}\">\n");
|
||||
$oP->add("<h1>".Dict::Format('UI:ManageObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."</h1>\n");
|
||||
$oP->add("</div>\n");
|
||||
$oP->add_script(
|
||||
<<<EOF
|
||||
function OnSelectChange()
|
||||
{
|
||||
var nbChecked = $('.selection:checked').length;
|
||||
if (nbChecked > 0)
|
||||
{
|
||||
$('#btnRemove').removeAttr('disabled');
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#btnRemove').attr('disabled','disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function RemoveSelected()
|
||||
{
|
||||
$('.selection:checked').each(
|
||||
function()
|
||||
{
|
||||
$('#linksToRemove').val($('#linksToRemove').val() + ' ' + this.value);
|
||||
$('#row_'+this.value).remove();
|
||||
}
|
||||
);
|
||||
// Disable the button since all the selected items have been removed
|
||||
$('#btnRemove').attr('disabled','disabled');
|
||||
// Re-run the zebra plugin to properly highlight the remaining lines
|
||||
$('.listResults').trigger('update');
|
||||
|
||||
}
|
||||
|
||||
function AddObjects()
|
||||
{
|
||||
// TO DO: compute the list of objects already linked with the current Object
|
||||
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { 'operation': 'addObjects',
|
||||
'class': '{$this->m_sClass}',
|
||||
'linkageAttr': '{$this->m_sLinkageAttr}',
|
||||
'linkedClass': '{$this->m_sLinkedClass}',
|
||||
'objectId': '{$this->m_iObjectId}'
|
||||
},
|
||||
function(data)
|
||||
{
|
||||
$('#ModalDlg').html(data);
|
||||
dlgWidth = $(document).width() - 100;
|
||||
$('#ModalDlg').css('width', dlgWidth);
|
||||
$('#ModalDlg').css('left', 50);
|
||||
$('#ModalDlg').css('top', 50);
|
||||
$('#ModalDlg').dialog( 'open' );
|
||||
},
|
||||
'html'
|
||||
);
|
||||
}
|
||||
|
||||
function SearchObjectsToAdd(currentFormId)
|
||||
{
|
||||
var theMap = { 'class': '{$this->m_sClass}',
|
||||
'linkageAttr': '{$this->m_sLinkageAttr}',
|
||||
'linkedClass': '{$this->m_sLinkedClass}',
|
||||
'objectId': '{$this->m_iObjectId}'
|
||||
}
|
||||
if ($('#'+currentFormId+' :input[name=class]').val() != undefined)
|
||||
{
|
||||
theMap.linkedClass = $('#'+currentFormId+' :input[name=class]').val();
|
||||
}
|
||||
// Gather the parameters from the search form
|
||||
$('#'+currentFormId+' :input').each(
|
||||
function(i)
|
||||
{
|
||||
if (this.name != '')
|
||||
{
|
||||
theMap[this.name] = this.value;
|
||||
}
|
||||
}
|
||||
);
|
||||
theMap['operation'] = 'searchObjectsToAdd';
|
||||
|
||||
// Run the query and display the results
|
||||
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
|
||||
function(data)
|
||||
{
|
||||
$('#SearchResultsToAdd').html(data);
|
||||
$('#SearchResultsToAdd .listResults').tablesorter( { headers: {0: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
|
||||
|
||||
},
|
||||
'html'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function DoAddObjects(currentFormId)
|
||||
{
|
||||
var theMap = { 'class': '{$this->m_sClass}',
|
||||
'linkageAttr': '{$this->m_sLinkageAttr}',
|
||||
'linkedClass': '{$this->m_sLinkedClass}',
|
||||
'objectId': '{$this->m_iObjectId}'
|
||||
}
|
||||
|
||||
// Gather the parameters from the search form
|
||||
$('#'+currentFormId+' :input').each(
|
||||
function(i)
|
||||
{
|
||||
if ( (this.name != '') && ((this.type != 'checkbox') || (this.checked)) )
|
||||
{
|
||||
//console.log(this.type);
|
||||
arrayExpr = /\[\]$/;
|
||||
if (arrayExpr.test(this.name))
|
||||
{
|
||||
// Array
|
||||
if (theMap[this.name] == undefined)
|
||||
{
|
||||
theMap[this.name] = new Array();
|
||||
}
|
||||
theMap[this.name].push(this.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
theMap[this.name] = this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
theMap['operation'] = 'doAddObjects';
|
||||
|
||||
// Run the query and display the results
|
||||
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
|
||||
function(data)
|
||||
{
|
||||
//console.log('Data: ' + data);
|
||||
if (data != '')
|
||||
{
|
||||
$('#empty_row').remove();
|
||||
}
|
||||
$('.listResults tbody').append(data);
|
||||
$('.listResults').trigger('update');
|
||||
$('.listResults').tablesorter( { headers: {0: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
|
||||
},
|
||||
'html'
|
||||
);
|
||||
$('#ModalDlg').dialog('close');
|
||||
return false;
|
||||
}
|
||||
|
||||
function InitForm()
|
||||
{
|
||||
// make sure that the form is clean
|
||||
$('.selection').each( function() { this.checked = false; });
|
||||
$('#btnRemove').attr('disabled','disabled');
|
||||
$('#linksToRemove').val('');
|
||||
}
|
||||
|
||||
function SubmitHook()
|
||||
{
|
||||
var the_form = this;
|
||||
SearchObjectsToAdd(the_form.id);
|
||||
return false;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$oP->add_ready_script("InitForm();");
|
||||
$oFilter = new DBObjectSearch($this->m_sClass);
|
||||
$oFilter->AddCondition($this->m_sLinkageAttr, $this->m_iObjectId, '=');
|
||||
$oSet = new DBObjectSet($oFilter);
|
||||
$aForm = array();
|
||||
while($oCurrentLink = $oSet->Fetch())
|
||||
{
|
||||
$aRow = array();
|
||||
$key = $oCurrentLink->GetKey();
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $oCurrentLink->Get($this->m_sLinkingAttCode));
|
||||
|
||||
$aForm[$key] = $this->GetFormRow($oP, $oLinkedObj, $oCurrentLink);
|
||||
}
|
||||
//var_dump($aTableLabels);
|
||||
//var_dump($aForm);
|
||||
$this->DisplayFormTable($oP, $this->m_aTableConfig, $aForm);
|
||||
$oP->add("<span style=\"float:left;\"> <img src=\"../images/tv-item-last.gif\"> <input id=\"btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"RemoveSelected();\" >");
|
||||
$oP->add(" <input id=\"btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sLinkedClass))."\" onClick=\"AddObjects();\"></span>\n");
|
||||
$oP->add("<span style=\"float:right;\"><input id=\"btnCancel\" type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"BackToDetails('".$sTargetClass."', ".$this->m_iObjectId.", '', '');\">");
|
||||
$oP->add(" <input id=\"btnOk\" type=\"submit\" value=\"".Dict::S('UI:Button:Ok')."\"></span>\n");
|
||||
$oP->add("<span style=\"clear:both;\"><p> </p></span>\n");
|
||||
$oP->add("</div>\n");
|
||||
$oP->add("</form>\n");
|
||||
if (isset($aExtraParams['StartWithAdd']) && ($aExtraParams['StartWithAdd']))
|
||||
{
|
||||
$oP->add_ready_script("AddObjects();");
|
||||
}
|
||||
}
|
||||
|
||||
protected function GetFormRow($oP, $oLinkedObj, $currentLink = null )
|
||||
{
|
||||
$aRow = array();
|
||||
if(is_object($currentLink))
|
||||
{
|
||||
$key = $currentLink->GetKey();
|
||||
$sNameSuffix = "[$key]"; // To make a tabular form
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onChange=\"OnSelectChange();\" value=\"$key\">";
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"linkId[$key]\" value=\"$key\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, $currentLink->Get($sFieldCode), '' /* DisplayValue */, $key, $sNameSuffix);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// form for creating a new record
|
||||
$sNameSuffix = "[$currentLink]"; // To make a tabular form
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onChange=\"OnSelectChange();\" value=\"$currentLink\">";
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"linkId[$currentLink]\" value=\"$currentLink\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, '' /* id */, $sNameSuffix);
|
||||
}
|
||||
}
|
||||
foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode)
|
||||
{
|
||||
$aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode);
|
||||
}
|
||||
return $aRow;
|
||||
}
|
||||
|
||||
protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
|
||||
{
|
||||
$oP->add("<table class=\"listResults\">\n");
|
||||
// Header
|
||||
$oP->add("<thead>\n");
|
||||
$oP->add("<tr>\n");
|
||||
foreach($aConfig as $sName=>$aDef)
|
||||
{
|
||||
$oP->add("<th title=\"".$aDef['description']."\">".$aDef['label']."</th>\n");
|
||||
}
|
||||
$oP->add("</tr>\n");
|
||||
$oP->add("</thead>\n");
|
||||
|
||||
// Content
|
||||
$oP->add("</tbody>\n");
|
||||
if (count($aData) == 0)
|
||||
{
|
||||
$oP->add("<tr id=\"empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."</td></td>");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($aData as $iRowId => $aRow)
|
||||
{
|
||||
$this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
|
||||
}
|
||||
}
|
||||
$oP->add("</tbody>\n");
|
||||
|
||||
// Footer
|
||||
$oP->add("</table>\n");
|
||||
}
|
||||
|
||||
protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId)
|
||||
{
|
||||
$oP->add("<tr id=\"row_$iRowId\">\n");
|
||||
foreach($aConfig as $sName=>$void)
|
||||
{
|
||||
$oP->add("<td>".$aRow[$sName]."</td>\n");
|
||||
}
|
||||
$oP->add("</tr>\n");
|
||||
}
|
||||
|
||||
public function DisplayAddForm(WebPage $oP)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId);
|
||||
$oP->add("<div class=\"wizContainer\">\n");
|
||||
//$oP->add("<div class=\"page_header\">\n");
|
||||
//$oP->add("<h1>".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."</h1>\n");
|
||||
//$oP->add("</div>\n");
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sLinkedClass);
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$oBlock->Display($oP, 'SearchFormToAdd', array('open' => true));
|
||||
$oP->Add("<form id=\"ObjectsAddForm\" OnSubmit=\"return DoAddObjects(this.id);\">\n");
|
||||
$oP->Add("<div id=\"SearchResultsToAdd\">\n");
|
||||
$oP->Add("<div style=\"height: 100px; background: #fff;border-color:#F6F6F1 #E6E6E1 #E6E6E1 #F6F6F1; border-style:solid; border-width:3px; text-align: center; vertical-align: center;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n");
|
||||
$oP->Add("</div>\n");
|
||||
$oP->add("<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ModalDlg').dialog('close');\"> <input type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">");
|
||||
$oP->Add("</div>\n");
|
||||
$oP->Add("</form>\n");
|
||||
$oP->add_ready_script("$('#ModalDlg').dialog('option', {title:'".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."'});");
|
||||
$oP->add_ready_script("$('div#SearchFormToAdd form').bind('submit.uilinksWizard', SubmitHook);");
|
||||
}
|
||||
|
||||
public function SearchObjectsToAdd(WebPage $oP)
|
||||
{
|
||||
//$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sLinkedClass);
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true)); // Don't display the 'Actions' menu on the results
|
||||
}
|
||||
|
||||
public function DoAddObjects(WebPage $oP, $aLinkedObjectIds = array())
|
||||
{
|
||||
//$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
|
||||
//$sTargetClass = $oAttDef->GetTargetClass();
|
||||
//$oP->Add("<!-- nb of objects to add: ".count($aLinkedObjectIds)." -->\n"); // Just to make sure it's not empty
|
||||
$aTable = array();
|
||||
foreach($aLinkedObjectIds as $iObjectId)
|
||||
{
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $iObjectId);
|
||||
if (is_object($oLinkedObj))
|
||||
{
|
||||
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids
|
||||
$this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo Dict::Format('UI:Error:Object_Class_Id_NotFound', $this->m_sLinkedClass, $iObjectId);
|
||||
}
|
||||
}
|
||||
//var_dump($aTable);
|
||||
//$oP->Add("<!-- end of added list -->\n"); // Just to make sure it's not empty
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Store and retrieve user's preferences (i.e persistent per user settings)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
require_once(APPROOT.'/core/dbobject.class.php');
|
||||
@@ -50,7 +50,7 @@ class appUserPreferences extends DBObject
|
||||
self::Load();
|
||||
}
|
||||
$aPrefs = self::$oUserPrefs->Get('preferences');
|
||||
if (isset($aPrefs[$sCode]))
|
||||
if (array_key_exists($sCode, $aPrefs))
|
||||
{
|
||||
return $aPrefs[$sCode];
|
||||
}
|
||||
@@ -72,9 +72,16 @@ class appUserPreferences extends DBObject
|
||||
self::Load();
|
||||
}
|
||||
$aPrefs = self::$oUserPrefs->Get('preferences');
|
||||
$aPrefs[$sCode] = $sValue;
|
||||
self::$oUserPrefs->Set('preferences', $aPrefs);
|
||||
self::Save();
|
||||
if (array_key_exists($sCode, $aPrefs) && ($aPrefs[$sCode] === $sValue))
|
||||
{
|
||||
// Do not write it again
|
||||
}
|
||||
else
|
||||
{
|
||||
$aPrefs[$sCode] = $sValue;
|
||||
self::$oUserPrefs->Set('preferences', $aPrefs);
|
||||
self::Save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,7 +155,9 @@ class appUserPreferences extends DBObject
|
||||
{
|
||||
if (self::$oUserPrefs->IsModified())
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
self::$oUserPrefs->DBUpdate();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +181,9 @@ class appUserPreferences extends DBObject
|
||||
$oObj->Set('preferences', array()); // Default preferences: an empty array
|
||||
try
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
$oObj->DBInsert();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
@@ -207,7 +218,8 @@ class appUserPreferences extends DBObject
|
||||
*/
|
||||
public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
$this->DBDelete($oDeletionPlan);
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
use Html2Text\Html2Text;
|
||||
use Leafo\ScssPhp\Compiler;
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -22,7 +22,7 @@ use Leafo\ScssPhp\Compiler;
|
||||
/**
|
||||
* Static class utils
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -140,6 +140,81 @@ class utils
|
||||
}
|
||||
}
|
||||
|
||||
protected static $bPageMode = null;
|
||||
/**
|
||||
* @var boolean[]
|
||||
*/
|
||||
protected static $aModes = array();
|
||||
|
||||
public static function InitArchiveMode()
|
||||
{
|
||||
if (isset($_SESSION['archive_mode']))
|
||||
{
|
||||
$iDefault = $_SESSION['archive_mode'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$iDefault = 0;
|
||||
}
|
||||
// Read and record the value for switching the archive mode
|
||||
$iCurrent = self::ReadParam('with-archive', $iDefault);
|
||||
if (isset($_SESSION))
|
||||
{
|
||||
$_SESSION['archive_mode'] = $iCurrent;
|
||||
}
|
||||
// Read and use the value for the current page (web services)
|
||||
$iCurrent = self::ReadParam('with_archive', $iCurrent, true);
|
||||
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);
|
||||
}
|
||||
|
||||
public static function PopArchiveMode()
|
||||
{
|
||||
array_pop(self::$aModes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean true if archive mode is enabled
|
||||
*/
|
||||
public static function IsArchiveMode()
|
||||
{
|
||||
if (count(self::$aModes) > 0)
|
||||
{
|
||||
$bRet = end(self::$aModes);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self::$bPageMode === null)
|
||||
{
|
||||
self::InitArchiveMode();
|
||||
}
|
||||
$bRet = self::$bPageMode;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to be called by the GUI and define if the user will see obsolete data (otherwise, the user will have to dig further)
|
||||
* @return bool
|
||||
*/
|
||||
public static function ShowObsoleteData()
|
||||
{
|
||||
$bDefault = MetaModel::GetConfig()->Get('obsolescence.show_obsolete_data'); // default is false
|
||||
$bShow = appUserPreferences::GetPref('show_obsolete_data', $bDefault);
|
||||
if (static::IsArchiveMode())
|
||||
{
|
||||
$bShow = true;
|
||||
}
|
||||
return $bShow;
|
||||
}
|
||||
|
||||
public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false, $sSanitizationFilter = 'parameter')
|
||||
{
|
||||
@@ -236,7 +311,7 @@ class utils
|
||||
switch($sSanitizationFilter)
|
||||
{
|
||||
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':
|
||||
@@ -338,8 +413,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)
|
||||
{
|
||||
@@ -373,6 +450,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 === '')
|
||||
{
|
||||
throw new CoreException('selectionMode is mandatory');
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -437,6 +559,45 @@ class utils
|
||||
return $iReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
|
||||
*
|
||||
* @param type $value
|
||||
* @return string
|
||||
*/
|
||||
public static function BytesToFriendlyFormat($value)
|
||||
{
|
||||
$sReturn = '';
|
||||
// Kilobytes
|
||||
if ($value >= 1024)
|
||||
{
|
||||
$sReturn = 'K';
|
||||
$value = $value / 1024;
|
||||
}
|
||||
// Megabytes
|
||||
if ($value >= 1024)
|
||||
{
|
||||
$sReturn = 'M';
|
||||
$value = $value / 1024;
|
||||
}
|
||||
// Gigabytes
|
||||
if ($value >= 1024)
|
||||
{
|
||||
$sReturn = 'G';
|
||||
$value = $value / 1024;
|
||||
}
|
||||
// Terabytes
|
||||
if ($value >= 1024)
|
||||
{
|
||||
$sReturn = 'T';
|
||||
$value = $value / 1024;
|
||||
}
|
||||
|
||||
$value = round($value, 1);
|
||||
|
||||
return $value . '' . $sReturn . 'B';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert a string to a date, given a format specification. It replaces strtotime which does not allow for specifying a date in a french format (for instance)
|
||||
* Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s')
|
||||
@@ -497,23 +658,49 @@ 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)
|
||||
{
|
||||
$sConfigFile = self::GetConfigFilePath();
|
||||
if (file_exists($sConfigFile))
|
||||
{
|
||||
self::$oConfig = new Config($sConfigFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// When executing the setup, the config file may be still missing
|
||||
self::$oConfig = new Config();
|
||||
}
|
||||
self::$oConfig = MetaModel::GetConfig();
|
||||
|
||||
if (self::$oConfig == null)
|
||||
{
|
||||
$sConfigFile = self::GetConfigFilePath();
|
||||
if (!file_exists($sConfigFile))
|
||||
{
|
||||
$sConfigFile = self::GetConfigFilePath('production');
|
||||
if (!file_exists($sConfigFile))
|
||||
{
|
||||
$sConfigFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -661,7 +848,7 @@ class utils
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the CAS client
|
||||
*/
|
||||
@@ -813,7 +1000,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
|
||||
@@ -843,14 +1030,18 @@ class utils
|
||||
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')).")");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
|
||||
$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')");
|
||||
if (extension_loaded('gd'))
|
||||
{
|
||||
// PDF export requires GD
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
|
||||
}
|
||||
}
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
|
||||
|
||||
break;
|
||||
@@ -946,8 +1137,7 @@ class utils
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
@@ -955,33 +1145,67 @@ class utils
|
||||
return $sUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
|
||||
* require_once(__DIR__.'/../../approot.inc.php');
|
||||
*
|
||||
* @return string ...
|
||||
*/
|
||||
/**
|
||||
* 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');
|
||||
* ```
|
||||
*
|
||||
* @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'))
|
||||
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';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1275,10 +1499,21 @@ class utils
|
||||
*/
|
||||
public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight)
|
||||
{
|
||||
// If image size smaller than maximums, we do nothing
|
||||
if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight))
|
||||
{
|
||||
return $oImage;
|
||||
}
|
||||
|
||||
|
||||
// If gd extension is not loaded, we put a warning in the log and return the image as is
|
||||
if (extension_loaded('gd') === false)
|
||||
{
|
||||
IssueLog::Warning('Image could not be resized as the "gd" extension does not seem to be loaded. It will remain as ' . $iWidth . 'x' . $iHeight . ' instead of ' . $iMaxImageWidth . 'x' . $iMaxImageHeight);
|
||||
return $oImage;
|
||||
}
|
||||
|
||||
|
||||
switch($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
@@ -1342,5 +1577,368 @@ class utils
|
||||
return $oResampledImage;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 128 bit UUID in the format: {########-####-####-####-############}
|
||||
*
|
||||
* Note: this method can be run from the command line as well as from the web server.
|
||||
* Note2: this method is not cryptographically secure! If you need a cryptographically secure value
|
||||
* consider using open_ssl or PHP 7 methods.
|
||||
* @param string $sPrefix
|
||||
* @return string
|
||||
*/
|
||||
static public function CreateUUID($sPrefix = '')
|
||||
{
|
||||
$uid = uniqid("", true);
|
||||
$data = $sPrefix;
|
||||
$data .= __FILE__;
|
||||
$data .= mt_rand();
|
||||
$hash = strtoupper(hash('ripemd128', $uid . md5($data)));
|
||||
$sUUID = '{' .
|
||||
substr($hash, 0, 8) .
|
||||
'-' .
|
||||
substr($hash, 8, 4) .
|
||||
'-' .
|
||||
substr($hash, 12, 4) .
|
||||
'-' .
|
||||
substr($hash, 16, 4) .
|
||||
'-' .
|
||||
substr($hash, 20, 12) .
|
||||
'}';
|
||||
return $sUUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the module containing the file where the call to this function is made
|
||||
* or an empty string if no such module is found (or not called within a module file)
|
||||
* @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
|
||||
* @return string
|
||||
*/
|
||||
static public function GetCurrentModuleName($iCallDepth = 0)
|
||||
{
|
||||
$sCurrentModuleName = '';
|
||||
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
|
||||
|
||||
foreach(GetModulesInfo() as $sModuleName => $aInfo)
|
||||
{
|
||||
if ($aInfo['root_dir'] !== '')
|
||||
{
|
||||
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
|
||||
|
||||
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
|
||||
{
|
||||
$sCurrentModuleName = $sModuleName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sCurrentModuleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative (to APPROOT) path of the root directory of the module containing the file where the call to this function is made
|
||||
* or an empty string if no such module is found (or not called within a module file)
|
||||
* @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
|
||||
* @return string
|
||||
*/
|
||||
static public function GetCurrentModuleDir($iCallDepth)
|
||||
{
|
||||
$sCurrentModuleDir = '';
|
||||
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
|
||||
|
||||
foreach(GetModulesInfo() as $sModuleName => $aInfo)
|
||||
{
|
||||
if ($aInfo['root_dir'] !== '')
|
||||
{
|
||||
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
|
||||
|
||||
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
|
||||
{
|
||||
$sCurrentModuleDir = basename($sRootDir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sCurrentModuleDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base URL for all files in the current module from which this method is called
|
||||
* or an empty string if no such module is found (or not called within a module file)
|
||||
* @return string
|
||||
*/
|
||||
static public function GetCurrentModuleUrl()
|
||||
{
|
||||
$sDir = static::GetCurrentModuleDir(1);
|
||||
if ( $sDir !== '')
|
||||
{
|
||||
return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a given setting for the current module
|
||||
* @param string $sProperty The name of the property to retrieve
|
||||
* @param mixed $defaultvalue
|
||||
* @return mixed
|
||||
*/
|
||||
static public function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
|
||||
{
|
||||
$sModuleName = static::GetCurrentModuleName(1);
|
||||
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the compiled version of a given module, as it was seen by the compiler
|
||||
* @param string $sModuleName
|
||||
* @return string|NULL
|
||||
*/
|
||||
static public function GetCompiledModuleVersion($sModuleName)
|
||||
{
|
||||
$aModulesInfo = GetModulesInfo();
|
||||
if (array_key_exists($sModuleName, $aModulesInfo))
|
||||
{
|
||||
return $aModulesInfo[$sModuleName]['version'];
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,26 @@ class WizardHelper
|
||||
$oTargetObj = MetaModel::GetObject($sLinkedAttDef->GetTargetClass(), $aLinkedObject[$sLinkedAttCode]);
|
||||
$oLinkedObj->Set($sLinkedAttCode, $oTargetObj);
|
||||
}
|
||||
elseif($sLinkedAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$sDateClass = get_class($sLinkedAttDef);
|
||||
$sDate = $aLinkedObject[$sLinkedAttCode];
|
||||
if($sDate !== null && $sDate !== '')
|
||||
{
|
||||
$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);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oLinkedObj->Set($sLinkedAttCode, $aLinkedObject[$sLinkedAttCode]);
|
||||
@@ -138,6 +158,22 @@ class WizardHelper
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
}
|
||||
else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
if ($value != null)
|
||||
{
|
||||
$oDate = $oAttDef->GetFormat()->Parse($value);
|
||||
if ($oDate instanceof DateTime)
|
||||
{
|
||||
$value = $oDate->format($oAttDef->GetInternalFormat());
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oObj->Set($sAttCode, $value);
|
||||
@@ -254,11 +290,21 @@ class WizardHelper
|
||||
{
|
||||
return $this->m_aData['m_sClass'];
|
||||
}
|
||||
|
||||
public function GetFormPrefix()
|
||||
{
|
||||
return $this->m_aData['m_sFormPrefix'];
|
||||
}
|
||||
|
||||
public function GetFormPrefix()
|
||||
{
|
||||
return $this->m_aData['m_sFormPrefix'];
|
||||
}
|
||||
|
||||
public function GetInitialState()
|
||||
{
|
||||
return isset($this->m_aData['m_sInitialState']) ? $this->m_aData['m_sInitialState'] : null;
|
||||
}
|
||||
|
||||
public function GetStimulus()
|
||||
{
|
||||
return isset($this->m_aData['m_sStimulus']) ? $this->m_aData['m_sStimulus'] : null;
|
||||
}
|
||||
|
||||
public function GetIdForField($sFieldName)
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($oLog)
|
||||
{
|
||||
$oLog->DBUpdate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
|
||||
@@ -348,7 +360,6 @@ 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->SetReferences($sReference);
|
||||
$oEmail->SetMessageId($sMessageId);
|
||||
}
|
||||
|
||||
98
core/apc-compat.php
Normal file
98
core/apc-compat.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// Copyright (C) 2016-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
// Emulate the API of APC, over APCU
|
||||
// Note: for PHP < 7, this compatibility used to be provided by APCU itself (if compiled with some options)
|
||||
// for PHP 7+, it can be provided by the mean of apcu_bc, which is not so simple to install
|
||||
// The current emulation aims at skipping this complexity
|
||||
if (!function_exists('apc_store') && function_exists('apcu_store'))
|
||||
{
|
||||
function apc_add($key, $var, $ttl = 0)
|
||||
{
|
||||
return apcu_add($key, $var, $ttl);
|
||||
}
|
||||
function apc_cache_info($cache_type = '', $limited = false)
|
||||
{
|
||||
return apcu_cache_info($limited);
|
||||
}
|
||||
function apc_cas($key, $old, $new)
|
||||
{
|
||||
return apcu_cas($key, $old, $new);
|
||||
}
|
||||
function apc_clear_cache($cache_type = '')
|
||||
{
|
||||
return apcu_clear_cache();
|
||||
}
|
||||
function apc_dec($key, $step = 1, &$success = null)
|
||||
{
|
||||
apcu_dec($key, $step, $success);
|
||||
}
|
||||
function apc_delete($key)
|
||||
{
|
||||
return apcu_delete($key);
|
||||
}
|
||||
function apc_exists($keys)
|
||||
{
|
||||
return apcu_exists($keys);
|
||||
}
|
||||
function apc_fetch($key)
|
||||
{
|
||||
return apcu_fetch($key);
|
||||
}
|
||||
function apc_inc($key, $step = 1, &$success = null)
|
||||
{
|
||||
apcu_inc($key, $step, $success);
|
||||
}
|
||||
function apc_sma_info($limited = false)
|
||||
{
|
||||
return apcu_sma_info($limited);
|
||||
}
|
||||
function apc_store($key, $var, $ttl = 0)
|
||||
{
|
||||
return apcu_store($key, $var, $ttl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user cache info... beware of the format of the returned structure that may vary (See usages)
|
||||
* @return array
|
||||
*/
|
||||
function apc_cache_info_compat()
|
||||
{
|
||||
if (!function_exists('apc_cache_info')) return array();
|
||||
|
||||
$oFunction = new ReflectionFunction('apc_cache_info');
|
||||
if ($oFunction->getNumberOfParameters() != 2)
|
||||
{
|
||||
// Beware: APCu behaves slightly differently from APC !!
|
||||
// Worse: the compatibility layer integrated into APC differs from apcu-bc (testing the number of parameters is a must)
|
||||
// In CLI mode (PHP > 7) apc_cache_info returns null and outputs an error message.
|
||||
$aCacheUserData = @apc_cache_info();
|
||||
}
|
||||
else
|
||||
{
|
||||
$aCacheUserData = @apc_cache_info('user');
|
||||
}
|
||||
return $aCacheUserData;
|
||||
}
|
||||
|
||||
// Cache emulation
|
||||
if (!function_exists('apc_store'))
|
||||
{
|
||||
require_once(APPROOT.'core/apc-emulation.php');
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -186,7 +186,7 @@ abstract class AsyncTask extends DBObject
|
||||
* @return boolean True if the task record can be deleted
|
||||
*/
|
||||
public function Process()
|
||||
{
|
||||
{
|
||||
// By default: consider that the task is not completed
|
||||
$bRet = false;
|
||||
|
||||
@@ -196,16 +196,15 @@ abstract class AsyncTask extends DBObject
|
||||
{
|
||||
try
|
||||
{
|
||||
$sStatus = $this->DoProcess();
|
||||
if ($this->Get('event_id') != 0)
|
||||
{
|
||||
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
|
||||
$oEventLog->Set('message', $sStatus);
|
||||
$oEventLog->DBUpdate();
|
||||
$sStatus = $this->DoProcess();
|
||||
if ($this->Get('event_id') != 0)
|
||||
{
|
||||
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
|
||||
$oEventLog->Set('message', $sStatus);
|
||||
$oEventLog->DBUpdate();
|
||||
}
|
||||
$bRet = true;
|
||||
}
|
||||
catch(Exception $e)
|
||||
} catch (Exception $e)
|
||||
{
|
||||
$this->HandleError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
@@ -215,6 +214,7 @@ abstract class AsyncTask extends DBObject
|
||||
// Already done or being handled by another process... skip...
|
||||
$bRet = false;
|
||||
}
|
||||
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
@@ -238,7 +238,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 +260,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
@@ -22,6 +22,7 @@ MetaModel::IncludeModule('application/user.preferences.class.inc.php');
|
||||
MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('core/event.class.inc.php');
|
||||
MetaModel::IncludeModule('core/action.class.inc.php');
|
||||
|
||||
55
core/background.inc.php
Normal file
55
core/background.inc.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// Copyright (C) 2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Tasks performed in the background
|
||||
*
|
||||
* @copyright Copyright (C) 2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
class ObsolescenceDateUpdater implements iBackgroundProcess
|
||||
{
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return MetaModel::GetConfig()->Get('obsolescence.date_update_interval'); // 10 mn
|
||||
}
|
||||
|
||||
public function Process($iUnixTimeLimit)
|
||||
{
|
||||
$iCountSet = 0;
|
||||
$iCountReset = 0;
|
||||
$iClasses = 0;
|
||||
foreach (MetaModel::EnumObsoletableClasses() as $sClass)
|
||||
{
|
||||
$oObsoletedToday = new DBObjectSearch($sClass);
|
||||
$oObsoletedToday->AddCondition('obsolescence_flag', 1, '=');
|
||||
$oObsoletedToday->AddCondition('obsolescence_date', null, '=');
|
||||
$sToday = date(AttributeDate::GetSQLFormat());
|
||||
$iCountSet += MetaModel::BulkUpdate($oObsoletedToday, array('obsolescence_date' => $sToday));
|
||||
|
||||
$oObsoletedToday = new DBObjectSearch($sClass);
|
||||
$oObsoletedToday->AddCondition('obsolescence_flag', 1, '!=');
|
||||
$oObsoletedToday->AddCondition('obsolescence_date', null, '!=');
|
||||
$iCountReset += MetaModel::BulkUpdate($oObsoletedToday, array('obsolescence_date' => null));
|
||||
}
|
||||
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,13 +44,11 @@ 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
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -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
|
||||
@@ -367,6 +367,7 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
|
||||
$aCacheKeys = array();
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
{
|
||||
@@ -385,7 +386,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...
|
||||
@@ -472,6 +472,10 @@ class BulkChange
|
||||
if ($sAttCode == 'id') continue;
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
|
||||
// skip reconciliation keys
|
||||
if (!$oAttDef->IsWritable() && in_array($sAttCode, $this->m_aReconcilKeys)){ continue; }
|
||||
|
||||
$aReasons = array();
|
||||
$iFlags = $oTargetObj->GetAttributeFlags($sAttCode, $aReasons);
|
||||
if ( (($iFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY) && ( $oTargetObj->Get($sAttCode) != $aRowData[$iCol]) )
|
||||
@@ -528,13 +532,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]))
|
||||
{
|
||||
@@ -605,10 +607,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
|
||||
@@ -646,6 +644,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)
|
||||
@@ -679,7 +718,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);
|
||||
}
|
||||
@@ -828,8 +867,8 @@ class BulkChange
|
||||
$sFormat = $sDateFormat;
|
||||
}
|
||||
$oFormat = new DateTimeFormat($sFormat);
|
||||
$sRegExp = $oFormat->ToRegExpr();
|
||||
if (!preg_match('/'.$sRegExp.'/', $this->m_aData[$iRow][$iCol]))
|
||||
$sRegExp = $oFormat->ToRegExpr('/');
|
||||
if (!preg_match($sRegExp, $this->m_aData[$iRow][$iCol]))
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
}
|
||||
@@ -1171,6 +1210,9 @@ EOF
|
||||
|
||||
/**
|
||||
* Display the details of an import
|
||||
* @param iTopWebPage $oPage
|
||||
* @param $iChange
|
||||
* @throws Exception
|
||||
*/
|
||||
static function DisplayImportHistoryDetails(iTopWebPage $oPage, $iChange)
|
||||
{
|
||||
@@ -1306,5 +1348,3 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
||||
@@ -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());
|
||||
@@ -112,7 +118,9 @@ class BulkExportResultGC implements iBackgroundProcess
|
||||
}
|
||||
$iProcessed++;
|
||||
@unlink($oResult->Get('temp_file_path'));
|
||||
utils::PushArchiveMode(false);
|
||||
$oResult->DBDelete();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
return "Cleaned $iProcessed old export results(s).";
|
||||
}
|
||||
@@ -145,13 +153,16 @@ abstract class BulkExport
|
||||
$this->sTmpFile = '';
|
||||
$this->bLocalizeOutput = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first class capable of exporting the data in the given format
|
||||
* @param string $sFormat The lowercase format (e.g. html, csv, spreadsheet, xlsx, xml, json, pdf...)
|
||||
* @param DBSearch $oSearch The search/filter defining the set of objects to export or null when listing the supported formats
|
||||
* @return iBulkExport|NULL
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find the first class capable of exporting the data in the given format
|
||||
*
|
||||
* @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 BulkExport|null
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
static public function FindExporter($sFormatCode, $oSearch = null)
|
||||
{
|
||||
foreach(get_declared_classes() as $sPHPClass)
|
||||
@@ -172,12 +183,17 @@ abstract class BulkExport
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the exporter corresponding to the given persistent token
|
||||
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
|
||||
* @return iBulkExport|NULL
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find the exporter corresponding to the given persistent token
|
||||
*
|
||||
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
|
||||
*
|
||||
* @return iBulkExport|null
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreException
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
static public function FindExporterFromToken($iPersistentToken = null)
|
||||
{
|
||||
$oBulkExporter = null;
|
||||
@@ -186,7 +202,7 @@ abstract class BulkExport
|
||||
{
|
||||
$sFormatCode = $oInfo->Get('format');
|
||||
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
|
||||
|
||||
|
||||
$oBulkExporter = self::FindExporter($sFormatCode, $oSearch);
|
||||
if ($oBulkExporter)
|
||||
{
|
||||
@@ -194,13 +210,21 @@ 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;
|
||||
}
|
||||
}
|
||||
return $oBulkExporter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @throws Exception
|
||||
*/
|
||||
public function AppendToTmpFile($data)
|
||||
{
|
||||
if ($this->sTmpFile == '')
|
||||
@@ -219,10 +243,10 @@ abstract class BulkExport
|
||||
{
|
||||
return $this->sTmpFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lists all possible export formats. The output is a hash array in the form: 'format_code' => 'localized format label'
|
||||
* @return multitype:string
|
||||
* @return array :string
|
||||
*/
|
||||
static public function FindSupportedFormats()
|
||||
{
|
||||
@@ -248,6 +272,14 @@ abstract class BulkExport
|
||||
{
|
||||
$this->iChunkSize = $iChunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $bLocalizeOutput
|
||||
*/
|
||||
public function SetLocalizeOutput($bLocalizeOutput)
|
||||
{
|
||||
$this->bLocalizeOutput = $bLocalizeOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
@@ -286,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()
|
||||
@@ -302,11 +342,15 @@ abstract class BulkExport
|
||||
$this->oBulkExportResult = new BulkExportResult();
|
||||
$this->oBulkExportResult->Set('format', $this->sFormatCode);
|
||||
$this->oBulkExportResult->Set('search', $this->oSearch->serialize());
|
||||
$this->oBulkExportResult->Set('chunk_size', $this->iChunkSize);
|
||||
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
|
||||
}
|
||||
$this->oBulkExportResult->Set('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()));
|
||||
return $this->oBulkExportResult->DBWrite();
|
||||
utils::PushArchiveMode(false);
|
||||
$ret = $this->oBulkExportResult->DBWrite();
|
||||
utils::PopArchiveMode();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function Cleanup()
|
||||
@@ -318,7 +362,9 @@ abstract class BulkExport
|
||||
{
|
||||
@unlink($sFilename);
|
||||
}
|
||||
utils::PushArchiveMode(false);
|
||||
$this->oBulkExportResult->DBDelete();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,13 +394,21 @@ abstract class BulkExport
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetMimeType()
|
||||
{
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetFileExtension()
|
||||
{
|
||||
|
||||
return '';
|
||||
}
|
||||
public function GetCharacterSet()
|
||||
{
|
||||
@@ -381,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"))
|
||||
@@ -394,7 +453,6 @@ abstract class BulkExport
|
||||
}
|
||||
|
||||
$iNum = rand();
|
||||
$sFileName = '';
|
||||
do
|
||||
{
|
||||
$iNum++;
|
||||
@@ -412,7 +470,11 @@ abstract class BulkExport
|
||||
// The built-in exports
|
||||
require_once(APPROOT.'core/tabularbulkexport.class.inc.php');
|
||||
require_once(APPROOT.'core/htmlbulkexport.class.inc.php');
|
||||
require_once(APPROOT.'core/pdfbulkexport.class.inc.php');
|
||||
if (extension_loaded('gd'))
|
||||
{
|
||||
// PDF export - via TCPDF - requires GD
|
||||
require_once(APPROOT.'core/pdfbulkexport.class.inc.php');
|
||||
}
|
||||
require_once(APPROOT.'core/csvbulkexport.class.inc.php');
|
||||
require_once(APPROOT.'core/excelbulkexport.class.inc.php');
|
||||
require_once(APPROOT.'core/spreadsheetbulkexport.class.inc.php');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class cmdbObject
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -231,6 +231,202 @@ abstract class CMDBObject extends DBObject
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sAttCode
|
||||
* @param $original Original value
|
||||
* @param $value Current value
|
||||
*/
|
||||
protected function RecordAttChange($sAttCode, $original, $value)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef->IsExternalField()) return;
|
||||
if ($oAttDef->IsLinkSet()) return;
|
||||
if ($oAttDef->GetTrackingLevel() == ATTRIBUTE_TRACKING_NONE) return;
|
||||
|
||||
if ($oAttDef instanceOf AttributeOneWayPassword)
|
||||
{
|
||||
// One Way encrypted passwords' history is stored -one way- encrypted
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = '';
|
||||
}
|
||||
$oMyChangeOp->Set("prev_pwd", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeEncryptedString)
|
||||
{
|
||||
// Encrypted string history is stored encrypted
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = '';
|
||||
}
|
||||
$oMyChangeOp->Set("prevstring", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeBlob)
|
||||
{
|
||||
// Data blobs
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = new ormDocument();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeStopWatch)
|
||||
{
|
||||
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
|
||||
//
|
||||
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
|
||||
{
|
||||
$item_value = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $value, $this);
|
||||
$item_original = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $original, $this);
|
||||
|
||||
if ($item_value != $item_original)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
|
||||
|
||||
$oMyChangeOp->Set("oldvalue", $item_original);
|
||||
$oMyChangeOp->Set("newvalue", $item_value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCaseLog)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
$oMyChangeOp->Set("lastentry", $value->GetLatestEntryIndex());
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeLongText)
|
||||
{
|
||||
// Data blobs
|
||||
if ($oAttDef->GetFormat() == 'html')
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
|
||||
}
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (!is_null($original) && ($original instanceof ormCaseLog))
|
||||
{
|
||||
$original = $original->GetText();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeText)
|
||||
{
|
||||
// Data blobs
|
||||
if ($oAttDef->GetFormat() == 'html')
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
|
||||
}
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (!is_null($original) && ($original instanceof ormCaseLog))
|
||||
{
|
||||
$original = $original->GetText();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeBoolean)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original ? 1 : 0);
|
||||
$oMyChangeOp->Set("newvalue", $value ? 1 : 0);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeHierarchicalKey)
|
||||
{
|
||||
// Hierarchical keys
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCustomFields)
|
||||
{
|
||||
// Custom fields
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCustomFields");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("prevdata", json_encode($original->GetValues()));
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeURL)
|
||||
{
|
||||
// URLs
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeURL");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scalars
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aValues
|
||||
* @param array $aOrigValues
|
||||
*/
|
||||
protected function RecordAttChanges(array $aValues, array $aOrigValues)
|
||||
{
|
||||
parent::RecordAttChanges($aValues, $aOrigValues);
|
||||
@@ -239,11 +435,6 @@ abstract class CMDBObject extends DBObject
|
||||
//
|
||||
foreach ($aValues as $sAttCode=> $value)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef->IsExternalField()) continue;
|
||||
if ($oAttDef->IsLinkSet()) continue;
|
||||
if ($oAttDef->GetTrackingLevel() == ATTRIBUTE_TRACKING_NONE) continue;
|
||||
|
||||
if (array_key_exists($sAttCode, $aOrigValues))
|
||||
{
|
||||
$original = $aOrigValues[$sAttCode];
|
||||
@@ -252,185 +443,7 @@ abstract class CMDBObject extends DBObject
|
||||
{
|
||||
$original = null;
|
||||
}
|
||||
|
||||
if ($oAttDef instanceOf AttributeOneWayPassword)
|
||||
{
|
||||
// One Way encrypted passwords' history is stored -one way- encrypted
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = '';
|
||||
}
|
||||
$oMyChangeOp->Set("prev_pwd", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeEncryptedString)
|
||||
{
|
||||
// Encrypted string history is stored encrypted
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = '';
|
||||
}
|
||||
$oMyChangeOp->Set("prevstring", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeBlob)
|
||||
{
|
||||
// Data blobs
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = new ormDocument();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeStopWatch)
|
||||
{
|
||||
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
|
||||
//
|
||||
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
|
||||
{
|
||||
$item_value = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $value, $this);
|
||||
$item_original = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $original, $this);
|
||||
|
||||
if ($item_value != $item_original)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
|
||||
|
||||
$oMyChangeOp->Set("oldvalue", $item_original);
|
||||
$oMyChangeOp->Set("newvalue", $item_value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCaseLog)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
$oMyChangeOp->Set("lastentry", $value->GetLatestEntryIndex());
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeLongText)
|
||||
{
|
||||
// Data blobs
|
||||
if ($oAttDef->GetFormat() == 'html')
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
|
||||
}
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (!is_null($original) && ($original instanceof ormCaseLog))
|
||||
{
|
||||
$original = $original->GetText();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeText)
|
||||
{
|
||||
// Data blobs
|
||||
if ($oAttDef->GetFormat() == 'html')
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
|
||||
}
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (!is_null($original) && ($original instanceof ormCaseLog))
|
||||
{
|
||||
$original = $original->GetText();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeBoolean)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original ? 1 : 0);
|
||||
$oMyChangeOp->Set("newvalue", $value ? 1 : 0);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeHierarchicalKey)
|
||||
{
|
||||
// Hierarchical keys
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCustomFields)
|
||||
{
|
||||
// Custom fields
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCustomFields");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("prevdata", json_encode($original->GetValues()));
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeURL)
|
||||
{
|
||||
// URLs
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeURL");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scalars
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
$this->RecordAttChange($sAttCode, $original, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,6 +609,34 @@ abstract class CMDBObject extends DBObject
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function DBArchive()
|
||||
{
|
||||
// Note: do the job anyway, so as to repair any DB discrepancy
|
||||
$bOriginal = $this->Get('archive_flag');
|
||||
parent::DBArchive();
|
||||
|
||||
if (!$bOriginal)
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
$this->RecordAttChange('archive_flag', false, true);
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
|
||||
public function DBUnarchive()
|
||||
{
|
||||
// Note: do the job anyway, so as to repair any DB discrepancy
|
||||
$bOriginal = $this->Get('archive_flag');
|
||||
parent::DBUnarchive();
|
||||
|
||||
if ($bOriginal)
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
$this->RecordAttChange('archive_flag', true, false);
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -649,5 +690,5 @@ class CMDBObjectSet extends DBObjectSet
|
||||
$oRetSet->AddObjectExtended($aObjectsByClassAlias);
|
||||
}
|
||||
return $oRetSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -54,41 +108,136 @@ class MySQLException extends CoreException
|
||||
*/
|
||||
class CMDBSource
|
||||
{
|
||||
/**
|
||||
* SQL charset & collation declaration for text columns
|
||||
*
|
||||
* Using an attribute instead of a constant to avoid crash in the setup for older PHP versions
|
||||
*
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-column.html
|
||||
* @since 2.5 #1001 switch to utf8mb4
|
||||
*/
|
||||
public static $SQL_STRING_COLUMNS_CHARSET_DEFINITION = ' CHARACTER SET '.DEFAULT_CHARACTER_SET.' COLLATE '.DEFAULT_COLLATION;
|
||||
|
||||
protected static $m_sDBHost;
|
||||
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 = '')
|
||||
/**
|
||||
* @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 +245,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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static function SetCharacterSet($sCharset = 'utf8', $sCollation = 'utf8_general_ci')
|
||||
/**
|
||||
* <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)
|
||||
{
|
||||
@@ -164,7 +401,12 @@ class CMDBSource
|
||||
$aVersions = self::QueryToCol('SELECT Version() as version', 'version');
|
||||
return $aVersions[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sSource
|
||||
*
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public static function SelectDB($sSource)
|
||||
{
|
||||
if (!((bool)self::$m_oMysqli->query("USE `$sSource`")))
|
||||
@@ -174,9 +416,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 +455,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 +492,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 +533,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 +554,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 +609,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
|
||||
@@ -343,20 +634,28 @@ class CMDBSource
|
||||
{
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
||||
}
|
||||
|
||||
|
||||
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 +683,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 +701,12 @@ class CMDBSource
|
||||
return $aColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSql
|
||||
*
|
||||
* @return array
|
||||
* @throws \MySQLException if query cannot be processed
|
||||
*/
|
||||
public static function ExplainQuery($sSql)
|
||||
{
|
||||
$aData = array();
|
||||
@@ -410,8 +722,8 @@ 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 +734,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 +777,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 +866,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 +912,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,9 +930,24 @@ 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);
|
||||
|
||||
return ($sSearchedIndex == $sExistingIndex);
|
||||
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)
|
||||
@@ -608,35 +967,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")
|
||||
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"]
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch(MySQLException $e)
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Table does not exist
|
||||
self::$m_aTablesInfo[strtolower($sTableName)] = null;
|
||||
$aFields = array();
|
||||
foreach($aMapping as $sKey => $sColumn)
|
||||
{
|
||||
$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 +1018,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::$SQL_STRING_COLUMNS_CHARSET_DEFINITION.';';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sTable
|
||||
*
|
||||
* @return array
|
||||
* @throws \MySQLException if query cannot be processed
|
||||
*/
|
||||
public static function DumpTable($sTable)
|
||||
{
|
||||
$sSql = "SELECT * FROM `$sTable`";
|
||||
@@ -726,6 +1124,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 +1183,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::$SQL_STRING_COLUMNS_CHARSET_DEFINITION.';';
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
// Copyright (C) 2016-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -29,13 +29,13 @@
|
||||
*
|
||||
* if (ContextTag::Check('GUI:Portal'))
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2016-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class ContextTag
|
||||
{
|
||||
protected static $aStack;
|
||||
protected static $aStack = array();
|
||||
|
||||
/**
|
||||
* Store a context tag on the stack
|
||||
|
||||
@@ -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,13 @@ class SecurityException extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Throwned when querying on an object that exists in the database but is archived
|
||||
*
|
||||
* @see N.1108
|
||||
*/
|
||||
class ArchivedObjectException extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -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>
|
||||
@@ -413,10 +413,16 @@ EOF
|
||||
/**
|
||||
* Get the regular expression to (approximately) validate a date/time for the current format
|
||||
* The validation does not take into account the number of days in a month (i.e. June 31st will pass, as well as Feb 30th!)
|
||||
* @param string $sDelimiter Surround the regexp (and escape) if needed
|
||||
* @return string The regular expression in PCRE syntax
|
||||
*/
|
||||
public function ToRegExpr()
|
||||
public function ToRegExpr($sDelimiter = null)
|
||||
{
|
||||
return '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]/:').'$';
|
||||
$sRet = '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]:').'$';
|
||||
if ($sDelimiter !== null)
|
||||
{
|
||||
$sRet = $sDelimiter.str_replace($sDelimiter, '\\'.$sDelimiter, $sRet).$sDelimiter;
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -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();
|
||||
|
||||
@@ -116,7 +116,7 @@ abstract class DBObject implements iDisplay
|
||||
// set default values
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
|
||||
{
|
||||
$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue($this);
|
||||
$this->m_aCurrValues[$sAttCode] = $this->GetDefaultValue($sAttCode);
|
||||
$this->m_aOrigValues[$sAttCode] = null;
|
||||
if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
|
||||
{
|
||||
@@ -159,18 +159,13 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$sRet = '';
|
||||
$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>";
|
||||
return $sRet;
|
||||
$sRet = '';
|
||||
$sClass = get_class($this);
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
$iPKey = $this->GetKey();
|
||||
$sFriendlyname = $this->Get('friendlyname');
|
||||
$sRet .= "<b title=\"$sRootClass\">$sClass</b>::$iPKey ($sFriendlyname)<br/>\n";
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
// Restore initial values... mmmm, to be discussed
|
||||
@@ -192,10 +187,14 @@ abstract class DBObject implements iDisplay
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bAllowAllData DEPRECATED: the reload must never fail!
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function Reload($bAllowAllData = false)
|
||||
{
|
||||
assert($this->m_bIsInDB);
|
||||
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false /* must be found */, $bAllowAllData/* in the future $this->m_bAllowAllData ??*/);
|
||||
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false /* must be found */, true /* AllowAllData */);
|
||||
if (empty($aRow))
|
||||
{
|
||||
throw new CoreException("Failed to reload object of class '".get_class($this)."', id = ".$this->m_iKey);
|
||||
@@ -208,23 +207,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
if (!$oAttDef->IsLinkSet()) continue;
|
||||
|
||||
// Load the link information
|
||||
$sLinkClass = $oAttDef->GetLinkedClass();
|
||||
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
||||
|
||||
// The class to target is not the current class, because if this is a derived class,
|
||||
// it may differ from the target class, then things start to become confusing
|
||||
$oRemoteExtKeyAtt = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToMe);
|
||||
$sMyClass = $oRemoteExtKeyAtt->GetTargetClass();
|
||||
|
||||
$oMyselfSearch = new DBObjectSearch($sMyClass);
|
||||
$oMyselfSearch->AddCondition('id', $this->m_iKey, '=');
|
||||
|
||||
$oLinkSearch = new DBObjectSearch($sLinkClass);
|
||||
$oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe);
|
||||
$oLinks = new DBObjectSet($oLinkSearch);
|
||||
|
||||
$this->m_aCurrValues[$sAttCode] = $oLinks;
|
||||
$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue($this);
|
||||
$this->m_aOrigValues[$sAttCode] = clone $this->m_aCurrValues[$sAttCode];
|
||||
$this->m_aLoadedAtt[$sAttCode] = true;
|
||||
}
|
||||
@@ -348,7 +331,14 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
return $bFullyLoaded;
|
||||
}
|
||||
|
||||
|
||||
protected function _Set($sAttCode, $value)
|
||||
{
|
||||
$this->m_aCurrValues[$sAttCode] = $value;
|
||||
$this->m_aTouchedAtt[$sAttCode] = true;
|
||||
unset($this->m_aModifiedAtt[$sAttCode]);
|
||||
}
|
||||
|
||||
public function Set($sAttCode, $value)
|
||||
{
|
||||
if ($sAttCode == 'finalclass')
|
||||
@@ -356,8 +346,15 @@ abstract class DBObject implements iDisplay
|
||||
// Ignore it - this attribute is set upon object creation and that's it
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
|
||||
if (!$oAttDef->IsWritable())
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
throw new Exception("Attempting to set the value on the read-only attribute $sClass::$sAttCode");
|
||||
}
|
||||
|
||||
if ($this->m_bIsInDB && !$this->m_bFullyLoaded && !$this->m_bDirty)
|
||||
{
|
||||
// First time Set is called... ensure that the object gets fully loaded
|
||||
@@ -380,7 +377,7 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
|
||||
{
|
||||
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
{
|
||||
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
|
||||
$this->m_aLoadedAtt[$sCode] = true;
|
||||
@@ -393,48 +390,28 @@ abstract class DBObject implements iDisplay
|
||||
// Invalidate the corresponding fields so that they get reloaded in case they are needed (See Get())
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
|
||||
{
|
||||
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
{
|
||||
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue($this);
|
||||
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
|
||||
unset($this->m_aLoadedAtt[$sCode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if($oAttDef->IsLinkSet())
|
||||
if ($oAttDef->IsLinkSet() && ($value != null))
|
||||
{
|
||||
if (is_null($value))
|
||||
{
|
||||
// Normalize
|
||||
$value = DBObjectSet::FromScratch($oAttDef->GetLinkedClass());
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((get_class($value) != 'DBObjectSet') && !is_subclass_of($value, 'DBObjectSet'))
|
||||
{
|
||||
throw new CoreUnexpectedValue("expecting a set of persistent objects (found a '".get_class($value)."'), setting default value (empty list)");
|
||||
}
|
||||
}
|
||||
|
||||
$oObjectSet = $value;
|
||||
$sSetClass = $oObjectSet->GetClass();
|
||||
$sLinkClass = $oAttDef->GetLinkedClass();
|
||||
// not working fine :-( if (!is_subclass_of($sSetClass, $sLinkClass))
|
||||
if ($sSetClass != $sLinkClass)
|
||||
{
|
||||
throw new CoreUnexpectedValue("expecting a set of '$sLinkClass' objects (found a set of '$sSetClass'), setting default value (empty list)");
|
||||
}
|
||||
$realvalue = clone $this->m_aCurrValues[$sAttCode];
|
||||
$realvalue->UpdateFromCompleteList($value);
|
||||
}
|
||||
|
||||
$realvalue = $oAttDef->MakeRealValue($value, $this);
|
||||
|
||||
$this->m_aCurrValues[$sAttCode] = $realvalue;
|
||||
$this->m_aTouchedAtt[$sAttCode] = true;
|
||||
unset($this->m_aModifiedAtt[$sAttCode]);
|
||||
else
|
||||
{
|
||||
$realvalue = $oAttDef->MakeRealValue($value, $this);
|
||||
}
|
||||
$this->_Set($sAttCode, $realvalue);
|
||||
|
||||
foreach (MetaModel::ListMetaAttributes(get_class($this), $sAttCode) as $sMetaAttCode => $oMetaAttDef)
|
||||
{
|
||||
$this->Set($sMetaAttCode, $oMetaAttDef->MapValue($this));
|
||||
$this->_Set($sMetaAttCode, $oMetaAttDef->MapValue($this));
|
||||
}
|
||||
|
||||
// The object has changed, reset caches
|
||||
@@ -538,7 +515,7 @@ abstract class DBObject implements iDisplay
|
||||
else
|
||||
{
|
||||
// Not loaded... is it related to an external key?
|
||||
if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
|
||||
if ($oAttDef->IsExternalField())
|
||||
{
|
||||
// Let's get the object and compute all of the corresponding attributes
|
||||
// (i.e not only the requested attribute)
|
||||
@@ -560,7 +537,7 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
|
||||
{
|
||||
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sExtKeyAttCode))
|
||||
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sExtKeyAttCode))
|
||||
{
|
||||
if ($oRemote)
|
||||
{
|
||||
@@ -568,7 +545,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue($this);
|
||||
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
|
||||
}
|
||||
$this->m_aLoadedAtt[$sCode] = true;
|
||||
}
|
||||
@@ -578,7 +555,7 @@ abstract class DBObject implements iDisplay
|
||||
$value = $this->m_aCurrValues[$sAttCode];
|
||||
}
|
||||
|
||||
if ($value instanceof DBObjectSet)
|
||||
if ($value instanceof ormLinkSet)
|
||||
{
|
||||
$value->Rewind();
|
||||
}
|
||||
@@ -594,6 +571,19 @@ abstract class DBObject implements iDisplay
|
||||
return $this->m_aOrigValues[$sAttCode];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default value of the $sAttCode. By default, returns the default value of the AttributeDefinition.
|
||||
* Overridable.
|
||||
*
|
||||
* @param $sAttCode
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetDefaultValue($sAttCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
return $oAttDef->GetDefaultValue($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data loaded by the mean of a dynamic and explicit JOIN
|
||||
*/
|
||||
@@ -738,7 +728,9 @@ abstract class DBObject implements iDisplay
|
||||
else
|
||||
{
|
||||
$sHtmlLabel = htmlentities($this->Get($sAttCode.'_friendlyname'), ENT_QUOTES, 'UTF-8');
|
||||
return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sHtmlLabel);
|
||||
$bArchived = $this->IsArchived($sAttCode);
|
||||
$bObsolete = $this->IsObsolete($sAttCode);
|
||||
return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sHtmlLabel, null, true, $bArchived, $bObsolete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -817,10 +809,12 @@ abstract class DBObject implements iDisplay
|
||||
* @param string $sHtmlLabel Label with HTML entities escaped (< escaped as <)
|
||||
* @param null $sUrlMakerClass
|
||||
* @param bool|true $bWithNavigationContext
|
||||
* @param bool|false $bArchived
|
||||
* @param bool|false $bObsolete
|
||||
* @return string
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public static function MakeHyperLink($sObjClass, $sObjKey, $sHtmlLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)
|
||||
public static function MakeHyperLink($sObjClass, $sObjKey, $sHtmlLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true, $bArchived = false, $bObsolete = false)
|
||||
{
|
||||
if ($sObjKey <= 0) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
|
||||
|
||||
@@ -843,19 +837,58 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
|
||||
$sUrl = ApplicationContext::MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass, $bWithNavigationContext);
|
||||
if (strlen($sUrl) > 0)
|
||||
|
||||
$bClickable = !$bArchived || utils::IsArchiveMode();
|
||||
if ($bArchived)
|
||||
{
|
||||
return "<a href=\"$sUrl\" title=\"$sHint\">$sHtmlLabel</a>";
|
||||
$sSpanClass = 'archived';
|
||||
$sFA = 'fa-archive object-archived';
|
||||
$sHint = Dict::S('ObjectRef:Archived');
|
||||
}
|
||||
elseif ($bObsolete)
|
||||
{
|
||||
$sSpanClass = 'obsolete';
|
||||
$sFA = 'fa-eye-slash object-obsolete';
|
||||
$sHint = Dict::S('ObjectRef:Obsolete');
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sHtmlLabel;
|
||||
$sSpanClass = '';
|
||||
$sFA = '';
|
||||
}
|
||||
if ($sFA == '')
|
||||
{
|
||||
$sIcon = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($bClickable)
|
||||
{
|
||||
$sIcon = "<span class=\"object-ref-icon fa $sFA fa-1x fa-fw\"></span>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sIcon = "<span class=\"object-ref-icon-disabled fa $sFA fa-1x fa-fw\"></span>";
|
||||
}
|
||||
}
|
||||
|
||||
if ($bClickable && (strlen($sUrl) > 0))
|
||||
{
|
||||
$sHLink = "<a class=\"object-ref-link\" href=\"$sUrl\">$sIcon$sHtmlLabel</a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHLink = $sIcon.$sHtmlLabel;
|
||||
}
|
||||
$sRet = "<span class=\"object-ref $sSpanClass\" title=\"$sHint\">$sHLink</span>";
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetHyperlink($sUrlMakerClass = null, $bWithNavigationContext = true)
|
||||
{
|
||||
return self::MakeHyperLink(get_class($this), $this->GetKey(), $this->GetName(), $sUrlMakerClass, $bWithNavigationContext);
|
||||
$bArchived = $this->IsArchived();
|
||||
$bObsolete = $this->IsObsolete();
|
||||
return self::MakeHyperLink(get_class($this), $this->GetKey(), $this->GetName(), $sUrlMakerClass, $bWithNavigationContext, $bArchived, $bObsolete);
|
||||
}
|
||||
|
||||
public static function ComputeStandardUIPage($sClass)
|
||||
@@ -1015,24 +1048,24 @@ 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 (($aReadOnlyAtts != null) && (in_array($sAttCode, $aReadOnlyAtts)))
|
||||
{
|
||||
if (in_array($sAttCode, $aReadOnlyAtts))
|
||||
{
|
||||
return OPT_ATT_READONLY;
|
||||
}
|
||||
return OPT_ATT_READONLY;
|
||||
}
|
||||
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
|
||||
@@ -1056,6 +1089,80 @@ 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
|
||||
* @param $sAttCode string $sAttCode The code of the attribute
|
||||
* @param $sStimulus string The stimulus code to apply
|
||||
* @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas)
|
||||
* @param $sOriginState string The state from which to apply $sStimulus, if empty current state will be used
|
||||
* @return integer Flags: the binary combination of the flags applicable to this attribute
|
||||
*/
|
||||
public function GetTransitionFlags($sAttCode, $sStimulus, &$aReasons = array(), $sOriginState = '')
|
||||
{
|
||||
$iFlags = 0; // By default (if no lifecycle) no flag at all
|
||||
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
|
||||
// If no state attribute, there is no lifecycle
|
||||
if (empty($sStateAttCode))
|
||||
{
|
||||
return $iFlags;
|
||||
}
|
||||
|
||||
// Retrieving current state if necessary
|
||||
if ($sOriginState === '')
|
||||
{
|
||||
$sOriginState = $this->Get($sStateAttCode);
|
||||
}
|
||||
|
||||
// Retrieving attribute flags
|
||||
$iAttributeFlags = $this->GetAttributeFlags($sAttCode, $aReasons, $sOriginState);
|
||||
|
||||
// Retrieving transition flags
|
||||
$iTransitionFlags = MetaModel::GetTransitionFlags(get_class($this), $sOriginState, $sStimulus, $sAttCode);
|
||||
|
||||
// Merging transition flags with attribute flags
|
||||
$iFlags = $iTransitionFlags | $iAttributeFlags;
|
||||
|
||||
return $iFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of attribute codes (with their flags) when $sStimulus is applied on the object in the $sOriginState state.
|
||||
* Note: Attributes (and flags) from the target state and the transition are combined.
|
||||
*
|
||||
* @param $sStimulus string
|
||||
* @param $sOriginState string Default is current state
|
||||
* @return array
|
||||
*/
|
||||
public function GetTransitionAttributes($sStimulus, $sOriginState = null)
|
||||
{
|
||||
$sObjClass = get_class($this);
|
||||
|
||||
// Defining current state as origin state if not specified
|
||||
if($sOriginState === null)
|
||||
{
|
||||
$sOriginState = $this->GetState();
|
||||
}
|
||||
|
||||
$aAttributes = MetaModel::GetTransitionAttributes($sObjClass, $sStimulus, $sOriginState);
|
||||
|
||||
return $aAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
|
||||
* for the given attribute for the current state of the object considered as an INITIAL state
|
||||
@@ -1117,16 +1224,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))
|
||||
{
|
||||
@@ -1167,7 +1275,7 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
$aChanges = $this->ListChanges();
|
||||
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
|
||||
foreach($aChanges as $sAttCode => $value)
|
||||
{
|
||||
$res = $this->CheckValue($sAttCode);
|
||||
if ($res !== true)
|
||||
@@ -1407,42 +1515,23 @@ abstract class DBObject implements iDisplay
|
||||
return true;
|
||||
}
|
||||
|
||||
// used only by insert
|
||||
protected function OnObjectKeyReady()
|
||||
{
|
||||
// Meant to be overloaded
|
||||
}
|
||||
|
||||
// used both by insert/update
|
||||
private function DBWriteLinks()
|
||||
{
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (!$oAttDef->IsLinkSet()) continue;
|
||||
if (!array_key_exists($sAttCode, $this->m_aTouchedAtt)) continue;
|
||||
if (array_key_exists($sAttCode, $this->m_aModifiedAtt) && ($this->m_aModifiedAtt[$sAttCode] == false)) continue;
|
||||
|
||||
// Note: any change to this algorithm must be reproduced into the implementation of AttributeLinkSet::Equals()
|
||||
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
||||
$sAdditionalKey = null;
|
||||
if ($oAttDef->IsIndirect() && !$oAttDef->DuplicatesAllowed())
|
||||
{
|
||||
$sAdditionalKey = $oAttDef->GetExtKeyToRemote();
|
||||
}
|
||||
$oComparator = new DBObjectSetComparator($this->m_aOrigValues[$sAttCode], $this->Get($sAttCode), array($sExtKeyToMe), $sAdditionalKey);
|
||||
$aChanges = $oComparator->GetDifferences();
|
||||
|
||||
foreach($aChanges['added'] as $oLink)
|
||||
{
|
||||
// Make sure that the objects in the set point to "this"
|
||||
$oLink->Set($oAttDef->GetExtKeyToMe(), $this->m_iKey);
|
||||
$id = $oLink->DBWrite();
|
||||
}
|
||||
|
||||
foreach($aChanges['modified'] as $oLink)
|
||||
{
|
||||
// Objects in the set either remain attached or have been detached -> leave the link as is
|
||||
$oLink->DBWrite();
|
||||
}
|
||||
|
||||
foreach($aChanges['removed'] as $oLink)
|
||||
{
|
||||
$oLink->DBDelete();
|
||||
}
|
||||
|
||||
$oLinkSet = $this->m_aCurrValues[$sAttCode];
|
||||
$oLinkSet->DBWrite($this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1644,11 +1733,21 @@ abstract class DBObject implements iDisplay
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
$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();
|
||||
|
||||
@@ -1873,7 +1972,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true;
|
||||
if ($oAttDef->IsDirectField())
|
||||
if ($oAttDef->IsBasedOnDBColumns())
|
||||
{
|
||||
$aDBChanges[$sAttCode] = $aChanges[$sAttCode];
|
||||
}
|
||||
@@ -1931,6 +2030,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
$oFilter = new DBObjectSearch(get_class($this));
|
||||
$oFilter->AddCondition('id', $this->m_iKey, '=');
|
||||
$oFilter->AllowAllData();
|
||||
|
||||
$sSQL = $oFilter->MakeUpdateQuery($aDBChanges);
|
||||
CMDBSource::Query($sSQL);
|
||||
@@ -2303,8 +2403,7 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function Reset($sAttCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$this->Set($sAttCode, $oAttDef->GetDefaultValue($this));
|
||||
$this->Set($sAttCode, $this->GetDefaultValue($sAttCode));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2840,7 +2939,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,
|
||||
@@ -3308,8 +3407,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$this->Set($sAttCode, $oAttDef->GetDefaultValue());
|
||||
$this->Set($sAttCode, $this->GetDefaultValue($sAttCode));
|
||||
break;
|
||||
|
||||
case 'nullify':
|
||||
@@ -3393,7 +3491,7 @@ abstract class DBObject implements iDisplay
|
||||
throw new Exception('Missing argument #1: source attribute');
|
||||
}
|
||||
$sSourceKeyAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($oObjectToRead), $sSourceKeyAttCode))
|
||||
if (($sSourceKeyAttCode != 'id') && !MetaModel::IsValidAttCode(get_class($oObjectToRead), $sSourceKeyAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($oObjectToRead)."::".$sSourceKeyAttCode);
|
||||
}
|
||||
@@ -3470,5 +3568,165 @@ abstract class DBObject implements iDisplay
|
||||
throw new Exception("Invalid verb");
|
||||
}
|
||||
}
|
||||
|
||||
public function IsArchived($sKeyAttCode = null)
|
||||
{
|
||||
$bRet = false;
|
||||
$sFlagAttCode = is_null($sKeyAttCode) ? 'archive_flag' : $sKeyAttCode.'_archive_flag';
|
||||
if (MetaModel::IsValidAttCode(get_class($this), $sFlagAttCode) && $this->Get($sFlagAttCode))
|
||||
{
|
||||
$bRet = true;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public function IsObsolete($sKeyAttCode = null)
|
||||
{
|
||||
$bRet = false;
|
||||
$sFlagAttCode = is_null($sKeyAttCode) ? 'obsolescence_flag' : $sKeyAttCode.'_obsolescence_flag';
|
||||
if (MetaModel::IsValidAttCode(get_class($this), $sFlagAttCode) && $this->Get($sFlagAttCode))
|
||||
{
|
||||
$bRet = true;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $bArchive
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function DBWriteArchiveFlag($bArchive)
|
||||
{
|
||||
if (!MetaModel::IsArchivable(get_class($this)))
|
||||
{
|
||||
throw new Exception(get_class($this).' is not an archivable class');
|
||||
}
|
||||
|
||||
$iFlag = $bArchive ? 1 : 0;
|
||||
$sDate = $bArchive ? '"'.date(AttributeDate::GetSQLFormat()).'"' : 'null';
|
||||
|
||||
$sClass = get_class($this);
|
||||
$sArchiveRoot = MetaModel::GetAttributeOrigin($sClass, 'archive_flag');
|
||||
$sRootTable = MetaModel::DBGetTable($sArchiveRoot);
|
||||
$sRootKey = MetaModel::DBGetKey($sArchiveRoot);
|
||||
$aJoins = array("`$sRootTable`");
|
||||
$aUpdates = array();
|
||||
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL) as $sParentClass)
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode($sParentClass, 'archive_flag')) continue;
|
||||
|
||||
$sTable = MetaModel::DBGetTable($sParentClass);
|
||||
$aUpdates[] = "`$sTable`.`archive_flag` = $iFlag";
|
||||
if ($sParentClass == $sArchiveRoot)
|
||||
{
|
||||
if (!$bArchive || $this->Get('archive_date') == '')
|
||||
{
|
||||
// Erase or set the date (do not change it)
|
||||
$aUpdates[] = "`$sTable`.`archive_date` = $sDate";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sKey = MetaModel::DBGetKey($sParentClass);
|
||||
$aJoins[] = "`$sTable` ON `$sTable`.`$sKey` = `$sRootTable`.`$sRootKey`";
|
||||
}
|
||||
}
|
||||
$sJoins = implode(' INNER JOIN ', $aJoins);
|
||||
$sValues = implode(', ', $aUpdates);
|
||||
$sUpdateQuery = "UPDATE $sJoins SET $sValues WHERE `$sRootTable`.`$sRootKey` = ".$this->GetKey();
|
||||
CMDBSource::Query($sUpdateQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called to repair the database (tables consistency)
|
||||
* The archive_date will be preserved
|
||||
* @throws Exception
|
||||
*/
|
||||
public function DBArchive()
|
||||
{
|
||||
$this->DBWriteArchiveFlag(true);
|
||||
$this->m_aCurrValues['archive_flag'] = true;
|
||||
$this->m_aOrigValues['archive_flag'] = true;
|
||||
}
|
||||
|
||||
public function DBUnarchive()
|
||||
{
|
||||
$this->DBWriteArchiveFlag(false);
|
||||
$this->m_aCurrValues['archive_flag'] = false;
|
||||
$this->m_aOrigValues['archive_flag'] = false;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
63
core/dbobjectiterator.php
Normal file
63
core/dbobjectiterator.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* A set of persistent objects, could be heterogeneous as long as the objects in the set have a common ancestor class
|
||||
*
|
||||
* @package iTopORM
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
interface iDBObjectSetIterator extends Countable
|
||||
{
|
||||
/**
|
||||
* The class of the objects of the collection (at least a common ancestor)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetClass();
|
||||
|
||||
/**
|
||||
* The total number of objects in the collection
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function Count();
|
||||
|
||||
/**
|
||||
* Reset the cursor to the first item in the collection. Equivalent to Seek(0)
|
||||
*
|
||||
* @return DBObject The fetched object or null when at the end
|
||||
*/
|
||||
public function Rewind();
|
||||
|
||||
/**
|
||||
* Position the cursor to the given 0-based position
|
||||
*
|
||||
* @param int $iRow
|
||||
*/
|
||||
public function Seek($iPosition);
|
||||
|
||||
/**
|
||||
* Fetch the object at the current position in the collection and move the cursor to the next position.
|
||||
*
|
||||
* @return DBObject The fetched object or null when at the end
|
||||
*/
|
||||
public function Fetch();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -16,11 +16,12 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
require_once('dbobjectiterator.php');
|
||||
|
||||
/**
|
||||
* Object set management
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -30,31 +31,66 @@
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class DBObjectSet
|
||||
class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aAddedIds; // Ids of objects added (discrete lists)
|
||||
/**
|
||||
* @var hash array of (row => array of (classalias) => object/null) storing the objects added "in memory"
|
||||
*/
|
||||
protected $m_aAddedObjects;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aArgs;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aAttToLoad;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aOrderBy;
|
||||
/**
|
||||
* @var bool True when the filter has been used OR the set is built step by step (AddObject...)
|
||||
*/
|
||||
public $m_bLoaded;
|
||||
/**
|
||||
* @var int Total number of rows for the query without LIMIT. null if unknown yet
|
||||
*/
|
||||
protected $m_iNumTotalDBRows;
|
||||
/**
|
||||
* @var int Total number of rows LOADED in $this->m_oSQLResult via a SQL query. 0 by default
|
||||
*/
|
||||
protected $m_iNumLoadedDBRows;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $m_iCurrRow;
|
||||
/**
|
||||
* @var DBSearch
|
||||
*/
|
||||
protected $m_oFilter;
|
||||
/**
|
||||
* @var mysqli_result
|
||||
*/
|
||||
protected $m_oSQLResult;
|
||||
protected $m_bSort;
|
||||
|
||||
/**
|
||||
* Create a new set based on a Search definition.
|
||||
*
|
||||
* @param 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();
|
||||
@@ -64,11 +100,12 @@ class DBObjectSet
|
||||
$this->m_aExtendedDataSpec = $aExtendedDataSpec;
|
||||
$this->m_iLimitCount = $iLimitCount;
|
||||
$this->m_iLimitStart = $iLimitStart;
|
||||
$this->m_bSort = $bSort;
|
||||
|
||||
$this->m_iNumTotalDBRows = null; // Total number of rows for the query without LIMIT. null if unknown yet
|
||||
$this->m_iNumLoadedDBRows = 0; // Total number of rows LOADED in $this->m_oSQLResult via a SQL query. 0 by default
|
||||
$this->m_bLoaded = false; // true when the filter has been used OR the set is built step by step (AddObject...)
|
||||
$this->m_aAddedObjects = array(); // array of (row => array of (classalias) => object/null) storing the objects added "in memory"
|
||||
$this->m_iNumTotalDBRows = null;
|
||||
$this->m_iNumLoadedDBRows = 0;
|
||||
$this->m_bLoaded = false;
|
||||
$this->m_aAddedObjects = array();
|
||||
$this->m_iCurrRow = 0;
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
@@ -123,10 +160,21 @@ class DBObjectSet
|
||||
$this->m_iCurrRow = 0;
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
|
||||
public function SetShowObsoleteData($bShow)
|
||||
{
|
||||
$this->m_oFilter->SetShowObsoleteData($bShow);
|
||||
}
|
||||
|
||||
public function GetShowObsoleteData()
|
||||
{
|
||||
return $this->m_oFilter->GetShowObsoleteData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the subset of attributes to load (for each class of objects) before performing the SQL query for retrieving the rows from the DB
|
||||
*
|
||||
* @param hash $aAttToLoad Format: alias => array of attribute_codes
|
||||
*
|
||||
* @param array $aAttToLoad Format: alias => array of attribute_codes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -150,11 +198,25 @@ class DBObjectSet
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad);
|
||||
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad] = $oAttDef;
|
||||
if ($oAttDef->IsExternalKey())
|
||||
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
|
||||
{
|
||||
// Add the external key friendly name anytime
|
||||
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_friendlyname');
|
||||
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_friendlyname'] = $oFriendlyNameAttDef;
|
||||
|
||||
if (MetaModel::IsArchivable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
|
||||
{
|
||||
// Add the archive flag if necessary
|
||||
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_archive_flag');
|
||||
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_archive_flag'] = $oArchiveFlagAttDef;
|
||||
}
|
||||
|
||||
if (MetaModel::IsObsoletable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
|
||||
{
|
||||
// Add the obsolescence flag if necessary
|
||||
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_obsolescence_flag');
|
||||
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_obsolescence_flag'] = $oObsoleteFlagAttDef;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +224,20 @@ class DBObjectSet
|
||||
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, 'friendlyname');
|
||||
$aAttToLoadWithAttDef[$sClassAlias]['friendlyname'] = $oFriendlyNameAttDef;
|
||||
|
||||
if (MetaModel::IsArchivable($sClass))
|
||||
{
|
||||
// Add the archive flag if necessary
|
||||
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, 'archive_flag');
|
||||
$aAttToLoadWithAttDef[$sClassAlias]['archive_flag'] = $oArchiveFlagAttDef;
|
||||
}
|
||||
|
||||
if (MetaModel::IsObsoletable($sClass))
|
||||
{
|
||||
// Add the obsolescence flag if necessary
|
||||
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, 'obsolescence_flag');
|
||||
$aAttToLoadWithAttDef[$sClassAlias]['obsolescence_flag'] = $oObsoleteFlagAttDef;
|
||||
}
|
||||
|
||||
// Make sure that the final class is requested anytime, whatever the specification (needed for object construction!)
|
||||
if (!MetaModel::IsStandaloneClass($sClass) && !array_key_exists('finalclass', $aAttToLoadWithAttDef[$sClassAlias]))
|
||||
{
|
||||
@@ -192,7 +268,7 @@ class DBObjectSet
|
||||
*
|
||||
* @param string $sClass The class (or an ancestor) for the objects to be added in this set
|
||||
*
|
||||
* @return DBObject The empty set
|
||||
* @return DBObjectSet The empty set
|
||||
*/
|
||||
static public function FromScratch($sClass)
|
||||
{
|
||||
@@ -264,8 +340,14 @@ class DBObjectSet
|
||||
}
|
||||
|
||||
return self::FromArray($sTargetClass, $aTargets);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: After calling this method, the set cursor will be at the end of the set. You might want to rewind it.
|
||||
*
|
||||
* @param bool $bWithId
|
||||
* @return array
|
||||
*/
|
||||
public function ToArray($bWithId = true)
|
||||
{
|
||||
$aRet = array();
|
||||
@@ -332,8 +414,15 @@ class DBObjectSet
|
||||
$iRow++;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: After calling this method, the set cursor will be at the end of the set. You might want to rewind it.
|
||||
*
|
||||
* @param string $sAttCode
|
||||
* @param bool $bWithId
|
||||
* @return array
|
||||
*/
|
||||
public function GetColumnAsArray($sAttCode, $bWithId = true)
|
||||
{
|
||||
$aRet = array();
|
||||
@@ -364,6 +453,7 @@ class DBObjectSet
|
||||
{
|
||||
// Make sure that we carry on the parameters of the set with the filter
|
||||
$oFilter = $this->m_oFilter->DeepClone();
|
||||
$oFilter->SetShowObsoleteData(true);
|
||||
// Note: the arguments found within a set can be object (but not in a filter)
|
||||
// That's why PrepareQueryArguments must be invoked there
|
||||
$oFilter->SetInternalParams(array_merge($oFilter->GetInternalParams(), $this->m_aArgs));
|
||||
@@ -445,8 +535,8 @@ class DBObjectSet
|
||||
|
||||
/**
|
||||
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
|
||||
*
|
||||
* @param hash $aOrderBy Format: field_code => boolean (true = ascending, false = descending)
|
||||
*
|
||||
* @param hash $aOrderBy Format: [alias.]attcode => boolean (true = ascending, false = descending)
|
||||
*/
|
||||
public function SetOrderBy($aOrderBy)
|
||||
{
|
||||
@@ -461,6 +551,34 @@ class DBObjectSet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
|
||||
*
|
||||
* @param hash $aAliases Format: alias => boolean (true = ascending, false = descending). If omitted, then it defaults to all the selected classes
|
||||
*/
|
||||
public function SetOrderByClasses($aAliases = null)
|
||||
{
|
||||
if ($aAliases === null)
|
||||
{
|
||||
$aAliases = array();
|
||||
foreach ($this->GetSelectedClasses() as $sAlias => $sClass)
|
||||
{
|
||||
$aAliases[$sAlias] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$aAttributes = array();
|
||||
foreach ($aAliases as $sAlias => $bClassDirection)
|
||||
{
|
||||
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClassName($sAlias)) as $sAttCode => $bAttributeDirection)
|
||||
{
|
||||
$bDirection = $bClassDirection ? $bAttributeDirection : !$bAttributeDirection;
|
||||
$aAttributes[$sAlias.'.'.$sAttCode] = $bDirection;
|
||||
}
|
||||
}
|
||||
$this->SetOrderBy($aAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 'count' limit for loading the rows from the DB
|
||||
*
|
||||
@@ -486,10 +604,15 @@ class DBObjectSet
|
||||
*
|
||||
* Limitation: the sort order has no effect on objects added in-memory
|
||||
*
|
||||
* @return hash Format: field_code => boolean (true = ascending, false = descending)
|
||||
* @return array Format: field_code => boolean (true = ascending, false = descending)
|
||||
*/
|
||||
public function GetRealSortOrder()
|
||||
{
|
||||
if (!$this->m_bSort)
|
||||
{
|
||||
// No order by
|
||||
return array();
|
||||
}
|
||||
// Get the class default sort order if not specified with the API
|
||||
//
|
||||
if (empty($this->m_aOrderBy))
|
||||
@@ -511,14 +634,7 @@ class DBObjectSet
|
||||
// Note: it is mandatory to set this value now, to protect against reentrance
|
||||
$this->m_bLoaded = true;
|
||||
|
||||
if ($this->m_iLimitCount > 0)
|
||||
{
|
||||
$sSQL = $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))
|
||||
{
|
||||
@@ -526,23 +642,74 @@ class DBObjectSet
|
||||
$this->m_oSQLResult->free();
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
$this->m_iNumTotalDBRows = null;
|
||||
|
||||
$this->m_oSQLResult = CMDBSource::Query($sSQL);
|
||||
|
||||
try
|
||||
{
|
||||
$this->m_oSQLResult = CMDBSource::Query($sSQL);
|
||||
} catch (MySQLException $e)
|
||||
{
|
||||
// 1116 = ER_TOO_MANY_TABLES
|
||||
// https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_too_many_tables
|
||||
if ($e->getCode() != 1116)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// N.689 Workaround for the 61 max joins in MySQL : full lazy load !
|
||||
$aAttToLoad = array();
|
||||
foreach($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
|
||||
{
|
||||
$aAttToLoad[$sClassAlias] = array();
|
||||
$bIsAbstractClass = MetaModel::IsAbstract($sClass);
|
||||
$bIsClassWithChildren = MetaModel::HasChildrenClasses($sClass);
|
||||
if ($bIsAbstractClass || $bIsClassWithChildren)
|
||||
{
|
||||
// we need finalClass field at least to be able to instantiate the real corresponding object !
|
||||
$aAttToLoad[$sClassAlias]['finalclass'] = MetaModel::GetAttributeDef($sClass, 'finalclass');
|
||||
}
|
||||
}
|
||||
$sSQL = $this->_makeSelectQuery($aAttToLoad);
|
||||
$this->m_oSQLResult = CMDBSource::Query($sSQL); // may fail again
|
||||
}
|
||||
|
||||
if ($this->m_oSQLResult === false) return;
|
||||
|
||||
if (($this->m_iLimitCount == 0) && ($this->m_iLimitStart == 0))
|
||||
|
||||
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.
|
||||
*
|
||||
* May actually perform the SQL query SELECT COUNT... if the set was not previously loaded, or loaded with a SetLimit
|
||||
*
|
||||
* @param string[] $aAttToLoad
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function Count()
|
||||
@@ -552,14 +719,83 @@ class DBObjectSet
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
if (!$resQuery) return 0;
|
||||
|
||||
|
||||
$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)
|
||||
*
|
||||
@@ -849,22 +1085,6 @@ class DBObjectSet
|
||||
return $oComparator->SetsAreEquivalent();
|
||||
}
|
||||
|
||||
protected function GetObjectAt($iIndex)
|
||||
{
|
||||
if (!$this->m_bLoaded) $this->Load();
|
||||
|
||||
// Save the current position for iteration
|
||||
$iCurrPos = $this->m_iCurrRow;
|
||||
|
||||
$this->Seek($iIndex);
|
||||
$oObject = $this->Fetch();
|
||||
|
||||
// Restore the current position for iteration
|
||||
$this->Seek($this->m_iCurrRow);
|
||||
|
||||
return $oObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new set (in memory) made of objects of the given set which are NOT present in the current set
|
||||
*
|
||||
@@ -1126,19 +1346,27 @@ class DBObjectSetComparator
|
||||
protected $aIDs1;
|
||||
protected $aIDs2;
|
||||
protected $aExcludedColumns;
|
||||
|
||||
/**
|
||||
* @var iDBObjectSetIterator
|
||||
*/
|
||||
protected $oSet1;
|
||||
/**
|
||||
* @var iDBObjectSetIterator
|
||||
*/
|
||||
protected $oSet2;
|
||||
|
||||
protected $sAdditionalKeyColumn;
|
||||
protected $aAdditionalKeys;
|
||||
|
||||
/**
|
||||
* Initializes the comparator
|
||||
* @param DBObjectSet $oSet1 The first set of objects to compare, or null
|
||||
* @param DBObjectSet $oSet2 The second set of objects to compare, or null
|
||||
* @param iDBObjectSetIterator $oSet1 The first set of objects to compare, or null
|
||||
* @param iDBObjectSetIterator $oSet2 The second set of objects to compare, or null
|
||||
* @param array $aExcludedColumns The list of columns (= attribute codes) to exclude from the comparison
|
||||
* @param string $sAdditionalKeyColumn The attribute code of an additional column to be considered as a key indentifying the object (useful for n:n links)
|
||||
*/
|
||||
public function __construct($oSet1, $oSet2, $aExcludedColumns = array(), $sAdditionalKeyColumn = null)
|
||||
public function __construct(iDBObjectSetIterator $oSet1, iDBObjectSetIterator $oSet2, $aExcludedColumns = array(), $sAdditionalKeyColumn = null)
|
||||
{
|
||||
$this->aFingerprints1 = null;
|
||||
$this->aFingerprints2 = null;
|
||||
@@ -1164,9 +1392,6 @@ class DBObjectSetComparator
|
||||
|
||||
if ($this->oSet1 !== null)
|
||||
{
|
||||
$aAliases = $this->oSet1->GetSelectedClasses();
|
||||
if (count($aAliases) > 1) throw new Exception('DBObjectSetComparator does not support Sets with more than one column. $oSet1: ('.print_r($aAliases, true).')');
|
||||
|
||||
$this->oSet1->Rewind();
|
||||
while($oObj = $this->oSet1->Fetch())
|
||||
{
|
||||
@@ -1182,9 +1407,6 @@ class DBObjectSetComparator
|
||||
|
||||
if ($this->oSet2 !== null)
|
||||
{
|
||||
$aAliases = $this->oSet2->GetSelectedClasses();
|
||||
if (count($aAliases) > 1) throw new Exception('DBObjectSetComparator does not support Sets with more than one column. $oSet2: ('.print_r($aAliases, true).')');
|
||||
|
||||
$this->oSet2->Rewind();
|
||||
while($oObj = $this->oSet2->Fetch())
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* A union of DBObjectSearches
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -55,6 +55,40 @@ class DBUnionSearch extends DBSearch
|
||||
$this->ComputeSelectedClasses();
|
||||
}
|
||||
|
||||
public function AllowAllData()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
}
|
||||
public function IsAllDataAllowed()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
if ($oSearch->IsAllDataAllowed() === false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function SetArchiveMode($bEnable)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->SetArchiveMode($bEnable);
|
||||
}
|
||||
parent::SetArchiveMode($bEnable);
|
||||
}
|
||||
|
||||
public function SetShowObsoleteData($bShow)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->SetShowObsoleteData($bShow);
|
||||
}
|
||||
parent::SetShowObsoleteData($bShow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest common ancestor for each of the selected class
|
||||
*/
|
||||
@@ -109,7 +143,7 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
/**
|
||||
* Limited to the selected classes
|
||||
*/
|
||||
*/
|
||||
public function GetClassName($sAlias)
|
||||
{
|
||||
if (array_key_exists($sAlias, $this->aSelectedClasses))
|
||||
@@ -187,6 +221,23 @@ class DBUnionSearch extends DBSearch
|
||||
$this->ComputeSelectedClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change any alias of the query tree
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
* @return bool True if the alias has been found and changed
|
||||
*/
|
||||
public function RenameAlias($sOldName, $sNewName)
|
||||
{
|
||||
$bRet = false;
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$bRet = $oSearch->RenameAlias($sOldName, $sNewName) || $bRet;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public function IsAny()
|
||||
{
|
||||
$bIsAny = true;
|
||||
@@ -282,19 +333,33 @@ class DBUnionSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
* @throws CoreException
|
||||
* @throws CoreWarning
|
||||
*/
|
||||
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode);
|
||||
$oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
*/
|
||||
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode);
|
||||
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,12 +422,12 @@ class DBUnionSearch extends DBSearch
|
||||
/**
|
||||
* Overloads for query building
|
||||
*/
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null)
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
|
||||
{
|
||||
$aSubQueries = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams);
|
||||
$aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams, $bWithAllowAllFlag);
|
||||
}
|
||||
$sRet = implode(' UNION ', $aSubQueries);
|
||||
return $sRet;
|
||||
@@ -409,15 +474,17 @@ class DBUnionSearch extends DBSearch
|
||||
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
|
||||
}
|
||||
|
||||
protected function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
{
|
||||
if (count($this->aSearches) == 1)
|
||||
{
|
||||
return $this->aSearches[0]->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $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());
|
||||
@@ -431,19 +498,30 @@ class DBUnionSearch extends DBSearch
|
||||
$aSearchSelectedClasses[$sSearchAlias] = $this->aSelectedClasses[$sAlias];
|
||||
}
|
||||
|
||||
if (is_null($aAttToLoad))
|
||||
if ($bGetCount)
|
||||
{
|
||||
$aQueryAttToLoad = null;
|
||||
// Select only ids for the count to allow optimization of joins
|
||||
foreach($aSearchAliases as $sSearchAlias)
|
||||
{
|
||||
$aQueryAttToLoad[$sSearchAlias] = array();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// (Eventually) Transform the aliases
|
||||
$aQueryAttToLoad = array();
|
||||
foreach ($aAttToLoad as $sAlias => $aAttributes)
|
||||
if (is_null($aAttToLoad))
|
||||
{
|
||||
$iColumn = array_search($sAlias, $aAliases);
|
||||
$sQueryAlias = ($iColumn === false) ? $sAlias : $aSearchAliases[$iColumn];
|
||||
$aQueryAttToLoad[$sQueryAlias] = $aAttributes;
|
||||
$aQueryAttToLoad = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// (Eventually) Transform the aliases
|
||||
$aQueryAttToLoad = array();
|
||||
foreach($aAttToLoad as $sAlias => $aAttributes)
|
||||
{
|
||||
$iColumn = array_search($sAlias, $aAliases);
|
||||
$sQueryAlias = ($iColumn === false) ? $sAlias : $aSearchAliases[$iColumn];
|
||||
$aQueryAttToLoad[$sQueryAlias] = $aAttributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,11 +546,53 @@ class DBUnionSearch extends DBSearch
|
||||
$aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
|
||||
}
|
||||
}
|
||||
$oSubQuery = $oSearch->MakeSQLQuery($aQueryAttToLoad, false, $aModifierProperties, $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
|
||||
// (default order of selected fields depending on the order of JOINS)
|
||||
$oSubQuery->SortSelectedFields();
|
||||
}
|
||||
$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();
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
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 null
|
||||
* @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 null
|
||||
* @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)
|
||||
if (is_null($sDefault))
|
||||
{
|
||||
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 $sStringCode;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1438,22 +1438,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 +1476,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,29 @@ class EMail
|
||||
|
||||
$aFailedRecipients = array();
|
||||
$this->m_oMessage->setMaxLineLength(0);
|
||||
IssueLog::Info(__METHOD__.' '.$this->m_oMessage->getMaxLineLength());
|
||||
IssueLog::Info(__METHOD__.' '.$this->m_oMessage->toString());
|
||||
$iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients);
|
||||
if ($iSent === 0)
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
// 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.');
|
||||
return EMAIL_SEND_ERROR;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception $e)
|
||||
{
|
||||
$aIssues = array();
|
||||
return EMAIL_SEND_OK;
|
||||
$oKPI->ComputeStats('Email Sent', 'Error received');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +261,11 @@ IssueLog::Info(__METHOD__.' '.$this->m_oMessage->toString());
|
||||
|
||||
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.
|
||||
//
|
||||
@@ -264,8 +264,8 @@ class EventIssue extends Event
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a string
|
||||
$aPost[$sKey] = (string) $sValue;
|
||||
// Not a string (avoid warnings in case the value cannot be easily casted into a string)
|
||||
$aPost[$sKey] = @(string) $sValue;
|
||||
}
|
||||
}
|
||||
$this->Set('arguments_post', $aPost);
|
||||
@@ -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
|
||||
{
|
||||
|
||||
110
core/expressioncache.class.inc.php
Normal file
110
core/expressioncache.class.inc.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// Copyright (c) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
class ExpressionCache
|
||||
{
|
||||
static private $aCache = array();
|
||||
|
||||
static public function GetCachedExpression($sClass, $sAttCode)
|
||||
{
|
||||
// read current cache
|
||||
@include_once (static::GetCacheFileName());
|
||||
|
||||
$oExpr = null;
|
||||
$sKey = static::GetKey($sClass, $sAttCode);
|
||||
if (array_key_exists($sKey, static::$aCache))
|
||||
{
|
||||
$oExpr = static::$aCache[$sKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (class_exists('ExpressionCacheData'))
|
||||
{
|
||||
if (array_key_exists($sKey, ExpressionCacheData::$aCache))
|
||||
{
|
||||
$sVal = ExpressionCacheData::$aCache[$sKey];
|
||||
$oExpr = unserialize($sVal);
|
||||
static::$aCache[$sKey] = $oExpr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $oExpr;
|
||||
}
|
||||
|
||||
|
||||
static public function Warmup()
|
||||
{
|
||||
$sFilePath = static::GetCacheFileName();
|
||||
|
||||
if (!is_file($sFilePath))
|
||||
{
|
||||
$content = <<<EOF
|
||||
<?php
|
||||
// Copyright (c) 2010-2017 Combodo SARL
|
||||
// Generated Expression Cache file
|
||||
|
||||
class ExpressionCacheData
|
||||
{
|
||||
static \$aCache = array(
|
||||
EOF;
|
||||
|
||||
foreach(MetaModel::GetClasses() as $sClass)
|
||||
{
|
||||
$content .= static::GetSerializedExpression($sClass, 'friendlyname');
|
||||
if (MetaModel::IsObsoletable($sClass))
|
||||
{
|
||||
$content .= static::GetSerializedExpression($sClass, 'obsolescence_flag');
|
||||
}
|
||||
}
|
||||
|
||||
$content .= <<<EOF
|
||||
);
|
||||
}
|
||||
EOF;
|
||||
|
||||
file_put_contents($sFilePath, $content);
|
||||
}
|
||||
}
|
||||
|
||||
static private function GetSerializedExpression($sClass, $sAttCode)
|
||||
{
|
||||
$sKey = static::GetKey($sClass, $sAttCode);
|
||||
$oExpr = DBObjectSearch::GetPolymorphicExpression($sClass, $sAttCode);
|
||||
return "'".$sKey."' => '".serialize($oExpr)."',\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sClass
|
||||
* @param $sAttCode
|
||||
* @return string
|
||||
*/
|
||||
static private function GetKey($sClass, $sAttCode)
|
||||
{
|
||||
return $sClass.'::'.$sAttCode;
|
||||
}
|
||||
|
||||
public static function GetCacheFileName()
|
||||
{
|
||||
return utils::GetCachePath().'expressioncache.php';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<?php
|
||||
// Copyright (C) 2016-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
/**
|
||||
* Base class for all possible implementations of HTML Sanitization
|
||||
*/
|
||||
@@ -135,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'),
|
||||
'a' => array('href', 'name', 'style', 'target', 'title'),
|
||||
'p' => array('style'),
|
||||
'blockquote' => array('style'),
|
||||
'br' => array(),
|
||||
'span' => array('style'),
|
||||
'div' => array('style'),
|
||||
@@ -148,7 +170,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'u' => array(),
|
||||
'em' => array(),
|
||||
'strong' => array(),
|
||||
'img' => array('src','style'),
|
||||
'img' => array('src', 'style', 'alt', 'title'),
|
||||
'ul' => array('style'),
|
||||
'ol' => array('style'),
|
||||
'li' => array('style'),
|
||||
@@ -159,19 +181,18 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'nav' => array('style'),
|
||||
'section' => array('style'),
|
||||
'code' => array('style'),
|
||||
'table' => array('style', 'width'),
|
||||
'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(),
|
||||
@@ -182,22 +203,64 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'q' => array(),
|
||||
'hr' => array('style'),
|
||||
'pre' => array(),
|
||||
'center' => array(),
|
||||
);
|
||||
|
||||
|
||||
protected static $aAttrsWhiteList = array(
|
||||
'href' => '/^(http:|https:)/i',
|
||||
'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',
|
||||
'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))
|
||||
{
|
||||
// Regular urls
|
||||
$sUrlPattern = utils::GetConfig()->Get('url_validation_pattern');
|
||||
// Mailto urls
|
||||
$sMailtoPattern = '(mailto:(' . utils::GetConfig()->Get('email_validation_pattern') . ')(?:\?(?:subject|body)=([a-zA-Z0-9+\$_.-]*)(?:&(?:subject|body)=([a-zA-Z0-9+\$_.-]*))?)?)';
|
||||
|
||||
$sPattern = $sUrlPattern . '|' . $sMailtoPattern;
|
||||
$sPattern = '/'.str_replace('/', '\/', $sPattern).'/i';
|
||||
self::$aAttrsWhiteList['href'] = $sPattern;
|
||||
}
|
||||
}
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
|
||||
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
|
||||
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
|
||||
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
|
||||
// therefore we have to do the transformation upfront
|
||||
$sHTML = preg_replace('@<o:p>\s*</o:p>@', '<br>', $sHTML);
|
||||
|
||||
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
|
||||
|
||||
$this->CleanNode($this->oDoc);
|
||||
|
||||
@@ -464,7 +464,7 @@ EOF
|
||||
oEditor.on( 'instanceReady', function() {
|
||||
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .editor_magnifier').length == 0)
|
||||
{
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="editor_magnifier" title="$sToggleFullScreen" style="display:block;width:12px;height:11px;border:1px #A6A6A6 solid;cursor:pointer; background-image:url($sAppRootUrl/images/full-screen.png)"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="editor_magnifier" title="$sToggleFullScreen" style="display:block;width:12px;height:11px;border:1px #A6A6A6 solid;cursor:pointer; background-image:url(\\'$sAppRootUrl/images/full-screen.png\\')"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox .editor_magnifier').on('click', function() {
|
||||
oEditor.execCommand('maximize');
|
||||
if ($(this).closest('.cke_maximized').length != 0)
|
||||
@@ -473,12 +473,15 @@ EOF
|
||||
}
|
||||
});
|
||||
}
|
||||
oEditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
|
||||
var oData = JSON.parse(upload.xhr.responseText);
|
||||
this.replaceWith( '<img src="' + upload.url + '" ' +
|
||||
'width="' + oData.width + '" ' +
|
||||
'height="' + oData.height + '">' );
|
||||
}
|
||||
if (oEditor.widgets.registered.uploadimage)
|
||||
{
|
||||
oEditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
|
||||
var oData = JSON.parse(upload.xhr.responseText);
|
||||
this.replaceWith( '<img src="' + upload.url + '" ' +
|
||||
'width="' + oData.width + '" ' +
|
||||
'height="' + oData.height + '">' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
EOF
|
||||
@@ -494,10 +497,10 @@ EOF
|
||||
*/
|
||||
class InlineImageGC implements iBackgroundProcess
|
||||
{
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return 3600; // Runs every 3600 seconds
|
||||
}
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return 3600; // Runs every hour
|
||||
}
|
||||
|
||||
public function Process($iTimeLimit)
|
||||
{
|
||||
|
||||
93
core/introspection.class.inc.php
Normal file
93
core/introspection.class.inc.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
// Copyright (C) 2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* require_once(...'introspection.class.inc.php');
|
||||
*/
|
||||
|
||||
require_once('attributedef.class.inc.php');
|
||||
|
||||
class Introspection
|
||||
{
|
||||
protected $aAttributeHierarchy = array(); // class => child classes
|
||||
protected $aAttributes = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->InitAttributes();
|
||||
}
|
||||
|
||||
protected function InitAttributes()
|
||||
{
|
||||
foreach(get_declared_classes() as $sPHPClass)
|
||||
{
|
||||
$oRefClass = new ReflectionClass($sPHPClass);
|
||||
if ($sPHPClass == 'AttributeDefinition' || $oRefClass->isSubclassOf('AttributeDefinition'))
|
||||
{
|
||||
if ($oParentClass = $oRefClass->getParentClass())
|
||||
{
|
||||
$sParentClass = $oParentClass->getName();
|
||||
if (!array_key_exists($sParentClass, $this->aAttributeHierarchy))
|
||||
{
|
||||
$this->aAttributeHierarchy[$sParentClass] = array();
|
||||
}
|
||||
$this->aAttributeHierarchy[$sParentClass][] = $sPHPClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sParentClass = null;
|
||||
}
|
||||
$this->aAttributes[$sPHPClass] = array(
|
||||
'parent' => $sParentClass,
|
||||
'LoadInObject' => $sPHPClass::LoadInObject(),
|
||||
'LoadFromDB' => $sPHPClass::LoadFromDB(),
|
||||
'IsBasedOnDBColumns' => $sPHPClass::IsBasedOnDBColumns(),
|
||||
'IsBasedOnOQLExpression' => $sPHPClass::IsBasedOnOQLExpression(),
|
||||
'IsExternalField' => $sPHPClass::IsExternalField(),
|
||||
'IsScalar' => $sPHPClass::IsScalar(),
|
||||
'IsLinkset' => $sPHPClass::IsLinkset(),
|
||||
'IsHierarchicalKey' => $sPHPClass::IsHierarchicalKey(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
public function GetAttributes()
|
||||
{
|
||||
return $this->aAttributes;
|
||||
}
|
||||
public function GetAttributeHierarchy()
|
||||
{
|
||||
return $this->aAttributeHierarchy;
|
||||
}
|
||||
public function EnumAttributeCharacteristics()
|
||||
{
|
||||
return array(
|
||||
'LoadInObject' => 'Is the value stored in the object itself?',
|
||||
'LoadFromDB' => 'Is the value read from the DB?',
|
||||
'IsBasedOnDBColumns' => 'Is this a value stored within one or several columns?',
|
||||
'IsBasedOnOQLExpression' => 'Is this a value computed after other attributes, by the mean of an OQL expression?',
|
||||
'IsExternalField' => 'Is this a value stored on a related object (external key)?',
|
||||
'IsScalar' => 'Is this a value that makes sense in a SQL/OQL expression?',
|
||||
'IsLinkset' => 'Is this a collection (1-N or N-N)?',
|
||||
'IsHierarchicalKey' => 'Is this attribute an external key pointing to the host class?',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* File logging
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -69,81 +69,54 @@ class FileLog
|
||||
}
|
||||
}
|
||||
|
||||
class SetupLog
|
||||
abstract class LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
static::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Error($sText);
|
||||
}
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Info($sText);
|
||||
}
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IssueLog
|
||||
class SetupLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
|
||||
class ToolsLog
|
||||
class IssueLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
|
||||
class ToolsLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
?>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -89,8 +89,7 @@ class ModuleDesign extends \Combodo\iTop\DesignDocument
|
||||
}
|
||||
else
|
||||
{
|
||||
var_dump($aFiles);
|
||||
$aAvailable = array();
|
||||
$aAvailable = array();
|
||||
foreach ($aFiles as $sFile)
|
||||
{
|
||||
$aAvailable[] = "'".basename($sFile, '.xml')."'";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2013-2016 Combodo SARL
|
||||
// Copyright (C) 2013-2018 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -24,32 +24,58 @@
|
||||
* Relies on MySQL locks because the API sem_get is not always present in the
|
||||
* installed PHP.
|
||||
*
|
||||
* @copyright Copyright (C) 2013-2016 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
|
||||
$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)
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
if ($oConfig === null)
|
||||
{
|
||||
$oConfig = utils::GetConfig(); // Will return an empty config when called during the setup
|
||||
}
|
||||
$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))
|
||||
@@ -57,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()
|
||||
@@ -75,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()
|
||||
{
|
||||
@@ -193,26 +218,26 @@ class iTopMutex
|
||||
self::$aAcquiredLocks[$this->sName]--;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function InitMySQLSession($sHost, $sUser, $sPwd)
|
||||
/**
|
||||
* Initialize database connection. Mandatory attributes must be already set !
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function InitMySQLSession()
|
||||
{
|
||||
$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);
|
||||
}
|
||||
$sServer = $this->sDBHost;
|
||||
$sUser = $this->sDBUser;
|
||||
$sPwd = $this->sDBPwd;
|
||||
$sSource = $this->sDBName;
|
||||
$bTlsEnabled = $this->bDBTlsEnabled;
|
||||
$sTlsCA = $this->sDBTlsCA;
|
||||
|
||||
$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
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Wrapper to execute the parser, lexical analyzer and normalization of an OQL query
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -83,6 +83,10 @@ class OqlInterpreter
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OqlQuery
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function ParseQuery()
|
||||
{
|
||||
$oRes = $this->Parse();
|
||||
@@ -93,6 +97,9 @@ class OqlInterpreter
|
||||
return $oRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Expression
|
||||
*/
|
||||
public function ParseExpression()
|
||||
{
|
||||
$oRes = $this->Parse();
|
||||
@@ -103,5 +110,3 @@ class OqlInterpreter
|
||||
return $oRes;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -283,10 +283,10 @@ abstract class OqlQuery
|
||||
/**
|
||||
* Check the validity of the expression with regard to the data model
|
||||
* and the query in which it is used
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @throws OqlNormalizeException
|
||||
*/
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @param string $sSourceQuery
|
||||
*/
|
||||
abstract public function Check(ModelReflection $oModelReflection, $sSourceQuery);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -23,7 +23,7 @@ define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\
|
||||
/**
|
||||
* Class to store a "case log" in a structured way, keeping track of its successive entries
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
class ormCaseLog {
|
||||
@@ -191,8 +191,15 @@ class ormCaseLog {
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->m_sLog;
|
||||
if($this->IsEmpty()) return '';
|
||||
|
||||
return $this->m_sLog;
|
||||
}
|
||||
|
||||
public function IsEmpty()
|
||||
{
|
||||
return ($this->m_sLog === null);
|
||||
}
|
||||
|
||||
public function ClearModifiedFlag()
|
||||
{
|
||||
@@ -388,8 +395,9 @@ class ormCaseLog {
|
||||
if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
|
||||
{
|
||||
// Don't display the first element, that is still considered as editable
|
||||
$iPos = $aIndex[0]['separator_length'] + $aIndex[0]['text_length'];
|
||||
array_shift($aIndex);
|
||||
$aLastEntry = end($aIndex);
|
||||
$iPos = $aLastEntry['separator_length'] + $aLastEntry['text_length'];
|
||||
array_pop($aIndex);
|
||||
}
|
||||
for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
|
||||
{
|
||||
@@ -519,57 +527,36 @@ class ormCaseLog {
|
||||
if ($this->m_bModified)
|
||||
{
|
||||
$aLatestEntry = end($this->m_aIndex);
|
||||
if ($aLatestEntry['user_name'] != $sOnBehalfOf)
|
||||
if ($aLatestEntry['user_name'] == $sOnBehalfOf)
|
||||
{
|
||||
$bMergeEntries = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$bMergeEntries = true;
|
||||
// Append the new text to the previous one
|
||||
$sPreviousText = substr($this->m_sLog, $aLatestEntry['separator_length'], $aLatestEntry['text_length']);
|
||||
$sText = $sPreviousText."\n".$sText;
|
||||
|
||||
// Cleanup the previous entry
|
||||
array_pop($this->m_aIndex);
|
||||
$this->m_sLog = substr($this->m_sLog, $aLatestEntry['separator_length'] + $aLatestEntry['text_length']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($bMergeEntries)
|
||||
{
|
||||
$aLatestEntry = end($this->m_aIndex);
|
||||
$this->m_sLog = substr($this->m_sLog, $aLatestEntry['separator_length']);
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
|
||||
$iSepLength = strlen($sSeparator);
|
||||
$iTextlength = strlen($sText."\n");
|
||||
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
|
||||
$this->m_aIndex[] = array(
|
||||
'user_name' => $sOnBehalfOf,
|
||||
'user_id' => $iUserId,
|
||||
'date' => time(),
|
||||
'text_length' => $aLatestEntry['text_length'] + $iTextlength,
|
||||
'separator_length' => $iSepLength,
|
||||
'format' => 'html',
|
||||
);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
|
||||
$iSepLength = strlen($sSeparator);
|
||||
$iTextlength = strlen($sText);
|
||||
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
|
||||
$this->m_aIndex[] = array(
|
||||
'user_name' => $sOnBehalfOf,
|
||||
'user_id' => $iUserId,
|
||||
'date' => time(),
|
||||
'text_length' => $iTextlength,
|
||||
'separator_length' => $iSepLength,
|
||||
'format' => 'html',
|
||||
);
|
||||
}
|
||||
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
|
||||
$iSepLength = strlen($sSeparator);
|
||||
$iTextlength = strlen($sText);
|
||||
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
|
||||
$this->m_aIndex[] = array(
|
||||
'user_name' => $sOnBehalfOf,
|
||||
'user_id' => $iUserId,
|
||||
'date' => time(),
|
||||
'text_length' => $iTextlength,
|
||||
'separator_length' => $iSepLength,
|
||||
'format' => 'html',
|
||||
);
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
|
||||
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
|
||||
{
|
||||
$sText = HTMLSanitizer::Sanitize(isset($oJson->message) ? $oJson->message : '');
|
||||
|
||||
if (isset($oJson->user_id))
|
||||
{
|
||||
if (!UserRights::IsAdministrator())
|
||||
@@ -616,10 +603,16 @@ class ormCaseLog {
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: what is the default format ? text ?
|
||||
// The default is HTML
|
||||
$sFormat = 'html';
|
||||
}
|
||||
|
||||
|
||||
$sText = isset($oJson->message) ? $oJson->message : '';
|
||||
if ($sFormat == 'html')
|
||||
{
|
||||
$sText = HTMLSanitizer::Sanitize($sText);
|
||||
}
|
||||
|
||||
$sDate = date(AttributeDateTime::GetInternalFormat(), $iDate);
|
||||
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
|
||||
@@ -639,12 +632,12 @@ class ormCaseLog {
|
||||
}
|
||||
|
||||
|
||||
public function GetModifiedEntry()
|
||||
public function GetModifiedEntry($sFormat = 'text')
|
||||
{
|
||||
$sModifiedEntry = '';
|
||||
if ($this->m_bModified)
|
||||
{
|
||||
$sModifiedEntry = $this->GetLatestEntry();
|
||||
$sModifiedEntry = $this->GetLatestEntry($sFormat);
|
||||
}
|
||||
return $sModifiedEntry;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class ormCustomFieldsValue
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
|
||||
$oHandler = $oAttDef->GetHandler($this->GetValues());
|
||||
return 'template...verb='.$sVerb.' sur "'.json_encode($this->aCurrentValues).'"';
|
||||
return $oHandler->GetForTemplate($this->aCurrentValues, $sVerb, $bLocalize);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,6 +51,8 @@ class ormDocument
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if($this->IsEmpty()) return '';
|
||||
|
||||
return MyHelpers::beautifulstr($this->m_data, 100, true);
|
||||
}
|
||||
|
||||
@@ -115,21 +117,29 @@ class ormDocument
|
||||
*/
|
||||
public function GetDownloadLink($sClass, $Id, $sAttCode)
|
||||
{
|
||||
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8')."</a>\n";
|
||||
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8')."</a>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an URL to display a document like an image
|
||||
* @return string
|
||||
*/
|
||||
public function GetDisplayURL($sClass, $Id, $sAttCode)
|
||||
{
|
||||
return utils::GetAbsoluteUrlAppRoot() . "pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an URL to download a document like an image (uses HTTP caching)
|
||||
* @return string
|
||||
*/
|
||||
*/
|
||||
public function GetDownloadURL($sClass, $Id, $sAttCode)
|
||||
{
|
||||
// Compute a signature to reset the cache anytime the data changes (this is acceptable if used only with icon files)
|
||||
$sSignature = md5($this->GetData());
|
||||
return utils::GetAbsoluteUrlAppRoot()."pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode&s=$sSignature&cache=86400";
|
||||
return utils::GetAbsoluteUrlAppRoot() . "pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode&s=$sSignature&cache=86400";
|
||||
}
|
||||
|
||||
|
||||
public function IsPreviewAvailable()
|
||||
{
|
||||
$bRet = false;
|
||||
@@ -176,7 +186,7 @@ class ormDocument
|
||||
{
|
||||
$oPage->TrashUnexpectedOutput();
|
||||
$oPage->SetContentType($oDocument->GetMimeType());
|
||||
//$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
|
||||
$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
|
||||
$oPage->add($oDocument->GetData());
|
||||
}
|
||||
}
|
||||
|
||||
750
core/ormlinkset.class.inc.php
Normal file
750
core/ormlinkset.class.inc.php
Normal file
@@ -0,0 +1,750 @@
|
||||
<?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/>
|
||||
|
||||
require_once('dbobjectiterator.php');
|
||||
|
||||
|
||||
/**
|
||||
* The value for an attribute representing a set of links between the host object and "remote" objects
|
||||
*
|
||||
* @package iTopORM
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
{
|
||||
protected $sHostClass; // subclass of DBObject
|
||||
protected $sAttCode; // xxxxxx_list
|
||||
protected $sClass; // class of the links
|
||||
|
||||
/**
|
||||
* @var DBObjectSet
|
||||
*/
|
||||
protected $oOriginalSet;
|
||||
|
||||
/**
|
||||
* @var DBObject[] array of iObjectId => DBObject
|
||||
*/
|
||||
protected $aOriginalObjects = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $bHasDelta = false;
|
||||
|
||||
/**
|
||||
* Object from the original set, minus the removed objects
|
||||
* @var DBObject[] array of iObjectId => DBObject
|
||||
*/
|
||||
protected $aPreserved = array();
|
||||
|
||||
/**
|
||||
* @var DBObject[] New items
|
||||
*/
|
||||
protected $aAdded = array();
|
||||
|
||||
/**
|
||||
* @var DBObject[] Modified items (could also be found in aPreserved)
|
||||
*/
|
||||
protected $aModified = array();
|
||||
|
||||
/**
|
||||
* @var int[] Removed items
|
||||
*/
|
||||
protected $aRemoved = array();
|
||||
|
||||
/**
|
||||
* @var int Position in the collection
|
||||
*/
|
||||
protected $iCursor = 0;
|
||||
|
||||
/**
|
||||
* __toString magical function overload.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* ormLinkSet constructor.
|
||||
* @param $sHostClass
|
||||
* @param $sAttCode
|
||||
* @param DBObjectSet|null $oOriginalSet
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($sHostClass, $sAttCode, DBObjectSet $oOriginalSet = null)
|
||||
{
|
||||
$this->sHostClass = $sHostClass;
|
||||
$this->sAttCode = $sAttCode;
|
||||
$this->oOriginalSet = $oOriginalSet ? clone $oOriginalSet : null;
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sHostClass, $sAttCode);
|
||||
if (!$oAttDef instanceof AttributeLinkedSet)
|
||||
{
|
||||
throw new Exception("ormLinkSet: $sAttCode is not a link set");
|
||||
}
|
||||
$this->sClass = $oAttDef->GetLinkedClass();
|
||||
if ($oOriginalSet && ($oOriginalSet->GetClass() != $this->sClass))
|
||||
{
|
||||
throw new Exception("ormLinkSet: wrong class for the original set, found {$oOriginalSet->GetClass()} while expecting {$oAttDef->GetLinkedClass()}");
|
||||
}
|
||||
}
|
||||
|
||||
public function GetFilter()
|
||||
{
|
||||
return clone $this->oOriginalSet->GetFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function OptimizeColumnLoad($aAttToLoad)
|
||||
{
|
||||
$this->oOriginalSet->OptimizeColumnLoad($aAttToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObject $oLink
|
||||
*/
|
||||
public function AddItem(DBObject $oLink)
|
||||
{
|
||||
assert($oLink instanceof $this->sClass);
|
||||
// No impact on the iteration algorithm
|
||||
$iObjectId = $oLink->GetKey();
|
||||
$this->aAdded[$iObjectId] = $oLink;
|
||||
$this->bHasDelta = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObject $oObject
|
||||
* @param string $sClassAlias
|
||||
* @deprecated Since iTop 2.4, use ormLinkset->AddItem() instead.
|
||||
*/
|
||||
public function AddObject(DBObject $oObject, $sClassAlias = '')
|
||||
{
|
||||
$this->AddItem($oObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $iObjectId
|
||||
*/
|
||||
public function RemoveItem($iObjectId)
|
||||
{
|
||||
if (array_key_exists($iObjectId, $this->aPreserved))
|
||||
{
|
||||
unset($this->aPreserved[$iObjectId]);
|
||||
$this->aRemoved[$iObjectId] = $iObjectId;
|
||||
$this->bHasDelta = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (array_key_exists($iObjectId, $this->aAdded))
|
||||
{
|
||||
unset($this->aAdded[$iObjectId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObject $oLink
|
||||
*/
|
||||
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()
|
||||
{
|
||||
if ($this->aOriginalObjects === null)
|
||||
{
|
||||
if ($this->oOriginalSet)
|
||||
{
|
||||
$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))
|
||||
{
|
||||
unset($this->aPreserved[$iObjectId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Nothing to load
|
||||
$this->aOriginalObjects = array();
|
||||
$this->aPreserved = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Since iTop 2.4, use foreach($this as $oItem){} instead
|
||||
*/
|
||||
public function ToArray($bWithId = true)
|
||||
{
|
||||
$aRet = array();
|
||||
foreach($this as $oItem)
|
||||
{
|
||||
if ($bWithId)
|
||||
{
|
||||
$aRet[$oItem->GetKey()] = $oItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRet[] = $oItem;
|
||||
}
|
||||
}
|
||||
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)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetClass()
|
||||
{
|
||||
return $this->sClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of objects in the collection
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function Count()
|
||||
{
|
||||
$this->LoadOriginalIds();
|
||||
$iRet = count($this->aPreserved) + count($this->aAdded) + count($this->aModified);
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the cursor to the given 0-based position
|
||||
*
|
||||
* @param $iPosition
|
||||
* @throws Exception
|
||||
* @internal param int $iRow
|
||||
*/
|
||||
public function Seek($iPosition)
|
||||
{
|
||||
$this->LoadOriginalIds();
|
||||
|
||||
$iCount = $this->Count();
|
||||
if ($iPosition >= $iCount)
|
||||
{
|
||||
throw new Exception("Invalid position $iPosition: the link set is made of $iCount items.");
|
||||
}
|
||||
$this->rewind();
|
||||
for($iPos = 0 ; $iPos < $iPosition ; $iPos++)
|
||||
{
|
||||
$this->next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the object at the current position in the collection and move the cursor to the next position.
|
||||
*
|
||||
* @return DBObject|null The fetched object or null when at the end
|
||||
*/
|
||||
public function Fetch()
|
||||
{
|
||||
$this->LoadOriginalIds();
|
||||
|
||||
$ret = $this->current();
|
||||
if ($ret === false)
|
||||
{
|
||||
$ret = null;
|
||||
}
|
||||
$this->next();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
* @link http://php.net/manual/en/iterator.current.php
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
$this->LoadOriginalIds();
|
||||
|
||||
$iPreservedCount = count($this->aPreserved);
|
||||
if ($this->iCursor < $iPreservedCount)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
* @link http://php.net/manual/en/iterator.next.php
|
||||
* @return void Any returned value is ignored.
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
$this->LoadOriginalIds();
|
||||
|
||||
$iPreservedCount = count($this->aPreserved);
|
||||
if ($this->iCursor < $iPreservedCount)
|
||||
{
|
||||
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 aModified / aAdded, we must leave it intact
|
||||
$this->iCursor++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element
|
||||
* @link http://php.net/manual/en/iterator.key.php
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->iCursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
* @link http://php.net/manual/en/iterator.valid.php
|
||||
* @return boolean The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
$this->LoadOriginalIds();
|
||||
|
||||
$iCount = $this->Count();
|
||||
$bRet = ($this->iCursor < $iCount);
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element
|
||||
* @link http://php.net/manual/en/iterator.rewind.php
|
||||
* @return void Any returned value is ignored.
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->LoadOriginalIds();
|
||||
|
||||
$this->iCursor = 0;
|
||||
reset($this->aPreserved);
|
||||
reset($this->aAdded);
|
||||
reset($this->aModified);
|
||||
}
|
||||
|
||||
public function HasDelta()
|
||||
{
|
||||
return $this->bHasDelta;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method has been designed specifically for AttributeLinkedSet:Equals and as such it assumes that the passed argument is a clone of this.
|
||||
* @param ormLinkSet $oFellow
|
||||
* @return bool|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public function Equals(ormLinkSet $oFellow)
|
||||
{
|
||||
$bRet = null;
|
||||
if ($this === $oFellow)
|
||||
{
|
||||
$bRet = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( ($this->oOriginalSet !== $oFellow->oOriginalSet)
|
||||
&& ($this->oOriginalSet->GetFilter()->ToOQL() != $oFellow->oOriginalSet->GetFilter()->ToOQL()) )
|
||||
{
|
||||
throw new Exception('ormLinkSet::Equals assumes that compared link sets have the same original scope');
|
||||
}
|
||||
if ($this->HasDelta())
|
||||
{
|
||||
throw new Exception('ormLinkSet::Equals assumes that left link set had no delta');
|
||||
}
|
||||
$bRet = !$oFellow->HasDelta();
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public function UpdateFromCompleteList(iDBObjectSetIterator $oFellow)
|
||||
{
|
||||
if ($oFellow === $this)
|
||||
{
|
||||
throw new Exception('ormLinkSet::UpdateFromCompleteList assumes that the passed link set is at least a clone of the current one');
|
||||
}
|
||||
$bUpdateFromDelta = false;
|
||||
if ($oFellow instanceof ormLinkSet)
|
||||
{
|
||||
if ( ($this->oOriginalSet === $oFellow->oOriginalSet)
|
||||
|| ($this->oOriginalSet->GetFilter()->ToOQL() == $oFellow->oOriginalSet->GetFilter()->ToOQL()) )
|
||||
{
|
||||
$bUpdateFromDelta = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bUpdateFromDelta)
|
||||
{
|
||||
// Same original set -> simply update the delta
|
||||
$this->iCursor = 0;
|
||||
$this->aAdded = $oFellow->aAdded;
|
||||
$this->aRemoved = $oFellow->aRemoved;
|
||||
$this->aModified = $oFellow->aModified;
|
||||
$this->aPreserved = $oFellow->aPreserved;
|
||||
$this->bHasDelta = $oFellow->bHasDelta;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For backward compatibility reasons, let's rebuild a delta...
|
||||
|
||||
// Reset the delta
|
||||
$this->iCursor = 0;
|
||||
$this->aAdded = array();
|
||||
$this->aRemoved = array();
|
||||
$this->aModified = array();
|
||||
$this->aPreserved = ($this->aOriginalObjects === null) ? array() : $this->aOriginalObjects;
|
||||
$this->bHasDelta = false;
|
||||
|
||||
/** @var AttributeLinkedSet $oAttDef */
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->sHostClass, $this->sAttCode);
|
||||
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
||||
$sAdditionalKey = null;
|
||||
if ($oAttDef->IsIndirect() && !$oAttDef->DuplicatesAllowed())
|
||||
{
|
||||
$sAdditionalKey = $oAttDef->GetExtKeyToRemote();
|
||||
}
|
||||
// Compare both collections by iterating the whole sets, order them, a build a fingerprint based on meaningful data (what make the difference)
|
||||
$oComparator = new DBObjectSetComparator($this, $oFellow, array($sExtKeyToMe), $sAdditionalKey);
|
||||
$aChanges = $oComparator->GetDifferences();
|
||||
foreach ($aChanges['added'] as $oLink)
|
||||
{
|
||||
$this->AddItem($oLink);
|
||||
}
|
||||
|
||||
foreach ($aChanges['modified'] as $oLink)
|
||||
{
|
||||
$this->ModifyItem($oLink);
|
||||
}
|
||||
|
||||
foreach ($aChanges['removed'] as $oLink)
|
||||
{
|
||||
$this->RemoveItem($oLink->GetKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function DBWrite(DBObject $oHostObject)
|
||||
{
|
||||
/** @var AttributeLinkedSet $oAttDef */
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oHostObject), $this->sAttCode);
|
||||
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
||||
$sExtKeyToRemote = $oAttDef->IsIndirect() ? $oAttDef->GetExtKeyToRemote() : 'n/a';
|
||||
|
||||
$aCheckLinks = array();
|
||||
$aCheckRemote = array();
|
||||
foreach ($this->aAdded as $oLink)
|
||||
{
|
||||
if ($oLink->IsNew())
|
||||
{
|
||||
if ($oAttDef->IsIndirect() && !$oAttDef->DuplicatesAllowed())
|
||||
{
|
||||
//todo: faire un test qui passe dans cette branche !
|
||||
$aCheckRemote[] = $oLink->Get($sExtKeyToRemote);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//todo: faire un test qui passe dans cette branche !
|
||||
$aCheckLinks[] = $oLink->GetKey();
|
||||
}
|
||||
}
|
||||
foreach ($this->aRemoved as $iLinkId)
|
||||
{
|
||||
$aCheckLinks[] = $iLinkId;
|
||||
}
|
||||
foreach ($this->aModified as $iLinkId => $oLink)
|
||||
{
|
||||
$aCheckLinks[] = $oLink->GetKey();
|
||||
}
|
||||
|
||||
// Critical section : serialize any write access to these links
|
||||
//
|
||||
$oMtx = new iTopMutex('Write-'.$this->sClass);
|
||||
$oMtx->Lock();
|
||||
|
||||
// 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);
|
||||
$aExistingLinks = $oSet->ToArray();
|
||||
}
|
||||
|
||||
// Check for the existing remote objects
|
||||
//
|
||||
if (count($aCheckRemote) > 0)
|
||||
{
|
||||
$oSearch = new DBObjectSearch($this->sClass);
|
||||
$oSearch->AddCondition($sExtKeyToMe, $oHostObject->GetKey(), '=');
|
||||
$oSearch->AddCondition($sExtKeyToRemote, $aCheckRemote, 'IN');
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$aExistingRemote = $oSet->GetColumnAsArray($sExtKeyToRemote, true);
|
||||
}
|
||||
|
||||
// Write the links according to the existing links
|
||||
//
|
||||
foreach ($this->aAdded as $oLink)
|
||||
{
|
||||
// Make sure that the objects in the set point to "this"
|
||||
$oLink->Set($sExtKeyToMe, $oHostObject->GetKey());
|
||||
|
||||
if ($oLink->IsNew())
|
||||
{
|
||||
if (count($aCheckRemote) > 0)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!array_key_exists($oLink->GetKey(), $aExistingLinks))
|
||||
{
|
||||
$oLink->DBClone();
|
||||
}
|
||||
}
|
||||
$oLink->DBWrite();
|
||||
}
|
||||
foreach ($this->aRemoved as $iLinkId)
|
||||
{
|
||||
if (array_key_exists($iLinkId, $aExistingLinks))
|
||||
{
|
||||
$oLink = $aExistingLinks[$iLinkId];
|
||||
if ($oAttDef->IsIndirect())
|
||||
{
|
||||
$oLink->DBDelete();
|
||||
}
|
||||
else
|
||||
{
|
||||
$oExtKeyToRemote = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToMe);
|
||||
if ($oExtKeyToRemote->IsNullAllowed())
|
||||
{
|
||||
if ($oLink->Get($sExtKeyToMe) == $oHostObject->GetKey())
|
||||
{
|
||||
// Detach the link object from this
|
||||
$oLink->Set($sExtKeyToMe, 0);
|
||||
$oLink->DBUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oLink->DBDelete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: process modifications at the end: if a link to remove has also been listed as modified, then it will be gracefully ignored
|
||||
foreach ($this->aModified as $iLinkId => $oLink)
|
||||
{
|
||||
if (array_key_exists($oLink->GetKey(), $aExistingLinks))
|
||||
{
|
||||
$oLink->DBUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
$oLink->DBClone();
|
||||
}
|
||||
}
|
||||
|
||||
// End of the critical section
|
||||
//
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -22,7 +22,7 @@ require_once('backgroundprocess.inc.php');
|
||||
* ormStopWatch
|
||||
* encapsulate the behavior of a stop watch that will be stored as an attribute of class AttributeStopWatch
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -260,7 +260,7 @@ class ormStopWatch
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
protected function ComputeDeadline($oObject, $oAttDef, $iStartTime, $iDurationSec)
|
||||
protected function ComputeDeadline($oObject, $oAttDef, $iPercent, $iStartTime, $iDurationSec)
|
||||
{
|
||||
$sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
|
||||
if ($sWorkingTimeComputer == '')
|
||||
@@ -280,7 +280,7 @@ class ormStopWatch
|
||||
}
|
||||
// GetDeadline($oObject, $iDuration, DateTime $oStartDate)
|
||||
$oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2
|
||||
$oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate);
|
||||
$oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate, $iPercent);
|
||||
$iRet = $oDeadline->format('U');
|
||||
return $iRet;
|
||||
}
|
||||
@@ -384,8 +384,8 @@ class ormStopWatch
|
||||
$sAttCode = $oAttDef->GetCode();
|
||||
WorkingTimeRecorder::Start($oObject, $iComputationRefTime, "ormStopWatch-Deadline-$iPercent-$sAttCode", 'Core:ExplainWTC:StopWatch-Deadline', array("Class:$sClass/Attribute:$sAttCode", $iPercent));
|
||||
}
|
||||
$aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
|
||||
// OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iStarted, $iThresholdDuration);
|
||||
$aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $iPercent, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
|
||||
// OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $iPercent, $this->iStarted, $iThresholdDuration);
|
||||
|
||||
if (class_exists('WorkingTimeRecorder'))
|
||||
{
|
||||
@@ -494,6 +494,7 @@ class CheckStopWatchThresholds implements iBackgroundProcess
|
||||
$sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < '$sNow'";
|
||||
$oFilter = DBObjectSearch::FromOQL($sExpression);
|
||||
$oSet = new DBObjectSet($oFilter);
|
||||
$oSet->OptimizeColumnLoad(array($sAttCode));
|
||||
while ((time() < $iTimeLimit) && ($oObj = $oSet->Fetch()))
|
||||
{
|
||||
$sClass = get_class($oObj);
|
||||
|
||||
@@ -226,9 +226,9 @@ EOF
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
|
||||
$sUrl = 'data:' . $value->GetMimeType() . ';base64,' . base64_encode($value->GetData());
|
||||
$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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Associated with the metamodel -> MakeQuery/MakeQuerySingleTable
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -30,16 +30,18 @@ class QueryBuilderContext
|
||||
protected $m_aTableAliases;
|
||||
protected $m_aModifierProperties;
|
||||
protected $m_aSelectedClasses;
|
||||
protected $m_aFilteredTables;
|
||||
|
||||
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();
|
||||
$this->m_aFilteredTables = array();
|
||||
|
||||
$this->m_aModifierProperties = $aModifierProperties;
|
||||
if (is_null($aSelectedClasses))
|
||||
@@ -84,4 +86,21 @@ class QueryBuilderContext
|
||||
{
|
||||
return $this->m_aSelectedClasses[$sAlias];
|
||||
}
|
||||
|
||||
public function AddFilteredTable($sTableAlias, $oCondition)
|
||||
{
|
||||
if (array_key_exists($sTableAlias, $this->m_aFilteredTables))
|
||||
{
|
||||
$this->m_aFilteredTables[$sTableAlias][] = $oCondition;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->m_aFilteredTables[$sTableAlias] = array($oCondition);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetFilteredTables()
|
||||
{
|
||||
return $this->m_aFilteredTables;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 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 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
@@ -38,8 +40,12 @@ class RelationObjectNode extends GraphNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a normalized ID to ensure the uniqueness of such a node
|
||||
*/
|
||||
* Make a normalized ID to ensure the uniqueness of such a node
|
||||
*
|
||||
* @param string $oObject
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function MakeId($oObject)
|
||||
{
|
||||
return get_class($oObject).'::'.$oObject->GetKey();
|
||||
@@ -47,7 +53,11 @@ class RelationObjectNode extends GraphNode
|
||||
|
||||
/**
|
||||
* Formatting for GraphViz
|
||||
*/
|
||||
*
|
||||
* @param bool $bNoLabel
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetDotAttributes($bNoLabel = false)
|
||||
{
|
||||
$sDot = parent::GetDotAttributes();
|
||||
@@ -76,7 +86,10 @@ 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)
|
||||
{
|
||||
if (is_null($this->GetProperty($sProperty)) && ($this->GetProperty($sProperty.'_allowed') !== false))
|
||||
@@ -104,16 +117,27 @@ class RelationRedundancyNode extends GraphNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a normalized ID to ensure the uniqueness of such a node
|
||||
*/
|
||||
public static function MakeId($sRelCode, $sNeighbourId, $oSinkObject)
|
||||
* 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)
|
||||
{
|
||||
return 'redundancy-'.$sRelCode.'-'.$sNeighbourId.'-'.get_class($oSinkObject).'::'.$oSinkObject->GetKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatting for GraphViz
|
||||
*/
|
||||
*
|
||||
* @param bool $bNoLabel
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetDotAttributes($bNoLabel = false)
|
||||
{
|
||||
$sDisplayThreshold = sprintf('%.1f', $this->GetProperty('threshold'));
|
||||
@@ -123,7 +147,10 @@ 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)
|
||||
{
|
||||
$this->SetProperty($sProperty.'_count', $this->GetProperty($sProperty.'_count', 0) + 1);
|
||||
@@ -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,7 +219,9 @@ 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)
|
||||
{
|
||||
$oSourceNode = new RelationObjectNode($this, $oObject);
|
||||
@@ -192,24 +231,30 @@ 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)
|
||||
{
|
||||
$oSinkNode = new RelationObjectNode($this, $oObject);
|
||||
$oSinkNode->SetProperty('sink', true);
|
||||
$this->aSinkNodes[$oSinkNode->GetId()] = $oSinkNode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 $sOQL The OQL query defining the context objects
|
||||
*
|
||||
* @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 = DBObjectSearch::FromOQL($sOQL);
|
||||
$oSearch = static::MakeSearch($sOQL);
|
||||
$aAliases = $oSearch->GetSelectedClasses();
|
||||
if (count($aAliases) < 2 )
|
||||
{
|
||||
@@ -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);
|
||||
|
||||
@@ -228,11 +272,14 @@ class RelationGraph extends SimpleGraph
|
||||
}
|
||||
$this->aContextSearches[$sClass][] = array('key' => $key, 'search' => $oSearch);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the given DBObject is part of a 'context'
|
||||
*
|
||||
* @param DBObject $oObj
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function IsPartOfContext(DBObject $oObj, &$aRootCauses)
|
||||
{
|
||||
@@ -271,7 +318,15 @@ 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())
|
||||
{
|
||||
//echo "<h5>Sources only...</h5>\n".$this->DumpAsHtmlImage()."<br/>\n";
|
||||
@@ -316,7 +371,14 @@ 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)
|
||||
{
|
||||
//echo "<h5>Sinks only...</h5>\n".$this->DumpAsHtmlImage()."<br/>\n";
|
||||
@@ -326,7 +388,7 @@ class RelationGraph extends SimpleGraph
|
||||
$this->AddRelatedObjects($sRelCode, false, $oSinkNode, $iMaxDepth, $bEnableRedundancy);
|
||||
//echo "<h5>After processing of {$oSinkNode->GetId()}</h5>\n".$this->DumpAsHtmlImage()."<br/>\n";
|
||||
}
|
||||
|
||||
|
||||
// Mark also the "context" nodes as reached and record the "root causes" for each node
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $oNode)
|
||||
@@ -344,14 +406,14 @@ class RelationGraph extends SimpleGraph
|
||||
|
||||
/**
|
||||
* Recursively find related objects, and add them into the graph
|
||||
*
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @return void
|
||||
* @param boolean $bEnableRedundancy
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function AddRelatedObjects($sRelCode, $bDown, $oObjectNode, $iMaxDepth, $bEnableRedundancy)
|
||||
{
|
||||
@@ -359,7 +421,7 @@ class RelationGraph extends SimpleGraph
|
||||
{
|
||||
if ($oObjectNode instanceof RelationRedundancyNode)
|
||||
{
|
||||
// Note: this happens when recursing on an existing part of the graph
|
||||
// Note: this happens when recursing on an existing part of the graph
|
||||
// Skip that redundancy node
|
||||
$aRelatedEdges = $bDown ? $oObjectNode->GetOutgoingEdges() : $oObjectNode->GetIncomingEdges();
|
||||
foreach ($aRelatedEdges as $oRelatedEdge)
|
||||
@@ -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
|
||||
{
|
||||
@@ -393,7 +459,7 @@ class RelationGraph extends SimpleGraph
|
||||
$sQuery = $bDown ? $aQueryInfo['sQueryDown'] : $aQueryInfo['sQueryUp'];
|
||||
try
|
||||
{
|
||||
$oFlt = DBObjectSearch::FromOQL($sQuery);
|
||||
$oFlt = static::MakeSearch($sQuery);
|
||||
$oObjSet = new DBObjectSet($oFlt, array(), $oObject->ToArgsForQuery());
|
||||
$oRelatedObj = $oObjSet->Fetch();
|
||||
}
|
||||
@@ -407,11 +473,11 @@ class RelationGraph extends SimpleGraph
|
||||
do
|
||||
{
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
|
||||
|
||||
$sObjectRef = RelationObjectNode::MakeId($oRelatedObj);
|
||||
$oRelatedNode = $this->GetNode($sObjectRef);
|
||||
if (is_null($oRelatedNode))
|
||||
{
|
||||
{
|
||||
$oRelatedNode = new RelationObjectNode($this, $oRelatedObj);
|
||||
}
|
||||
$oSourceNode = $bDown ? $oObjectNode : $oRelatedNode;
|
||||
@@ -441,25 +507,37 @@ class RelationGraph extends SimpleGraph
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there is a redundancy (or use the existing one) and add the corresponding nodes/edges
|
||||
*/
|
||||
* 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)
|
||||
{
|
||||
$oRedundancyNode = null;
|
||||
$oObject = $oToNode->GetProperty('object');
|
||||
if ($this->IsRedundancyEnabled($sRelCode, $aQueryInfo, $oToNode))
|
||||
{
|
||||
|
||||
$sId = RelationRedundancyNode::MakeId($sRelCode, $aQueryInfo['sNeighbour'], $oToNode->GetProperty('object'));
|
||||
$sUniqueNeighbourId = $aQueryInfo['sDefinedInClass'].'-'.$aQueryInfo['sNeighbour'];
|
||||
$sId = RelationRedundancyNode::MakeId($sRelCode, $sUniqueNeighbourId, $oFromNode->GetProperty('object'), $oToNode->GetProperty('object'));
|
||||
|
||||
$oRedundancyNode = $this->GetNode($sId);
|
||||
if (is_null($oRedundancyNode))
|
||||
{
|
||||
// Get the upper neighbours
|
||||
$sQuery = $aQueryInfo['sQueryUp'];
|
||||
if (!$sQuery)
|
||||
{
|
||||
throw new Exception("Redundancy cannot be enabled on the relation $sRelCode/{$aQueryInfo['sDefinedInClass']}/{$aQueryInfo['sNeighbour']}: its direction is \"{$aQueryInfo['sDirection']}\"");
|
||||
}
|
||||
try
|
||||
{
|
||||
$oFlt = DBObjectSearch::FromOQL($sQuery);
|
||||
$oFlt = static::MakeSearch($sQuery);
|
||||
$oObjSet = new DBObjectSet($oFlt, array(), $oObject->ToArgsForQuery());
|
||||
$iCount = $oObjSet->Count();
|
||||
}
|
||||
@@ -489,8 +567,14 @@ class RelationGraph extends SimpleGraph
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to determine the redundancy setting on a given relation
|
||||
*/
|
||||
* 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)
|
||||
{
|
||||
$bRet = false;
|
||||
@@ -505,8 +589,15 @@ class RelationGraph extends SimpleGraph
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to determine the redundancy threshold, given the count of objects upstream
|
||||
*/
|
||||
* 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)
|
||||
{
|
||||
$iMinUp = 0;
|
||||
@@ -529,8 +620,14 @@ class RelationGraph extends SimpleGraph
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to search for the redundancy attribute
|
||||
*/
|
||||
* 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)
|
||||
{
|
||||
$oRet = null;
|
||||
@@ -540,10 +637,13 @@ class RelationGraph extends SimpleGraph
|
||||
{
|
||||
if ($oAttDef->Get('relation_code') == $sRelCode)
|
||||
{
|
||||
if ($oAttDef->Get('neighbour_id') == $aQueryInfo['sNeighbour'])
|
||||
if ($oAttDef->Get('from_class') == $aQueryInfo['sFromClass'])
|
||||
{
|
||||
$oRet = $oAttDef;
|
||||
break;
|
||||
if ($oAttDef->Get('neighbour_id') == $aQueryInfo['sNeighbour'])
|
||||
{
|
||||
$oRet = $oAttDef;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -553,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()
|
||||
{
|
||||
@@ -573,5 +673,25 @@ class RelationGraph extends SimpleGraph
|
||||
}
|
||||
}
|
||||
return $aResults;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sOQL
|
||||
*
|
||||
* @return \DBSearch
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
protected static function MakeSearch($sOQL)
|
||||
{
|
||||
$oSearch = DBSearch::FromOQL($sOQL);
|
||||
if (MetaModel::IsObsoletable($oSearch->GetClass()))
|
||||
{
|
||||
// Exclude obsolete objects anytime
|
||||
$oSearch->AddCondition('obsolescence_flag', 0);
|
||||
}
|
||||
// Exclude archived objects anytime
|
||||
$oSearch->SetArchiveMode(false);
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -18,7 +18,7 @@
|
||||
/**
|
||||
* Data structures (i.e. PHP classes) to manage "graphs"
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
* Example:
|
||||
@@ -346,14 +346,15 @@ class SimpleGraph
|
||||
}
|
||||
unset($this->aNodes[$oNode->GetId()]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given node but preserves the connectivity of the graph
|
||||
* all "source" nodes are connected to all "sink" nodes
|
||||
* @param GraphNode $oNode
|
||||
* @param bool $bAllowLoopingEdge
|
||||
* @throws SimpleGraphException
|
||||
*/
|
||||
public function FilterNode(GraphNode $oNode)
|
||||
public function FilterNode(GraphNode $oNode, $bAllowLoopingEdge = false)
|
||||
{
|
||||
if (!array_key_exists($oNode->GetId(), $this->aNodes)) throw new SimpleGraphException('Cannot filter the node (id='.$oNode->GetId().') from the graph. The node was not found in the graph.');
|
||||
|
||||
@@ -362,13 +363,19 @@ class SimpleGraph
|
||||
foreach($oNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
$sSinkId = $oEdge->GetSinkNode()->GetId();
|
||||
$aSinkNodes[$sSinkId] = $oEdge->GetSinkNode();
|
||||
if ($sSinkId != $oNode->GetId())
|
||||
{
|
||||
$aSinkNodes[$sSinkId] = $oEdge->GetSinkNode();
|
||||
}
|
||||
$this->_RemoveEdge($oEdge);
|
||||
}
|
||||
foreach($oNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$sSourceId = $oEdge->GetSourceNode()->GetId();
|
||||
$aSourceNodes[$sSourceId] = $oEdge->GetSourceNode();
|
||||
if ($sSourceId != $oNode->GetId())
|
||||
{
|
||||
$aSourceNodes[$sSourceId] = $oEdge->GetSourceNode();
|
||||
}
|
||||
$this->_RemoveEdge($oEdge);
|
||||
}
|
||||
unset($this->aNodes[$oNode->GetId()]);
|
||||
@@ -377,7 +384,10 @@ class SimpleGraph
|
||||
{
|
||||
foreach($aSinkNodes as $sSinkId => $oSinkNode)
|
||||
{
|
||||
$oEdge = new RelationEdge($this, $oSourceNode, $oSinkNode);
|
||||
if ($bAllowLoopingEdge || ($oSourceNode->GetId() != $oSinkNode->GetId()))
|
||||
{
|
||||
$oEdge = new RelationEdge($this, $oSourceNode, $oSinkNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
|
||||
$oP->p(" *\tno_localize: (optional) pass 1 to retrieve the raw (untranslated) values for enumerated fields. Default: 0.");
|
||||
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
|
||||
$oP->p(" *\tformatted_text: set to 1 to formatted text fields with their HTML markup, 0 to remove formatting. Default is 1 (= formatted text)");
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
@@ -51,12 +52,19 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:SpreadsheetOptions').'</legend>');
|
||||
$oP->add('<table>');
|
||||
$oP->add('<tr>');
|
||||
$oP->add('<td><input type="checkbox" id="spreadsheet_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="spreadsheet_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label></td>');
|
||||
|
||||
|
||||
$oP->add('<td style="vertical-align:top">');
|
||||
$sChecked = (utils::ReadParam('formatted_text', 1) == 1) ? ' checked ' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
|
||||
$oP->add('<input type="hidden" name="formatted_text" value="0">'); // Trick to pass the zero value if the checkbox below is unchecked, since we want the default value to be "1"
|
||||
$oP->add('<input type="checkbox" id="spreadsheet_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="spreadsheet_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label><br/><br/>');
|
||||
$oP->add('<input type="checkbox" id="spreadsheet_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="spreadsheet_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label>');
|
||||
$oP->add('</td>');
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
|
||||
|
||||
$oP->add('<td>');
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
@@ -65,23 +73,23 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="spreadsheet_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oP->add('<input type="radio" id="spreadsheet_date_time_format_custom" name="spreadsheet_date_format_radio" value="custom"'.$sCustomChecked.'><label for="spreadsheet_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
|
||||
$oP->add('</td>');
|
||||
|
||||
|
||||
$oP->add('</tr>');
|
||||
$oP->add('</table>');
|
||||
$oP->add('</fieldset>');
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
<<<EOF
|
||||
$('#spreadsheet_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_spreadsheet_options').on('preview_updated', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_date_time_format_default').on('click', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_date_time_format_custom').on('click', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); });
|
||||
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); FormatDatesInPreview('spreadsheet', 'spreadsheet'); }).on('keyup', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); FormatDatesInPreview('spreadsheet', 'spreadsheet'); }).on('keyup', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
EOF
|
||||
);
|
||||
);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
return parent:: DisplayFormPart($oP, $sPartId);
|
||||
}
|
||||
@@ -90,26 +98,27 @@ EOF
|
||||
public function ReadParameters()
|
||||
{
|
||||
parent::ReadParameters();
|
||||
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 1, true);
|
||||
|
||||
$sDateFormatRadio = utils::ReadParam('spreadsheet_date_format_radio', '');
|
||||
switch($sDateFormatRadio)
|
||||
{
|
||||
case 'default':
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
@@ -126,6 +135,7 @@ EOF
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
{
|
||||
$bFormattedText = (array_key_exists('formatted_text', $this->aStatusInfo) ? $this->aStatusInfo['formatted_text'] : false);
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
@@ -147,13 +157,26 @@ EOF
|
||||
{
|
||||
$sRet = '';
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeText)
|
||||
{
|
||||
if ($bFormattedText)
|
||||
{
|
||||
// Replace paragraphs (<p...>...</p>, etc) by line breaks (<br/>) since Excel (pre-2016) splits the cells when there is a paragraph
|
||||
$sRet = static::HtmlToSpreadsheet($oObj->GetAsHTML($sAttCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = utils::HtmlToText($oObj->GetAsHTML($sAttCode));
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeString)
|
||||
{
|
||||
$sRet = $oObj->GetAsHTML($sAttCode);
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeCustomFields)
|
||||
{
|
||||
$sRet = $oObj->GetAsHTML($sAttCode);
|
||||
// Stick to the weird implementation made in GetNextChunk
|
||||
$sRet = utils::TextToHtml($oObj->GetEditValue($sAttCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -175,7 +198,7 @@ EOF
|
||||
{
|
||||
// Integration within MS-Excel web queries + HTTPS + IIS:
|
||||
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
|
||||
// Then the fix is to force the reset of header values Pragma and Cache-control
|
||||
// Then the fix is to force the reset of header values Pragma and Cache-control
|
||||
$oPage->add_header("Pragma:", true);
|
||||
$oPage->add_header("Cache-control:", true);
|
||||
}
|
||||
@@ -230,13 +253,14 @@ EOF
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
|
||||
$this->OptimizeColumnLoad($oSet);
|
||||
|
||||
|
||||
$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
|
||||
$bFormattedText = (array_key_exists('formatted_text', $this->aStatusInfo) ? $this->aStatusInfo['formatted_text'] : false);
|
||||
// Date & time formats
|
||||
$oDateTimeFormat = new DateTimeFormat($sExportDateTimeFormat);
|
||||
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
|
||||
$oTimeFormat = new DateTimeFormat($oDateTimeFormat->ToTimeFormat());
|
||||
|
||||
|
||||
$iCount = 0;
|
||||
$sData = '';
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
@@ -258,55 +282,75 @@ EOF
|
||||
$sData .= "<td x:str></td>";
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sField = $oObj->GetKey();
|
||||
$sData .= "<td>$sField</td>";
|
||||
break;
|
||||
$sField = $oObj->GetKey();
|
||||
$sData .= "<td>$sField</td>";
|
||||
break;
|
||||
|
||||
default:
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$oFinalAttDef = $oAttDef->GetFinalAttDef();
|
||||
if (get_class($oFinalAttDef) == 'AttributeDateTime')
|
||||
{
|
||||
// Split the date and time in two columns
|
||||
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
|
||||
$sTime = $oTimeFormat->Format($oObj->Get($sAttCode));
|
||||
$sData .= "<td>$sDate</td>";
|
||||
$sData .= "<td>$sTime</td>";
|
||||
}
|
||||
else if (get_class($oFinalAttDef) == 'AttributeDate')
|
||||
{
|
||||
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
|
||||
$sData .= "<td>$sDate</td>";
|
||||
}
|
||||
else if($oAttDef instanceof AttributeCaseLog)
|
||||
{
|
||||
$rawValue = $oObj->Get($sAttCode);
|
||||
$sField = str_replace("\n", "<br/>", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8'));
|
||||
// Trick for Excel: treat the content as text even if it begins with an equal sign
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else if($oAttDef instanceof AttributeString)
|
||||
{
|
||||
$sField = $oObj->GetAsHTML($sAttCode, $this->bLocalizeOutput);
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$rawValue = $oObj->Get($sAttCode);
|
||||
if ($this->bLocalizeOutput)
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$oFinalAttDef = $oAttDef->GetFinalAttDef();
|
||||
if (get_class($oFinalAttDef) == 'AttributeDateTime')
|
||||
{
|
||||
$sField = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8');
|
||||
// Split the date and time in two columns
|
||||
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
|
||||
$sTime = $oTimeFormat->Format($oObj->Get($sAttCode));
|
||||
$sData .= "<td>$sDate</td>";
|
||||
$sData .= "<td>$sTime</td>";
|
||||
}
|
||||
else if (get_class($oFinalAttDef) == 'AttributeDate')
|
||||
{
|
||||
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
|
||||
$sData .= "<td>$sDate</td>";
|
||||
}
|
||||
else if($oAttDef instanceof AttributeCaseLog)
|
||||
{
|
||||
$rawValue = $oObj->Get($sAttCode);
|
||||
$sField = str_replace("\n", "<br/>", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8'));
|
||||
// Trick for Excel: treat the content as text even if it begins with an equal sign
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeText)
|
||||
{
|
||||
if ($bFormattedText)
|
||||
{
|
||||
// Replace paragraphs (<p...>...</p>, etc) by line breaks (<br/>) since Excel (pre-2016) splits the cells when there is a paragraph
|
||||
$sField = static::HtmlToSpreadsheet($oObj->GetAsHTML($sAttCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert to plain text
|
||||
$sField = utils::HtmlToText($oObj->GetAsHTML($sAttCode));
|
||||
}
|
||||
$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);
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sField = htmlentities($rawValue, ENT_QUOTES, 'UTF-8');
|
||||
$rawValue = $oObj->Get($sAttCode);
|
||||
if ($this->bLocalizeOutput)
|
||||
{
|
||||
$sField = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sField = htmlentities($rawValue, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
$sData .= "<td>$sField</td>";
|
||||
}
|
||||
$sData .= "<td>$sField</td>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -354,4 +398,51 @@ EOF
|
||||
{
|
||||
return 'html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup all markup displayed as line breaks (except <br> tags) since this
|
||||
* causes Excel (pre-2016) to generate extra lines in the table, thus breaking
|
||||
* the tabular disposition of the export
|
||||
* Note: Excel 2016 also refuses line breaks, so the only solution for this case is alas plain text
|
||||
* @param string $sHtml The HTML to cleanup
|
||||
* @return string The cleaned HTML
|
||||
*/
|
||||
public static function HtmlToSpreadsheet($sHtml)
|
||||
{
|
||||
if (trim(strip_tags($sHtml)) === '')
|
||||
{
|
||||
// Display this value as an empty cell in the table
|
||||
return ' ';
|
||||
}
|
||||
// The tags listed here are a subset of the whitelist defined in HTMLDOMSanitizer
|
||||
// Tags causing a visual "line break" in the displayed page (i.e. display: block) are to be replaced by a <span> followed by a <br/>
|
||||
// in order to preserve any inline style/attribute of the removed tag
|
||||
$aTagsToReplace = array(
|
||||
'pre', 'div', 'p', 'hr', 'center', 'h1', 'h2', 'h3', 'h4', 'li', 'fieldset', 'legend', 'nav', 'section', 'tr', 'caption',
|
||||
);
|
||||
// Tags to completely remove from the markup
|
||||
$aTagsToRemove = array(
|
||||
'table', 'thead', 'tbody', 'ul', 'ol', 'td', 'th',
|
||||
);
|
||||
|
||||
// Remove the englobing <div class="HTML" >...</div> to prevent an extra line break
|
||||
$sHtml = preg_replace('|^<div class="HTML" >(.*)</div>$|s', '$1', $sHtml); // Must use the "s" (. matches newline) modifier
|
||||
|
||||
foreach($aTagsToReplace as $sTag)
|
||||
{
|
||||
$sHtml = preg_replace("|<{$sTag} ?([^>]*)>|is", '<span $1>', $sHtml);
|
||||
$sHtml = preg_replace("|</{$sTag}>|i", '</span><br/>', $sHtml);
|
||||
}
|
||||
|
||||
foreach($aTagsToRemove as $sTag)
|
||||
{
|
||||
$sHtml = preg_replace("|<{$sTag} ?([^>]*)>|is", '', $sHtml);
|
||||
$sHtml = preg_replace("|</{$sTag}>|i", '', $sHtml);
|
||||
}
|
||||
|
||||
// Remove any trailing <br/>, if any, to prevent an extra line break
|
||||
$sHtml = preg_replace("|<br/>$|", '', $sHtml);
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* SQLObjectQuery
|
||||
* build a mySQL compatible SQL query
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
@@ -138,6 +139,11 @@ class SQLObjectQuery extends SQLQuery
|
||||
$this->m_aFields = $aExpressions;
|
||||
}
|
||||
|
||||
public function SortSelectedFields()
|
||||
{
|
||||
ksort($this->m_aFields);
|
||||
}
|
||||
|
||||
public function AddSelect($sAlias, $oExpression)
|
||||
{
|
||||
$this->m_aFields[$sAlias] = $oExpression;
|
||||
@@ -161,7 +167,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->m_oConditionExpr->LogAnd($oConditionExpr);
|
||||
$this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oConditionExpr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,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();
|
||||
@@ -264,6 +276,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
$sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs);
|
||||
return "DELETE $sDelete FROM $sFrom WHERE $sWhere";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,16 +290,25 @@ class SQLObjectQuery extends SQLQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for the unions
|
||||
* 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();
|
||||
@@ -297,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;
|
||||
@@ -306,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)
|
||||
@@ -316,47 +357,70 @@ 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);
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy);
|
||||
$sSelect = self::ClauseSelect($this->__aFields, $sLineSep);
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy, $this->__aFields);
|
||||
if (!empty($sOrderBy))
|
||||
{
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
|
||||
}
|
||||
if ($iLimitCount > 0)
|
||||
{
|
||||
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
$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)
|
||||
|
||||
/**
|
||||
* @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);
|
||||
$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";
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy, $this->__aFields);
|
||||
if (!empty($sGroupBy))
|
||||
{
|
||||
$sGroupBy = "GROUP BY $sGroupBy$sLineSep";
|
||||
}
|
||||
if (!empty($sOrderBy))
|
||||
{
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
|
||||
}
|
||||
if ($iLimitCount > 0)
|
||||
{
|
||||
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLimit = '';
|
||||
}
|
||||
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sGroupBy $sOrderBy$sLineSep $sLimit";
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
@@ -379,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)
|
||||
//
|
||||
@@ -496,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;
|
||||
|
||||
@@ -505,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
|
||||
@@ -529,7 +595,18 @@ class SQLObjectQuery extends SQLQuery
|
||||
return (count($this->m_aJoinSelects) == 0);
|
||||
}
|
||||
|
||||
protected function CollectUsedTables(&$aTables)
|
||||
public function CountTables()
|
||||
{
|
||||
$iRet = 1;
|
||||
foreach ($this->m_aJoinSelects as $i => $aJoinInfo)
|
||||
{
|
||||
$oSQLQuery = $aJoinInfo["select"];
|
||||
$iRet += $oSQLQuery->CountTables();
|
||||
}
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
public function CollectUsedTables(&$aTables)
|
||||
{
|
||||
$this->m_oConditionExpr->CollectUsedParents($aTables);
|
||||
foreach($this->m_aFields as $sFieldAlias => $oField)
|
||||
@@ -560,7 +637,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
}
|
||||
if (isset($aJoinInfo["on_expression"]))
|
||||
{
|
||||
$sJoinCond = $aJoinInfo["on_expression"]->CollectUsedParents($aTables);
|
||||
$aJoinInfo["on_expression"]->CollectUsedParents($aTables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -588,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;
|
||||
}
|
||||
@@ -596,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,6 @@ 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)
|
||||
@@ -93,46 +103,52 @@ class SQLUnionQuery extends SQLQuery
|
||||
// Render SELECTS without orderby/limit/count
|
||||
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
|
||||
}
|
||||
$sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')';
|
||||
|
||||
if ($bGetCount)
|
||||
if ($iLimitCount > 0)
|
||||
{
|
||||
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
|
||||
$sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep";
|
||||
$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
|
||||
{
|
||||
$aSelects = array();
|
||||
foreach ($this->aQueries as $oSQLQuery)
|
||||
{
|
||||
// Render SELECT without orderby/limit/count
|
||||
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
|
||||
}
|
||||
$sSelect = $this->aQueries[0]->RenderSelectClause();
|
||||
$sOrderBy = $this->aQueries[0]->RenderOrderByClause($aOrderBy);
|
||||
if (!empty($sOrderBy))
|
||||
{
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
|
||||
}
|
||||
if ($iLimitCount > 0)
|
||||
{
|
||||
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep $sLimit";
|
||||
$sSQL = '('.implode(")$sLineSep UNION$sLineSep (", $aSelects).')'.$sLineSep.$sOrderBy;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLimit = '';
|
||||
$sSQL = '('.implode(" $sLimit)$sLineSep UNION$sLineSep (", $aSelects)." $sLimit)";
|
||||
}
|
||||
$sSQL = $sSelects.$sLineSep.$sOrderBy.' '.$sLimit;
|
||||
}
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
// Interface, build the SQL query
|
||||
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false)
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
$aSelects = array();
|
||||
foreach ($this->aQueries as $oSQLQuery)
|
||||
@@ -143,15 +159,41 @@ class SQLUnionQuery extends SQLQuery
|
||||
$sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')';
|
||||
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
|
||||
|
||||
$aAliases = array();
|
||||
$aSelectAliases = array();
|
||||
$aGroupAliases = array();
|
||||
foreach ($this->aGroupBy as $sGroupAlias => $trash)
|
||||
{
|
||||
$aAliases[] = "`$sGroupAlias`";
|
||||
$aSelectAliases[$sGroupAlias] = "`$sGroupAlias`";
|
||||
$aGroupAliases[] = "`$sGroupAlias`";
|
||||
}
|
||||
foreach($this->aSelectExpr as $sSelectAlias => $oExpr)
|
||||
{
|
||||
$aSelectAliases[$sSelectAlias] = $oExpr->Render()." AS `$sSelectAlias`";
|
||||
}
|
||||
$sSelect = implode(', ', $aAliases);
|
||||
$sGroupBy = implode(', ', $aAliases);
|
||||
|
||||
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep GROUP BY $sGroupBy";
|
||||
$sSelect = implode(",$sLineSep ", $aSelectAliases);
|
||||
$sGroupBy = implode(', ', $aGroupAliases);
|
||||
|
||||
$sOrderBy = self::ClauseOrderBy($aOrderBy, $aSelectAliases);
|
||||
if (!empty($sGroupBy))
|
||||
{
|
||||
$sGroupBy = "GROUP BY $sGroupBy$sLineSep";
|
||||
}
|
||||
if (!empty($sOrderBy))
|
||||
{
|
||||
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
|
||||
}
|
||||
if ($iLimitCount > 0)
|
||||
{
|
||||
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLimit = '';
|
||||
}
|
||||
|
||||
|
||||
$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" => "",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* User rights management API
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -161,13 +161,14 @@ abstract class UserRightsAddOnAPI
|
||||
}
|
||||
|
||||
|
||||
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
|
||||
abstract class User extends cmdbAbstractObject
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core",
|
||||
"category" => "core,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "login",
|
||||
"state_attcode" => "",
|
||||
@@ -184,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())));
|
||||
|
||||
@@ -194,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);
|
||||
@@ -239,13 +241,13 @@ 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()
|
||||
{
|
||||
if (is_null($this->oContactObject))
|
||||
{
|
||||
if ($this->Get('contactid') != 0)
|
||||
if (MetaModel::IsValidAttCode(get_class($this), 'contactid') && ($this->Get('contactid') != 0))
|
||||
{
|
||||
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
|
||||
}
|
||||
@@ -253,9 +255,11 @@ abstract class User extends cmdbAbstractObject
|
||||
return $this->oContactObject;
|
||||
}
|
||||
|
||||
/*
|
||||
* Overload the standard behavior
|
||||
*/
|
||||
/**
|
||||
* Overload the standard behavior.
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function DoCheckToWrite()
|
||||
{
|
||||
parent::DoCheckToWrite();
|
||||
@@ -279,11 +283,57 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check that this user has at least one profile assigned
|
||||
$oSet = $this->Get('profile_list');
|
||||
if ($oSet->Count() == 0)
|
||||
// Check that this user has at least one profile assigned when profiles have changed
|
||||
if (array_key_exists('profile_list', $aChanges))
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneProfileIsNeeded');
|
||||
$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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,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,
|
||||
);
|
||||
}
|
||||
@@ -349,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);
|
||||
}
|
||||
@@ -359,8 +413,8 @@ 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)
|
||||
{
|
||||
@@ -417,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" => "",
|
||||
@@ -433,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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -518,6 +571,7 @@ interface iSelfRegister
|
||||
*/
|
||||
class UserRights
|
||||
{
|
||||
/** @var UserRightsAddOnAPI $m_oAddOn */
|
||||
protected static $m_oAddOn;
|
||||
protected static $m_oUser;
|
||||
protected static $m_oRealUser;
|
||||
@@ -563,7 +617,7 @@ class UserRights
|
||||
return $bRes;
|
||||
}
|
||||
|
||||
protected static function IsLoggedIn()
|
||||
public static function IsLoggedIn()
|
||||
{
|
||||
if (self::$m_oUser == null)
|
||||
{
|
||||
@@ -583,6 +637,13 @@ class UserRights
|
||||
return false;
|
||||
}
|
||||
self::$m_oUser = $oUser;
|
||||
|
||||
if (isset($_SESSION['impersonate_user']))
|
||||
{
|
||||
self::$m_oRealUser = self::$m_oUser;
|
||||
self::$m_oUser = self::FindUser($_SESSION['impersonate_user']);
|
||||
}
|
||||
|
||||
Dict::SetUserLanguage(self::GetUserLanguage());
|
||||
return true;
|
||||
}
|
||||
@@ -641,6 +702,29 @@ class UserRights
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether or not the archive mode is allowed to the current user
|
||||
* @return boolean
|
||||
*/
|
||||
static function CanBrowseArchive()
|
||||
{
|
||||
if (is_null(self::$m_oUser))
|
||||
{
|
||||
$bRet = false;
|
||||
}
|
||||
elseif (isset($_SESSION['archive_allowed']))
|
||||
{
|
||||
$bRet = $_SESSION['archive_allowed'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// As of now, anybody can switch to the archive mode as soon as there is an archivable class
|
||||
$bRet = (count(MetaModel::EnumArchivableClasses()) > 0);
|
||||
$_SESSION['archive_allowed'] = $bRet;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public static function CanChangePassword()
|
||||
{
|
||||
if (MetaModel::DBIsReadOnly())
|
||||
@@ -675,28 +759,55 @@ class UserRights
|
||||
}
|
||||
else
|
||||
{
|
||||
$oUser->AllowWrite(true);
|
||||
return $oUser->ChangePassword($sOldPassword, $sNewPassword);
|
||||
}
|
||||
}
|
||||
|
||||
public static function Impersonate($sName, $sPassword)
|
||||
/**
|
||||
* @param string $sName Login identifier of the user to impersonate
|
||||
* @return bool True if an impersonation occurred
|
||||
*/
|
||||
public static function Impersonate($sName)
|
||||
{
|
||||
if (!self::CheckLogin()) return false;
|
||||
|
||||
$bRet = false;
|
||||
$oUser = self::FindUser($sName);
|
||||
if (is_null($oUser))
|
||||
if ($oUser)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!$oUser->CheckCredentials($sPassword))
|
||||
{
|
||||
return false;
|
||||
$bRet = true;
|
||||
if (is_null(self::$m_oRealUser))
|
||||
{
|
||||
// First impersonation
|
||||
self::$m_oRealUser = self::$m_oUser;
|
||||
}
|
||||
if (self::$m_oRealUser && (self::$m_oRealUser->GetKey() == $oUser->GetKey()))
|
||||
{
|
||||
// Equivalent to "Deimpersonate"
|
||||
self::Deimpersonate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do impersonate!
|
||||
self::$m_oUser = $oUser;
|
||||
Dict::SetUserLanguage(self::GetUserLanguage());
|
||||
$_SESSION['impersonate_user'] = $sName;
|
||||
self::_ResetSessionCache();
|
||||
}
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
self::$m_oRealUser = self::$m_oUser;
|
||||
self::$m_oUser = $oUser;
|
||||
Dict::SetUserLanguage(self::GetUserLanguage());
|
||||
return true;
|
||||
public static function Deimpersonate()
|
||||
{
|
||||
if (!is_null(self::$m_oRealUser))
|
||||
{
|
||||
self::$m_oUser = self::$m_oRealUser;
|
||||
Dict::SetUserLanguage(self::GetUserLanguage());
|
||||
unset($_SESSION['impersonate_user']);
|
||||
self::_ResetSessionCache();
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetUser()
|
||||
@@ -828,6 +939,11 @@ class UserRights
|
||||
return self::$m_oRealUser->Get('login');
|
||||
}
|
||||
|
||||
public static function GetRealUserObject()
|
||||
{
|
||||
return self::$m_oRealUser;
|
||||
}
|
||||
|
||||
public static function GetRealUserId()
|
||||
{
|
||||
if (is_null(self::$m_oRealUser))
|
||||
@@ -856,35 +972,57 @@ 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;}
|
||||
|
||||
if (MetaModel::HasCategory($sClass, 'bizmodel'))
|
||||
try
|
||||
{
|
||||
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);
|
||||
}
|
||||
else
|
||||
// Check Bug 1436 for details
|
||||
if (MetaModel::HasCategory($sClass, 'bizmodel'))
|
||||
{
|
||||
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e)
|
||||
{
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function IsActionAllowed($sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
|
||||
/**
|
||||
* @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'));
|
||||
@@ -893,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))
|
||||
{
|
||||
@@ -916,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -952,32 +1090,45 @@ class UserRights
|
||||
}
|
||||
}
|
||||
|
||||
public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
|
||||
/**
|
||||
* @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;
|
||||
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;
|
||||
}
|
||||
return self::$m_oAddOn->IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet);
|
||||
}
|
||||
|
||||
// this module is forbidden for non admins
|
||||
if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false;
|
||||
if (MetaModel::HasCategory($sClass, 'addon/userrights')) return UR_ALLOWED_NO;
|
||||
|
||||
// the rest is allowed
|
||||
return UR_ALLOWED_YES;
|
||||
|
||||
// the rest is allowed (#@# to be improved)
|
||||
if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true;
|
||||
|
||||
if (is_null($oUser))
|
||||
{
|
||||
$oUser = self::$m_oUser;
|
||||
}
|
||||
return self::$m_oAddOn->IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet);
|
||||
}
|
||||
|
||||
protected static $m_aAdmins = array();
|
||||
@@ -1045,7 +1196,12 @@ class UserRights
|
||||
{
|
||||
$oUser = self::$m_oUser;
|
||||
}
|
||||
if ($oUser->GetKey() == self::$m_oUser->GetKey())
|
||||
if ($oUser === null)
|
||||
{
|
||||
// Not logged in: no profile at all
|
||||
$aProfiles = array();
|
||||
}
|
||||
elseif ((self::$m_oUser !== null) && ($oUser->GetKey() == self::$m_oUser->GetKey()))
|
||||
{
|
||||
// Data about the current user can be found into the session data
|
||||
if (array_key_exists('profile_list', $_SESSION))
|
||||
@@ -1086,8 +1242,16 @@ class UserRights
|
||||
self::$m_aAdmins = array();
|
||||
self::$m_aPortalUsers = array();
|
||||
}
|
||||
if (!isset($_SESSION) && !utils::IsModeCLI())
|
||||
{
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
session_start();
|
||||
}
|
||||
self::_ResetSessionCache();
|
||||
return self::$m_oAddOn->FlushPrivileges();
|
||||
if (self::$m_oAddOn)
|
||||
{
|
||||
self::$m_oAddOn->FlushPrivileges();
|
||||
}
|
||||
}
|
||||
|
||||
static $m_aCacheUsers;
|
||||
@@ -1165,6 +1329,10 @@ class UserRights
|
||||
{
|
||||
unset($_SESSION['profile_list']);
|
||||
}
|
||||
if (isset($_SESSION['archive_allowed']))
|
||||
{
|
||||
unset($_SESSION['archive_allowed']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1670,5 +1838,4 @@ class CAS_SelfRegister implements iSelfRegister
|
||||
}
|
||||
|
||||
// By default enable the 'CAS_SelfRegister' defined above
|
||||
UserRights::SelectSelfRegister('CAS_SelfRegister');
|
||||
?>
|
||||
UserRights::SelectSelfRegister('CAS_SelfRegister');
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Value set definitions (from a fixed list or from a query, etc.)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
@@ -124,7 +131,7 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
$this->m_aExtraConditions[] = $oFilter;
|
||||
}
|
||||
|
||||
public function ToObjectSet($aArgs = array(), $sContains = '')
|
||||
public function ToObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null)
|
||||
{
|
||||
if ($this->m_bAllowAllData)
|
||||
{
|
||||
@@ -145,15 +152,38 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
|
||||
}
|
||||
}
|
||||
if ($iAdditionalValue > 0)
|
||||
{
|
||||
$oSearchAdditionalValue = new DBObjectSearch($oFilter->GetClass());
|
||||
$oSearchAdditionalValue->AddConditionExpression( new BinaryExpression(
|
||||
new FieldExpression('id', $oSearchAdditionalValue->GetClassAlias()),
|
||||
'=',
|
||||
new VariableExpression('current_extkey_id'))
|
||||
);
|
||||
$oSearchAdditionalValue->AllowAllData();
|
||||
$oSearchAdditionalValue->SetArchiveMode(true);
|
||||
$oSearchAdditionalValue->SetInternalParams( array('current_extkey_id' => $iAdditionalValue) );
|
||||
|
||||
$oFilter = new DBUnionSearch(array($oFilter, $oSearchAdditionalValue));
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -161,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();
|
||||
|
||||
@@ -188,12 +228,59 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
}
|
||||
}
|
||||
|
||||
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
|
||||
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
|
||||
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
|
||||
$oFilter->AddConditionExpression($oNewCondition);
|
||||
switch ($sOperation)
|
||||
{
|
||||
case 'equals':
|
||||
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($oFilter->GetClass());
|
||||
$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($oFilter->GetClass());
|
||||
$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;
|
||||
|
||||
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
|
||||
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, 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))
|
||||
@@ -217,79 +304,21 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
{
|
||||
return $this->m_sFilterExpr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set of existing values for a link set attribute, given a relation code
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class ValueSetRelatedObjectsFromLinkSet extends ValueSetDefinition
|
||||
{
|
||||
protected $m_sLinkSetAttCode;
|
||||
protected $m_sExtKeyToRemote;
|
||||
protected $m_sRelationCode;
|
||||
protected $m_iMaxDepth;
|
||||
protected $m_sTargetClass;
|
||||
protected $m_sTargetExtKey;
|
||||
// protected $m_aOrderBy;
|
||||
|
||||
public function __construct($sLinkSetAttCode, $sExtKeyToRemote, $sRelationCode, $iMaxDepth, $sTargetClass, $sTargetLinkClass, $sTargetExtKey)
|
||||
/**
|
||||
* @param $iLimit
|
||||
*/
|
||||
public function SetLimit($iLimit)
|
||||
{
|
||||
$this->m_sLinkSetAttCode = $sLinkSetAttCode;
|
||||
$this->m_sExtKeyToRemote = $sExtKeyToRemote;
|
||||
$this->m_sRelationCode = $sRelationCode;
|
||||
$this->m_iMaxDepth = $iMaxDepth;
|
||||
$this->m_sTargetClass = $sTargetClass;
|
||||
$this->m_sTargetLinkClass = $sTargetLinkClass;
|
||||
$this->m_sTargetExtKey = $sTargetExtKey;
|
||||
// $this->m_aOrderBy = $aOrderBy;
|
||||
$this->m_iLimit = $iLimit;
|
||||
}
|
||||
|
||||
protected function LoadValues($aArgs)
|
||||
/**
|
||||
* @param $bSort
|
||||
*/
|
||||
public function SetSort($bSort)
|
||||
{
|
||||
$this->m_aValues = array();
|
||||
|
||||
if (!array_key_exists('this', $aArgs))
|
||||
{
|
||||
throw new CoreException("Missing 'this' in arguments", array('args' => $aArgs));
|
||||
}
|
||||
|
||||
$oTarget = $aArgs['this->object()'];
|
||||
|
||||
// Nodes from which we will start the search for neighbourhood
|
||||
$oNodes = DBObjectSet::FromLinkSet($oTarget, $this->m_sLinkSetAttCode, $this->m_sExtKeyToRemote);
|
||||
|
||||
// Neighbours, whatever their class
|
||||
$aRelated = $oNodes->GetRelatedObjects($this->m_sRelationCode, $this->m_iMaxDepth);
|
||||
|
||||
$sRootClass = MetaModel::GetRootClass($this->m_sTargetClass);
|
||||
if (array_key_exists($sRootClass, $aRelated))
|
||||
{
|
||||
$aLinksToCreate = array();
|
||||
foreach($aRelated[$sRootClass] as $iKey => $oObject)
|
||||
{
|
||||
if (MetaModel::IsParentClass($this->m_sTargetClass, get_class($oObject)))
|
||||
{
|
||||
$oNewLink = MetaModel::NewObject($this->m_sTargetLinkClass);
|
||||
$oNewLink->Set($this->m_sTargetExtKey, $iKey);
|
||||
//$oNewLink->Set('role', 'concerned by an impacted CI');
|
||||
|
||||
$aLinksToCreate[] = $oNewLink;
|
||||
}
|
||||
}
|
||||
// #@# or AddObjectArray($aObjects) ?
|
||||
$oSetToCreate = DBObjectSet::FromArray($this->m_sTargetLinkClass, $aLinksToCreate);
|
||||
$this->m_aValues[$oObject->GetKey()] = $oObject->GetName();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function GetValuesDescription()
|
||||
{
|
||||
return 'Filter: '.$this->m_sFilterExpr;
|
||||
$this->m_bSort = $bSort;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Bulk export: XML export
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
@@ -118,7 +118,7 @@ class XMLBulkExport extends BulkExport
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!$oAttDef->IsWritable())
|
||||
if ($oAttDef->IsExternalField())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class XMLBulkExport extends BulkExport
|
||||
$aClass2Attributes[$sAlias] = $aAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
|
||||
|
||||
@@ -1,8 +1,53 @@
|
||||
$highlight-color: #E87C1E;
|
||||
// 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;
|
||||
$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;
|
||||
|
||||
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
|
||||
$version: "v2.3.0";
|
||||
$version: "v2.5.0-beta";
|
||||
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user