Compare commits
570 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0321d0c81 | ||
|
|
79a2ab8abd | ||
|
|
af13b42eab | ||
|
|
75721091f2 | ||
|
|
6e327e245b | ||
|
|
6bfead405d | ||
|
|
a4f5620076 | ||
|
|
134a427901 | ||
|
|
27c3facb2b | ||
|
|
fa9848d1be | ||
|
|
1afd7d2ae4 | ||
|
|
c622efe95c | ||
|
|
c0949421ad | ||
|
|
0e21edcc77 | ||
|
|
df85186407 | ||
|
|
403ecf7fba | ||
|
|
d143f0880b | ||
|
|
c185e1fc4f | ||
|
|
68c180f7eb | ||
|
|
c903fc2246 | ||
|
|
7d5898f302 | ||
|
|
a4bdd3aaf4 | ||
|
|
5bae9deecc | ||
|
|
ffbd666aca | ||
|
|
4ec3aeec92 | ||
|
|
c5a00a1bf1 | ||
|
|
1b1e91f0dc | ||
|
|
21fc272674 | ||
|
|
5716c11450 | ||
|
|
f7e77e9fa6 | ||
|
|
ad8c3db6b4 | ||
|
|
d5c3b8d8e2 | ||
|
|
6ac6aea29f | ||
|
|
7ce06c0797 | ||
|
|
53bf1f424a | ||
|
|
b793cded34 | ||
|
|
47ec6d4917 | ||
|
|
e586ba8d6e | ||
|
|
8a913a18cf | ||
|
|
a09e579451 | ||
|
|
79c5dc2ce2 | ||
|
|
95d7bc5319 | ||
|
|
d3f5d05063 | ||
|
|
b30a35ceb5 | ||
|
|
fac22c9729 | ||
|
|
44e329c38d | ||
|
|
e48ad8cb61 | ||
|
|
359dc73526 | ||
|
|
8169e81e9a | ||
|
|
f62f087bcc | ||
|
|
4eb0b3086d | ||
|
|
1c81650572 | ||
|
|
59e3367da8 | ||
|
|
2b4b0fed83 | ||
|
|
fe6ae6f2eb | ||
|
|
79d994acf7 | ||
|
|
9dbbb8e012 | ||
|
|
17fafbf85b | ||
|
|
f3a6455ed8 | ||
|
|
5336d9e965 | ||
|
|
97d11ba910 | ||
|
|
2fa68a3abc | ||
|
|
a88365ca49 | ||
|
|
f3053c39c2 | ||
|
|
c7ac39b86a | ||
|
|
88eece7188 | ||
|
|
b7d101cdfb | ||
|
|
56ade2b44c | ||
|
|
2a38eb757d | ||
|
|
954ba60611 | ||
|
|
f9a984f3e4 | ||
|
|
8fd9acb2ce | ||
|
|
915a88afd4 | ||
|
|
c755b66d8c | ||
|
|
2f8dc0fa0f | ||
|
|
a34747f893 | ||
|
|
4dd6c813b1 | ||
|
|
8e96094977 | ||
|
|
dd41ebf861 | ||
|
|
f732df751c | ||
|
|
acfab8fb63 | ||
|
|
23193153c6 | ||
|
|
a908dcd752 | ||
|
|
b8a80cb267 | ||
|
|
ad1412c7e2 | ||
|
|
4bf51515be | ||
|
|
05f97dd75f | ||
|
|
90cab29a3c | ||
|
|
445b488603 | ||
|
|
7141e0f4b0 | ||
|
|
ffc756e4a4 | ||
|
|
8bed267feb | ||
|
|
13933e488c | ||
|
|
d48f76e965 | ||
|
|
a025c95054 | ||
|
|
b2fa9c468f | ||
|
|
8feef7fd8a | ||
|
|
8330f3d498 | ||
|
|
88e7bd225c | ||
|
|
1b0f818c4b | ||
|
|
32dc1225ca | ||
|
|
2b35cf5047 | ||
|
|
df66e28545 | ||
|
|
0ab060c958 | ||
|
|
dfceef4ca6 | ||
|
|
49f82e6377 | ||
|
|
c5957f1146 | ||
|
|
b357e7d7d6 | ||
|
|
e7342b0eb8 | ||
|
|
f33f4e3406 | ||
|
|
3d57c720e0 | ||
|
|
ef5d156b98 | ||
|
|
8722447f2f | ||
|
|
e6047dcbf5 | ||
|
|
df1d10f1cb | ||
|
|
d1dd60f928 | ||
|
|
7bea59fea1 | ||
|
|
17703ce572 | ||
|
|
6d556249aa | ||
|
|
16b5db448b | ||
|
|
8ef7f073b3 | ||
|
|
22c647e3f8 | ||
|
|
bf94dfa894 | ||
|
|
43d3cfefb5 | ||
|
|
e0855093ea | ||
|
|
c147062aaa | ||
|
|
480c2fab04 | ||
|
|
53fb619da1 | ||
|
|
c0a7bbaa72 | ||
|
|
12b27778f5 | ||
|
|
babcd6a92a | ||
|
|
d80d24c348 | ||
|
|
2c78a91a00 | ||
|
|
109e5dfe2c | ||
|
|
93ff327b54 | ||
|
|
359c188089 | ||
|
|
6682eafb4d | ||
|
|
b64c79d34e | ||
|
|
e553c0bbe1 | ||
|
|
18f15ba9cc | ||
|
|
f4e45b6c8d | ||
|
|
1013cbc22f | ||
|
|
12c64bd6e5 | ||
|
|
c209b75f6b | ||
|
|
775ed7d437 | ||
|
|
dee3d55af2 | ||
|
|
0d48e40c18 | ||
|
|
a0965c2a52 | ||
|
|
daccb122ae | ||
|
|
0a8532e27a | ||
|
|
e7adf6559f | ||
|
|
07db5855a2 | ||
|
|
db47b2d05c | ||
|
|
a2eab87b7b | ||
|
|
396c4564b4 | ||
|
|
8582f6da70 | ||
|
|
fc7a10ff03 | ||
|
|
7330154dd0 | ||
|
|
beb53fd9dc | ||
|
|
9dbad63d6f | ||
|
|
21e5ca484c | ||
|
|
41bee2b9b2 | ||
|
|
41556ba00b | ||
|
|
33b46483a9 | ||
|
|
970f75d5e2 | ||
|
|
3a25916f00 | ||
|
|
c44284dc3c | ||
|
|
0ac9e21e5e | ||
|
|
17abb729e1 | ||
|
|
00d131e3fc | ||
|
|
9d05c1c79c | ||
|
|
71f3313070 | ||
|
|
18de167d5e | ||
|
|
c177264113 | ||
|
|
31cafcf2dd | ||
|
|
1e6ab3bdf0 | ||
|
|
0ab344edee | ||
|
|
f82b5833aa | ||
|
|
daea9f0925 | ||
|
|
ebd0ae85a4 | ||
|
|
3b1886a435 | ||
|
|
eabc2a4eab | ||
|
|
29cd969d49 | ||
|
|
31a375f640 | ||
|
|
218a2e2f01 | ||
|
|
94295f11da | ||
|
|
d7b58a7730 | ||
|
|
04133a8853 | ||
|
|
619fe22a15 | ||
|
|
8071962bf9 | ||
|
|
9b87d15f9a | ||
|
|
3c9072bb65 | ||
|
|
fa2c234a43 | ||
|
|
a0d16b868e | ||
|
|
2773419faa | ||
|
|
f89fa885d2 | ||
|
|
b34ea69cb3 | ||
|
|
b54f78ab1a | ||
|
|
963a09ba6f | ||
|
|
feebe3f9a9 | ||
|
|
636cd646aa | ||
|
|
9b774d3f72 | ||
|
|
12857ceba1 | ||
|
|
ff5a96f92d | ||
|
|
c4660f1caf | ||
|
|
dc2ee3472b | ||
|
|
ce6ed190aa | ||
|
|
52309bb1e5 | ||
|
|
2e71aee720 | ||
|
|
41cb94bcf0 | ||
|
|
5b9d2182dd | ||
|
|
e6fefcc361 | ||
|
|
181e14f48e | ||
|
|
8446fb028d | ||
|
|
d88249eabc | ||
|
|
304080d74d | ||
|
|
f74afac781 | ||
|
|
066e7bedc1 | ||
|
|
899ea36bcc | ||
|
|
8ed94c4609 | ||
|
|
5dc86ebc5d | ||
|
|
4f7cc48fd4 | ||
|
|
dfa855b4f7 | ||
|
|
49aeeb0202 | ||
|
|
c9a0d2bc80 | ||
|
|
4a63989237 | ||
|
|
bd4aca770e | ||
|
|
a4adf0b9ba | ||
|
|
e48a1b1645 | ||
|
|
09fad78952 | ||
|
|
2a62b43848 | ||
|
|
e8fb1cf236 | ||
|
|
ac2492958c | ||
|
|
ddfc20fb7d | ||
|
|
3147f36d96 | ||
|
|
1d379245ee | ||
|
|
cb0fa2a5c8 | ||
|
|
4c59d64025 | ||
|
|
c92b0ea8f7 | ||
|
|
7ebb0c40f3 | ||
|
|
b24c2b8455 | ||
|
|
281925755d | ||
|
|
f5ae5f7c42 | ||
|
|
80789ccaa9 | ||
|
|
54a40c42cd | ||
|
|
2c5d95a638 | ||
|
|
10ca60893b | ||
|
|
f86c4bb8fa | ||
|
|
3edf777aa6 | ||
|
|
8a2fbdfd56 | ||
|
|
07056013c2 | ||
|
|
ae61a1e5eb | ||
|
|
e0909766fd | ||
|
|
e9dde4ee58 | ||
|
|
0050de641a | ||
|
|
445349d2d9 | ||
|
|
d026c86c50 | ||
|
|
1fb346da67 | ||
|
|
df466faddf | ||
|
|
43106d3a77 | ||
|
|
9fc3b52b24 | ||
|
|
026edb523c | ||
|
|
f640558349 | ||
|
|
ae52521a3f | ||
|
|
13ef1a4084 | ||
|
|
a8f67116ea | ||
|
|
af9bd61bb7 | ||
|
|
fdb0a8116c | ||
|
|
ffcc6ded74 | ||
|
|
6152277910 | ||
|
|
62b47556bc | ||
|
|
c9bfeedb50 | ||
|
|
0dcff56969 | ||
|
|
16d0df2553 | ||
|
|
564b8a9726 | ||
|
|
3830d2414c | ||
|
|
2e6e6d52ca | ||
|
|
22f73506a2 | ||
|
|
fc4c3c9bb9 | ||
|
|
461153967a | ||
|
|
c4e20ea0fe | ||
|
|
1ea66646b6 | ||
|
|
201f5118b3 | ||
|
|
4bebcdc63a | ||
|
|
f6b185388a | ||
|
|
b97b9bf1d5 | ||
|
|
eab0320ef0 | ||
|
|
93adab0644 | ||
|
|
adc70103e0 | ||
|
|
cf3d0bde5b | ||
|
|
4187b074a9 | ||
|
|
1a5637352b | ||
|
|
1e719b97d8 | ||
|
|
2299d23099 | ||
|
|
a91723befe | ||
|
|
e1ea466053 | ||
|
|
2c4ccd2302 | ||
|
|
930e51f94c | ||
|
|
534eef1f72 | ||
|
|
89fa0365fd | ||
|
|
9f8ed3ada3 | ||
|
|
047fd088cc | ||
|
|
db010e4ddc | ||
|
|
bc6f73b9ec | ||
|
|
7761404755 | ||
|
|
7a6e47f067 | ||
|
|
409ca87f8c | ||
|
|
6ce0940f67 | ||
|
|
127d2a3295 | ||
|
|
f4ff96a552 | ||
|
|
64b3e12258 | ||
|
|
7aa1495c4a | ||
|
|
4a1ec12cba | ||
|
|
f4ab48a2d4 | ||
|
|
eeb0d5c98c | ||
|
|
1ebafb0566 | ||
|
|
4a81d70bf6 | ||
|
|
ccc0c7e7aa | ||
|
|
788f7bb029 | ||
|
|
70774f1923 | ||
|
|
c914344a32 | ||
|
|
3b38388c73 | ||
|
|
d9a5b85c67 | ||
|
|
1aacf1adae | ||
|
|
88866ac199 | ||
|
|
316d1f9b14 | ||
|
|
6b465688a2 | ||
|
|
6e8ee09399 | ||
|
|
754604b009 | ||
|
|
7790f770a7 | ||
|
|
72b4c549c7 | ||
|
|
1515178500 | ||
|
|
165dbaf245 | ||
|
|
3c112eb078 | ||
|
|
5540fdb7db | ||
|
|
6e074f5486 | ||
|
|
636140bfdd | ||
|
|
69165396d4 | ||
|
|
dab860cfbd | ||
|
|
7380f56a50 | ||
|
|
d0d761236b | ||
|
|
760f3a788e | ||
|
|
73274ec461 | ||
|
|
291a5847f0 | ||
|
|
2720f6e54b | ||
|
|
58b571f08a | ||
|
|
14a2d9960f | ||
|
|
88c46813d9 | ||
|
|
bcb5e4304a | ||
|
|
793d4f814d | ||
|
|
1693f73742 | ||
|
|
4c9edf04dd | ||
|
|
9b11b12b07 | ||
|
|
6297809716 | ||
|
|
6540c547a4 | ||
|
|
9d8a2cb7bb | ||
|
|
35c0bfea1c | ||
|
|
748c1853ec | ||
|
|
0e5c2c3e80 | ||
|
|
cc0019c090 | ||
|
|
e00667c2e4 | ||
|
|
c1a4c0185b | ||
|
|
1c997a5973 | ||
|
|
236de6ce34 | ||
|
|
681e07ca73 | ||
|
|
e388e4b163 | ||
|
|
1018dc6e74 | ||
|
|
06075805e0 | ||
|
|
37a6a5183d | ||
|
|
ebd89194ee | ||
|
|
243dee4312 | ||
|
|
d677a20c96 | ||
|
|
ccddf1d4f0 | ||
|
|
1d3ab23699 | ||
|
|
0182822f76 | ||
|
|
c911ce38a6 | ||
|
|
89328e1662 | ||
|
|
a618f2804b | ||
|
|
242f7785e6 | ||
|
|
3335d0a453 | ||
|
|
1621f2ba31 | ||
|
|
45ddc7f71b | ||
|
|
ae22bbbc81 | ||
|
|
3e1607047e | ||
|
|
da69985970 | ||
|
|
6999458de6 | ||
|
|
37a8db125a | ||
|
|
3d74c1ccaa | ||
|
|
6fae298c0c | ||
|
|
e85c6ca0c5 | ||
|
|
aa788a7aad | ||
|
|
3c4845cf99 | ||
|
|
2b12a86fa8 | ||
|
|
dc5040c1d2 | ||
|
|
b02e163ecc | ||
|
|
0e25c9a7a1 | ||
|
|
79f73256d7 | ||
|
|
2513f0489c | ||
|
|
3579f557d1 | ||
|
|
668e822fc6 | ||
|
|
dd41dc05f5 | ||
|
|
f247b89342 | ||
|
|
5386662146 | ||
|
|
7d4e9ce069 | ||
|
|
9fd07125e2 | ||
|
|
5d5b61d956 | ||
|
|
4d91e92344 | ||
|
|
75b32f2552 | ||
|
|
8eba9ae714 | ||
|
|
b318d27b19 | ||
|
|
90cdd28bc8 | ||
|
|
e51a6f8ff2 | ||
|
|
585a73e641 | ||
|
|
2a835e5be4 | ||
|
|
0386c53a6a | ||
|
|
e262dbfcf2 | ||
|
|
8834e1b49c | ||
|
|
c9c6b2f7d5 | ||
|
|
7abb048b7c | ||
|
|
e27d61a525 | ||
|
|
f436cece4a | ||
|
|
e7eb1ec7e3 | ||
|
|
21564ff340 | ||
|
|
c32ef34307 | ||
|
|
2d05b110b8 | ||
|
|
25287a8c04 | ||
|
|
e877ec431f | ||
|
|
272051ea99 | ||
|
|
725c7d45d1 | ||
|
|
b991f0a6c6 | ||
|
|
ed035b3699 | ||
|
|
e9f57fd9e2 | ||
|
|
fd124ef53b | ||
|
|
0259071bdd | ||
|
|
32ce26aa7d | ||
|
|
3997ea3a23 | ||
|
|
f41c4f80f8 | ||
|
|
3eacf2e7fa | ||
|
|
c3f804bb29 | ||
|
|
1784653678 | ||
|
|
447fc85867 | ||
|
|
f3773f6047 | ||
|
|
b741532231 | ||
|
|
f01bd61692 | ||
|
|
a5d3208599 | ||
|
|
f3cc54fe8d | ||
|
|
70e0fab267 | ||
|
|
7868c4364c | ||
|
|
2a5ca467fd | ||
|
|
2ab12d9d11 | ||
|
|
0104c3fe41 | ||
|
|
847c1d2736 | ||
|
|
922354320b | ||
|
|
462af27157 | ||
|
|
ea31d71d16 | ||
|
|
2150682a92 | ||
|
|
24fcb20927 | ||
|
|
414b94405b | ||
|
|
5328feb58b | ||
|
|
bc7176f07e | ||
|
|
8f4a8fc7be | ||
|
|
81317d4df9 | ||
|
|
af87ef3623 | ||
|
|
c201ae4147 | ||
|
|
37e3cb6285 | ||
|
|
4b7fb20eaf | ||
|
|
92d9c778e5 | ||
|
|
1c90cd2312 | ||
|
|
4006fce0f2 | ||
|
|
a31be78cbd | ||
|
|
f29af948be | ||
|
|
44ba3d7bf8 | ||
|
|
7ea5176b56 | ||
|
|
e6887ab317 | ||
|
|
67c92ab946 | ||
|
|
daa090d4fe | ||
|
|
ced87e71cb | ||
|
|
e26eed3142 | ||
|
|
d33dad51f8 | ||
|
|
37f6c6ed7d | ||
|
|
7e3d526de3 | ||
|
|
53029f9fc3 | ||
|
|
22ccb317d6 | ||
|
|
ad91dc14b8 | ||
|
|
6bd89f31d3 | ||
|
|
608e94a613 | ||
|
|
9c16b08e22 | ||
|
|
63b6b95f71 | ||
|
|
17127a5157 | ||
|
|
bfadbc4098 | ||
|
|
08c6bb5c5e | ||
|
|
4e24e9899e | ||
|
|
c72bdae8d7 | ||
|
|
3687657dd7 | ||
|
|
21f0adb41b | ||
|
|
e0fad5e0e6 | ||
|
|
77f8129fac | ||
|
|
064ae11ba8 | ||
|
|
d7a69118bc | ||
|
|
f2fabe4eec | ||
|
|
dc92e40429 | ||
|
|
9c080d51f7 | ||
|
|
cf0541c93e | ||
|
|
54be542355 | ||
|
|
225ace0d02 | ||
|
|
ca24ae9632 | ||
|
|
9f69fd0811 | ||
|
|
b978a5d219 | ||
|
|
e7759aa79a | ||
|
|
1f4ca07b5f | ||
|
|
3ecd768982 | ||
|
|
3cfcbeb654 | ||
|
|
e1409ba39c | ||
|
|
172e255cc2 | ||
|
|
ef6299c6b4 | ||
|
|
8a99b09e83 | ||
|
|
9da19de860 | ||
|
|
b8af72b402 | ||
|
|
764c551f0f | ||
|
|
410c47178d | ||
|
|
f53ce84f5d | ||
|
|
ab0d425d93 | ||
|
|
95ca14b05c | ||
|
|
61e2f97d6c | ||
|
|
9e6c024beb | ||
|
|
879f5d89b9 | ||
|
|
161041d379 | ||
|
|
6d23d64e8f | ||
|
|
8c4e84dfaf | ||
|
|
706940769b | ||
|
|
3fe2aa3b1d | ||
|
|
1f4d7f2a32 | ||
|
|
57f0cce318 | ||
|
|
e95d0a4722 | ||
|
|
f37030fe26 | ||
|
|
3be0bc8ca8 | ||
|
|
65a7a8ee56 | ||
|
|
ab38ce63a5 | ||
|
|
e92c6e5298 | ||
|
|
76df404c8d | ||
|
|
d3a2841fef | ||
|
|
d60a4aa740 | ||
|
|
6538d7af1e | ||
|
|
c69279ee20 | ||
|
|
a16e746aa1 | ||
|
|
b1f62c8409 | ||
|
|
4a85f7f12b | ||
|
|
818be68c2d | ||
|
|
7511391aed | ||
|
|
d0a50adf32 | ||
|
|
c9576c696a | ||
|
|
908b442b26 | ||
|
|
9687e9985e | ||
|
|
07e4b18d06 | ||
|
|
93654dc656 | ||
|
|
4be5334829 | ||
|
|
0f4301af01 | ||
|
|
b071c47674 | ||
|
|
05e9f394f0 | ||
|
|
2027becad2 | ||
|
|
e7170755d8 | ||
|
|
d0a0d3c93c | ||
|
|
2db785477c | ||
|
|
31ec3152f9 | ||
|
|
7b8b469f5a | ||
|
|
bc4737ac23 | ||
|
|
6dc190d369 | ||
|
|
9980d2e72a | ||
|
|
2d34510f50 | ||
|
|
ef57f870ac |
@@ -405,12 +405,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
protected $m_aAdmins = array(); // id -> bool, true if the user has the well-known admin profile
|
||||
protected $m_aPortalUsers = array(); // id -> bool, true if the user has the well-known portal user profile
|
||||
|
||||
protected $m_aProfiles; // id -> object
|
||||
protected $m_aUserProfiles = array(); // userid,profileid -> object
|
||||
protected $m_aUserOrgs = array(); // userid -> array of orgid
|
||||
|
||||
// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
|
||||
@@ -458,114 +452,65 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
return $this->m_aUserOrgs[$iUser];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and cache profiles of the given user
|
||||
*/
|
||||
protected function GetUserProfiles($iUser)
|
||||
{
|
||||
if (!array_key_exists($iUser, $this->m_aUserProfiles))
|
||||
{
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AllowAllData();
|
||||
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
|
||||
$oSearch->AddConditionExpression($oCondition);
|
||||
|
||||
$this->m_aUserProfiles[$iUser] = array();
|
||||
$oUserProfileSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
|
||||
while ($oUserProfile = $oUserProfileSet->Fetch())
|
||||
{
|
||||
$this->m_aUserProfiles[$iUser][$oUserProfile->Get('profileid')] = $oUserProfile;
|
||||
}
|
||||
}
|
||||
return $this->m_aUserProfiles[$iUser];
|
||||
|
||||
}
|
||||
|
||||
public function ResetCache()
|
||||
{
|
||||
// Loaded by Load cache
|
||||
$this->m_aProfiles = null;
|
||||
$this->m_aUserProfiles = array();
|
||||
$this->m_aUserOrgs = array();
|
||||
|
||||
$this->m_aAdmins = array();
|
||||
$this->m_aPortalUsers = array();
|
||||
|
||||
// Cache
|
||||
$this->m_aObjectActionGrants = array();
|
||||
}
|
||||
|
||||
public function LoadCache()
|
||||
{
|
||||
if (!is_null($this->m_aProfiles)) return;
|
||||
// Could be loaded in a shared memory (?)
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
|
||||
if (self::HasSharing())
|
||||
static $bSharedObjectInitialized = false;
|
||||
if (!$bSharedObjectInitialized)
|
||||
{
|
||||
SharedObject::InitSharedClassProperties();
|
||||
$bSharedObjectInitialized = true;
|
||||
if (self::HasSharing())
|
||||
{
|
||||
SharedObject::InitSharedClassProperties();
|
||||
}
|
||||
}
|
||||
|
||||
$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
|
||||
$this->m_aProfiles = array();
|
||||
while ($oProfile = $oProfileSet->Fetch())
|
||||
{
|
||||
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
|
||||
}
|
||||
|
||||
$oKPI->ComputeAndReport('Load of user management cache (excepted Action Grants)');
|
||||
|
||||
/*
|
||||
echo "<pre>\n";
|
||||
print_r($this->m_aProfiles);
|
||||
print_r($this->m_aUserProfiles);
|
||||
print_r($this->m_aUserOrgs);
|
||||
echo "</pre>\n";
|
||||
exit;
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return array
|
||||
*/
|
||||
public function IsAdministrator($oUser)
|
||||
{
|
||||
//$this->LoadCache();
|
||||
$iUser = $oUser->GetKey();
|
||||
if (!array_key_exists($iUser, $this->m_aAdmins))
|
||||
{
|
||||
$bIsAdmin = false;
|
||||
foreach($this->GetUserProfiles($iUser) as $oUserProfile)
|
||||
{
|
||||
if ($oUserProfile->Get('profile') == ADMIN_PROFILE_NAME)
|
||||
{
|
||||
$bIsAdmin = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->m_aAdmins[$iUser] = $bIsAdmin;
|
||||
}
|
||||
return $this->m_aAdmins[$iUser];
|
||||
// UserRights caches the list for us
|
||||
return UserRights::HasProfile(ADMIN_PROFILE_NAME, $oUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return array
|
||||
*/
|
||||
public function IsPortalUser($oUser)
|
||||
{
|
||||
//$this->LoadCache();
|
||||
$iUser = $oUser->GetKey();
|
||||
if (!array_key_exists($iUser, $this->m_aPortalUsers))
|
||||
{
|
||||
$bIsPortalUser = false;
|
||||
foreach($this->GetUserProfiles($iUser) as $oUserProfile)
|
||||
{
|
||||
if ($oUserProfile->Get('profile') == PORTAL_PROFILE_NAME)
|
||||
{
|
||||
$bIsPortalUser = true;
|
||||
break;
|
||||
}
|
||||
// UserRights caches the list for us
|
||||
return UserRights::HasProfile(PORTAL_PROFILE_NAME, $oUser);
|
||||
}
|
||||
$this->m_aPortalUsers[$iUser] = $bIsPortalUser;
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return bool
|
||||
*/
|
||||
public function ListProfiles($oUser)
|
||||
{
|
||||
$aRet = array();
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AllowAllData();
|
||||
$oSearch->NoContextParameters();
|
||||
$oSearch->Addcondition('userid', $oUser->GetKey(), '=');
|
||||
$oProfiles = new DBObjectSet($oSearch);
|
||||
while ($oUserProfile = $oProfiles->Fetch())
|
||||
{
|
||||
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
|
||||
}
|
||||
return $this->m_aPortalUsers[$iUser];
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
|
||||
@@ -621,8 +566,8 @@ exit;
|
||||
$sAction = self::$m_aActionCodes[$iActionCode];
|
||||
|
||||
$bStatus = null;
|
||||
$aAttributes = array();
|
||||
foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
|
||||
{
|
||||
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
|
||||
if (!is_null($bGrant))
|
||||
@@ -645,12 +590,11 @@ exit;
|
||||
|
||||
$aRes = array(
|
||||
'permission' => $iPermission,
|
||||
// 'attributes' => $aAttributes,
|
||||
);
|
||||
$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
|
||||
public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null)
|
||||
{
|
||||
$this->LoadCache();
|
||||
@@ -752,7 +696,8 @@ exit;
|
||||
// Note: The object set is ignored because it was interesting to optimize for huge data sets
|
||||
// and acceptable to consider only the root class of the object set
|
||||
$bStatus = null;
|
||||
foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
|
||||
{
|
||||
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
|
||||
if (!is_null($bGrant))
|
||||
|
||||
328
application/Html2Text.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
namespace Html2Text;
|
||||
|
||||
if (!function_exists('mb_split'))
|
||||
{
|
||||
function mb_split($pattern, $subject, $limit = -1)
|
||||
{
|
||||
return preg_split('/'.$pattern.'/', $subject, $limit);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Replace all occurrences of the search string with the replacement string.
|
||||
*
|
||||
* @author Sean Murphy <sean@iamseanmurphy.com>
|
||||
* @copyright Copyright 2012 Sean Murphy. All rights reserved.
|
||||
* @license http://creativecommons.org/publicdomain/zero/1.0/
|
||||
* @link http://php.net/manual/function.str-replace.php
|
||||
*
|
||||
* @param mixed $search
|
||||
* @param mixed $replace
|
||||
* @param mixed $subject
|
||||
* @param int $count
|
||||
* @return mixed
|
||||
*/
|
||||
function mb_str_replace($search, $replace, $subject, &$count = 0) {
|
||||
if (!is_array($subject)) {
|
||||
// Normalize $search and $replace so they are both arrays of the same length
|
||||
$searches = is_array($search) ? array_values($search) : array($search);
|
||||
$replacements = is_array($replace) ? array_values($replace) : array($replace);
|
||||
$replacements = array_pad($replacements, count($searches), '');
|
||||
foreach ($searches as $key => $search) {
|
||||
$parts = mb_split(preg_quote($search), $subject);
|
||||
$count += count($parts) - 1;
|
||||
$subject = implode($replacements[$key], $parts);
|
||||
}
|
||||
} else {
|
||||
// Call mb_str_replace for each subject in array, recursively
|
||||
foreach ($subject as $key => $value) {
|
||||
$subject[$key] = mb_str_replace($search, $replace, $value, $count);
|
||||
}
|
||||
}
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2010 Jevon Wright and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* or
|
||||
*
|
||||
* LGPL which is available at http://www.gnu.org/licenses/lgpl.html
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Jevon Wright - initial API and implementation
|
||||
* Denis Flaven - some fixes for properly handling UTF-8 characters
|
||||
****************************************************************************/
|
||||
|
||||
class Html2Text {
|
||||
|
||||
/**
|
||||
* Tries to convert the given HTML into a plain text format - best suited for
|
||||
* e-mail display, etc.
|
||||
*
|
||||
* <p>In particular, it tries to maintain the following features:
|
||||
* <ul>
|
||||
* <li>Links are maintained, with the 'href' copied over
|
||||
* <li>Information in the <head> is lost
|
||||
* </ul>
|
||||
*
|
||||
* @param string html the input HTML
|
||||
* @return string the HTML converted, as best as possible, to text
|
||||
* @throws Html2TextException if the HTML could not be loaded as a {@link DOMDocument}
|
||||
*/
|
||||
static function convert($html) {
|
||||
// replace with spaces
|
||||
|
||||
$html = str_replace(" ", " ", $html);
|
||||
$html = mb_str_replace("\xc2\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
|
||||
|
||||
$html = static::fixNewlines($html);
|
||||
|
||||
$doc = new \DOMDocument();
|
||||
if (!@$doc->loadHTML('<?xml encoding="UTF-8">'.$html)) // Forces the UTF-8 character set for HTML fragments
|
||||
{
|
||||
throw new Html2TextException("Could not load HTML - badly formed?", $html);
|
||||
}
|
||||
|
||||
$output = static::iterateOverNode($doc);
|
||||
|
||||
// remove leading and trailing spaces on each line
|
||||
$output = preg_replace("/[ \t]*\n[ \t]*/im", "\n", $output);
|
||||
$output = preg_replace("/ *\t */im", "\t", $output);
|
||||
|
||||
// remove unnecessary empty lines
|
||||
$output = preg_replace("/\n\n\n*/im", "\n\n", $output);
|
||||
|
||||
// remove leading and trailing whitespace
|
||||
$output = trim($output);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unify newlines; in particular, \r\n becomes \n, and
|
||||
* then \r becomes \n. This means that all newlines (Unix, Windows, Mac)
|
||||
* all become \ns.
|
||||
*
|
||||
* @param string text text with any number of \r, \r\n and \n combinations
|
||||
* @return string the fixed text
|
||||
*/
|
||||
static function fixNewlines($text) {
|
||||
// replace \r\n to \n
|
||||
$text = str_replace("\r\n", "\n", $text);
|
||||
// remove \rs
|
||||
$text = str_replace("\r", "\n", $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
static function nextChildName($node) {
|
||||
// get the next child
|
||||
$nextNode = $node->nextSibling;
|
||||
while ($nextNode != null) {
|
||||
if ($nextNode instanceof \DOMElement) {
|
||||
break;
|
||||
}
|
||||
$nextNode = $nextNode->nextSibling;
|
||||
}
|
||||
$nextName = null;
|
||||
if ($nextNode instanceof \DOMElement && $nextNode != null) {
|
||||
$nextName = strtolower($nextNode->nodeName);
|
||||
}
|
||||
|
||||
return $nextName;
|
||||
}
|
||||
|
||||
static function prevChildName($node) {
|
||||
// get the previous child
|
||||
$nextNode = $node->previousSibling;
|
||||
while ($nextNode != null) {
|
||||
if ($nextNode instanceof \DOMElement) {
|
||||
break;
|
||||
}
|
||||
$nextNode = $nextNode->previousSibling;
|
||||
}
|
||||
$nextName = null;
|
||||
if ($nextNode instanceof \DOMElement && $nextNode != null) {
|
||||
$nextName = strtolower($nextNode->nodeName);
|
||||
}
|
||||
|
||||
return $nextName;
|
||||
}
|
||||
|
||||
static function iterateOverNode($node) {
|
||||
if ($node instanceof \DOMText) {
|
||||
// Replace whitespace characters with a space (equivilant to \s)
|
||||
return preg_replace("/[\\t\\n\\f\\r ]+/im", " ", $node->wholeText);
|
||||
}
|
||||
if ($node instanceof \DOMDocumentType) {
|
||||
// ignore
|
||||
return "";
|
||||
}
|
||||
|
||||
$nextName = static::nextChildName($node);
|
||||
$prevName = static::prevChildName($node);
|
||||
|
||||
$name = strtolower($node->nodeName);
|
||||
|
||||
// start whitespace
|
||||
switch ($name) {
|
||||
case "hr":
|
||||
return "---------------------------------------------------------------\n";
|
||||
|
||||
case "style":
|
||||
case "head":
|
||||
case "title":
|
||||
case "meta":
|
||||
case "script":
|
||||
// ignore these tags
|
||||
return "";
|
||||
|
||||
case "h1":
|
||||
case "h2":
|
||||
case "h3":
|
||||
case "h4":
|
||||
case "h5":
|
||||
case "h6":
|
||||
case "ol":
|
||||
case "ul":
|
||||
// add two newlines, second line is added below
|
||||
$output = "\n";
|
||||
break;
|
||||
|
||||
case "td":
|
||||
case "th":
|
||||
// add tab char to separate table fields
|
||||
$output = "\t";
|
||||
break;
|
||||
|
||||
case "tr":
|
||||
case "p":
|
||||
case "div":
|
||||
// add one line
|
||||
$output = "\n";
|
||||
break;
|
||||
|
||||
case "li":
|
||||
$output = "- ";
|
||||
break;
|
||||
|
||||
default:
|
||||
// print out contents of unknown tags
|
||||
$output = "";
|
||||
break;
|
||||
}
|
||||
|
||||
// debug
|
||||
//$output .= "[$name,$nextName]";
|
||||
|
||||
if (isset($node->childNodes)) {
|
||||
for ($i = 0; $i < $node->childNodes->length; $i++) {
|
||||
$n = $node->childNodes->item($i);
|
||||
|
||||
$text = static::iterateOverNode($n);
|
||||
|
||||
$output .= $text;
|
||||
}
|
||||
}
|
||||
|
||||
// end whitespace
|
||||
switch ($name) {
|
||||
case "h1":
|
||||
case "h2":
|
||||
case "h3":
|
||||
case "h4":
|
||||
case "h5":
|
||||
case "h6":
|
||||
$output .= "\n";
|
||||
break;
|
||||
|
||||
case "p":
|
||||
case "br":
|
||||
// add one line
|
||||
if ($nextName != "div")
|
||||
$output .= "\n";
|
||||
break;
|
||||
|
||||
case "div":
|
||||
// add one line only if the next child isn't a div
|
||||
if ($nextName != "div" && $nextName != null)
|
||||
$output .= "\n";
|
||||
break;
|
||||
|
||||
case "a":
|
||||
// links are returned in [text](link) format
|
||||
$href = $node->getAttribute("href");
|
||||
|
||||
$output = trim($output);
|
||||
|
||||
// remove double [[ ]] s from linking images
|
||||
if (substr($output, 0, 1) == "[" && substr($output, -1) == "]") {
|
||||
$output = substr($output, 1, strlen($output) - 2);
|
||||
|
||||
// for linking images, the title of the <a> overrides the title of the <img>
|
||||
if ($node->getAttribute("title")) {
|
||||
$output = $node->getAttribute("title");
|
||||
}
|
||||
}
|
||||
|
||||
// if there is no link text, but a title attr
|
||||
if (!$output && $node->getAttribute("title")) {
|
||||
$output = $node->getAttribute("title");
|
||||
}
|
||||
|
||||
if ($href == null) {
|
||||
// it doesn't link anywhere
|
||||
if ($node->getAttribute("name") != null) {
|
||||
$output = "[$output]";
|
||||
}
|
||||
} else {
|
||||
if ($href == $output || $href == "mailto:$output" || $href == "http://$output" || $href == "https://$output") {
|
||||
// link to the same address: just use link
|
||||
$output;
|
||||
} else {
|
||||
// replace it
|
||||
if ($output) {
|
||||
$output = "[$output]($href)";
|
||||
} else {
|
||||
// empty string
|
||||
$output = $href;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// does the next node require additional whitespace?
|
||||
switch ($nextName) {
|
||||
case "h1": case "h2": case "h3": case "h4": case "h5": case "h6":
|
||||
$output .= "\n";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "img":
|
||||
if ($node->getAttribute("title")) {
|
||||
$output = "[" . $node->getAttribute("title") . "]";
|
||||
} elseif ($node->getAttribute("alt")) {
|
||||
$output = "[" . $node->getAttribute("alt") . "]";
|
||||
} else {
|
||||
$output = "";
|
||||
}
|
||||
break;
|
||||
|
||||
case "li":
|
||||
$output .= "\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
||||
28
application/Html2TextException.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2010 Jevon Wright and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* or
|
||||
*
|
||||
* LGPL which is available at http://www.gnu.org/licenses/lgpl.html
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Jevon Wright - initial API and implementation
|
||||
****************************************************************************/
|
||||
|
||||
namespace Html2Text;
|
||||
|
||||
class Html2TextException extends \Exception {
|
||||
var $more_info;
|
||||
|
||||
public function __construct($message = "", $more_info = "") {
|
||||
parent::__construct($message);
|
||||
$this->more_info = $more_info;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 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-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -202,32 +202,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'))
|
||||
{
|
||||
@@ -252,7 +236,6 @@ EOF
|
||||
echo "<script type=\"text/javascript\">\n";
|
||||
echo "$('#inner_menu').html($('#accordion_temp_$uid').html());\n";
|
||||
echo "$('#accordion_temp_$uid').remove();\n";
|
||||
echo "$('#accordion').accordion({ header: 'h3', navigation: true, autoHeight: false, collapsible: false, icons: false });\n";
|
||||
echo "\n</script>\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
84
application/capturewebpage.class.inc.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* Adapter class: when an API requires WebPage and you want to produce something else
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT."/application/webpage.class.inc.php");
|
||||
|
||||
class CaptureWebPage extends WebPage
|
||||
{
|
||||
protected $aReadyScripts;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct('capture web page');
|
||||
$this->aReadyScripts = array();
|
||||
}
|
||||
|
||||
public function GetHtml()
|
||||
{
|
||||
$trash = $this->ob_get_clean_safe();
|
||||
return $this->s_content;
|
||||
}
|
||||
|
||||
public function GetJS()
|
||||
{
|
||||
$sRet = implode("\n", $this->a_scripts);
|
||||
if (!empty($this->s_deferred_content))
|
||||
{
|
||||
$sRet .= "\n\$('body').append('".addslashes(str_replace("\n", '', $this->s_deferred_content))."');";
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetReadyJS()
|
||||
{
|
||||
return "\$(document).ready(function() {\n".implode("\n", $this->aReadyScripts)."\n});";
|
||||
}
|
||||
|
||||
public function GetCSS()
|
||||
{
|
||||
return $this->a_styles;
|
||||
}
|
||||
|
||||
public function GetJSFiles()
|
||||
{
|
||||
return $this->a_linked_scripts;
|
||||
}
|
||||
|
||||
public function GetCSSFiles()
|
||||
{
|
||||
return $this->a_linked_stylesheets;
|
||||
}
|
||||
|
||||
public function output()
|
||||
{
|
||||
throw new Exception(__method__.' should not be called');
|
||||
}
|
||||
|
||||
public function add_ready_script($sScript)
|
||||
{
|
||||
$this->aReadyScripts[] = $sScript;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* Abstract class that implements some common and useful methods for displaying
|
||||
* the objects
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -44,13 +44,33 @@ require_once(APPROOT.'/application/ui.passwordwidget.class.inc.php');
|
||||
require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
|
||||
require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php');
|
||||
require_once(APPROOT.'/application/datatable.class.inc.php');
|
||||
require_once(APPROOT.'/sources/renderer/console/consoleformrenderer.class.inc.php');
|
||||
|
||||
abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
|
||||
{
|
||||
protected $m_iFormId; // The ID of the form used to edit the object (when in edition mode !)
|
||||
static $iGlobalFormId = 1;
|
||||
protected $aFieldsMap;
|
||||
|
||||
/**
|
||||
* If true, bypass IsActionAllowedOnAttribute when writing this object
|
||||
* @var bool
|
||||
*/
|
||||
protected $bAllowWrite;
|
||||
|
||||
/**
|
||||
* Constructor from a row of data (as a hash 'attcode' => value)
|
||||
* @param hash $aRow
|
||||
* @param string $sClassAlias
|
||||
* @param hash $aAttToLoad
|
||||
* @param hash $aExtendedDataSpec
|
||||
*/
|
||||
public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null)
|
||||
{
|
||||
parent::__construct($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
|
||||
$this->bAllowWrite = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns what will be the next ID for the forms
|
||||
*/
|
||||
@@ -262,10 +282,15 @@ EOF
|
||||
$sTip .= "<p><b>".Dict::S('Core:Synchro:ListOfDataSources')."</b></p>";
|
||||
foreach($aMasterSources as $aStruct)
|
||||
{
|
||||
// Formatting last synchro date
|
||||
$oDateTime = DateTime::createFromFormat('Y-m-d H:i:s', $aStruct['last_synchro']);
|
||||
$oDateTimeFormat = AttributeDateTime::GetFormat();
|
||||
$sLastSynchro = $oDateTimeFormat->Format($oDateTime);
|
||||
|
||||
$oDataSource = $aStruct['datasource'];
|
||||
$sLink = $aStruct['url'];
|
||||
$sTip .= "<p style=\"white-space:nowrap\">".$oDataSource->GetIcon(true, 'style="vertical-align:middle"')." $sLink<br/>";
|
||||
$sTip .= Dict::S('Core:Synchro:LastSynchro').'<br/>'.$aStruct['last_synchro']."</p>";
|
||||
$sTip .= Dict::S('Core:Synchro:LastSynchro') . '<br/>' . $sLastSynchro . "</p>";
|
||||
}
|
||||
$aIcons[] = ' <img style="vertical-align:middle;" id="synchro_icon" src="../images/locked.png"/>';
|
||||
$sTip = addslashes($sTip);
|
||||
@@ -627,56 +652,43 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iFlags & OPT_ATT_HIDDEN)
|
||||
if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
|
||||
{
|
||||
// Attribute is hidden, add a hidden input
|
||||
$oPage->add('<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/>');
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
// Check if the attribute is not read-only because of a synchro...
|
||||
$aReasons = array();
|
||||
$sSynchroIcon = '';
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
|
||||
}
|
||||
$sTip = addslashes($sTip);
|
||||
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
$sComments = $sSynchroIcon;
|
||||
}
|
||||
|
||||
// Attribute is read-only
|
||||
$sHTMLValue = "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode).'</span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
|
||||
{
|
||||
|
||||
// Check if the attribute is not read-only because of a synchro...
|
||||
$aReasons = array();
|
||||
$sSynchroIcon = '';
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
|
||||
}
|
||||
$sTip = addslashes($sTip);
|
||||
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
}
|
||||
|
||||
// Attribute is read-only
|
||||
$sHTMLValue = "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode);
|
||||
$sHTMLValue .= '<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/></span>';
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
$sComments = $sSynchroIcon;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = $this->Get($sAttCode);
|
||||
$sDisplayValue = $this->GetEditValue($sAttCode);
|
||||
$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
|
||||
$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
|
||||
}
|
||||
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
|
||||
$sValue = $this->Get($sAttCode);
|
||||
$sDisplayValue = $this->GetEditValue($sAttCode);
|
||||
$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
|
||||
$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
|
||||
}
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode)."</span>", 'comments' => $sComments, 'infos' => $sInfos);
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1323,8 +1335,8 @@ EOF
|
||||
else
|
||||
{
|
||||
$iDate = AttributeDateTime::GetAsUnixSeconds($sDate);
|
||||
$aRow[] = '<td>'.date('Y-m-d', $iDate).'</td>';
|
||||
$aRow[] = '<td>'.date('H:i:s', $iDate).'</td>';
|
||||
$aRow[] = '<td>'.date('Y-m-d', $iDate).'</td>'; // Format kept as-is for 100% backward compatibility of the exports
|
||||
$aRow[] = '<td>'.date('H:i:s', $iDate).'</td>'; // Format kept as-is for 100% backward compatibility of the exports
|
||||
}
|
||||
}
|
||||
else if($oAttDef instanceof AttributeCaseLog)
|
||||
@@ -1712,7 +1724,8 @@ EOF
|
||||
{
|
||||
$bMandatory = 'true';
|
||||
}
|
||||
$sValidationField = "<span class=\"form_validation\" id=\"v_{$iId}\"></span>";
|
||||
$sValidationSpan = "<span class=\"form_validation\" id=\"v_{$iId}\"></span>";
|
||||
$sReloadSpan = "<span class=\"field_status\" id=\"fstatus_{$iId}\"></span>";
|
||||
$sHelpText = htmlentities($oAttDef->GetHelpOnEdition(), ENT_QUOTES, 'UTF-8');
|
||||
$aEventsList = array();
|
||||
switch($oAttDef->GetEditClass())
|
||||
@@ -1721,22 +1734,18 @@ EOF
|
||||
$aEventsList[] ='validate';
|
||||
$aEventsList[] ='keyup';
|
||||
$aEventsList[] ='change';
|
||||
if (($iFlags & OPT_ATT_MANDATORY) && (empty($sDisplayValue)))
|
||||
{
|
||||
$sDisplayValue = date($oAttDef->GetDateFormat());
|
||||
}
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"12\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationField}";
|
||||
$sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDate::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"';
|
||||
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"12\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationSpan}{$sReloadSpan}";
|
||||
break;
|
||||
|
||||
case 'DateTime':
|
||||
$aEventsList[] ='validate';
|
||||
$aEventsList[] ='keyup';
|
||||
$aEventsList[] ='change';
|
||||
if (($iFlags & OPT_ATT_MANDATORY) && (empty($sDisplayValue)))
|
||||
{
|
||||
$sDisplayValue = date($oAttDef->GetDateFormat());
|
||||
}
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"20\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationField}";
|
||||
|
||||
$sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"';
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"19\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationSpan}{$sReloadSpan}";
|
||||
break;
|
||||
|
||||
case 'Duration':
|
||||
@@ -1752,7 +1761,7 @@ EOF
|
||||
$sMinutes = "<input title=\"$sHelpText\" type=\"text\" style=\"text-align:right\" size=\"2\" name=\"attr_{$sFieldPrefix}{$sAttCode}[m]{$sNameSuffix}\" value=\"{$aVal['minutes']}\" id=\"{$iId}_m\"/>";
|
||||
$sSeconds = "<input title=\"$sHelpText\" type=\"text\" style=\"text-align:right\" size=\"2\" name=\"attr_{$sFieldPrefix}{$sAttCode}[s]{$sNameSuffix}\" value=\"{$aVal['seconds']}\" id=\"{$iId}_s\"/>";
|
||||
$sHidden = "<input type=\"hidden\" id=\"{$iId}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\"/>";
|
||||
$sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden." ".$sValidationField;
|
||||
$sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan;
|
||||
$oPage->add_ready_script("$('#{$iId}').bind('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });");
|
||||
break;
|
||||
|
||||
@@ -1760,7 +1769,7 @@ EOF
|
||||
$aEventsList[] ='validate';
|
||||
$aEventsList[] ='keyup';
|
||||
$aEventsList[] ='change';
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" type=\"password\" size=\"30\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationField}";
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" type=\"password\" size=\"30\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationSpan}{$sReloadSpan}";
|
||||
break;
|
||||
|
||||
case 'OQLExpression':
|
||||
@@ -1799,7 +1808,7 @@ EOF
|
||||
$sAdditionalStuff = "";
|
||||
}
|
||||
// Ok, the text area is drawn here
|
||||
$sHTMLValue = "<table><tr><td><textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\" $sStyle>".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sAdditionalStuff</td><td>{$sValidationField}</td></tr></table>";
|
||||
$sHTMLValue = "<table><tr><td><textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\" $sStyle>".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sAdditionalStuff</td><td>{$sValidationSpan}{$sReloadSpan}</td></tr></table>";
|
||||
|
||||
break;
|
||||
|
||||
@@ -1820,17 +1829,18 @@ EOF
|
||||
{
|
||||
$sStyle = 'style="'.implode('; ', $aStyles).'"';
|
||||
}
|
||||
$sHeader = '<div class="caselog_input_header"> '.Dict::S('UI:CaseLogTypeYourTextHere').'</div>';
|
||||
$sHeader = '<div class="caselog_input_header"></div>'; // will be hidden in CSS (via :empty) if it remains empty
|
||||
$sEditValue = $oAttDef->GetEditValue($value);
|
||||
$sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, array('AttributeText', 'RenderWikiHtml')) : '';
|
||||
$iEntriesCount = is_object($value) ? count($value->GetIndex()) : 0;
|
||||
$sHidden = "<input type=\"hidden\" id=\"{$iId}_count\" value=\"$iEntriesCount\"/>"; // To know how many entries the case log already contains
|
||||
$sHTMLValue = "<div class=\"caselog\" $sStyle><table style=\"width:100%;\"><tr><td>$sHeader<textarea style=\"border:0;width:100%\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sPreviousLog</td><td>{$sValidationField}</td></tr></table>$sHidden</div>";
|
||||
$sHTMLValue = "<div class=\"caselog\" $sStyle><table style=\"width:100%;\"><tr><td>$sHeader<textarea class=\"htmlEditor\" style=\"border:0;width:100%\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sPreviousLog</td><td>{$sValidationSpan}{$sReloadSpan}</td></tr></table>$sHidden</div>";
|
||||
$oPage->add_ready_script("$('#$iId').bind('keyup change validate', function(evt, sFormId) { return ValidateCaseLogField('$iId', $bMandatory, sFormId) } );"); // Custom validation function
|
||||
break;
|
||||
|
||||
case 'HTML':
|
||||
$oWidget = new UIHTMLEditorWidget($iId, $oAttDef, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationField, $value, $bMandatory);
|
||||
$sEditValue = $oAttDef->GetEditValue($value);
|
||||
$oWidget = new UIHTMLEditorWidget($iId, $oAttDef, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationSpan.$sReloadSpan, $sEditValue, $bMandatory);
|
||||
$sHTMLValue = $oWidget->Display($oPage, $aArgs);
|
||||
break;
|
||||
|
||||
@@ -1861,10 +1871,45 @@ EOF
|
||||
$iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize'));
|
||||
$sHTMLValue = "<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"$iMaxFileSize\" />\n";
|
||||
$sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[filename]\" type=\"hidden\" id=\"$iId\" \" value=\"".htmlentities($sFileName, ENT_QUOTES, 'UTF-8')."\"/>\n";
|
||||
$sHTMLValue .= "<span id=\"name_$iInputId\">$sFileName</span><br/>\n";
|
||||
$sHTMLValue .= "<input title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[fcontents]\" type=\"file\" id=\"file_$iId\" onChange=\"UpdateFileName('$iId', this.value)\"/> {$sValidationField}\n";
|
||||
$sHTMLValue .= "<span id=\"name_$iInputId\">".htmlentities($sFileName, ENT_QUOTES, 'UTF-8')."</span><br/>\n";
|
||||
$sHTMLValue .= "<input title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[fcontents]\" type=\"file\" id=\"file_$iId\" onChange=\"UpdateFileName('$iId', this.value)\"/> {$sValidationSpan}{$sReloadSpan}\n";
|
||||
break;
|
||||
|
||||
|
||||
case 'Image':
|
||||
$aEventsList[] ='validate';
|
||||
$aEventsList[] ='change';
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/edit_image.js');
|
||||
$oDocument = $value; // Value is an ormDocument object
|
||||
$sDefaultUrl = $oAttDef->Get('default_image');
|
||||
if (is_object($oDocument) && !$oDocument->IsEmpty())
|
||||
{
|
||||
$sUrl = 'data:'.$oDocument->GetMimeType().';base64,'.base64_encode($oDocument->GetData());
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = $sDefaultUrl;
|
||||
}
|
||||
|
||||
$sHTMLValue = "<div id=\"edit_$iInputId\" class=\"edit-image\"></div>";
|
||||
$sHTMLValue .= " {$sValidationSpan}{$sReloadSpan}\n";
|
||||
|
||||
$aEditImage = array(
|
||||
'input_name' => 'attr_'.$sFieldPrefix.$sAttCode.$sNameSuffix,
|
||||
'max_file_size' => utils::ConvertToBytes(ini_get('upload_max_filesize')),
|
||||
'max_width_px' => $oAttDef->Get('display_max_width'),
|
||||
'max_height_px' => $oAttDef->Get('display_max_height'),
|
||||
'current_image_url' => $sUrl,
|
||||
'default_image_url' => $sDefaultUrl,
|
||||
'labels' => array(
|
||||
'reset_button' => htmlentities(Dict::S('UI:Button:ResetImage'), ENT_QUOTES, 'UTF-8'),
|
||||
'remove_button' => htmlentities(Dict::S('UI:Button:RemoveImage'), ENT_QUOTES, 'UTF-8'),
|
||||
'upload_button' => $sHelpText
|
||||
)
|
||||
);
|
||||
$sEditImageOptions = json_encode($aEditImage);
|
||||
$oPage->add_ready_script("$('#edit_$iInputId').edit_image($sEditImageOptions);");
|
||||
break;
|
||||
|
||||
case 'StopWatch':
|
||||
$sHTMLValue = "The edition of a stopwatch is not allowed!!!";
|
||||
break;
|
||||
@@ -1902,12 +1947,61 @@ EOF
|
||||
$sHTMLValue .= $oAttDef->GetDisplayForm($value, $oPage, true);
|
||||
$sHTMLValue .= '</div>';
|
||||
$sHTMLValue .= '</td>';
|
||||
$sHTMLValue .= '<td>'.$sValidationField.'</td>';
|
||||
$sHTMLValue .= '<td>'.$sValidationSpan.$sReloadSpan.'</td>';
|
||||
$sHTMLValue .= '</tr>';
|
||||
$sHTMLValue .= '</table>';
|
||||
$oPage->add_ready_script("$('#$iId :input').bind('keyup change validate', function(evt, sFormId) { return ValidateRedundancySettings('$iId',sFormId); } );"); // Custom validation function
|
||||
break;
|
||||
|
||||
case 'CustomFields':
|
||||
$sHTMLValue = '<table>';
|
||||
$sHTMLValue .= '<tr>';
|
||||
$sHTMLValue .= '<td>';
|
||||
$sHTMLValue .= '<div id="'.$iId.'_console_form">';
|
||||
$sHTMLValue .= '<div id="'.$iId.'_field_set">';
|
||||
$sHTMLValue .= '</div>';
|
||||
$sHTMLValue .= '</div>';
|
||||
$sHTMLValue .= '</td>';
|
||||
$sHTMLValue .= '<td>'.$sReloadSpan.'</td>'; // No validation span for this one: it does handle its own validation!
|
||||
$sHTMLValue .= '</tr>';
|
||||
$sHTMLValue .= '</table>';
|
||||
$sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" type=\"hidden\" id=\"$iId\" value=\"\"/>\n";
|
||||
|
||||
$oForm = $value->GetForm($sFormPrefix);
|
||||
$oRenderer = new \Combodo\iTop\Renderer\Console\ConsoleFormRenderer($oForm);
|
||||
$aRenderRes = $oRenderer->Render();
|
||||
|
||||
$aFormHandlerOptions = array(
|
||||
'wizard_helper_var_name' => 'oWizardHelper'.$sFormPrefix,
|
||||
'custom_field_attcode' => $sAttCode
|
||||
);
|
||||
$sFormHandlerOptions = json_encode($aFormHandlerOptions);
|
||||
$aFieldSetOptions = array(
|
||||
'field_identifier_attr' => 'data-field-id', // convention: fields are rendered into a div and are identified by this attribute
|
||||
'fields_list' => $aRenderRes,
|
||||
'fields_impacts' => $oForm->GetFieldsImpacts(),
|
||||
'form_path' => $oForm->GetId()
|
||||
);
|
||||
$sFieldSetOptions = json_encode($aFieldSetOptions);
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_handler.js');
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/console_form_handler.js');
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_set.js');
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_field.js');
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/subform_field.js');
|
||||
$oPage->add_ready_script("$('#{$iId}_console_form').console_form_handler($sFormHandlerOptions);");
|
||||
$oPage->add_ready_script("$('#{$iId}_field_set').field_set($sFieldSetOptions);");
|
||||
$oPage->add_ready_script("$('#{$iId}_console_form').console_form_handler('alignColumns');");
|
||||
$oPage->add_ready_script("$('#{$iId}_console_form').console_form_handler('option', 'field_set', $('#{$iId}_field_set'));");
|
||||
// field_change must be processed to refresh the hidden value at anytime
|
||||
$oPage->add_ready_script("$('#{$iId}_console_form').bind('value_change', function() { $('#{$iId}').val(JSON.stringify($('#{$iId}_field_set').triggerHandler('get_current_values'))); });");
|
||||
// Initialize the hidden value with current state
|
||||
$oPage->add_ready_script("$('#{$iId}_console_form').trigger('value_change');");
|
||||
// update_value is triggered when preparing the wizard helper object for ajax calls
|
||||
$oPage->add_ready_script("$('#{$iId}').bind('update_value', function() { $(this).val(JSON.stringify($('#{$iId}_field_set').triggerHandler('get_current_values'))); });");
|
||||
// validate is triggered by CheckFields, on all the input fields, once at page init and once before submitting the form
|
||||
$oPage->add_ready_script("$('#{$iId}').bind('validate', function(evt, sFormId) { return ValidateCustomFields('$iId', sFormId) } );"); // Custom validation function
|
||||
break;
|
||||
|
||||
case 'String':
|
||||
default:
|
||||
$aEventsList[] ='validate';
|
||||
@@ -1925,7 +2019,7 @@ EOF
|
||||
case 'radio_vertical':
|
||||
$sHTMLValue = '';
|
||||
$bVertical = ($sDisplayStyle != 'radio_horizontal');
|
||||
$sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $iId, "attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}", $bMandatory, $bVertical, $sValidationField);
|
||||
$sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $iId, "attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}", $bMandatory, $bVertical, $sValidationSpan.$sReloadSpan);
|
||||
$aEventsList[] ='change';
|
||||
break;
|
||||
|
||||
@@ -1946,13 +2040,13 @@ EOF
|
||||
}
|
||||
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
|
||||
}
|
||||
$sHTMLValue .= "</select> {$sValidationField}\n";
|
||||
$sHTMLValue .= "</select> {$sValidationSpan}{$sReloadSpan}\n";
|
||||
$aEventsList[] ='change';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" type=\"text\" size=\"30\" maxlength=\"$iFieldSize\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationField}";
|
||||
$sHTMLValue = "<input title=\"$sHelpText\" type=\"text\" size=\"30\" maxlength=\"$iFieldSize\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/> {$sValidationSpan}{$sReloadSpan}";
|
||||
$aEventsList[] ='keyup';
|
||||
$aEventsList[] ='change';
|
||||
}
|
||||
@@ -2216,6 +2310,8 @@ EOF
|
||||
$sJsonFieldsMap = json_encode($aFieldsMap);
|
||||
$sState = $this->GetState();
|
||||
$sSessionStorageKey = $sClass.'_'.$iKey;
|
||||
$sTempId = session_id().'_'.$iTransactionId;
|
||||
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
|
||||
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
@@ -2287,7 +2383,7 @@ EOF
|
||||
$aDeps = array();
|
||||
foreach($aDetailsList as $sAttCode)
|
||||
{
|
||||
$aDeps[$sAttCode] = MetaModel::GetPrequisiteAttributes($sClass, $sAttCode);
|
||||
$aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
|
||||
}
|
||||
$aList = self::OrderDependentFields($aDeps);
|
||||
|
||||
@@ -2407,7 +2503,7 @@ EOF
|
||||
$aDeps = array();
|
||||
foreach($aAttributes as $sAttCode => $trash)
|
||||
{
|
||||
$aDeps[$sAttCode] = MetaModel::GetPrequisiteAttributes($sClass, $sAttCode);
|
||||
$aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
|
||||
}
|
||||
$aList = $this->OrderDependentFields($aDeps);
|
||||
|
||||
@@ -2952,6 +3048,23 @@ EOF
|
||||
$this->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'Image')
|
||||
{
|
||||
// There should be an uploaded file with the named attr_<attCode>
|
||||
if ($value['remove'])
|
||||
{
|
||||
$this->Set($sAttCode, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDocument = $value['fcontents'];
|
||||
if (!$oDocument->IsEmpty())
|
||||
{
|
||||
// A new file has been uploaded
|
||||
$this->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'One Way Password')
|
||||
{
|
||||
// Check if the password was typed/changed
|
||||
@@ -2975,6 +3088,10 @@ EOF
|
||||
$this->Set($sAttCode, $iValue);
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'CustomFields')
|
||||
{
|
||||
$this->Set($sAttCode, $value);
|
||||
}
|
||||
else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() &&
|
||||
(($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE) || ($oAttDef->GetEditMode() == LINKSET_EDITMODE_ADDREMOVE)))
|
||||
{
|
||||
@@ -3083,10 +3200,29 @@ EOF
|
||||
{
|
||||
$value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'));
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'Image')
|
||||
{
|
||||
$oImage = utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents');
|
||||
$aSize = utils::GetImageSize($oImage->GetData());
|
||||
$oImage = utils::ResizeImageToFit($oImage, $aSize[0], $aSize[1], $oAttDef->Get('storage_max_width'), $oAttDef->Get('storage_max_height'));
|
||||
$aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
|
||||
if (is_array($aOtherData))
|
||||
{
|
||||
$value = array('fcontents' => $oImage, 'remove' => $aOtherData['remove']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'RedundancySetting')
|
||||
{
|
||||
$value = $oAttDef->ReadValueFromPostedForm($sFormPrefix);
|
||||
}
|
||||
elseif ($oAttDef->GetEditClass() == 'CustomFields')
|
||||
{
|
||||
$value = $oAttDef->ReadValueFromPostedForm($this, $sFormPrefix);
|
||||
}
|
||||
else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() &&
|
||||
(($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE) || ($oAttDef->GetEditMode() == LINKSET_EDITMODE_ADDREMOVE)) )
|
||||
{
|
||||
@@ -3112,6 +3248,22 @@ EOF
|
||||
'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', 'raw_data'), true),
|
||||
'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', 'raw_data'), true) );
|
||||
}
|
||||
else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
|
||||
if ($value != null)
|
||||
{
|
||||
$oDate = $oAttDef->GetFormat()->Parse($value);
|
||||
if ($oDate instanceof DateTime)
|
||||
{
|
||||
$value = $oDate->format($oAttDef->GetInternalFormat());
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
|
||||
@@ -3129,6 +3281,10 @@ EOF
|
||||
$aFinalValues[$sAttCode] = $aValues[$sAttCode];
|
||||
}
|
||||
$this->UpdateObjectFromArray($aFinalValues);
|
||||
if (!$this->IsNew()) // for new objects this is performed in DBInsertNoReload()
|
||||
{
|
||||
InlineImage::FinalizeInlineImages($this);
|
||||
}
|
||||
|
||||
// Invoke extensions after the update of the object from the form
|
||||
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
|
||||
@@ -3172,6 +3328,8 @@ EOF
|
||||
{
|
||||
$res = parent::DBInsertNoReload();
|
||||
|
||||
InlineImage::FinalizeInlineImages($this);
|
||||
|
||||
// Invoke extensions after insertion (the object must exist, have an id, etc.)
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
@@ -3240,7 +3398,16 @@ EOF
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bypass the check of the user rights when writing this object
|
||||
* @param bool $bAllow True to bypass the checks, false to restore the default behavior
|
||||
*/
|
||||
public function AllowWrite($bAllow = true)
|
||||
{
|
||||
$this->bAllowWrite = $bAllow;
|
||||
}
|
||||
|
||||
public function DoCheckToWrite()
|
||||
{
|
||||
parent::DoCheckToWrite();
|
||||
@@ -3258,24 +3425,27 @@ EOF
|
||||
|
||||
// User rights
|
||||
//
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) > 0)
|
||||
if (!$this->bAllowWrite)
|
||||
{
|
||||
$aForbiddenFields = array();
|
||||
foreach ($this->ListChanges() as $sAttCode => $value)
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) > 0)
|
||||
{
|
||||
$bUpdateAllowed = UserRights::IsActionAllowedOnAttribute(get_class($this), $sAttCode, UR_ACTION_MODIFY, DBObjectSet::FromObject($this));
|
||||
if (!$bUpdateAllowed)
|
||||
$aForbiddenFields = array();
|
||||
foreach ($this->ListChanges() as $sAttCode => $value)
|
||||
{
|
||||
$oAttCode = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$aForbiddenFields[] = $oAttCode->GetLabel();
|
||||
$bUpdateAllowed = UserRights::IsActionAllowedOnAttribute(get_class($this), $sAttCode, UR_ACTION_MODIFY, DBObjectSet::FromObject($this));
|
||||
if (!$bUpdateAllowed)
|
||||
{
|
||||
$oAttCode = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$aForbiddenFields[] = $oAttCode->GetLabel();
|
||||
}
|
||||
}
|
||||
if (count($aForbiddenFields) > 0)
|
||||
{
|
||||
// Security issue
|
||||
$this->m_bSecurityIssue = true;
|
||||
$this->m_aCheckIssues[] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields',implode(', ', $aForbiddenFields));
|
||||
}
|
||||
}
|
||||
if (count($aForbiddenFields) > 0)
|
||||
{
|
||||
// Security issue
|
||||
$this->m_bSecurityIssue = true;
|
||||
$this->m_aCheckIssues[] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields',implode(', ', $aForbiddenFields));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3364,9 +3534,23 @@ EOF
|
||||
{
|
||||
$sHTMLValue = '<span>'.$sComment.'</span><br/>';
|
||||
}
|
||||
$sHTMLValue .= "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
|
||||
$sHTMLValue .= "<span style=\"font-family:Tahoma,Verdana,Arial,Helvetica;font-size:12px;\" id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
|
||||
$aFieldsMap[$sAttCode] = $sInputId;
|
||||
|
||||
// Replace the text area with CKEditor
|
||||
// To change the default settings of the editor,
|
||||
// a) edit the file /js/ckeditor/config.js
|
||||
// b) or override some of the configuration settings, using the second parameter of ckeditor()
|
||||
$aConfig = array();
|
||||
$sLanguage = strtolower(trim(UserRights::GetUserLanguage()));
|
||||
$aConfig['language'] = $sLanguage;
|
||||
$aConfig['contentsLanguage'] = $sLanguage;
|
||||
$aConfig['extraPlugins'] = 'disabler';
|
||||
$aConfig['placeholder'] = Dict::S('UI:CaseLogTypeYourTextHere');
|
||||
$sConfigJS = json_encode($aConfig);
|
||||
|
||||
$oPage->add_ready_script("$('#$sInputId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
|
||||
|
||||
}
|
||||
//$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
|
||||
$oPage->add('<fieldset><legend>'.$oAttDef->GetLabel().'</legend>');
|
||||
@@ -3489,7 +3673,7 @@ EOF
|
||||
$sFormPrefix = '2_';
|
||||
foreach($aList as $sAttCode => $oAttDef)
|
||||
{
|
||||
$aPrerequisites = MetaModel::GetPrequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one
|
||||
$aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one
|
||||
if (count($aPrerequisites) > 0)
|
||||
{
|
||||
// When 'enabling' a field, all its prerequisites must be enabled too
|
||||
|
||||
@@ -596,7 +596,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
switch($sStyle)
|
||||
{
|
||||
case 'bars':
|
||||
$sType = 'open_flash_chart';
|
||||
$sType = 'chart';
|
||||
$aExtraParams = array(
|
||||
'chart_type' => 'bars',
|
||||
'chart_title' => $sTitle,
|
||||
@@ -607,7 +607,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
break;
|
||||
|
||||
case 'pie':
|
||||
$sType = 'open_flash_chart';
|
||||
$sType = 'chart';
|
||||
$aExtraParams = array(
|
||||
'chart_type' => 'pie',
|
||||
'chart_title' => $sTitle,
|
||||
@@ -733,7 +733,8 @@ abstract class DashletGroupBy extends Dashlet
|
||||
if (is_subclass_of($sAttType, 'AttributeFriendlyName')) continue;
|
||||
if ($sAttType == 'AttributeExternalField') continue;
|
||||
if (is_subclass_of($sAttType, 'AttributeExternalField')) continue;
|
||||
|
||||
if ($sAttType == 'AttributeOneWayPassword') continue;
|
||||
|
||||
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
|
||||
$aGroupBy[$sAttCode] = $sLabel;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* DisplayBlock and derived class
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -319,13 +319,16 @@ class DisplayBlock
|
||||
{
|
||||
$externalFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data');
|
||||
$condition = null;
|
||||
$bParseSearchString = true;
|
||||
if (isset($aExtraParams[$sFilterCode]))
|
||||
{
|
||||
$bParseSearchString = false;
|
||||
$condition = $aExtraParams[$sFilterCode];
|
||||
}
|
||||
if ($bDoSearch && $externalFilterValue != "")
|
||||
{
|
||||
// Search takes precedence over context params...
|
||||
$bParseSearchString = true;
|
||||
unset($aExtraParams[$sFilterCode]);
|
||||
if (!is_array($externalFilterValue))
|
||||
{
|
||||
@@ -350,7 +353,7 @@ class DisplayBlock
|
||||
$sOpCode = 'IN';
|
||||
}
|
||||
|
||||
$this->AddCondition($sFilterCode, $condition, $sOpCode);
|
||||
$this->AddCondition($sFilterCode, $condition, $sOpCode, $bParseSearchString);
|
||||
}
|
||||
}
|
||||
if ($bDoSearch)
|
||||
@@ -394,7 +397,7 @@ class DisplayBlock
|
||||
{
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
{
|
||||
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
|
||||
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
|
||||
$sGroupByLabel = $aExtraParams['group_by_label'];
|
||||
}
|
||||
else
|
||||
@@ -405,6 +408,21 @@ class DisplayBlock
|
||||
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
|
||||
}
|
||||
|
||||
// Security filtering
|
||||
$aFields = $oGroupByExp->ListRequiredFields();
|
||||
foreach($aFields as $sFieldAlias)
|
||||
{
|
||||
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
|
||||
{
|
||||
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
|
||||
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
|
||||
if ($oAttDef instanceof AttributeOneWayPassword)
|
||||
{
|
||||
throw new Exception('Grouping on password fields is not supported.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aGroupBy = array();
|
||||
$aGroupBy['grouped_by_1'] = $oGroupByExp;
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true);
|
||||
@@ -777,9 +795,17 @@ class DisplayBlock
|
||||
$sCsvFile = strtolower($this->m_oFilter->GetClass()).'.csv';
|
||||
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($sCsvFile);
|
||||
$sLinkToToggle = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($this->m_oFilter->serialize()).'&format=csv';
|
||||
// Pass the parameters via POST, since expression may be very long
|
||||
$aParamsToPost = array(
|
||||
'expression' => $this->m_oFilter->ToOQL(true),
|
||||
'format' => 'csv',
|
||||
'filename' => $sCsvFile,
|
||||
'charset' => 'UTF-8',
|
||||
);
|
||||
if ($bAdvancedMode)
|
||||
{
|
||||
$sDownloadLink .= '&fields_advanced=1';
|
||||
$aParamsToPost['fields_advance'] = 1;
|
||||
$sChecked = 'CHECKED';
|
||||
}
|
||||
else
|
||||
@@ -787,7 +813,7 @@ class DisplayBlock
|
||||
$sLinkToToggle = $sLinkToToggle.'&advanced=1';
|
||||
$sChecked = '';
|
||||
}
|
||||
$sAjaxLink = $sDownloadLink.'&charset=UTF-8'; // Includes &fields_advanced=1 if in advanced mode
|
||||
$sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php';
|
||||
|
||||
/*
|
||||
$sCSVData = cmdbAbstractObject::GetSetAsCSV($this->m_oSet, array('fields_advanced' => $bAdvancedMode));
|
||||
@@ -838,7 +864,8 @@ class DisplayBlock
|
||||
$sHtml .= "<div id=\"csv_content_loading\"><div style=\"width: 250px; height: 20px; background: url(../setup/orange-progress.gif); border: 1px #999 solid; margin-left:auto; margin-right: auto; text-align: center;\">".Dict::S('UI:Loading')."</div></div><textarea id=\"csv_content\" style=\"display:none;\">\n";
|
||||
//$sHtml .= htmlentities($sCSVData, ENT_QUOTES, 'UTF-8');
|
||||
$sHtml .= "</textarea>\n";
|
||||
$oPage->add_ready_script("$.post('$sAjaxLink', {}, function(data) { $('#csv_content').html(data); $('#csv_content_loading').hide(); $('#csv_content').show();} );");
|
||||
$sJsonParams = json_encode($aParamsToPost);
|
||||
$oPage->add_ready_script("$.post('$sAjaxLink', $sJsonParams, function(data) { $('#csv_content').html(data); $('#csv_content_loading').hide(); $('#csv_content').show();} );");
|
||||
break;
|
||||
|
||||
case 'modify':
|
||||
@@ -872,33 +899,44 @@ EOF
|
||||
}
|
||||
break;
|
||||
|
||||
case 'open_flash_chart':
|
||||
case 'chart':
|
||||
static $iChartCounter = 0;
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
if (!empty($sContext))
|
||||
{
|
||||
$sContext = '&'.$sContext;
|
||||
}
|
||||
$iChartCounter++;
|
||||
|
||||
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
|
||||
$sTitle = isset($aExtraParams['chart_title']) ? $aExtraParams['chart_title'] : '';
|
||||
$sTitle = isset($aExtraParams['chart_title']) ? '<h1 style="text-align:center">'.htmlentities(Dict::S($aExtraParams['chart_title']), ENT_QUOTES, 'UTF-8').'</h1>' : '';
|
||||
$sHtml = "$sTitle<div style=\"height:200px;width:100%\" id=\"my_chart_$sId{$iChartCounter}\"><div style=\"height:200px;line-height:200px;vertical-align:center;text-align:center;width:100%\"><img src=\"../images/indicator.gif\"></div></div>\n";
|
||||
$sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : '';
|
||||
$sGroupByExpr = isset($aExtraParams['group_by_expr']) ? '¶ms[group_by_expr]='.$aExtraParams['group_by_expr'] : '';
|
||||
$sFilter = $this->m_oFilter->serialize();
|
||||
$sHtml .= "<div id=\"my_chart_$sId{$iChartCounter}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\n";
|
||||
$oPage->add_script("function ofc_resize(left, width, top, height) { /* do nothing special */ }");
|
||||
$oContext = new ApplicationContext();
|
||||
$sContextParam = $oContext->GetForLink();
|
||||
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
{
|
||||
$sUrl = urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[group_by_label]={$aExtraParams['group_by_label']}¶ms[chart_type]=$sChartType¶ms[chart_title]=$sTitle¶ms[currentId]=$sId&id=$sId&filter=".urlencode($sFilter));
|
||||
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[group_by_label]={$aExtraParams['group_by_label']}¶ms[chart_type]=$sChartType¶ms[currentId]=$sId{$iChartCounter}&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[chart_type]=$sChartType¶ms[chart_title]=$sTitle¶ms[currentId]=$sId&id=$sId&filter=".urlencode($sFilter));
|
||||
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[chart_type]=$sChartType¶ms[currentId]=$sId{$iChartCounter}&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
|
||||
}
|
||||
|
||||
$sType = ($sChartType == 'pie') ? 'pie' : 'bar';
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$.post($sUrl, {}, function(data) {
|
||||
$('body').append(data);
|
||||
});
|
||||
EOF
|
||||
);
|
||||
break;
|
||||
|
||||
case 'chart_ajax':
|
||||
$sHtml = '';
|
||||
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
|
||||
$sId = utils::ReadParam('id', '');
|
||||
|
||||
$oPage->add_ready_script("swfobject.embedSWF(\"../images/open-flash-chart.swf\", \"my_chart_$sId{$iChartCounter}\", \"100%\", \"300\",\"9.0.0\", \"expressInstall.swf\",
|
||||
{\"data-file\":\"".$sUrl."\"}, {wmode: 'transparent'} );\n");
|
||||
$iChartCounter++;
|
||||
if (isset($aExtraParams['group_by']))
|
||||
{
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
@@ -918,211 +956,134 @@ EOF
|
||||
$aGroupBy['grouped_by_1'] = $oGroupByExp;
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true);
|
||||
$aRes = CMDBSource::QueryToArray($sSql);
|
||||
$oContext = new ApplicationContext();
|
||||
$sContextParam = $oContext->GetForLink();
|
||||
|
||||
$aGroupBy = array();
|
||||
$aLabels = array();
|
||||
$aValues = array();
|
||||
$iTotalCount = 0;
|
||||
$aValues = array();
|
||||
$aURLs = array();
|
||||
foreach ($aRes as $iRow => $aRow)
|
||||
{
|
||||
$sValue = $aRow['grouped_by_1'];
|
||||
$aValues[$iRow] = $sValue;
|
||||
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
|
||||
$aLabels[$iRow] = $sHtmlValue;
|
||||
$aGroupBy[$iRow] = (int) $aRow['_itop_count_'];
|
||||
$aLabels[$iRow] = strip_tags($sHtmlValue);
|
||||
$aGroupBy[(int)$iRow] = (int) $aRow['_itop_count_'];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
}
|
||||
|
||||
$aData = array();
|
||||
$idx = 0;
|
||||
$aURLs = array();
|
||||
foreach($aGroupBy as $iRow => $iCount)
|
||||
{
|
||||
$aValues[] = array('label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label_html' => $sHtmlValue, 'value' => (int) $aRow['_itop_count_']);
|
||||
|
||||
// Build the search for this subset
|
||||
$oSubsetSearch = $this->m_oFilter->DeepClone();
|
||||
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($aValues[$iRow]));
|
||||
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
|
||||
$oSubsetSearch->AddConditionExpression($oCondition);
|
||||
$aURLs[$idx] = $oSubsetSearch->serialize();
|
||||
$idx++;
|
||||
$aURLs[] = utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html&filter=".urlencode($oSubsetSearch->serialize()).'&'.$sContextParam;
|
||||
}
|
||||
$sURLList = '';
|
||||
foreach($aURLs as $index => $sURL)
|
||||
{
|
||||
$sURLList .= "\taURLs[$index] = '".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html{$sContext}&filter=".urlencode($sURL)."';\n";
|
||||
}
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
function ofc_drill_down_{$sId}(index)
|
||||
{
|
||||
var aURLs = new Array();
|
||||
{$sURLList}
|
||||
window.location.href=aURLs[index];
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$sJSURLs = json_encode($aURLs);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'open_flash_chart_ajax':
|
||||
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
|
||||
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
|
||||
$sId = utils::ReadParam('id', '');
|
||||
|
||||
$oChart = new open_flash_chart();
|
||||
switch($sChartType)
|
||||
{
|
||||
case 'bars':
|
||||
$oChartElement = new bar_glass();
|
||||
|
||||
if (isset($aExtraParams['group_by']))
|
||||
$aNames = array();
|
||||
foreach($aValues as $idx => $aValue)
|
||||
{
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
{
|
||||
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
|
||||
$sGroupByLabel = $aExtraParams['group_by_label'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Backward compatibility: group_by is simply a field id
|
||||
$sAlias = $this->m_oFilter->GetClassAlias();
|
||||
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
|
||||
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
|
||||
}
|
||||
|
||||
$aGroupBy = array();
|
||||
$aGroupBy['grouped_by_1'] = $oGroupByExp;
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true);
|
||||
$aRes = CMDBSource::QueryToArray($sSql);
|
||||
|
||||
$aGroupBy = array();
|
||||
$aLabels = array();
|
||||
$iTotalCount = 0;
|
||||
foreach ($aRes as $iRow => $aRow)
|
||||
{
|
||||
$sValue = $aRow['grouped_by_1'];
|
||||
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
|
||||
$aLabels[$iRow] = strip_tags($sHtmlValue);
|
||||
$aGroupBy[$iRow] = (int) $aRow['_itop_count_'];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
}
|
||||
|
||||
$aData = array();
|
||||
$aChartLabels = array();
|
||||
$maxValue = 0;
|
||||
foreach($aGroupBy as $iRow => $iCount)
|
||||
{
|
||||
$oBarValue = new bar_value($iCount);
|
||||
$oBarValue->on_click("ofc_drill_down_$sId");
|
||||
$aData[] = $oBarValue;
|
||||
if ($iCount > $maxValue) $maxValue = $iCount;
|
||||
$aChartLabels[] = html_entity_decode($aLabels[$iRow], ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
$oYAxis = new y_axis();
|
||||
$aMagicValues = array(1,2,5,10);
|
||||
$iMultiplier = 1;
|
||||
$index = 0;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
while($maxValue > $iTop)
|
||||
{
|
||||
$index++;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
if (($index % count($aMagicValues)) == 0)
|
||||
{
|
||||
$iMultiplier = $iMultiplier * 10;
|
||||
}
|
||||
}
|
||||
//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
|
||||
$oYAxis->set_range(0, $iTop, $iMultiplier);
|
||||
$oChart->set_y_axis( $oYAxis );
|
||||
|
||||
$oChartElement->set_values( $aData );
|
||||
$oXAxis = new x_axis();
|
||||
$oXLabels = new x_axis_labels();
|
||||
// set them vertical
|
||||
$oXLabels->set_vertical();
|
||||
// set the label text
|
||||
$oXLabels->set_labels($aChartLabels);
|
||||
// Add the X Axis Labels to the X Axis
|
||||
$oXAxis->set_labels( $oXLabels );
|
||||
$oChart->set_x_axis( $oXAxis );
|
||||
$aNames[$idx] = $aValue['label_html'];
|
||||
}
|
||||
break;
|
||||
$sJSNames = json_encode($aNames);
|
||||
|
||||
$sJson = json_encode($aValues);
|
||||
$sJSCount = json_encode(Dict::S('UI:GroupBy:Count'));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
var chart = c3.generate({
|
||||
bindto: d3.select('#my_chart_$sId'),
|
||||
data: {
|
||||
json: $sJson,
|
||||
keys: {
|
||||
x: 'label',
|
||||
value: ["value"]
|
||||
},
|
||||
onclick: function (d, element) {
|
||||
var aURLs = $sJSURLs;
|
||||
window.location.href = aURLs[d.index];
|
||||
},
|
||||
selection: {
|
||||
enabled: true
|
||||
},
|
||||
type: 'bar'
|
||||
},
|
||||
axis: {
|
||||
x: {
|
||||
tick: {
|
||||
culling: {max: 25}, // Maximum 24 labels on x axis (2 years).
|
||||
centered: true,
|
||||
rotate: 90,
|
||||
multiline: false
|
||||
},
|
||||
type: 'category' // this needed to load string x value
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
y: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
grouped: false,
|
||||
format: {
|
||||
title: function() { return '' },
|
||||
name: function (name, ratio, id, index) {
|
||||
var aNames = $sJSNames;
|
||||
return aNames[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
break;
|
||||
|
||||
case 'pie':
|
||||
default:
|
||||
$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') );
|
||||
if (isset($aExtraParams['group_by']))
|
||||
$aColumns = array();
|
||||
$aNames = array();
|
||||
foreach($aValues as $idx => $aValue)
|
||||
{
|
||||
if (isset($aExtraParams['group_by_label']))
|
||||
{
|
||||
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
|
||||
$sGroupByLabel = $aExtraParams['group_by_label'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Backward compatibility: group_by is simply a field id
|
||||
$sAlias = $this->m_oFilter->GetClassAlias();
|
||||
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
|
||||
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
|
||||
}
|
||||
|
||||
$aGroupBy = array();
|
||||
$aGroupBy['grouped_by_1'] = $oGroupByExp;
|
||||
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true);
|
||||
$aRes = CMDBSource::QueryToArray($sSql);
|
||||
|
||||
$aGroupBy = array();
|
||||
$aLabels = array();
|
||||
$iTotalCount = 0;
|
||||
foreach ($aRes as $iRow => $aRow)
|
||||
{
|
||||
$sValue = $aRow['grouped_by_1'];
|
||||
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
|
||||
$aLabels[$iRow] = strip_tags($sHtmlValue);
|
||||
$aGroupBy[$iRow] = (int) $aRow['_itop_count_'];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
}
|
||||
|
||||
$aData = array();
|
||||
foreach($aGroupBy as $iRow => $iCount)
|
||||
{
|
||||
$sFlashLabel = html_entity_decode($aLabels[$iRow], ENT_QUOTES, 'UTF-8');
|
||||
$PieValue = new pie_value($iCount, $sFlashLabel); //@@ BUG: not passed via ajax !!!
|
||||
$PieValue->on_click("ofc_drill_down_$sId");
|
||||
$aData[] = $PieValue;
|
||||
}
|
||||
|
||||
$oChartElement->set_values( $aData );
|
||||
$oChart->x_axis = null;
|
||||
$aColumns[] = array('series_'.$idx, (int)$aValue['value']);
|
||||
$aNames['series_'.$idx] = $aValue['label'];
|
||||
}
|
||||
}
|
||||
if (isset($aExtraParams['chart_title']))
|
||||
{
|
||||
// The title has been given in an url, and urlencoded...
|
||||
// and urlencode transforms utf-8 into something similar to ISO-8859-1
|
||||
// Example: é (C3A9 becomes %E9)
|
||||
// As a consequence, json_encode (called within open-flash-chart.php)
|
||||
// was returning 'null' and the graph was not displayed at all
|
||||
// To make sure that the graph is displayed AND to get a correct title
|
||||
// (at least for european characters) let's transform back into utf-8 !
|
||||
$sTitle = iconv("ISO-8859-1", "UTF-8//IGNORE", $aExtraParams['chart_title']);
|
||||
|
||||
// If the title is a dictionnary entry, fetch it
|
||||
$sTitle = Dict::S($sTitle);
|
||||
|
||||
$oTitle = new title($sTitle);
|
||||
$oChart->set_title( $oTitle );
|
||||
$oTitle->set_style("{font-size: 16px; font-family: Tahoma; font-weight: bold; text-align: center;}");
|
||||
$sJSColumns = json_encode($aColumns);
|
||||
$sJSNames = json_encode($aNames);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
var chart = c3.generate({
|
||||
bindto: d3.select('#my_chart_$sId'),
|
||||
data: {
|
||||
columns: $sJSColumns,
|
||||
type: 'pie',
|
||||
names: $sJSNames,
|
||||
onclick: function (d, element) {
|
||||
var aURLs = $sJSURLs;
|
||||
window.location.href= aURLs[d.index];
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
},
|
||||
tooltip: {
|
||||
format: {
|
||||
value: function (value, ratio, id) { return value; }
|
||||
}
|
||||
}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
break;
|
||||
}
|
||||
$oChart->set_bg_colour('#FFFFFF');
|
||||
$oChart->add_element( $oChartElement );
|
||||
|
||||
$sHtml = $oChart->toPrettyString();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1136,7 +1097,7 @@ EOF
|
||||
* Add a condition (restriction) to the current DBSearch on which the display block is based
|
||||
* taking into account the hierarchical keys for which the condition is based on the 'below' operator
|
||||
*/
|
||||
protected function AddCondition($sFilterCode, $condition, $sOpCode = null)
|
||||
protected function AddCondition($sFilterCode, $condition, $sOpCode = null, $bParseSearchString = false)
|
||||
{
|
||||
// Workaround to an issue revealed whenever a condition on org_id is applied twice (with a hierarchy of organizations)
|
||||
// Moreover, it keeps the query as simple as possible
|
||||
@@ -1191,7 +1152,7 @@ EOF
|
||||
// In all other cases, just add the condition directly
|
||||
if (!$bConditionAdded)
|
||||
{
|
||||
$this->m_oFilter->AddCondition($sFilterCode, $condition); // Use the default 'loose' operator
|
||||
$this->m_oFilter->AddCondition($sFilterCode, $condition, null, $bParseSearchString); // Use the default 'loose' operator
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1203,6 +1164,15 @@ EOF
|
||||
$oNewCondition = Expression::FromOQL($sOQLCondition);
|
||||
return $oNewCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the result to be meaningful, this function must be called AFTER GetRenderContent() (or Display())
|
||||
* @return int
|
||||
*/
|
||||
public function GetDisplayedCount()
|
||||
{
|
||||
return $this->m_oSet->Count();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1289,8 +1259,25 @@ class HistoryBlock extends DisplayBlock
|
||||
else
|
||||
{
|
||||
$sHtml .= $this->GetHistoryTable($oPage, $oSet);
|
||||
}
|
||||
}
|
||||
$oPage->add_ready_script(InlineImage::FixImagesWidth());
|
||||
|
||||
$oPage->add_ready_script("$('.case-log-history-entry-toggle').on('click', function () { $(this).closest('.case-log-history-entry').toggleClass('expanded');});");
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('.history_entry').each(function() {
|
||||
var jMe = $(this);
|
||||
var oContent = $(this).find('.history_html_content');
|
||||
if (jMe.height() < oContent.height())
|
||||
{
|
||||
jMe.prepend('<span class="history_truncated_toggler"></span>');
|
||||
jMe.find('.history_truncated_toggler').on('click', function() {
|
||||
jMe.toggleClass('history_entry_truncated');
|
||||
});
|
||||
}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
@@ -1319,12 +1306,12 @@ class HistoryBlock extends DisplayBlock
|
||||
}
|
||||
$aAttribs = array('date' => array('label' => Dict::S('UI:History:Date'), 'description' => Dict::S('UI:History:Date+')),
|
||||
'userinfo' => array('label' => Dict::S('UI:History:User'), 'description' => Dict::S('UI:History:User+')),
|
||||
'log' => array('label' => Dict::S('UI:History:Changes'), 'description' => Dict::S('UI:History:Changes+')),
|
||||
'log' => array('label' => Dict::S('UI:History:Changes') , 'description' => Dict::S('UI:History:Changes+')),
|
||||
);
|
||||
$aValues = array();
|
||||
foreach($aChanges as $aChange)
|
||||
{
|
||||
$aValues[] = array('date' => $aChange['date'], 'userinfo' => htmlentities($aChange['userinfo'], ENT_QUOTES, 'UTF-8'), 'log' => "<ul><li>".implode('</li><li>', $aChange['log'])."</li></ul>");
|
||||
$aValues[] = array('date' => AttributeDateTime::GetFormat()->Format($aChange['date']), 'userinfo' => htmlentities($aChange['userinfo'], ENT_QUOTES, 'UTF-8'), 'log' => "<ul><li>".implode('</li><li>', $aChange['log'])."</li></ul>");
|
||||
}
|
||||
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
|
||||
return $sHtml;
|
||||
|
||||
@@ -339,7 +339,7 @@ EOF
|
||||
return '</tr>';
|
||||
}
|
||||
|
||||
public function RenderAsDialog($oPage, $sDialogId, $sDialogTitle, $iDialogWidth, $sOkButtonLabel, $sIntroduction = null)
|
||||
public function RenderAsDialog($oPage, $sDialogId, $sDialogTitle, $iDialogWidth, $sOkButtonLabel, $sIntroduction = null, $bAutoOpen = true)
|
||||
{
|
||||
$this->SetPrefix('dlg_'); // To make sure that the controls have different IDs that the property sheet which may be displayed at the same time
|
||||
|
||||
@@ -355,12 +355,14 @@ EOF
|
||||
$this->Render($oPage);
|
||||
$oPage->add('</div>');
|
||||
|
||||
$sAutoOpen = $bAutoOpen ? 'true' : 'false';
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sDialogId').dialog({
|
||||
height: 'auto',
|
||||
width: $iDialogWidth,
|
||||
modal: true,
|
||||
autoOpen: $sAutoOpen,
|
||||
title: '$sDialogTitle',
|
||||
buttons: [
|
||||
{ text: "$sOkButtonLabel", click: function() {
|
||||
@@ -879,7 +881,7 @@ class DesignerTextField extends DesignerFormField
|
||||
$this->sValidationPattern = $sValidationPattern;
|
||||
}
|
||||
|
||||
public function SetForbiddenValues($aValues, $sExplain)
|
||||
public function SetForbiddenValues($aValues, $sExplain, $bCaseSensitive = true)
|
||||
{
|
||||
$aForbiddenValues = $aValues;
|
||||
|
||||
@@ -891,7 +893,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')
|
||||
@@ -937,8 +939,8 @@ EOF
|
||||
public function ReadParam(&$aValues)
|
||||
{
|
||||
parent::ReadParam($aValues);
|
||||
|
||||
if (($this->sValidationPattern != '') &&(!preg_match('/'.$this->sValidationPattern.'/', $aValues[$this->sCode])) )
|
||||
$sPattern = '/'.str_replace('/', '\/', $this->sValidationPattern).'/'; // Escape the forward slashes since they are used as delimiters for preg_match
|
||||
if (($this->sValidationPattern != '') && (!preg_match($sPattern, $aValues[$this->sCode])) )
|
||||
{
|
||||
$aValues[$this->sCode] = $this->defaultValue;
|
||||
}
|
||||
@@ -1336,7 +1338,7 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = '<img src="'.$this->MakeFileUrl($this->defaultValue).'" />';
|
||||
$sValue = '<span style="display:inline-block;line-height:48px;height:48px;"><span><img style="vertical-align:middle" src="'.$this->aAllowedValues[$idx]['icon'].'" /> '.htmlentities($this->aAllowedValues[$idx]['label'], ENT_QUOTES, 'UTF-8').'</span></span>';
|
||||
}
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled' : '';
|
||||
return array('label' => $this->sLabel, 'value' => $sValue);
|
||||
@@ -1365,6 +1367,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
|
||||
@@ -1376,7 +1408,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
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class iTopWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -37,14 +37,32 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
private $m_sMessage;
|
||||
private $m_sInitScript;
|
||||
protected $m_oTabs;
|
||||
protected $bBreadCrumbEnabled;
|
||||
protected $sBreadCrumbEntryId;
|
||||
protected $sBreadCrumbEntryLabel;
|
||||
protected $sBreadCrumbEntryDescription;
|
||||
protected $sBreadCrumbEntryUrl;
|
||||
protected $sBreadCrumbEntryIcon;
|
||||
protected $oCtx;
|
||||
|
||||
public function __construct($sTitle, $bPrintable = false)
|
||||
{
|
||||
parent::__construct($sTitle, $bPrintable);
|
||||
$this->m_oTabs = new TabManager();
|
||||
$this->oCtx = new ContextTag('GUI:Console');
|
||||
|
||||
ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
|
||||
|
||||
if ((count($_POST) == 0) || (array_key_exists('loginop', $_POST)))
|
||||
{
|
||||
// Create a breadcrumb entry for the current page, but get its title as late as possible (page title could be changed later)
|
||||
$this->bBreadCrumbEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->bBreadCrumbEnabled = false;
|
||||
}
|
||||
|
||||
$this->m_sMenu = "";
|
||||
$this->m_sMessage = '';
|
||||
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
|
||||
@@ -52,13 +70,19 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
$this->add_linked_stylesheet("../css/jquery.treeview.css");
|
||||
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
|
||||
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
|
||||
$this->add_linked_stylesheet("../css/fg.menu.css");
|
||||
$this->add_linked_stylesheet("../css/jquery.multiselect.css");
|
||||
$this->add_linked_stylesheet("../css/magnific-popup.css");
|
||||
$this->add_linked_stylesheet("../css/c3.min.css");
|
||||
|
||||
$this->add_linked_script('../js/jquery.layout.min.js');
|
||||
$this->add_linked_script('../js/jquery.ba-bbq.min.js');
|
||||
$this->add_linked_script("../js/jquery.treeview.js");
|
||||
$this->add_linked_script("../js/jquery.autocomplete.js");
|
||||
$this->add_linked_script("../js/date.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
|
||||
$this->add_linked_script("../js/jquery.blockUI.js");
|
||||
$this->add_linked_script("../js/utils.js");
|
||||
$this->add_linked_script("../js/swfobject.js");
|
||||
@@ -69,13 +93,15 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->add_linked_script('../js/fg.menu.js');
|
||||
$this->add_linked_script('../js/icon_select.js');
|
||||
$this->add_linked_script('../js/raphael-min.js');
|
||||
$this->add_linked_script('../js/g.raphael.js');
|
||||
$this->add_linked_script('../js/g.pie.js');
|
||||
$this->add_linked_script('../js/g.dot.js');
|
||||
$this->add_linked_script('../js/charts.js');
|
||||
$this->add_linked_script('../js/d3.js');
|
||||
$this->add_linked_script('../js/c3.js');
|
||||
$this->add_linked_script('../js/jquery.multiselect.js');
|
||||
$this->add_linked_script('../js/ajaxfileupload.js');
|
||||
$this->add_linked_script('../js/jquery.mousewheel.js');
|
||||
$this->add_linked_script('../js/jquery.magnific-popup.min.js');
|
||||
$this->add_linked_script('../js/breadcrumb.js');
|
||||
$this->add_linked_script('../js/moment.min.js');
|
||||
|
||||
|
||||
$sSearchAny = addslashes(Dict::S('UI:SearchValue:Any'));
|
||||
$sSearchNbSelected = addslashes(Dict::S('UI:SearchValue:NbSelected'));
|
||||
@@ -86,20 +112,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
$this->PrepareLayout();
|
||||
}
|
||||
}
|
||||
|
||||
protected function PrepareLayout()
|
||||
{
|
||||
$bForceMenuPane = utils::ReadParam('force_menu_pane', null);
|
||||
$sInitClosed = '';
|
||||
if (($bForceMenuPane !== null) && ($bForceMenuPane == 0))
|
||||
{
|
||||
$sInitClosed = 'initClosed: true,';
|
||||
}
|
||||
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
function ShowAboutBox()
|
||||
{
|
||||
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'about_box'}, function(data){
|
||||
@@ -108,36 +122,126 @@ function ShowAboutBox()
|
||||
return false;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function IsMenuPaneVisible()
|
||||
{
|
||||
$bLeftPaneOpen = true;
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
// Leave the pane opened
|
||||
}
|
||||
else
|
||||
{
|
||||
if (utils::ReadParam('force_menu_pane', null) === 0)
|
||||
{
|
||||
$bLeftPaneOpen = false;
|
||||
}
|
||||
elseif (appUserPreferences::GetPref('menu_pane', 'open') == 'closed')
|
||||
{
|
||||
$bLeftPaneOpen = false;
|
||||
}
|
||||
}
|
||||
return $bLeftPaneOpen;
|
||||
}
|
||||
|
||||
protected function PrepareLayout()
|
||||
{
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
// No pin button
|
||||
$sConfigureWestPane = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sConfigureWestPane =
|
||||
<<<EOF
|
||||
if (GetUserPreference('menu_pane', 'open') == 'closed')
|
||||
{
|
||||
myLayout.close('west');
|
||||
}
|
||||
myLayout.addPinBtn( "#tPinMenu", "west" );
|
||||
EOF;
|
||||
}
|
||||
$sInitClosed = $this->IsMenuPaneVisible() ? '' : 'initClosed: true,';
|
||||
|
||||
|
||||
$sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage'));
|
||||
$sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle'));
|
||||
$sJSLoginAgain = json_encode(Dict::S('UI:LoginAgain'));
|
||||
$sJSStayOnThePage = json_encode(Dict::S('UI:StayOnThePage'));
|
||||
$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->m_sInitScript =
|
||||
<<< EOF
|
||||
try
|
||||
@@ -148,11 +252,12 @@ EOF;
|
||||
paneSize = GetUserPreference('menu_size', 300)
|
||||
myLayout = $('body').layout({
|
||||
west : {
|
||||
$sInitClosed minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "mouseover", hideTogglerOnSlide: true, enableCursorHotkey: false,
|
||||
$sInitClosed minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "click", hideTogglerOnSlide: true, enableCursorHotkey: false,
|
||||
onclose_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
$('.menu-pane-exclusive').show();
|
||||
SetUserPreference('menu_pane', 'closed', true);
|
||||
}
|
||||
},
|
||||
@@ -168,6 +273,7 @@ EOF;
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
$('.menu-pane-exclusive').hide();
|
||||
SetUserPreference('menu_pane', 'open', true);
|
||||
}
|
||||
}
|
||||
@@ -195,10 +301,7 @@ EOF;
|
||||
$sConfigureWestPane
|
||||
|
||||
$('#left-pane').layout({ resizable: false, spacing_open: 0, south: { size: 94 }, enableCursorHotkey: false });
|
||||
|
||||
// Accordion Menu
|
||||
$("#accordion").accordion({ header: "h3", navigation: true, heightStyle: "content", collapsible: false, icons: false }); // collapsible will be enabled once the item will be selected
|
||||
|
||||
|
||||
// Tabs, using JQuery BBQ to store the history
|
||||
// The "tab widgets" to handle.
|
||||
var tabs = $('div[id^=tabbedContent]');
|
||||
@@ -240,7 +343,7 @@ EOF;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('.resizable').filter(':visible').resizable();
|
||||
}
|
||||
catch(err)
|
||||
@@ -374,30 +477,7 @@ EOF
|
||||
|
||||
// End of Tabs handling
|
||||
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
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();
|
||||
|
||||
// Make sortable, everything that claims to be sortable
|
||||
$('.sortable').sortable( {axis: 'y', cursor: 'move', handle: '.drag_handle', stop: function()
|
||||
@@ -416,7 +496,7 @@ EOF
|
||||
ShowDebug();
|
||||
$('#logOffBtn>ul').popupmenu();
|
||||
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry').toggle(); });
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
|
||||
|
||||
$(document).ajaxSend(function(event, jqxhr, options) {
|
||||
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');
|
||||
@@ -436,9 +516,15 @@ EOF
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
EOF
|
||||
);
|
||||
$this->add_ready_script(InlineImage::FixImagesWidth());
|
||||
/*
|
||||
* Not used since the sorting of the tables is always performed server-side
|
||||
AttributeDateTime::InitTableSorter($this, 'custom_date_time');
|
||||
AttributeDate::InitTableSorter($this, 'custom_date');
|
||||
*/
|
||||
|
||||
$sUserPrefs = appUserPreferences::GetAsJSON();
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
@@ -515,6 +601,36 @@ EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sId Identifies the item, to search after it in the current breadcrumb
|
||||
* @param string $sLabel Label of the breadcrumb item
|
||||
* @param string $sDescription More information, displayed as a tooltip
|
||||
* @param string $sUrl Specify a URL if the current URL as perceived on the browser side is not relevant
|
||||
* @param string $sIcon Icon (relative or absolute) path that will be displayed next to the label
|
||||
*/
|
||||
public function SetBreadCrumbEntry($sId, $sLabel, $sDescription, $sUrl = '', $sIcon = '')
|
||||
{
|
||||
$this->bBreadCrumbEnabled = true;
|
||||
$this->sBreadCrumbEntryId = $sId;
|
||||
$this->sBreadCrumbEntryLabel = $sLabel;
|
||||
$this->sBreadCrumbEntryDescription = $sDescription;
|
||||
$this->sBreadCrumbEntryUrl = $sUrl;
|
||||
$this->sBreadCrumbEntryIcon = $sIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* State that there will be no breadcrumb item for the current page
|
||||
*/
|
||||
public function DisableBreadCrumb()
|
||||
{
|
||||
$this->bBreadCrumbEnabled = false;
|
||||
$this->sBreadCrumbEntryId = null;
|
||||
$this->sBreadCrumbEntryLabel = null;
|
||||
$this->sBreadCrumbEntryDescription = null;
|
||||
$this->sBreadCrumbEntryUrl = null;
|
||||
$this->sBreadCrumbEntryIcon = null;
|
||||
}
|
||||
|
||||
public function AddToMenu($sHtml)
|
||||
{
|
||||
$this->m_sMenu .= $sHtml;
|
||||
@@ -639,7 +755,6 @@ EOF
|
||||
'selectedList' => 1,
|
||||
);
|
||||
$sJSMultiselectOptions = json_encode($aMultiselectOptions);
|
||||
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
// Since the event is only triggered when the hash changes, we need to trigger
|
||||
@@ -652,9 +767,38 @@ EOF
|
||||
$('.multiselect').multiselect($sJSMultiselectOptions);
|
||||
|
||||
FixSearchFormsDisposition();
|
||||
|
||||
EOF
|
||||
);
|
||||
|
||||
$iBreadCrumbMaxCount = utils::GetConfig()->Get('breadcrumb.max_count');
|
||||
if ($iBreadCrumbMaxCount > 1)
|
||||
{
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$siTopInstanceId = json_encode(utils::GetAbsoluteUrlAppRoot().'==='.$oConfig->GetDBHost().'/'.$oConfig->GetDBName().'/'.$oConfig->GetDBSubname());
|
||||
if ($this->bBreadCrumbEnabled)
|
||||
{
|
||||
if (is_null($this->sBreadCrumbEntryId))
|
||||
{
|
||||
$this->sBreadCrumbEntryId = $this->s_title;
|
||||
$this->sBreadCrumbEntryLabel = $this->s_title;
|
||||
$this->sBreadCrumbEntryDescription = $this->s_title;
|
||||
$this->sBreadCrumbEntryUrl = '';
|
||||
$this->sBreadCrumbEntryIcon = utils::GetAbsoluteUrlAppRoot().'images/wrench.png';
|
||||
}
|
||||
$sNewEntry = json_encode(array('id' => $this->sBreadCrumbEntryId, 'url' => $this->sBreadCrumbEntryUrl, 'label' => htmlentities($this->sBreadCrumbEntryLabel, ENT_QUOTES, 'UTF-8'), 'description' => htmlentities($this->sBreadCrumbEntryDescription, ENT_QUOTES, 'UTF-8'), 'icon' => $this->sBreadCrumbEntryIcon));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sNewEntry = 'null';
|
||||
}
|
||||
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
$('#itop-breadcrumb').breadcrumb({itop_instance_id: $siTopInstanceId, new_entry: $sNewEntry, max_count: $iBreadCrumbMaxCount});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
foreach($this->a_headers as $s_header)
|
||||
@@ -695,7 +839,7 @@ EOF
|
||||
}
|
||||
// special stylesheet for printing, hides the navigation gadgets
|
||||
$sHtml .= "<link rel=\"stylesheet\" media=\"print\" type=\"text/css\" href=\"../css/print.css?itopversion=".ITOP_VERSION."\" />\n";
|
||||
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
$sHtml .= $this->output_dict_entries(true); // before any script so that they can benefit from the translations
|
||||
@@ -893,7 +1037,7 @@ EOF
|
||||
|
||||
if (!empty($sNorthPane))
|
||||
{
|
||||
$sNorthPane = '<div id="bottom-pane" class="ui-layout-north">'.$sNorthPane.'</div>';
|
||||
$sNorthPane = '<div id="top-pane" class="ui-layout-north">'.$sNorthPane.'</div>';
|
||||
}
|
||||
|
||||
if (!empty($sSouthPane))
|
||||
@@ -940,13 +1084,37 @@ EOF
|
||||
$sHtml .= '</div>';
|
||||
|
||||
$sHtml .= '<div class="ui-layout-center">';
|
||||
$sHtml .= ' <div id="top-bar" style="width:100%">';
|
||||
$sHtml .= ' <div id="top-bar" class="ui-helper-clearfix" style="width:100%">';
|
||||
$sHtml .= self::FilterXSS($sApplicationBanner);
|
||||
|
||||
$GoHomeInitialStyle = $this->IsMenuPaneVisible() ? 'display: none;' : '';
|
||||
|
||||
$sHtml .= ' <table id="top-bar-table">';
|
||||
$sHtml .= ' <tr>';
|
||||
$sHtml .= ' <td id="open-left-pane" class="menu-pane-exclusive" style="'.$GoHomeInitialStyle.'" onclick="$(\'body\').layout().open(\'west\');">';
|
||||
$sHtml .= ' <img src="../images/menu.png">';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' <td id="go-home" class="menu-pane-exclusive" style="'.$GoHomeInitialStyle.'">';
|
||||
$sHtml .= ' <a href="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><img src="../images/home.png"></a>';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' <td class="top-bar-spacer menu-pane-exclusive" style="'.$GoHomeInitialStyle.'">';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' <td id="top-bar-table-breadcrumb">';
|
||||
$sHtml .= ' <div id="itop-breadcrumb"></div>';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' <td id="top-bar-table-search">';
|
||||
$sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><table><tr><td></td><td><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"></div></div></td>';
|
||||
//$sHtml .= '<td><input type="image" src="../images/searchBtn.png"/></a></td>';
|
||||
$sHtml .= '<td><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?itopversion='.ITOP_VERSION.'"/></td>';
|
||||
$sHtml .= '<td>'.self::FilterXSS($sLogOffMenu).'</td><td><input type="hidden" name="operation" value="full_text"/></td></tr></table></form></div>';
|
||||
//echo '<td> <input type="hidden" name="operation" value="full_text"/></td></tr></table></form></div>';
|
||||
$sHtml .= ' <td><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?itopversion='.ITOP_VERSION.'"/></td>';
|
||||
$sHtml .= ' <td>'.self::FilterXSS($sLogOffMenu).'</td><td><input type="hidden" name="operation" value="full_text"/></td></tr></table></form></div>';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' </tr>';
|
||||
$sHtml .= ' </table>';
|
||||
|
||||
// $sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><table><tr><td></td><td><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"></div></div></td>';
|
||||
// $sHtml .= '<td><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?itopversion='.ITOP_VERSION.'"/></td>';
|
||||
// $sHtml .= '<td>'.self::FilterXSS($sLogOffMenu).'</td><td><input type="hidden" name="operation" value="full_text"/></td></tr></table></form></div>';
|
||||
// $sHtml .= ' <div id="itop-breadcrumb"></div>';
|
||||
|
||||
$sHtml .= ' </div>';
|
||||
$sHtml .= ' <div class="ui-layout-content" style="overflow:auto;">';
|
||||
$sHtml .= ' <!-- Beginning of page content -->';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class LoginWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -65,7 +65,7 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
public function SetStyleSheet()
|
||||
{
|
||||
$this->add_linked_stylesheet("../css/login.css");
|
||||
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/login.css');
|
||||
}
|
||||
|
||||
public static function SetLoginFailedMessage($sMessage)
|
||||
@@ -306,16 +306,20 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
|
||||
}
|
||||
elseif ($oUser->Get('reset_pwd_token') != $sToken)
|
||||
{
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
|
||||
|
||||
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
|
||||
$this->add_script(
|
||||
$oEncryptedToken = $oUser->Get('reset_pwd_token');
|
||||
|
||||
if (!$oEncryptedToken->CheckPassword($sToken))
|
||||
{
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
|
||||
|
||||
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
function DoCheckPwd()
|
||||
{
|
||||
@@ -327,18 +331,19 @@ function DoCheckPwd()
|
||||
return true;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$this->add("<form method=\"post\">\n");
|
||||
$this->add("<table>\n");
|
||||
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
|
||||
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
|
||||
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
|
||||
$this->add("</table>\n");
|
||||
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
|
||||
$this->add("</form>\n");
|
||||
$this->add("</div\n");
|
||||
);
|
||||
$this->add("<form method=\"post\">\n");
|
||||
$this->add("<table>\n");
|
||||
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
|
||||
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
|
||||
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
|
||||
$this->add("</table>\n");
|
||||
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
|
||||
$this->add("</form>\n");
|
||||
$this->add("</div\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,21 +363,25 @@ EOF
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
|
||||
}
|
||||
elseif ($oUser->Get('reset_pwd_token') != $sToken)
|
||||
{
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trash the token and change the password
|
||||
$oUser->Set('reset_pwd_token', '');
|
||||
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
|
||||
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
|
||||
$oEncryptedPassword = $oUser->Get('reset_pwd_token');
|
||||
if (!$oEncryptedPassword->CheckPassword($sToken))
|
||||
{
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trash the token and change the password
|
||||
$oUser->Set('reset_pwd_token', '');
|
||||
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
|
||||
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
|
||||
}
|
||||
$this->add("</div\n");
|
||||
}
|
||||
$this->add("</div\n");
|
||||
}
|
||||
|
||||
public function DisplayChangePwdForm($bFailedLogin = false)
|
||||
@@ -418,18 +427,10 @@ EOF
|
||||
|
||||
static function ResetSession()
|
||||
{
|
||||
if (isset($_SESSION['login_mode']))
|
||||
{
|
||||
$sPreviousLoginMode = $_SESSION['login_mode'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sPreviousLoginMode = '';
|
||||
}
|
||||
// Unset all of the session variables.
|
||||
unset($_SESSION['auth_user']);
|
||||
unset($_SESSION['login_mode']);
|
||||
unset($_SESSION['profile_list']);
|
||||
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!
|
||||
}
|
||||
@@ -477,117 +478,153 @@ EOF
|
||||
{
|
||||
//echo "User: ".$_SESSION['auth_user']."\n";
|
||||
// Already authentified
|
||||
UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
|
||||
return self::EXIT_CODE_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
$index = 0;
|
||||
$sLoginMode = '';
|
||||
$sAuthentication = 'internal';
|
||||
while(($sLoginMode == '') && ($index < count($aAllowedLoginTypes)))
|
||||
$bRet = UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
|
||||
if ($bRet)
|
||||
{
|
||||
$sLoginType = $aAllowedLoginTypes[$index];
|
||||
switch($sLoginType)
|
||||
{
|
||||
case 'cas':
|
||||
utils::InitCASClient();
|
||||
// check CAS authentication
|
||||
if (phpCAS::isAuthenticated())
|
||||
{
|
||||
$sAuthUser = phpCAS::getUser();
|
||||
$sAuthPwd = '';
|
||||
$sLoginMode = 'cas';
|
||||
$sAuthentication = 'external';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'form':
|
||||
// iTop standard mode: form based authentication
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, false, 'raw_data');
|
||||
if (($sAuthUser != '') && ($sAuthPwd !== null))
|
||||
{
|
||||
$sLoginMode = 'form';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'basic':
|
||||
// Standard PHP authentication method, works with Apache...
|
||||
// Case 1) Apache running in CGI mode + rewrite rules in .htaccess
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
|
||||
$sLoginMode = 'basic';
|
||||
}
|
||||
else if (isset($_SERVER['PHP_AUTH_USER']))
|
||||
{
|
||||
$sAuthUser = $_SERVER['PHP_AUTH_USER'];
|
||||
// Unfortunately, the RFC is not clear about the encoding...
|
||||
// IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8
|
||||
// So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base
|
||||
if (!self::LooksLikeUTF8($sAuthUser))
|
||||
{
|
||||
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
|
||||
// Supposed to be harmless in case of a plain ASCII string...
|
||||
$sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser);
|
||||
}
|
||||
$sAuthPwd = $_SERVER['PHP_AUTH_PW'];
|
||||
if (!self::LooksLikeUTF8($sAuthPwd))
|
||||
{
|
||||
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
|
||||
// Supposed to be harmless in case of a plain ASCII string...
|
||||
$sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd);
|
||||
}
|
||||
$sLoginMode = 'basic';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
// Web server supplied authentication
|
||||
$bExternalAuth = false;
|
||||
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
|
||||
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
|
||||
if ($sAuthUser && (strlen($sAuthUser) > 0))
|
||||
{
|
||||
$sAuthPwd = ''; // No password in this case the web server already authentified the user...
|
||||
$sLoginMode = 'external';
|
||||
$sAuthentication = 'external';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
// Credentials passed directly in the url
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
if (($sAuthUser != '') && ($sAuthPwd !== null))
|
||||
{
|
||||
$sLoginMode = 'url';
|
||||
}
|
||||
break;
|
||||
}
|
||||
$index++;
|
||||
return self::EXIT_CODE_OK;
|
||||
}
|
||||
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
|
||||
if ($sLoginMode == '')
|
||||
// The user account is no longer valid/enabled
|
||||
static::ResetSession();
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
$sLoginMode = '';
|
||||
$sAuthentication = 'internal';
|
||||
while(($sLoginMode == '') && ($index < count($aAllowedLoginTypes)))
|
||||
{
|
||||
$sLoginType = $aAllowedLoginTypes[$index];
|
||||
switch($sLoginType)
|
||||
{
|
||||
// First connection
|
||||
$sDesiredLoginMode = utils::ReadParam('login_mode');
|
||||
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
|
||||
case 'cas':
|
||||
utils::InitCASClient();
|
||||
// check CAS authentication
|
||||
if (phpCAS::isAuthenticated())
|
||||
{
|
||||
$sLoginMode = $sDesiredLoginMode;
|
||||
$sAuthUser = phpCAS::getUser();
|
||||
$sAuthPwd = '';
|
||||
$sLoginMode = 'cas';
|
||||
$sAuthentication = 'external';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'form':
|
||||
// iTop standard mode: form based authentication
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, false, 'raw_data');
|
||||
if (($sAuthUser != '') && ($sAuthPwd !== null))
|
||||
{
|
||||
$sLoginMode = 'form';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'basic':
|
||||
// Standard PHP authentication method, works with Apache...
|
||||
// Case 1) Apache running in CGI mode + rewrite rules in .htaccess
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
|
||||
$sLoginMode = 'basic';
|
||||
}
|
||||
else if (isset($_SERVER['PHP_AUTH_USER']))
|
||||
{
|
||||
$sAuthUser = $_SERVER['PHP_AUTH_USER'];
|
||||
// Unfortunately, the RFC is not clear about the encoding...
|
||||
// IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8
|
||||
// So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base
|
||||
if (!self::LooksLikeUTF8($sAuthUser))
|
||||
{
|
||||
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
|
||||
// Supposed to be harmless in case of a plain ASCII string...
|
||||
$sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser);
|
||||
}
|
||||
$sAuthPwd = $_SERVER['PHP_AUTH_PW'];
|
||||
if (!self::LooksLikeUTF8($sAuthPwd))
|
||||
{
|
||||
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
|
||||
// Supposed to be harmless in case of a plain ASCII string...
|
||||
$sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd);
|
||||
}
|
||||
$sLoginMode = 'basic';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
// Web server supplied authentication
|
||||
$bExternalAuth = false;
|
||||
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
|
||||
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
|
||||
if ($sAuthUser && (strlen($sAuthUser) > 0))
|
||||
{
|
||||
$sAuthPwd = ''; // No password in this case the web server already authentified the user...
|
||||
$sLoginMode = 'external';
|
||||
$sAuthentication = 'external';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
// Credentials passed directly in the url
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
if (($sAuthUser != '') && ($sAuthPwd !== null))
|
||||
{
|
||||
$sLoginMode = 'url';
|
||||
}
|
||||
break;
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
|
||||
if ($sLoginMode == '')
|
||||
{
|
||||
// First connection
|
||||
$sDesiredLoginMode = utils::ReadParam('login_mode');
|
||||
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
|
||||
{
|
||||
$sLoginMode = $sDesiredLoginMode;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
|
||||
}
|
||||
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
|
||||
{
|
||||
// X-Combodo-Ajax is a special header automatically added to all ajax requests
|
||||
// Let's reply that we're currently logged-out
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
exit;
|
||||
}
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
if (($sAuthUser !== '') && ($sAuthPwd === null))
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGPASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
|
||||
}
|
||||
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));
|
||||
@@ -597,65 +634,33 @@ EOF
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
if (($sAuthUser !== '') && ($sAuthPwd === null))
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGPASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGLOGIN;
|
||||
}
|
||||
return self::EXIT_CODE_WRONGCREDENTIALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
|
||||
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
|
||||
// User is Ok, let's save it in the session and proceed with normal login
|
||||
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
|
||||
|
||||
if (MetaModel::GetConfig()->Get('log_usage'))
|
||||
{
|
||||
//echo "Check Credentials returned false for user $sAuthUser!";
|
||||
self::ResetSession();
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
return self::EXIT_CODE_WRONGCREDENTIALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// User is Ok, let's save it in the session and proceed with normal login
|
||||
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
|
||||
|
||||
if (MetaModel::GetConfig()->Get('log_usage'))
|
||||
{
|
||||
$oLog = new EventLoginUsage();
|
||||
$oLog->Set('userinfo', UserRights::GetUser());
|
||||
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
|
||||
$oLog->Set('message', 'Successful login');
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
$oLog = new EventLoginUsage();
|
||||
$oLog->Set('userinfo', UserRights::GetUser());
|
||||
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
|
||||
$oLog->Set('message', 'Successful login');
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
UserRights::_InitSessionCache();
|
||||
}
|
||||
}
|
||||
return self::EXIT_CODE_OK;
|
||||
@@ -853,29 +858,4 @@ EOF
|
||||
}
|
||||
return false; // nothing matched !!
|
||||
}
|
||||
|
||||
public static function GetAllowedPortals()
|
||||
{
|
||||
$aAllowedPortals = array();
|
||||
$aPortalsConf = PortalDispatcherData::GetData();
|
||||
$aDispatchers = array();
|
||||
foreach($aPortalsConf as $sPortalId => $aConf)
|
||||
{
|
||||
$sHandlerClass = $aConf['handler'];
|
||||
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
|
||||
}
|
||||
|
||||
foreach($aDispatchers as $sPortalId => $oDispatcher)
|
||||
{
|
||||
if ($oDispatcher->IsUserAllowed())
|
||||
{
|
||||
$aAllowedPortals[] = array(
|
||||
'id' => $sPortalId,
|
||||
'label' => $oDispatcher->GetLabel(),
|
||||
'url' => $oDispatcher->GetUrl(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return $aAllowedPortals;
|
||||
}
|
||||
} // End of class
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Construction and display of the application's main menu
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -184,9 +184,12 @@ class ApplicationMenu
|
||||
$oPage->AddToMenu('</ul>');
|
||||
if ($bActive)
|
||||
{
|
||||
//$oPage->add_ready_script("$('#accordion').accordion('activate', $iAccordion);");
|
||||
// $oPage->add_ready_script("$('#accordion').accordion('option', {collapsible: true});"); // Make it auto-collapsible once it has been opened properly
|
||||
$oPage->add_ready_script("$('#accordion').accordion('option', {collapsible: true, active: $iAccordion});"); // Make it auto-collapsible once it has been opened properly
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
// Accordion Menu
|
||||
$("#accordion").css({display:'block'}).accordion({ header: "h3", navigation: true, heightStyle: "content", collapsible: true, active: $iAccordion, icons: false, animate:true }); // collapsible will be enabled once the item will be selected
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
$oPage->AddToMenu('</div>');
|
||||
@@ -296,6 +299,16 @@ class ApplicationMenu
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
|
||||
if ($sMenuId === null)
|
||||
{
|
||||
$sMenuId = self::GetDefaultMenuId();
|
||||
}
|
||||
return $sMenuId;
|
||||
}
|
||||
|
||||
static public function GetDefaultMenuId()
|
||||
{
|
||||
static $sDefaultMenuId = null;
|
||||
if (is_null($sDefaultMenuId))
|
||||
{
|
||||
// Make sure the root menu is sorted on 'rank'
|
||||
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
|
||||
@@ -303,10 +316,10 @@ class ApplicationMenu
|
||||
$aChildren = self::$aMenusIndex[$oFirstGroup->GetIndex()]['children'];
|
||||
usort($aChildren, array('ApplicationMenu', 'CompareOnRank'));
|
||||
$oMenuNode = self::GetMenuNode($aChildren[0]['index']);
|
||||
$sMenuId = $oMenuNode->GetMenuId();
|
||||
$sDefaultMenuId = $oMenuNode->GetMenuId();
|
||||
}
|
||||
return $sMenuId;
|
||||
}
|
||||
return $sDefaultMenuId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,6 +352,7 @@ abstract class MenuNode
|
||||
{
|
||||
protected $sMenuId;
|
||||
protected $index;
|
||||
protected $iParentIndex;
|
||||
|
||||
/**
|
||||
* Properties reflecting how the node has been declared
|
||||
@@ -379,6 +393,7 @@ abstract class MenuNode
|
||||
public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
$this->sMenuId = $sMenuId;
|
||||
$this->iParentIndex = $iParentIndex;
|
||||
$this->aReflectionProperties = array();
|
||||
if (strlen($sEnableClass) > 0)
|
||||
{
|
||||
@@ -411,7 +426,21 @@ abstract class MenuNode
|
||||
|
||||
public function GetLabel()
|
||||
{
|
||||
return Dict::S("Menu:$this->sMenuId+", "");
|
||||
$sRet = Dict::S("Menu:$this->sMenuId+", "");
|
||||
if ($sRet === '')
|
||||
{
|
||||
if ($this->iParentIndex != -1)
|
||||
{
|
||||
$oParentMenu = ApplicationMenu::GetMenuNode($this->iParentIndex);
|
||||
$sRet = $oParentMenu->GetTitle().' / '.$this->GetTitle();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = $this->GetTitle();
|
||||
}
|
||||
//$sRet = $this->GetTitle();
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetIndex()
|
||||
@@ -592,6 +621,7 @@ class OQLMenuNode extends MenuNode
|
||||
protected $sPageTitle;
|
||||
protected $sOQL;
|
||||
protected $bSearch;
|
||||
protected $bSearchFormOpen;
|
||||
|
||||
/**
|
||||
* Extra parameters to be passed to the display block to fine tune its appearence
|
||||
@@ -611,12 +641,20 @@ class OQLMenuNode extends MenuNode
|
||||
* @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them...
|
||||
* @return MenuNode
|
||||
*/
|
||||
public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null, $bSearchFormOpen = null)
|
||||
{
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
$this->sPageTitle = "Menu:$sMenuId+";
|
||||
$this->sOQL = $sOQL;
|
||||
$this->bSearch = $bSearch;
|
||||
if ($bSearchFormOpen == null)
|
||||
{
|
||||
$this->bSearchFormOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->bSearchFormOpen = $bSearchFormOpen;
|
||||
}
|
||||
$this->m_aParams = array();
|
||||
$this->aReflectionProperties['oql'] = $sOQL;
|
||||
$this->aReflectionProperties['do_search'] = $bSearch;
|
||||
@@ -645,13 +683,14 @@ class OQLMenuNode extends MenuNode
|
||||
Dict::S($this->sPageTitle),
|
||||
'Menu_'.$this->GetMenuId(),
|
||||
$this->bSearch, // Search pane
|
||||
true, // Search open
|
||||
$this->bSearchFormOpen, // Search open
|
||||
$oPage,
|
||||
array_merge($this->m_aParams, $aExtraParams)
|
||||
array_merge($this->m_aParams, $aExtraParams),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array())
|
||||
public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array(), $bEnableBreadcrumb = false)
|
||||
{
|
||||
$sUsageId = utils::GetSafeId($sUsageId);
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql);
|
||||
@@ -669,6 +708,15 @@ class OQLMenuNode extends MenuNode
|
||||
$aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
|
||||
$oBlock->Display($oPage, $sUsageId);
|
||||
|
||||
if ($bEnableBreadcrumb && ($oPage instanceof iTopWebPage))
|
||||
{
|
||||
// Breadcrumb
|
||||
//$iCount = $oBlock->GetDisplayedCount();
|
||||
$sPageId = "ui-search-".$oSearch->GetClass();
|
||||
$sLabel = MetaModel::GetName($oSearch->GetClass());
|
||||
$oPage->SetBreadCrumbEntry($sPageId, $sLabel, $sTitle, '', '../images/breadcrumb-search.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,9 +747,11 @@ class SearchMenuNode extends MenuNode
|
||||
$this->sClass = $sClass;
|
||||
$this->aReflectionProperties['class'] = $sClass;
|
||||
}
|
||||
|
||||
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', utils::GetAbsoluteUrlAppRoot().'images/search.png');
|
||||
|
||||
$oSearch = new DBObjectSearch($this->sClass);
|
||||
$aParams = array_merge(array('open' => true, 'table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
|
||||
@@ -920,6 +970,29 @@ EOF
|
||||
$sId = addslashes($this->sMenuId);
|
||||
$oPage->add_ready_script("EditDashboard('$sId');");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oParentMenu = ApplicationMenu::GetMenuNode($this->iParentIndex);
|
||||
$sParentTitle = $oParentMenu->GetTitle();
|
||||
$sThisTitle = $this->GetTitle();
|
||||
if ($sParentTitle != $sThisTitle)
|
||||
{
|
||||
$sDescription = $sParentTitle.' / '.$sThisTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDescription = $sThisTitle;
|
||||
}
|
||||
if ($this->sMenuId == ApplicationMenu::GetDefaultMenuId())
|
||||
{
|
||||
$sIcon = '../images/breadcrumb_home.png';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sIcon = '../images/breadcrumb-dashboard.png';
|
||||
}
|
||||
$oPage->SetBreadCrumbEntry("ui-dashboard-".$this->sMenuId, $this->GetTitle(), $sDescription, '', $sIcon);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -950,7 +1023,7 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
|
||||
throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class NiceWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -37,19 +37,19 @@ class NiceWebPage extends WebPage
|
||||
{
|
||||
parent::__construct($s_title, $bPrintable);
|
||||
$this->m_aReadyScripts = array();
|
||||
$this->add_linked_script("../js/jquery-1.10.0.min.js");
|
||||
$this->add_linked_script("../js/jquery-migrate-1.2.1.min.js"); // Needed since many other plugins still rely on oldies like $.browser
|
||||
$this->add_linked_stylesheet('../css/ui-lightness/jquery-ui-1.10.3.custom.min.css');
|
||||
$this->add_linked_script('../js/jquery-ui-1.10.3.custom.min.js');
|
||||
$this->add_linked_script("../js/hovertip.js");
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-1.10.0.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate-1.2.1.min.js'); // Needed since many other plugins still rely on oldies like $.browser
|
||||
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/ui-lightness/jquery-ui-1.10.3.custom.min.css');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-1.10.3.custom.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/hovertip.js');
|
||||
// table sorting
|
||||
$this->add_linked_script("../js/jquery.tablesorter.js");
|
||||
$this->add_linked_script("../js/jquery.tablesorter.pager.js");
|
||||
$this->add_linked_script("../js/jquery.tablehover.js");
|
||||
$this->add_linked_script('../js/field_sorter.js');
|
||||
$this->add_linked_script('../js/datatable.js');
|
||||
$this->add_linked_script("../js/jquery.positionBy.js");
|
||||
$this->add_linked_script("../js/jquery.popupmenu.js");
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.pager.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablehover.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_sorter.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/datatable.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.positionBy.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.popupmenu.js');
|
||||
$this->add_ready_script(
|
||||
<<< EOF
|
||||
//add new widget called TruncatedList to properly display truncated lists when they are sorted
|
||||
|
||||
@@ -13,20 +13,7 @@ class PortalDispatcher
|
||||
public function IsUserAllowed()
|
||||
{
|
||||
$bRet = true;
|
||||
if (array_key_exists('profile_list', $_SESSION))
|
||||
{
|
||||
$aProfiles = $_SESSION['profile_list'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$oSet = $oUser->Get('profile_list');
|
||||
while(($oLnkUserProfile = $oSet->Fetch()) !== null)
|
||||
{
|
||||
$aProfiles[] = $oLnkUserProfile->Get('profileid_friendlyname');
|
||||
}
|
||||
$_SESSION['profile_list'] = $aProfiles;
|
||||
}
|
||||
$aProfiles = UserRights::ListProfiles();
|
||||
|
||||
foreach($this->aData['deny'] as $sDeniedProfile)
|
||||
{
|
||||
@@ -55,7 +42,16 @@ class PortalDispatcher
|
||||
|
||||
public function GetURL()
|
||||
{
|
||||
return utils::GetAbsoluteUrlAppRoot().$this->aData['url'];
|
||||
$aOverloads = MetaModel::GetConfig()->Get('portal_dispatch_urls');
|
||||
if (array_key_exists($this->sPortalid, $aOverloads))
|
||||
{
|
||||
$sRet = $aOverloads[$this->sPortalid];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = utils::GetAbsoluteUrlAppRoot().$this->aData['url'];
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetLabel()
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Class PortalWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -49,9 +49,12 @@ class PortalWebPage extends NiceWebPage
|
||||
*/
|
||||
protected $m_sWelcomeMsg;
|
||||
protected $m_aMenuButtons;
|
||||
protected $m_oCtx;
|
||||
|
||||
|
||||
public function __construct($sTitle, $sAlternateStyleSheet = '')
|
||||
{
|
||||
$this->m_oCtx = new ContextTag('GUI:Portal');
|
||||
$this->m_sWelcomeMsg = '';
|
||||
$this->m_aMenuButtons = array();
|
||||
parent::__construct($sTitle);
|
||||
@@ -93,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
|
||||
@@ -143,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').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');
|
||||
@@ -260,7 +312,7 @@ EOF
|
||||
{
|
||||
var form = $('FORM');
|
||||
form.unbind('submit'); // De-activate validation
|
||||
window.location.href = window.location.href.replace(/[&?]operation=[^&]*/, '');
|
||||
window.location.href = window.location.href.replace(/operation=[^&]*&?/, '');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class QueryOQL extends Query
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php?format=spreadsheet&login_mode=basic&date_format='.urlencode((string)AttributeDateTime::GetFormat()).'&query='.$this->GetKey();
|
||||
}
|
||||
$sOql = $this->Get('oql');
|
||||
$sMessage = null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* Persistent class Shortcut and derived
|
||||
* Shortcuts of any kind
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -200,7 +200,7 @@ class ShortcutOQL extends Shortcut
|
||||
$bSearchOpen = false;
|
||||
try
|
||||
{
|
||||
OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams);
|
||||
OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams, true);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
|
||||
@@ -1,531 +0,0 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* SqlBlock - display tables or charts, given an SQL query - use cautiously!
|
||||
*
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
require_once(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
|
||||
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
|
||||
|
||||
/**
|
||||
* Helper class to design optimized dashboards, based on an SQL query
|
||||
*
|
||||
*/
|
||||
class SqlBlock
|
||||
{
|
||||
protected $m_sQuery;
|
||||
protected $m_aColumns;
|
||||
protected $m_sTitle;
|
||||
protected $m_sType;
|
||||
protected $m_aParams;
|
||||
|
||||
public function __construct($sQuery, $aColumns, $sTitle, $sType, $aParams = array())
|
||||
{
|
||||
$this->m_sQuery = $sQuery;
|
||||
$this->m_aColumns = $aColumns;
|
||||
$this->m_sTitle = $sTitle;
|
||||
$this->m_sType = $sType;
|
||||
$this->m_aParams = $aParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SqlBlock object from an XML template
|
||||
/*
|
||||
*
|
||||
* <sqlblock>
|
||||
* <sql>SELECT date_format(start_date, '%d') AS Date, count(*) AS Count FROM ticket WHERE DATE_SUB(NOW(), INTERVAL 15 DAY) < start_date AND finalclass = 'UserIssue' GROUP BY date_format(start_date, '%d') AND $CONDITION(param1, ticket.org_id)$</sql>
|
||||
* <type>table</type>
|
||||
* <title>UserRequest:Overview-Title</title>
|
||||
* <parameter>
|
||||
* <name>param1</name>
|
||||
* <type>context</type>
|
||||
* <mapping>org_id</mapping>
|
||||
* </parameter>
|
||||
* <column>
|
||||
* <name>Date</name>
|
||||
* <label>UserRequest:Overview-Date</label>
|
||||
* <drilldown></drilldown>
|
||||
* </column>
|
||||
* <column>
|
||||
* <name>Count</name>
|
||||
* <label>UserRequest:Overview-Count</label>
|
||||
* <drilldown>SELECT UserIssue WHERE date_format(start_date, '%d') = :Date</drilldown>
|
||||
* </column>
|
||||
* </sqlblock>
|
||||
*
|
||||
* Tags
|
||||
* - sql: a (My)SQL query. Do not forget to use html entities (e.g. < for <)
|
||||
* - type: table (default), bars or pie. If bars or pie is selected only the two first columns are taken into account.
|
||||
* - title: optional title, typed in clear or given as a dictionnary entry
|
||||
* - parameter: specifies how to map the context parameters (namely org_id) to a given named parameter in the query.
|
||||
* The expression $CONDITION(<param_name>, <sql_column_name>) will be automatically replaced by:
|
||||
* either the string "1" if there is no restriction on the organisation in iTop
|
||||
* or the string "(<sql_column_name>=<value_of_org_id>)" if there is a limitation to one organizations in iTop
|
||||
* or the string "(<sql_column_name> IN (<values_of_org_id>))" if there is a limitation to a given set of organizations in iTop
|
||||
* - column: specification of a column (not displayed if omitted)
|
||||
* - column / name: name of the column in the SQL query (use aliases)
|
||||
* - column / label: label, typed in clear or given as a dictionnary entry
|
||||
* - column / drilldown: NOT IMPLEMENTED YET - OQL with parameters corresponding to column names (in the query)
|
||||
*
|
||||
* @param $sTemplate string The XML template
|
||||
* @return DisplayBlock The DisplayBlock object, or null if the template is invalid
|
||||
*/
|
||||
public static function FromTemplate($sTemplate)
|
||||
{
|
||||
$oXml = simplexml_load_string('<root>'.$sTemplate.'</root>', 'SimpleXMLElement', LIBXML_NOCDATA);
|
||||
if (false)
|
||||
{
|
||||
// Debug
|
||||
echo "<pre>\n";
|
||||
print_r($oXml);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
|
||||
if (isset($oXml->title))
|
||||
{
|
||||
$sTitle = (string)$oXml->title;
|
||||
}
|
||||
if (isset($oXml->type))
|
||||
{
|
||||
$sType = (string)$oXml->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sType = 'table';
|
||||
}
|
||||
if (!isset($oXml->sql))
|
||||
{
|
||||
throw new Exception('Missing tag "sql" in sqlblock');
|
||||
}
|
||||
$sQuery = (string)$oXml->sql;
|
||||
|
||||
$aColumns = array();
|
||||
if (isset($oXml->column))
|
||||
{
|
||||
foreach ($oXml->column AS $oColumnData)
|
||||
{
|
||||
if (!isset($oColumnData->name))
|
||||
{
|
||||
throw new Exception("Missing tag 'name' in sqlblock/column");
|
||||
}
|
||||
$sName = (string) $oColumnData->name;
|
||||
if (strlen($sName) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'name' in sqlblock/column");
|
||||
}
|
||||
|
||||
$aColumns[$sName] = array();
|
||||
if (isset($oColumnData->label))
|
||||
{
|
||||
$sLabel = (string)$oColumnData->label;
|
||||
if (strlen($sLabel) > 0)
|
||||
{
|
||||
$aColumns[$sName]['label'] = Dict::S($sLabel);
|
||||
}
|
||||
}
|
||||
if (isset($oColumnData->drilldown))
|
||||
{
|
||||
$sDrillDown = (string)$oColumnData->drilldown;
|
||||
if (strlen($sDrillDown) > 0)
|
||||
{
|
||||
$aColumns[$sName]['drilldown'] = $sDrillDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$aParams = array();
|
||||
if (isset($oXml->parameter))
|
||||
{
|
||||
foreach ($oXml->parameter AS $oParamData)
|
||||
{
|
||||
if (!isset($oParamData->name))
|
||||
{
|
||||
throw new Exception("Missing tag 'name' for parameter in sqlblock/column");
|
||||
}
|
||||
$sName = (string) $oParamData->name;
|
||||
if (strlen($sName) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'name' for parameter in sqlblock/column");
|
||||
}
|
||||
if (!isset($oParamData->mapping))
|
||||
{
|
||||
throw new Exception("Missing tag 'mapping' for parameter in sqlblock/column");
|
||||
}
|
||||
$sMapping = (string) $oParamData->mapping;
|
||||
if (strlen($sMapping) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'mapping' for parameter in sqlblock/column");
|
||||
}
|
||||
|
||||
if (isset($oParamData->type))
|
||||
{
|
||||
$sParamType = $oParamData->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sParamType = 'context';
|
||||
}
|
||||
$aParams[$sName] = array('mapping' => $sMapping, 'type' => $sParamType);
|
||||
}
|
||||
}
|
||||
|
||||
return new SqlBlock($sQuery, $aColumns, $sTitle, $sType, $aParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the defined parameters into the SQL query
|
||||
* @return string the SQL query to execute
|
||||
*/
|
||||
public function BuildQuery()
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sQuery = $this->m_sQuery;
|
||||
$sQuery = str_replace('$DB_PREFIX$', MetaModel::GetConfig()->GetDBSubname(), $sQuery); // put the tables DB prefix (if any)
|
||||
foreach($this->m_aParams as $sName => $aParam)
|
||||
{
|
||||
if ($aParam['type'] == 'context')
|
||||
{
|
||||
$sSearchPattern = '/\$CONDITION\('.$sName.',([^\)]+)\)\$/';
|
||||
$value = $oAppContext->GetCurrentValue($aParam['mapping']);
|
||||
if (empty($value))
|
||||
{
|
||||
$sSQLExpr = '(1)';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Special case for managing the hierarchy of organizations
|
||||
if (($aParam['mapping'] == 'org_id') && ( MetaModel::IsValidClass('Organization')))
|
||||
{
|
||||
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
|
||||
if ($sHierarchicalKeyCode != false)
|
||||
{
|
||||
// organizations are in hierarchy... gather all the orgs below the given one...
|
||||
$sOQL = "SELECT Organization AS node JOIN Organization AS root ON node.$sHierarchicalKeyCode BELOW root.id WHERE root.id = :value";
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('value' => $value));
|
||||
$aOrgIds = array();
|
||||
while($oOrg = $oSet->Fetch())
|
||||
{
|
||||
$aOrgIds[]= $oOrg->GetKey();
|
||||
}
|
||||
$sSQLExpr = '($1 IN('.implode(',', $aOrgIds).'))';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
|
||||
}
|
||||
}
|
||||
$sQuery = preg_replace($sSearchPattern, $sSQLExpr, $sQuery);
|
||||
}
|
||||
}
|
||||
return $sQuery;
|
||||
}
|
||||
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
if (empty($aExtraParams['currentId']))
|
||||
{
|
||||
$sId = 'sqlblock_'.$oPage->GetUniqueId(); // Works only if the page is not an Ajax one !
|
||||
}
|
||||
else
|
||||
{
|
||||
$sId = $aExtraParams['currentId'];
|
||||
}
|
||||
// $oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId));
|
||||
|
||||
$sQuery = $this->BuildQuery();
|
||||
$res = CMDBSource::Query($sQuery);
|
||||
$aQueryCols = CMDBSource::GetColumns($res);
|
||||
|
||||
// Prepare column definitions (check + give default values)
|
||||
//
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
if (!in_array($sName, $aQueryCols))
|
||||
{
|
||||
throw new Exception("Unknown column name '$sName' in sqlblock column");
|
||||
}
|
||||
if (!isset($aColumnData['label']))
|
||||
{
|
||||
$this->m_aColumns[$sName]['label'] = $sName;
|
||||
}
|
||||
if (isset($aColumnData['drilldown']) && !empty($aColumnData['drilldown']))
|
||||
{
|
||||
// Check if the OQL is valid
|
||||
try
|
||||
{
|
||||
$this->m_aColumns[$sName]['filter'] = DBObjectSearch::FromOQL($aColumnData['drilldown']);
|
||||
}
|
||||
catch(OQLException $e)
|
||||
{
|
||||
unset($aColumnData['drilldown']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($this->m_sTitle) > 0)
|
||||
{
|
||||
$oPage->add("<h2>".Dict::S($this->m_sTitle)."</h2>\n");
|
||||
}
|
||||
|
||||
switch ($this->m_sType)
|
||||
{
|
||||
case 'bars':
|
||||
case 'pie':
|
||||
$aColNames = array_keys($this->m_aColumns);
|
||||
$sXColName = $aColNames[0];
|
||||
$sYColName = $aColNames[1];
|
||||
$aData = array();
|
||||
$aRows = array();
|
||||
while($aRow = CMDBSource::FetchArray($res))
|
||||
{
|
||||
$aData[$aRow[$sXColName]] = $aRow[$sYColName];
|
||||
$aRows[$aRow[$sXColName]] = $aRow;
|
||||
}
|
||||
$this->RenderChart($oPage, $sId, $aData, $this->m_aColumns[$sYColName]['drilldown'], $aRows);
|
||||
break;
|
||||
|
||||
default:
|
||||
case 'table':
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
if (!empty($sContext))
|
||||
{
|
||||
$sContext = '&'.$sContext;
|
||||
}
|
||||
$aDisplayConfig = array();
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
$aDisplayConfig[$sName] = array('label' => $aColumnData['label'], 'description' => '');
|
||||
}
|
||||
|
||||
$aDisplayData = array();
|
||||
while($aRow = CMDBSource::FetchArray($res))
|
||||
{
|
||||
$aSQLColNames = array_keys($aRow);
|
||||
$aDisplayRow = array();
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
if (isset($aColumnData['filter']))
|
||||
{
|
||||
$sFilter = $aColumnData['drilldown'];
|
||||
$sClass = $aColumnData['filter']->GetClass();
|
||||
$sFilter = str_replace('SELECT '.$sClass, '', $sFilter);
|
||||
foreach($aSQLColNames as $sColName)
|
||||
{
|
||||
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRow[$sColName] )."'", $sFilter);
|
||||
}
|
||||
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&oql_clause='.urlencode($sFilter).'&format=html'.$sContext;
|
||||
$aDisplayRow[$sName] = '<a href="'.$sURL.'">'.$aRow[$sName]."</a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$aDisplayRow[$sName] = $aRow[$sName];
|
||||
}
|
||||
}
|
||||
$aDisplayData[] = $aDisplayRow;
|
||||
}
|
||||
$oPage->table($aDisplayConfig, $aDisplayData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
protected function RenderChart($oPage, $sId, $aValues, $sDrillDown = '', $aRows = array())
|
||||
{
|
||||
// 1- Compute Open Flash Chart data
|
||||
//
|
||||
$aValueKeys = array();
|
||||
$index = 0;
|
||||
if ((count($aValues) > 0) && ($sDrillDown != ''))
|
||||
{
|
||||
$oFilter = DBObjectSearch::FromOQL($sDrillDown);
|
||||
$sClass = $oFilter->GetClass();
|
||||
$sOQLClause = str_replace('SELECT '.$sClass, '', $sDrillDown);
|
||||
$aSQLColNames = array_keys(current($aRows)); // Read the list of columns from the current (i.e. first) element of the array
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&format=html&'.$oAppContext->GetForLink().'&oql_clause=';
|
||||
}
|
||||
$aURLs = array();
|
||||
foreach($aValues as $key => $value)
|
||||
{
|
||||
// Make sure that values are integers (so that max() will work....)
|
||||
// and build an array of STRING with the keys (numeric keys are transformed into string by PHP :-(
|
||||
$aValues[$key] = (int)$value;
|
||||
$aValueKeys[] = (string)$key;
|
||||
|
||||
// Build the custom query for the 'drill down' on each element
|
||||
if ($sDrillDown != '')
|
||||
{
|
||||
$sFilter = $sOQLClause;
|
||||
foreach($aSQLColNames as $sColName)
|
||||
{
|
||||
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRows[$key][$sColName] )."'", $sFilter);
|
||||
$aURLs[$index] = $sURL.urlencode($sFilter);
|
||||
}
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
|
||||
$oChart = new open_flash_chart();
|
||||
|
||||
if ($this->m_sType == 'bars')
|
||||
{
|
||||
$oChartElement = new bar_glass();
|
||||
|
||||
if (count($aValues) > 0)
|
||||
{
|
||||
$maxValue = max($aValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
$maxValue = 1;
|
||||
}
|
||||
$oYAxis = new y_axis();
|
||||
$aMagicValues = array(1,2,5,10);
|
||||
$iMultiplier = 1;
|
||||
$index = 0;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
while($maxValue > $iTop)
|
||||
{
|
||||
$index++;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
if (($index % count($aMagicValues)) == 0)
|
||||
{
|
||||
$iMultiplier = $iMultiplier * 10;
|
||||
}
|
||||
}
|
||||
//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
|
||||
$oYAxis->set_range(0, $iTop, $iMultiplier);
|
||||
$oChart->set_y_axis( $oYAxis );
|
||||
$aBarValues = array();
|
||||
foreach($aValues as $iValue)
|
||||
{
|
||||
$oBarValue = new bar_value($iValue);
|
||||
$oBarValue->on_click("ofc_drilldown_{$sId}");
|
||||
$aBarValues[] = $oBarValue;
|
||||
}
|
||||
$oChartElement->set_values($aBarValues);
|
||||
//$oChartElement->set_values(array_values($aValues));
|
||||
$oXAxis = new x_axis();
|
||||
$oXLabels = new x_axis_labels();
|
||||
// set them vertical
|
||||
$oXLabels->set_vertical();
|
||||
// set the label text
|
||||
$oXLabels->set_labels($aValueKeys);
|
||||
// Add the X Axis Labels to the X Axis
|
||||
$oXAxis->set_labels( $oXLabels );
|
||||
$oChart->set_x_axis( $oXAxis );
|
||||
}
|
||||
else
|
||||
{
|
||||
$oChartElement = new pie();
|
||||
$oChartElement->set_start_angle( 35 );
|
||||
$oChartElement->set_animate( true );
|
||||
$oChartElement->set_tooltip( '#label# - #val# (#percent#)' );
|
||||
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
|
||||
|
||||
$aData = array();
|
||||
foreach($aValues as $sValue => $iValue)
|
||||
{
|
||||
$oPieValue = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!!
|
||||
$oPieValue->on_click("ofc_drilldown_{$sId}");
|
||||
$aData[] = $oPieValue;
|
||||
}
|
||||
|
||||
$oChartElement->set_values( $aData );
|
||||
$oChart->x_axis = null;
|
||||
}
|
||||
|
||||
// Title given in HTML
|
||||
//$oTitle = new title($this->m_sTitle);
|
||||
//$oChart->set_title($oTitle);
|
||||
$oChart->set_bg_colour('#FFFFFF');
|
||||
$oChart->add_element( $oChartElement );
|
||||
|
||||
$sData = $oChart->toPrettyString();
|
||||
$sData = json_encode($sData);
|
||||
|
||||
// 2- Declare the Javascript function that will render the chart data\
|
||||
//
|
||||
$oPage->add_script(
|
||||
<<< EOF
|
||||
function ofc_get_data_{$sId}()
|
||||
{
|
||||
return $sData;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
if (count($aURLs) > 0)
|
||||
{
|
||||
$sURLList = '';
|
||||
foreach($aURLs as $index => $sURL)
|
||||
{
|
||||
$sURLList .= "\taURLs[$index] = '".addslashes($sURL)."';\n";
|
||||
}
|
||||
|
||||
$oPage->add_script(
|
||||
<<< EOF
|
||||
function ofc_drilldown_{$sId}(index)
|
||||
{
|
||||
var aURLs = new Array();
|
||||
{$sURLList}
|
||||
var sURL = aURLs[index];
|
||||
|
||||
window.location.href = sURL; // Navigate !
|
||||
}
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
// 3- Insert the Open Flash chart
|
||||
//
|
||||
$oPage->add("<div id=\"$sId\"><div>\n");
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
swfobject.embedSWF( "../images/open-flash-chart.swf",
|
||||
"{$sId}",
|
||||
"100%", "300","9.0.0",
|
||||
"expressInstall.swf",
|
||||
{"get-data":"ofc_get_data_{$sId}", "id":"{$sId}"},
|
||||
{'wmode': 'transparent'}
|
||||
);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,18 +20,20 @@
|
||||
/**
|
||||
* File to include to initialize the datamodel in memory
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
require_once(APPROOT.'/core/contexttag.class.inc.php');
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
session_start();
|
||||
if (isset($_REQUEST['switch_env']))
|
||||
$sSwitchEnv = utils::ReadParam('switch_env', null);
|
||||
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)))
|
||||
{
|
||||
$sEnv = $_REQUEST['switch_env'];
|
||||
$_SESSION['itop_env'] = $sEnv;
|
||||
$_SESSION['itop_env'] = $sSwitchEnv;
|
||||
$sEnv = $sSwitchEnv;
|
||||
// TODO: reset the credentials as well ??
|
||||
}
|
||||
else if (isset($_SESSION['itop_env']))
|
||||
@@ -44,6 +46,4 @@ else
|
||||
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
|
||||
}
|
||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile);
|
||||
|
||||
?>
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -54,7 +54,7 @@
|
||||
* | | +--------+ +-----+ | |
|
||||
* | +--------------------------------------------+ |
|
||||
* +------------------------------------------------+
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -149,7 +149,7 @@ class UIExtKeyWidget
|
||||
case 'radio':
|
||||
case 'radio_horizontal':
|
||||
case 'radio_vertical':
|
||||
$sValidationField = "<span id=\"v_{$this->iId}\"></span>";
|
||||
$sValidationField = "<span id=\"v_{$this->iId}\"></span><span id=\"fstatus_{$this->iId}\"></span>";
|
||||
$sHTMLValue = '';
|
||||
$bVertical = ($sDisplayStyle != 'radio_horizontal');
|
||||
$bExtensions = false;
|
||||
@@ -305,7 +305,7 @@ EOF
|
||||
}
|
||||
if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
|
||||
{
|
||||
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span>";
|
||||
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span><span id=\"fstatus_{$this->iId}\"></span>";
|
||||
}
|
||||
$sHTMLValue .= "</span>"; // end of no wrap
|
||||
return $sHTMLValue;
|
||||
@@ -328,9 +328,10 @@ 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' => true, 'currentId' => $this->iId));
|
||||
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => $bOpen, 'currentId' => $this->iId));
|
||||
$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
|
||||
$sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHTML .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
@@ -451,7 +452,17 @@ EOF
|
||||
$sDialogTitle = '';
|
||||
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
|
||||
$oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n");
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true));
|
||||
$aFieldsFlags = array();
|
||||
$aFieldsComments = array();
|
||||
foreach(MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (($oAttDef instanceof AttributeBlob) || (false))
|
||||
{
|
||||
$aFieldsFlags[$sAttCode] = OPT_ATT_READONLY;
|
||||
$aFieldsComments[$sAttCode] = ' <img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
|
||||
}
|
||||
}
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
|
||||
$oPage->add('</div></div></div>');
|
||||
// $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
@@ -505,16 +516,23 @@ EOF
|
||||
*/
|
||||
public function DoCreateObject($oPage)
|
||||
{
|
||||
$oObj = MetaModel::NewObject($this->sTargetClass);
|
||||
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
|
||||
if (count($aErrors) == 0)
|
||||
try
|
||||
{
|
||||
$oObj->DBInsert();
|
||||
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
|
||||
$oObj = MetaModel::NewObject($this->sTargetClass);
|
||||
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
|
||||
if (count($aErrors) == 0)
|
||||
{
|
||||
$oObj->DBInsert();
|
||||
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
|
||||
}
|
||||
else
|
||||
{
|
||||
return array('error' => implode(' ', $aErrors), 'id' => 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch(Exception $e)
|
||||
{
|
||||
return array('name' => implode(' ', $aErrors), 'id' => 0);
|
||||
return array('error' => $e->getMessage(), 'id' => 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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')); } );\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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -89,7 +89,7 @@ class UILinksWidgetDirect
|
||||
$sDefault = "default[$sExtKeyToMe]=".$oCurrentObj->GetKey();
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sParams = $oAppContext->GetForLink();
|
||||
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
|
||||
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams&{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
|
||||
}
|
||||
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
|
||||
break;
|
||||
@@ -262,8 +262,9 @@ class UILinksWidgetDirect
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
}
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => true));
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => $bOpen));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->sInputid}\">\n";
|
||||
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->sInputid}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
@@ -294,7 +295,7 @@ class UILinksWidgetDirect
|
||||
$valuesDef = $oLinksetDef->GetValuesDef();
|
||||
if ($valuesDef === null)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($this->sLinkedClass);
|
||||
$oFilter = new DBObjectSearch($sRemoteClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -163,38 +163,10 @@ class UILinksWidget
|
||||
$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
|
||||
});
|
||||
PrepareWidgets();
|
||||
EOF
|
||||
);
|
||||
}
|
||||
@@ -356,11 +328,12 @@ EOF
|
||||
|
||||
public function GetObjectPickerDialog($oPage, $oCurrentObj)
|
||||
{
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true));
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => $bOpen));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\">\n";
|
||||
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
|
||||
@@ -58,7 +58,7 @@ class UIPasswordWidget
|
||||
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
|
||||
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
|
||||
$sHtmlValue = '';
|
||||
$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/> <span class="form_validation" id="v_'.$this->iId.'"></span><br/>';
|
||||
$sHtmlValue = '<input type="password" maxlength="255" 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 .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
use Html2Text\Html2Text;
|
||||
use Leafo\ScssPhp\Compiler;
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,12 +22,14 @@
|
||||
/**
|
||||
* Static class utils
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'/core/config.class.inc.php');
|
||||
require_once(APPROOT.'/application/transaction.class.inc.php');
|
||||
require_once(APPROOT.'application/Html2Text.php');
|
||||
require_once(APPROOT.'application/Html2TextException.php');
|
||||
|
||||
define('ITOP_CONFIG_FILE', 'config-itop.php');
|
||||
define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE);
|
||||
@@ -284,7 +288,7 @@ class utils
|
||||
$rInfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
|
||||
if ($rInfo !== false)
|
||||
{
|
||||
$sType = @finfo_file($rInfo, $file);
|
||||
$sType = @finfo_file($rInfo, $sTmpName);
|
||||
if ( ($sType !== false)
|
||||
&& is_string($sType)
|
||||
&& (strlen($sType)>0))
|
||||
@@ -383,7 +387,23 @@ class utils
|
||||
{
|
||||
return privUITransaction::RemoveTransaction($sId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a unique tmp id for the current upload based on the transaction system (db).
|
||||
*
|
||||
* Build as session_id() . '_' . static::GetNewTransactionId()
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function GetUploadTempId($sTransactionId = null)
|
||||
{
|
||||
if ($sTransactionId === null)
|
||||
{
|
||||
$sTransactionId = static::GetNewTransactionId();
|
||||
}
|
||||
return session_id() . '_' . $sTransactionId;
|
||||
}
|
||||
|
||||
public static function ReadFromFile($sFileName)
|
||||
{
|
||||
if (!file_exists($sFileName)) return false;
|
||||
@@ -417,6 +437,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')
|
||||
@@ -463,6 +522,19 @@ class utils
|
||||
}
|
||||
// http://www.spaweditor.com/scripts/regex/index.php
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an old date/time format specifciation (using % placeholders)
|
||||
* to a format compatible with DateTime::createFromFormat
|
||||
* @param string $sOldDateTimeFormat
|
||||
* @return string
|
||||
*/
|
||||
static public function DateTimeFormatToPHP($sOldDateTimeFormat)
|
||||
{
|
||||
$aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s');
|
||||
$aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's');
|
||||
return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat);
|
||||
}
|
||||
|
||||
static public function GetConfig()
|
||||
{
|
||||
@@ -491,7 +563,11 @@ class utils
|
||||
if ($sUrl === null)
|
||||
{
|
||||
$sUrl = self::GetConfig()->Get('app_root_url');
|
||||
if (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
|
||||
if ($sUrl == '')
|
||||
{
|
||||
$sUrl = self::GetDefaultUrlAppRoot();
|
||||
}
|
||||
elseif (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
|
||||
{
|
||||
if (isset($_SERVER['SERVER_NAME']))
|
||||
{
|
||||
@@ -768,7 +844,16 @@ class utils
|
||||
return ITOP_DEFAULT_ENV;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a path to a folder into which any module can store cache data
|
||||
* The corresponding folder is created or cleaned upon code compilation
|
||||
* @return string
|
||||
*/
|
||||
public static function GetCachePath()
|
||||
{
|
||||
return APPROOT.'data/cache-'.self::GetCurrentEnvironment().'/';
|
||||
}
|
||||
/**
|
||||
* Merge standard menu items with plugin provided menus items
|
||||
*/
|
||||
@@ -802,7 +887,11 @@ class utils
|
||||
// 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')).")");
|
||||
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')");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
|
||||
@@ -1121,4 +1210,191 @@ class utils
|
||||
asort($aPossibleEncodings);
|
||||
return $aPossibleEncodings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string containing some (valid) HTML markup to plain text
|
||||
* @param string $sHtml
|
||||
* @return string
|
||||
*/
|
||||
public static function HtmlToText($sHtml)
|
||||
{
|
||||
try
|
||||
{
|
||||
//return '<?xml encoding="UTF-8">'.$sHtml;
|
||||
return \Html2Text\Html2Text::convert('<?xml encoding="UTF-8">'.$sHtml);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags
|
||||
* and escaping HTML entities
|
||||
* @param string $sText
|
||||
* @return string
|
||||
*/
|
||||
public static function TextToHtml($sText)
|
||||
{
|
||||
$sText = str_replace("\r\n", "\n", $sText);
|
||||
$sText = str_replace("\r", "\n", $sText);
|
||||
return str_replace("\n", '<br/>', htmlentities($sText, ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Eventually compiles the SASS (.scss) file into the CSS (.css) file
|
||||
*
|
||||
* @param string $sSassRelPath Relative path to the SCSS file (must have the extension .scss)
|
||||
* @param array $aImportPaths Array of absolute paths to load imports from
|
||||
* @return string Relative path to the CSS file (<name>.css)
|
||||
*/
|
||||
static public function GetCSSFromSASS($sSassRelPath, $aImportPaths = null)
|
||||
{
|
||||
// Avoiding compilation if file is already a css file.
|
||||
if (preg_match('/\.css$/', $sSassRelPath))
|
||||
{
|
||||
return $sSassRelPath;
|
||||
}
|
||||
|
||||
// Setting import paths
|
||||
if ($aImportPaths === null)
|
||||
{
|
||||
$aImportPaths = array();
|
||||
}
|
||||
$aImportPaths[] = APPROOT . '/css';
|
||||
|
||||
$sSassPath = APPROOT.$sSassRelPath;
|
||||
$sCssRelPath = preg_replace('/\.scss$/', '.css', $sSassRelPath);
|
||||
$sCssPath = APPROOT.$sCssRelPath;
|
||||
clearstatcache();
|
||||
if (!file_exists($sCssPath) || (is_writable($sCssPath) && (filemtime($sCssPath) < filemtime($sSassPath))))
|
||||
{
|
||||
require_once(APPROOT.'lib/scssphp/scss.inc.php');
|
||||
$oScss = new Compiler();
|
||||
$oScss->setImportPaths($aImportPaths);
|
||||
$oScss->setFormatter('Leafo\\ScssPhp\\Formatter\\Expanded');
|
||||
// Temporary disabling max exec time while compiling
|
||||
$iCurrentMaxExecTime = (int) ini_get('max_execution_time');
|
||||
set_time_limit(0);
|
||||
$sCss = $oScss->compile(file_get_contents($sSassPath));
|
||||
set_time_limit($iCurrentMaxExecTime);
|
||||
file_put_contents($sCssPath, $sCss);
|
||||
}
|
||||
return $sCssRelPath;
|
||||
}
|
||||
|
||||
static public function GetImageSize($sImageData)
|
||||
{
|
||||
if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher
|
||||
{
|
||||
$aRet = @getimagesizefromstring($sImageData);
|
||||
}
|
||||
else if(ini_get('allow_url_fopen'))
|
||||
{
|
||||
// work around to avoid creating a tmp file
|
||||
$sUri = 'data://application/octet-stream;base64,'.base64_encode($sImageData);
|
||||
$aRet = @getimagesize($sUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Damned, need to create a tmp file
|
||||
$sTempFile = tempnam(SetupUtils::GetTmpDir(), 'img-');
|
||||
@file_put_contents($sTempFile, $sImageData);
|
||||
$aRet = @getimagesize($sTempFile);
|
||||
@unlink($sTempFile);
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize an image attachment so that it fits in the given dimensions
|
||||
* @param ormDocument $oImage The original image stored as an ormDocument
|
||||
* @param int $iWidth Image's original width
|
||||
* @param int $iHeight Image's original height
|
||||
* @param int $iMaxImageWidth Maximum width for the resized image
|
||||
* @param int $iMaxImageHeight Maximum height for the resized image
|
||||
* @return ormDocument The resampled image
|
||||
*/
|
||||
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':
|
||||
case 'image/jpeg':
|
||||
case 'image/png':
|
||||
$img = @imagecreatefromstring($oImage->GetData());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unsupported image type, return the image as-is
|
||||
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
|
||||
return $oImage;
|
||||
}
|
||||
if ($img === false)
|
||||
{
|
||||
//throw new Exception("Warning: corrupted image: '".$oImage->GetFileName()." / ".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
|
||||
return $oImage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's scale the image, preserving the transparency for GIFs and PNGs
|
||||
|
||||
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
|
||||
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
|
||||
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
|
||||
|
||||
// Preserve transparency
|
||||
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
|
||||
{
|
||||
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
|
||||
imagealphablending($new, false);
|
||||
imagesavealpha($new, true);
|
||||
}
|
||||
|
||||
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
|
||||
|
||||
ob_start();
|
||||
switch ($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
imagegif($new); // send image to output buffer
|
||||
break;
|
||||
|
||||
case 'image/jpeg':
|
||||
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
|
||||
break;
|
||||
|
||||
case 'image/png':
|
||||
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
|
||||
break;
|
||||
}
|
||||
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
|
||||
@ob_end_clean();
|
||||
|
||||
imagedestroy($img);
|
||||
imagedestroy($new);
|
||||
|
||||
return $oResampledImage;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,33 +270,19 @@ class WebPage implements Page
|
||||
{
|
||||
$this->a_linked_stylesheets[] = array( 'link' => $s_linked_stylesheet, 'condition' => $s_condition);
|
||||
}
|
||||
|
||||
public function add_saas($sSaasRelPath)
|
||||
{
|
||||
$sSaasPath = APPROOT.$sSaasRelPath;
|
||||
$sCssRelPath = preg_replace('/\.scss$/', '.css', $sSaasRelPath);
|
||||
$sCssPath = APPROOT.$sCssRelPath;
|
||||
clearstatcache();
|
||||
if (!file_exists($sCssPath) || (is_writable($sCssPath) && (filemtime($sCssPath) < filemtime($sSaasPath))))
|
||||
{
|
||||
// Rebuild the CSS file from the Saas file
|
||||
if (file_exists(APPROOT.'lib/sass/sass/SassParser.php'))
|
||||
{
|
||||
require_once(APPROOT.'lib/sass/sass/SassParser.php'); //including Sass libary (Syntactically Awesome Stylesheets)
|
||||
$oParser = new SassParser(array('style'=>'expanded'));
|
||||
$sCss = $oParser->toCss($sSaasPath);
|
||||
file_put_contents($sCssPath, $sCss);
|
||||
}
|
||||
}
|
||||
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
if ($sRootUrl === '')
|
||||
{
|
||||
// We're running the setup of the first install...
|
||||
$sRootUrl = '../';
|
||||
}
|
||||
$sCSSUrl = $sRootUrl.$sCssRelPath;
|
||||
$this->add_linked_stylesheet($sCSSUrl);
|
||||
}
|
||||
|
||||
public function add_saas($sSaasRelPath)
|
||||
{
|
||||
$sCssRelPath = utils::GetCSSFromSASS($sSaasRelPath);
|
||||
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
if ($sRootUrl === '')
|
||||
{
|
||||
// We're running the setup of the first install...
|
||||
$sRootUrl = '../';
|
||||
}
|
||||
$sCSSUrl = $sRootUrl.$sCssRelPath;
|
||||
$this->add_linked_stylesheet($sCSSUrl);
|
||||
}
|
||||
/**
|
||||
* Add some custom header to the page
|
||||
*/
|
||||
@@ -473,7 +459,7 @@ class WebPage implements Page
|
||||
{
|
||||
if (Utils::GetConfig() && Utils::GetConfig()->Get('debug_report_spurious_chars'))
|
||||
{
|
||||
IssueLog::Error("Trashing unexpected output:'$s_captured_output'\n");
|
||||
IssueLog::Error("Trashing unexpected output:'$sOutput'\n");
|
||||
}
|
||||
}
|
||||
$sOutput = '';
|
||||
@@ -497,6 +483,7 @@ class WebPage implements Page
|
||||
echo "<html>\n";
|
||||
echo "<head>\n";
|
||||
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
|
||||
echo "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\" />";
|
||||
echo "<title>".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."</title>\n";
|
||||
echo $this->get_base_tag();
|
||||
foreach($this->a_linked_scripts as $s_script)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class WizardHelper
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,7 @@ class WizardHelper
|
||||
}
|
||||
/**
|
||||
* Constructs the PHP target object from the parameters sent to the web page by the wizard
|
||||
* @param boolean $bReadUploadedFiles True to also ready any uploaded file (for blob/document fields)
|
||||
* @param boolean $bReadUploadedFiles True to also read any uploaded file (for blob/document fields)
|
||||
* @return object
|
||||
*/
|
||||
public function GetTargetObject($bReadUploadedFiles = false)
|
||||
@@ -52,7 +52,7 @@ class WizardHelper
|
||||
{
|
||||
// Because this is stored in a Javascript array, unused indexes
|
||||
// are filled with null values and unused keys (stored as strings) contain $$NULL$$
|
||||
if ( ($sAttCode !='id') && ($sAttCode !== false) && ($value !== null) && ($value !== '$$NULL$$'))
|
||||
if ( ($sAttCode !='id') && ($value !== '$$NULL$$'))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode);
|
||||
if (($oAttDef->IsLinkSet()) && ($value != '') )
|
||||
@@ -109,6 +109,20 @@ class WizardHelper
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
else if ( $oAttDef->GetEditClass() == 'Image' )
|
||||
{
|
||||
if ($bReadUploadedFiles)
|
||||
{
|
||||
$oDocument = utils::ReadPostedDocument('attr_'.$sAttCode, 'fcontents');
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new empty document, just for displaying the file name
|
||||
$oDocument = new ormDocument(null, '', $value);
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
else if (($oAttDef->IsExternalKey()) && (!empty($value)) && ($value > 0) )
|
||||
{
|
||||
// For external keys: load the target object so that external fields
|
||||
@@ -124,6 +138,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);
|
||||
@@ -284,4 +314,3 @@ class WizardHelper
|
||||
return $oSet;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -13,6 +13,8 @@ Class XLSXWriter
|
||||
protected $shared_strings = array();//unique set
|
||||
protected $shared_string_count = 0;//count of non-unique references to the unique set
|
||||
protected $temp_files = array();
|
||||
protected $date_format = 'YYYY-MM-DD';
|
||||
protected $date_time_format = 'YYYY-MM-DD\ HH:MM:SS';
|
||||
|
||||
public function __construct(){}
|
||||
public function setAuthor($author='') { $this->author=$author; }
|
||||
@@ -26,6 +28,16 @@ Class XLSXWriter
|
||||
}
|
||||
}
|
||||
|
||||
public function setDateFormat($date_format)
|
||||
{
|
||||
$this->date_format = $date_format;
|
||||
}
|
||||
|
||||
public function setDateTimeFormat($date_time_format)
|
||||
{
|
||||
$this->date_time_format = $date_time_format;
|
||||
}
|
||||
|
||||
protected function tempFilename()
|
||||
{
|
||||
$filename = tempnam("/tmp", "xlsx_writer_");
|
||||
@@ -152,18 +164,14 @@ Class XLSXWriter
|
||||
{
|
||||
static $styles = array('money'=>1,'dollar'=>1,'datetime'=>2,'date'=>3,'string'=>0);
|
||||
$cell = self::xlsCell($row_number, $column_number);
|
||||
$s = isset($styles[$cell_format]) ? $styles[$cell_format] : '0';
|
||||
$s = isset($styles[$cell_format]) && ($value !== '') ? $styles[$cell_format] : '0';
|
||||
|
||||
if (is_numeric($value)) {
|
||||
if (is_int($value) || is_float($value)) {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.($value*1).'</v></c>');//int,float, etc
|
||||
} else if ($cell_format=='date') {
|
||||
} else if (($cell_format=='date') && ($value != '')) {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.intval(self::convert_date_time($value)).'</v></c>');
|
||||
} else if ($cell_format=='datetime') {
|
||||
if ($value === '') {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="0"/>');
|
||||
} else {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.self::convert_date_time($value).'</v></c>');
|
||||
}
|
||||
} else if (($cell_format=='datetime') && ($value != '')) {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.self::convert_date_time($value).'</v></c>');
|
||||
} else if ($value==''){
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'"/>');
|
||||
} else if ($value{0}=='='){
|
||||
@@ -183,8 +191,8 @@ Class XLSXWriter
|
||||
fwrite($fd, '<numFmts count="4">');
|
||||
fwrite($fd, '<numFmt formatCode="GENERAL" numFmtId="164"/>');
|
||||
fwrite($fd, '<numFmt formatCode="[$$-1009]#,##0.00;[RED]\-[$$-1009]#,##0.00" numFmtId="165"/>');
|
||||
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD\ HH:MM:SS" numFmtId="166"/>');
|
||||
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD" numFmtId="167"/>');
|
||||
fwrite($fd, '<numFmt formatCode="'.$this->date_time_format.'" numFmtId="166"/>');
|
||||
fwrite($fd, '<numFmt formatCode="'.$this->date_format.'" numFmtId="167"/>');
|
||||
fwrite($fd, '</numFmts>');
|
||||
fwrite($fd, '<fonts count="4">');
|
||||
fwrite($fd, '<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Persistent classes (internal): user defined actions
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -157,7 +157,7 @@ class ActionEmail extends ActionNotification
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeTemplateText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeTemplateHTML("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
@@ -262,8 +262,10 @@ class ActionEmail extends ActionNotification
|
||||
{
|
||||
$sPrefix = '';
|
||||
}
|
||||
$oLog->Set('message', $sPrefix.$sRes);
|
||||
|
||||
if ($oLog)
|
||||
{
|
||||
$oLog->Set('message', $sPrefix . $sRes);
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
@@ -322,6 +324,8 @@ class ActionEmail extends ActionNotification
|
||||
if (isset($sSubject)) $oLog->Set('subject', $sSubject);
|
||||
if (isset($sBody)) $oLog->Set('body', $sBody);
|
||||
}
|
||||
$sStyles = file_get_contents(APPROOT.'css/email.css');
|
||||
$sStyles .= MetaModel::GetConfig()->Get('email_css');
|
||||
|
||||
$oEmail = new EMail();
|
||||
|
||||
@@ -342,7 +346,7 @@ class ActionEmail extends ActionNotification
|
||||
$sTestBody .= "</ul>\n";
|
||||
$sTestBody .= "</p>\n";
|
||||
$sTestBody .= "</div>\n";
|
||||
$oEmail->SetBody($sTestBody);
|
||||
$oEmail->SetBody($sTestBody, 'text/html', $sStyles);
|
||||
$oEmail->SetRecipientTO($this->Get('test_recipient'));
|
||||
$oEmail->SetRecipientFrom($this->Get('test_recipient'));
|
||||
$oEmail->SetReferences($sReference);
|
||||
@@ -351,7 +355,7 @@ class ActionEmail extends ActionNotification
|
||||
else
|
||||
{
|
||||
$oEmail->SetSubject($sSubject);
|
||||
$oEmail->SetBody($sBody);
|
||||
$oEmail->SetBody($sBody, 'text/html', $sStyles);
|
||||
$oEmail->SetRecipientTO($sTo);
|
||||
$oEmail->SetRecipientCC($sCC);
|
||||
$oEmail->SetRecipientBCC($sBCC);
|
||||
|
||||
69
core/apc-compat.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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/>
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class ExecAsyncTask implements iBackgroundProcess
|
||||
|
||||
public function Process($iTimeLimit)
|
||||
{
|
||||
$sNow = date('Y-m-d H:i:s');
|
||||
$sNow = date(AttributeDateTime::GetSQLFormat());
|
||||
// Criteria: planned, and expected to occur... ASAP or in the past
|
||||
$sOQL = "SELECT AsyncTask WHERE (status = 'planned') AND (ISNULL(planned) OR (planned < '$sNow'))";
|
||||
$iProcessed = 0;
|
||||
|
||||
38
core/autoload.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
MetaModel::IncludeModule('application/transaction.class.inc.php');
|
||||
MetaModel::IncludeModule('application/menunode.class.inc.php');
|
||||
MetaModel::IncludeModule('application/user.preferences.class.inc.php');
|
||||
MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('core/event.class.inc.php');
|
||||
MetaModel::IncludeModule('core/action.class.inc.php');
|
||||
MetaModel::IncludeModule('core/trigger.class.inc.php');
|
||||
MetaModel::IncludeModule('core/bulkexport.class.inc.php');
|
||||
MetaModel::IncludeModule('core/ownershiplock.class.inc.php');
|
||||
MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php');
|
||||
MetaModel::IncludeModule('core/backgroundtask.class.inc.php');
|
||||
MetaModel::IncludeModule('core/inlineimage.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('webservices/webservices.basic.php');
|
||||
|
||||
//MetaModel::IncludeModule('addons', 'user rights', 'addons/userrights/userrightsprofile.class.inc.php');
|
||||
@@ -258,7 +258,7 @@ class BulkChange
|
||||
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
|
||||
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
|
||||
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
|
||||
protected $m_sDateFormat; // Date format specification, see utils::StringToTime()
|
||||
protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat
|
||||
protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum)
|
||||
protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias
|
||||
|
||||
@@ -554,7 +554,15 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
// By default... nothing happens
|
||||
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$aResults[$iCol]= new CellStatus_Void($oAttDef->GetFormat()->Format($aRowData[$iCol]));
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -795,26 +803,55 @@ class BulkChange
|
||||
|
||||
if (!is_null($this->m_sDateFormat) && (strlen($this->m_sDateFormat) > 0))
|
||||
{
|
||||
$sDateTimeFormat = $this->m_sDateFormat; // the specified format is actually the date AND time format
|
||||
$oDateTimeFormat = new DateTimeFormat($sDateTimeFormat);
|
||||
$sDateFormat = $oDateTimeFormat->ToDateFormat();
|
||||
AttributeDateTime::SetFormat($oDateTimeFormat);
|
||||
AttributeDate::SetFormat(new DateTimeFormat($sDateFormat));
|
||||
// Translate dates from the source data
|
||||
//
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
{
|
||||
if ($sAttCode == 'id') continue;
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime)
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
foreach($this->m_aData as $iRow => $aRowData)
|
||||
{
|
||||
$sNewDate = utils::StringToTime($this->m_aData[$iRow][$iCol], $this->m_sDateFormat);
|
||||
if ($sNewDate !== false)
|
||||
$sFormat = $sDateTimeFormat;
|
||||
$sValue = $this->m_aData[$iRow][$iCol];
|
||||
if (!empty($sValue))
|
||||
{
|
||||
// Todo - improve the reporting
|
||||
$this->m_aData[$iRow][$iCol] = $sNewDate;
|
||||
if ($oAttDef instanceof AttributeDate)
|
||||
{
|
||||
$sFormat = $sDateFormat;
|
||||
}
|
||||
$oFormat = new DateTimeFormat($sFormat);
|
||||
$sRegExp = $oFormat->ToRegExpr('/');
|
||||
if (!preg_match($sRegExp, $this->m_aData[$iRow][$iCol]))
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDate = DateTime::createFromFormat($sFormat, $this->m_aData[$iRow][$iCol]);
|
||||
if ($oDate !== false)
|
||||
{
|
||||
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
|
||||
$this->m_aData[$iRow][$iCol] = $sNewDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Leave the cell unchanged
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, $this->m_aData[$iRow][$iCol], Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Leave the cell unchanged
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, $this->m_aData[$iRow][$iCol], Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$this->m_aData[$iRow][$iCol] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1173,7 +1210,7 @@ EOF
|
||||
{
|
||||
$sAttCode = $oOperation->Get('attcode');
|
||||
|
||||
if (get_class($oOperation) == 'CMDBChangeOpSetAttributeScalar')
|
||||
if ((get_class($oOperation) == 'CMDBChangeOpSetAttributeScalar') || (get_class($oOperation) == 'CMDBChangeOpSetAttributeURL'))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef->IsExternalKey())
|
||||
|
||||
@@ -95,7 +95,7 @@ class BulkExportResultGC implements iBackgroundProcess
|
||||
|
||||
public function Process($iTimeLimit)
|
||||
{
|
||||
$sDateLimit = date('Y-m-d H:i:s', time() - 24*3600); // Every BulkExportResult older than one day will be deleted
|
||||
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time() - 24*3600); // Every BulkExportResult older than one day will be deleted
|
||||
|
||||
$sOQL = "SELECT BulkExportResult WHERE created < '$sDateLimit'";
|
||||
$iProcessed = 0;
|
||||
@@ -412,7 +412,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-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Persistent classes (internal) : cmdbChangeOp and derived
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -242,6 +242,61 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Record the modification of an URL
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "",
|
||||
"name_attcode" => "change",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_changeop_setatt_url",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeURL("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeURL("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe (as a text string) the modifications corresponding to this change
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
$sResult = '';
|
||||
$oTargetObjectClass = $this->Get('objclass');
|
||||
$oTargetObjectKey = $this->Get('objkey');
|
||||
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
|
||||
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
|
||||
|
||||
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
|
||||
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes...
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
|
||||
$sAttName = $oAttDef->GetLabel();
|
||||
$sNewValue = $this->Get('newvalue');
|
||||
$sOldValue = $this->Get('oldvalue');
|
||||
$sResult = $oAttDef->DescribeChangeAsHTML($sOldValue, $sNewValue);
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the modification of a blob
|
||||
@@ -300,11 +355,19 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
$oPrevDoc = $this->Get('prevdata');
|
||||
$sDocView = $oPrevDoc->GetAsHtml();
|
||||
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_',$oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
|
||||
$sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n";
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView);
|
||||
if ($oPrevDoc->IsEmpty())
|
||||
{
|
||||
$sPrevious = '';
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sPrevious);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDocView = $oPrevDoc->GetAsHtml();
|
||||
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_', $oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
|
||||
$sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n";
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView);
|
||||
}
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
@@ -529,9 +592,6 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
// Temporary, until we change the options of GetDescription() -needs a more global revision
|
||||
$bIsHtml = true;
|
||||
|
||||
$sResult = '';
|
||||
$oTargetObjectClass = $this->Get('objclass');
|
||||
$oTargetObjectKey = $this->Get('objkey');
|
||||
@@ -560,6 +620,66 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the modification of a multiline string (text) containing some HTML markup
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "",
|
||||
"name_attcode" => "change",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_changeop_setatt_html",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
|
||||
}
|
||||
/**
|
||||
* Describe (as a text string) the modifications corresponding to this change
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
$sResult = '';
|
||||
$oTargetObjectClass = $this->Get('objclass');
|
||||
$oTargetObjectKey = $this->Get('objkey');
|
||||
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
|
||||
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
|
||||
|
||||
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
|
||||
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
|
||||
{
|
||||
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
|
||||
$sAttName = $oAttDef->GetLabel();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The attribute was renamed or removed from the object ?
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
$sTextView = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$this->Get('prevdata').'</div></div>';
|
||||
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView);
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the modification of a caselog (text)
|
||||
* since the caselog itself stores the history
|
||||
@@ -622,27 +742,8 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
|
||||
$oObj = $oMonoObjectSet->Fetch();
|
||||
$oCaseLog = $oObj->Get($this->Get('attcode'));
|
||||
$iMaxVisibleLength = MetaModel::getConfig()->Get('max_history_case_log_entry_length', 0);
|
||||
$sTextEntry = $oCaseLog->GetEntryAt($this->Get('lastentry'));
|
||||
if (($iMaxVisibleLength > 0) && (strlen($sTextEntry) > $iMaxVisibleLength))
|
||||
{
|
||||
if (function_exists('mb_strcut'))
|
||||
{
|
||||
// Safe with multi-byte strings
|
||||
$sBefore = $this->ToHtml(mb_strcut($sTextEntry, 0, $iMaxVisibleLength, 'UTF-8'));
|
||||
$sAfter = $this->ToHtml(mb_strcut($sTextEntry, $iMaxVisibleLength, null, 'UTF-8'));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's hope we have no multi-byte characters around the cuttting point...
|
||||
$sBefore = $this->ToHtml(substr($sTextEntry, 0, $iMaxVisibleLength));
|
||||
$sAfter = $this->ToHtml(substr($sTextEntry, $iMaxVisibleLength));
|
||||
}
|
||||
$sTextEntry = '<span class="case-log-history-entry">'.$sBefore.'<span class="case-log-history-entry-end">'.$sAfter.'<span class="case-log-history-entry-toggle ui-icon ui-icon-circle-minus"></span></span><span class="case-log-history-entry-more">...<span class="case-log-history-entry-toggle ui-icon ui-icon-circle-plus"></span></span></span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sTextEntry = $this->ToHtml($sTextEntry);
|
||||
}
|
||||
$sTextEntry = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$oCaseLog->GetEntryAt($this->Get('lastentry')).'</div></div>';
|
||||
|
||||
$sResult = Dict::Format('Change:AttName_EntryAdded', $sAttName, $sTextEntry);
|
||||
}
|
||||
return $sResult;
|
||||
@@ -872,4 +973,70 @@ class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
/**
|
||||
* Record the modification of custom fields
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "",
|
||||
"name_attcode" => "change",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_changeop_setatt_custfields",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeLongText("prevdata", array("allowed_values"=>null, "sql"=>"prevdata", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe (as a text string) the modifications corresponding to this change
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
$sResult = '';
|
||||
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
|
||||
{
|
||||
$oTargetObjectClass = $this->Get('objclass');
|
||||
$oTargetObjectKey = $this->Get('objkey');
|
||||
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
|
||||
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
|
||||
|
||||
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
|
||||
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
|
||||
{
|
||||
$aValues = json_decode($this->Get('prevdata'), true);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
|
||||
$sAttName = $oAttDef->GetLabel();
|
||||
|
||||
try
|
||||
{
|
||||
$oHandler = $oAttDef->GetHandler($aValues);
|
||||
$sValueDesc = $oHandler->GetAsHTML($aValues);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$sValueDesc = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
$sTextView = '<div>'.$sValueDesc.'</div>';
|
||||
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView);
|
||||
}
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class cmdbObject
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -46,18 +46,18 @@ require_once('stimulus.class.inc.php');
|
||||
require_once('valuesetdef.class.inc.php');
|
||||
require_once('MyHelpers.class.inc.php');
|
||||
|
||||
require_once('expression.class.inc.php');
|
||||
|
||||
require_once('cmdbsource.class.inc.php');
|
||||
require_once('sqlquery.class.inc.php');
|
||||
require_once('sqlobjectquery.class.inc.php');
|
||||
require_once('sqlunionquery.class.inc.php');
|
||||
require_once('oql/expression.class.inc.php');
|
||||
require_once('oql/oqlquery.class.inc.php');
|
||||
require_once('oql/oqlexception.class.inc.php');
|
||||
require_once('oql/oql-parser.php');
|
||||
require_once('oql/oql-lexer.php');
|
||||
require_once('oql/oqlinterpreter.class.inc.php');
|
||||
|
||||
require_once('cmdbsource.class.inc.php');
|
||||
require_once('sqlquery.class.inc.php');
|
||||
require_once('sqlobjectquery.class.inc.php');
|
||||
require_once('sqlunionquery.class.inc.php');
|
||||
|
||||
require_once('dbobject.class.php');
|
||||
require_once('dbsearch.class.php');
|
||||
require_once('dbobjectset.class.php');
|
||||
@@ -302,14 +302,10 @@ abstract class CMDBObject extends DBObject
|
||||
{
|
||||
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
|
||||
//
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = new OrmStopWatch();
|
||||
}
|
||||
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
|
||||
{
|
||||
$item_value = $oSubItemAttDef->GetValue($value);
|
||||
$item_original = $oSubItemAttDef->GetValue($original);
|
||||
$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)
|
||||
{
|
||||
@@ -337,7 +333,14 @@ abstract class CMDBObject extends DBObject
|
||||
elseif ($oAttDef instanceOf AttributeLongText)
|
||||
{
|
||||
// Data blobs
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
|
||||
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);
|
||||
@@ -352,7 +355,14 @@ abstract class CMDBObject extends DBObject
|
||||
elseif ($oAttDef instanceOf AttributeText)
|
||||
{
|
||||
// Data blobs
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
|
||||
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);
|
||||
@@ -386,6 +396,29 @@ abstract class CMDBObject extends DBObject
|
||||
$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
|
||||
@@ -526,13 +559,13 @@ abstract class CMDBObject extends DBObject
|
||||
|
||||
public static function BulkUpdate(DBSearch $oFilter, array $aValues)
|
||||
{
|
||||
return $this->BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
return static::BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
}
|
||||
|
||||
public static function BulkUpdateTracked(CMDBChange $oChange, DBSearch $oFilter, array $aValues)
|
||||
{
|
||||
self::SetCurrentChange($oChange);
|
||||
$this->BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
static::BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
}
|
||||
|
||||
protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -30,7 +30,7 @@ define('ACCESS_READONLY', 0);
|
||||
/**
|
||||
* Configuration read/write
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -77,7 +77,6 @@ class Config
|
||||
protected $m_aDataModels;
|
||||
protected $m_aWebServiceCategories;
|
||||
protected $m_aAddons;
|
||||
protected $m_aDictionaries;
|
||||
|
||||
protected $m_aModuleSettings;
|
||||
|
||||
@@ -247,7 +246,7 @@ class Config
|
||||
),
|
||||
'access_mode' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Combination of flags (ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE, or ACCESS_FULL)',
|
||||
'description' => 'Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3',
|
||||
'default' => ACCESS_FULL,
|
||||
'value' => ACCESS_FULL,
|
||||
'source_of_value' => '',
|
||||
@@ -405,6 +404,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'email_css' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'CSS that will override the standard stylesheet used for the notifications',
|
||||
'default' => "",
|
||||
'value' => "",
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'apc_cache.enabled' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'If set, the APC cache is allowed (the PHP extension must also be active)',
|
||||
@@ -718,6 +725,15 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'portal_dispatch_urls' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'Associative array of sPortalId => Home page URL (relatively to the application root)',
|
||||
// examples... not used
|
||||
'default' => array(),
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'max_execution_time_per_loop' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum execution time requested, per loop, during bulk operations. Zero means no limit.',
|
||||
@@ -866,6 +882,54 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'html_sanitizer' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'The class to use for HTML sanitization: HTMLDOMSanitizer, HTMLPurifierSanitizer or HTMLNullSanitizer',
|
||||
'default' => 'HTMLDOMSanitizer',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'inline_image_max_display_width' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.',
|
||||
'default' => '250',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'inline_image_max_storage_width' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.',
|
||||
'default' => '1600',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'date_and_time_format' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'Format for date and time display (per language)',
|
||||
'default' => array('default' => array('date' => 'Y-m-d', 'time' => 'H:i:s', 'date_time' => '$date $time')),
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'breadcrumb.max_count' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum number of items kept in the history breadcrumb. Set it to 0 to entirely disable the breadcrumb.',
|
||||
'default' => 8,
|
||||
'value' => 8,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'legacy_search_drawer_open' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to display the "search drawer" open by default as in previous versions of iTop.',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
);
|
||||
|
||||
public function IsProperty($sPropCode)
|
||||
@@ -990,36 +1054,10 @@ class Config
|
||||
$bLoadConfig = false;
|
||||
}
|
||||
|
||||
$this->m_aAppModules = array(
|
||||
// Some default modules, always present can be move to an official iTop Module later if needed
|
||||
'application/transaction.class.inc.php',
|
||||
'application/menunode.class.inc.php',
|
||||
'application/user.preferences.class.inc.php',
|
||||
'application/user.dashboard.class.inc.php',
|
||||
'application/audit.rule.class.inc.php',
|
||||
'application/query.class.inc.php',
|
||||
// Romain - That's dirty, because those classes are in fact part of the core
|
||||
// but I needed those classes to be derived from cmdbAbstractObject
|
||||
// (to be managed via the GUI) and this class in not really known from
|
||||
// the core, PLUS I needed the includes to be there also for the setup
|
||||
// to create the tables.
|
||||
'core/event.class.inc.php',
|
||||
'core/action.class.inc.php',
|
||||
'core/trigger.class.inc.php',
|
||||
'core/bulkexport.class.inc.php',
|
||||
'core/ownershiplock.class.inc.php',
|
||||
'synchro/synchrodatasource.class.inc.php',
|
||||
'core/backgroundtask.class.inc.php',
|
||||
);
|
||||
$this->m_aDataModels = array();
|
||||
$this->m_aWebServiceCategories = array(
|
||||
'webservices/webservices.basic.php',
|
||||
);
|
||||
$this->m_aAddons = array(
|
||||
// Default AddOn, always present can be moved to an official iTop Module later if needed
|
||||
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
|
||||
);
|
||||
$this->m_aDictionaries = self::ScanDictionariesDir();
|
||||
|
||||
foreach($this->m_aSettings as $sPropCode => $aSettingInfo)
|
||||
{
|
||||
@@ -1118,18 +1156,7 @@ class Config
|
||||
{
|
||||
throw new ConfigException('Missing array in configuration file', array('file' => $sConfigFile, 'expected' => '$MySettings'));
|
||||
}
|
||||
if (!isset($MyModules) || !is_array($MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules'));
|
||||
}
|
||||
if (!array_key_exists('application', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'application\']'));
|
||||
}
|
||||
if (!array_key_exists('business', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'business\']'));
|
||||
}
|
||||
|
||||
if (!array_key_exists('addons', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
|
||||
@@ -1139,18 +1166,8 @@ class Config
|
||||
// Add one, by default
|
||||
$MyModules['addons']['user rights'] = '/addons/userrights/userrightsnull.class.inc.php';
|
||||
}
|
||||
if (!array_key_exists('dictionaries', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'dictionaries\']'));
|
||||
}
|
||||
$this->m_aAppModules = $MyModules['application'];
|
||||
$this->m_aDataModels = $MyModules['business'];
|
||||
if (isset($MyModules['webservices']))
|
||||
{
|
||||
$this->m_aWebServiceCategories = $MyModules['webservices'];
|
||||
}
|
||||
|
||||
$this->m_aAddons = $MyModules['addons'];
|
||||
$this->m_aDictionaries = $MyModules['dictionaries'];
|
||||
|
||||
foreach($MySettings as $sPropCode => $rawvalue)
|
||||
{
|
||||
@@ -1234,33 +1251,6 @@ class Config
|
||||
$this->m_aModuleSettings[$sModule][$sProperty] = $value;
|
||||
}
|
||||
|
||||
public function GetAppModules()
|
||||
{
|
||||
return $this->m_aAppModules;
|
||||
}
|
||||
public function SetAppModules($aAppModules)
|
||||
{
|
||||
$this->m_aAppModules = $aAppModules;
|
||||
}
|
||||
|
||||
public function GetDataModels()
|
||||
{
|
||||
return $this->m_aDataModels;
|
||||
}
|
||||
public function SetDataModels($aDataModels)
|
||||
{
|
||||
$this->m_aDataModels = $aDataModels;
|
||||
}
|
||||
|
||||
public function GetWebServiceCategories()
|
||||
{
|
||||
return $this->m_aWebServiceCategories;
|
||||
}
|
||||
public function SetWebServiceCategories($aWebServiceCategories)
|
||||
{
|
||||
$this->m_aWebServiceCategories = $aWebServiceCategories;
|
||||
}
|
||||
|
||||
public function GetAddons()
|
||||
{
|
||||
return $this->m_aAddons;
|
||||
@@ -1270,15 +1260,6 @@ class Config
|
||||
$this->m_aAddons = $aAddons;
|
||||
}
|
||||
|
||||
public function GetDictionaries()
|
||||
{
|
||||
return $this->m_aDictionaries;
|
||||
}
|
||||
public function SetDictionaries($aDictionaries)
|
||||
{
|
||||
$this->m_aDictionaries = $aDictionaries;
|
||||
}
|
||||
|
||||
public function GetDBHost()
|
||||
{
|
||||
return $this->m_sDBHost;
|
||||
@@ -1558,26 +1539,10 @@ class Config
|
||||
$aSettings['module_settings'][$sModule][$sProperty] = $value;
|
||||
}
|
||||
}
|
||||
foreach($this->m_aAppModules as $sFile)
|
||||
{
|
||||
$aSettings['application_list'][] = $sFile;
|
||||
}
|
||||
foreach($this->m_aDataModels as $sFile)
|
||||
{
|
||||
$aSettings['datamodel_list'][] = $sFile;
|
||||
}
|
||||
foreach($this->m_aWebServiceCategories as $sFile)
|
||||
{
|
||||
$aSettings['webservice_list'][] = $sFile;
|
||||
}
|
||||
foreach($this->m_aAddons as $sKey => $sFile)
|
||||
{
|
||||
$aSettings['addon_list'][] = $sFile;
|
||||
}
|
||||
foreach($this->m_aDictionaries as $sFile)
|
||||
{
|
||||
$aSettings['dictionary_list'][] = $sFile;
|
||||
}
|
||||
return $aSettings;
|
||||
}
|
||||
|
||||
@@ -1722,36 +1687,12 @@ class Config
|
||||
fwrite($hFile, " *\n");
|
||||
fwrite($hFile, " */\n");
|
||||
fwrite($hFile, "\$MyModules = array(\n");
|
||||
fwrite($hFile, "\t'application' => array (\n");
|
||||
foreach($this->m_aAppModules as $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, "\t'business' => array (\n");
|
||||
foreach($this->m_aDataModels as $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, "\t'webservices' => array (\n");
|
||||
foreach($this->m_aWebServiceCategories as $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, "\t'addons' => array (\n");
|
||||
foreach($this->m_aAddons as $sKey => $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sKey' => '$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, "\t'dictionaries' => array (\n");
|
||||
foreach($this->m_aDictionaries as $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, ");\n");
|
||||
fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting !
|
||||
return fclose($hFile);
|
||||
@@ -1761,26 +1702,6 @@ class Config
|
||||
throw new ConfigException("Could not write to configuration file", array('file' => $sFileName));
|
||||
}
|
||||
}
|
||||
|
||||
protected static function ScanDictionariesDir()
|
||||
{
|
||||
$aResult = array();
|
||||
// Populate automatically the list of dictionary files
|
||||
$sDir = APPROOT.'/dictionaries';
|
||||
if ($hDir = @opendir($sDir))
|
||||
{
|
||||
while (($sFile = readdir($hDir)) !== false)
|
||||
{
|
||||
$aMatches = array();
|
||||
if (preg_match("/^([^\.]+\.)?dictionary\.itop\.(ui|core)\.php$/i", $sFile, $aMatches)) // Dictionary files named like [<Lang>.]dictionary.[core|ui].php are loaded automatically
|
||||
{
|
||||
$aResult[] = 'dictionaries/'.$sFile;
|
||||
}
|
||||
}
|
||||
closedir($hDir);
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to initialize a configuration from the page arguments
|
||||
@@ -1841,16 +1762,6 @@ class Config
|
||||
// Initialize the arrays below with default values for the application...
|
||||
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
|
||||
$aAddOns = $oEmptyConfig->GetAddOns();
|
||||
$aAppModules = $oEmptyConfig->GetAppModules();
|
||||
if (file_exists(APPROOT.$sModulesDir.'/core/main.php'))
|
||||
{
|
||||
$aAppModules[] = $sModulesDir.'/core/main.php';
|
||||
}
|
||||
$aDataModels = $oEmptyConfig->GetDataModels();
|
||||
$aWebServiceCategories = $oEmptyConfig->GetWebServiceCategories();
|
||||
$aDictionaries = $oEmptyConfig->GetDictionaries();
|
||||
// Merge the values with the ones provided by the modules
|
||||
// Make sure when don't load the same file twice...
|
||||
|
||||
$aModules = ModuleDiscovery::GetAvailableModules(array(APPROOT.$sModulesDir));
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo)
|
||||
@@ -1858,14 +1769,6 @@ class Config
|
||||
list ($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules))
|
||||
{
|
||||
if (isset($aModuleInfo['datamodel']))
|
||||
{
|
||||
$aDataModels = array_unique(array_merge($aDataModels, $aModuleInfo['datamodel']));
|
||||
}
|
||||
if (isset($aModuleInfo['webservice']))
|
||||
{
|
||||
$aWebServiceCategories = array_unique(array_merge($aWebServiceCategories, $aModuleInfo['webservice']));
|
||||
}
|
||||
if (isset($aModuleInfo['settings']))
|
||||
{
|
||||
list ($sName, $sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
@@ -1898,18 +1801,6 @@ class Config
|
||||
}
|
||||
}
|
||||
$this->SetAddOns($aAddOns);
|
||||
$this->SetAppModules($aAppModules);
|
||||
$this->SetDataModels($aDataModels);
|
||||
$this->SetWebServiceCategories($aWebServiceCategories);
|
||||
|
||||
// Scan dictionaries
|
||||
//
|
||||
foreach (glob(APPROOT.$sModulesDir.'/dictionaries/*.dict.php') as $sFilePath)
|
||||
{
|
||||
$sFile = basename($sFilePath);
|
||||
$aDictionaries[] = $sModulesDir.'/dictionaries/'.$sFile;
|
||||
}
|
||||
$this->SetDictionaries($aDictionaries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1928,15 +1819,12 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick an dirty way to clone a config file into another environment
|
||||
* Obsolete: kept only for backward compatibility of the Toolkit
|
||||
* Quick and dirty way to clone a config file into another environment
|
||||
*/
|
||||
public function ChangeModulesPath($sSourceEnv, $sTargetEnv)
|
||||
{
|
||||
$sSearchPrefix = 'env-'.$sSourceEnv.'/';
|
||||
$sNewPrefix = 'env-'.$sTargetEnv.'/';
|
||||
self::ChangePrefix($this->m_aDataModels, $sSearchPrefix, $sNewPrefix);
|
||||
self::ChangePrefix($this->m_aWebServiceCategories, $sSearchPrefix, $sNewPrefix);
|
||||
self::ChangePrefix($this->m_aDictionaries, $sSearchPrefix, $sNewPrefix);
|
||||
// Now does nothing since the includes are built into the environment itself
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
75
core/contexttag.class.inc.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?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/>
|
||||
|
||||
|
||||
/**
|
||||
* Simple helper class for keeping track of the context inside the call stack
|
||||
*
|
||||
* To check (anywhere in the code) if a particular context tag is present
|
||||
* in the call stack simply do:
|
||||
*
|
||||
* if (ContextTag::Check(<the_tag>)) ...
|
||||
*
|
||||
* For example to know if the code is being executed in the context of a portal do:
|
||||
*
|
||||
* if (ContextTag::Check('GUI:Portal'))
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class ContextTag
|
||||
{
|
||||
protected static $aStack;
|
||||
|
||||
/**
|
||||
* Store a context tag on the stack
|
||||
* @param string $sTag
|
||||
*/
|
||||
public function __construct($sTag)
|
||||
{
|
||||
static::$aStack[] = $sTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the context stack
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
array_pop(static::$aStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given tag is present in the stack
|
||||
* @param string $sTag
|
||||
* @return bool
|
||||
*/
|
||||
public static function Check($sTag)
|
||||
{
|
||||
return in_array($sTag, static::$aStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the whole stack as an array
|
||||
* @return hash
|
||||
*/
|
||||
public static function GetStack()
|
||||
{
|
||||
return static::$aStack;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Bulk export: CSV export
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -33,6 +33,8 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$oP->p(" *\tcharset: (optional) character set for encoding the result (default is 'UTF-8').");
|
||||
$oP->p(" *\ttext-qualifier: (optional) character to be used around text strings (default is '\"').");
|
||||
$oP->p(" *\tno_localize: set to 1 to retrieve non-localized values (for instance for ENUM values). Default is 0 (= localized values)");
|
||||
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
|
||||
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format used in the user interface). e.g. 'Y-m-d H:i:s'");
|
||||
}
|
||||
|
||||
public function ReadParameters()
|
||||
@@ -55,6 +57,25 @@ class CSVBulkExport extends TabularBulkExport
|
||||
}
|
||||
|
||||
$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data'));
|
||||
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
|
||||
|
||||
$sDateFormatRadio = utils::ReadParam('csv_date_format_radio', '');
|
||||
switch($sDateFormatRadio)
|
||||
{
|
||||
case 'default':
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +100,7 @@ class CSVBulkExport extends TabularBulkExport
|
||||
|
||||
public function EnumFormParts()
|
||||
{
|
||||
return array_merge(parent::EnumFormParts(), array('csv_options' => array('separator', 'charset', 'text-qualifier', 'no_localize') ,'interactive_fields_csv' => array('interactive_fields_csv')));
|
||||
return array_merge(parent::EnumFormParts(), array('csv_options' => array('separator', 'charset', 'text-qualifier', 'no_localize', 'formatted_text') ,'interactive_fields_csv' => array('interactive_fields_csv')));
|
||||
}
|
||||
|
||||
public function DisplayFormPart(WebPage $oP, $sPartId)
|
||||
@@ -95,6 +116,7 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
|
||||
$oP->add('<h3>'.Dict::S('UI:CSVImport:SeparatorCharacter').'</h3>');
|
||||
$sRawSeparator = utils::ReadParam('separator', ',', true, 'raw_data');
|
||||
$sCustomDateTimeFormat = utils::ReadParam('', ',', true, 'raw_data');
|
||||
$aSep = array(
|
||||
';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'),
|
||||
',' => Dict::S('UI:CSVImport:SeparatorComma+'),
|
||||
@@ -157,9 +179,35 @@ class CSVBulkExport extends TabularBulkExport
|
||||
}
|
||||
$oP->add('</select>');
|
||||
|
||||
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
|
||||
$oP->add('<input type="checkbox" id="csv_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="csv_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
|
||||
$oP->add('</td><td style="vertical-align:top">');
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$oP->add('<input type="radio" id="csv_date_time_format_default" name="csv_date_format_radio" value="default"'.$sDefaultChecked.'><label for="csv_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="csv_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oP->add('<input type="radio" id="csv_date_time_format_custom" name="csv_date_format_radio" value="custom"'.$sCustomChecked.'><label for="csv_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
|
||||
$oP->add('</td></tr></table>');
|
||||
|
||||
$oP->add('</fieldset>');
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#csv_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_csv_options').on('preview_updated', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
$('#csv_date_time_format_default').on('click', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
$('#csv_date_time_format_custom').on('click', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
$('#csv_custom_date_time_format').on('click', function() { $('#csv_date_time_format_custom').prop('checked', true); FormatDatesInPreview('csv', 'csv'); }).on('keyup', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
EOF
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
|
||||
@@ -169,7 +217,16 @@ class CSVBulkExport extends TabularBulkExport
|
||||
}
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
}
|
||||
return '<div class="text-preview">'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
|
||||
@@ -182,7 +239,7 @@ class CSVBulkExport extends TabularBulkExport
|
||||
break;
|
||||
|
||||
default:
|
||||
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
|
||||
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
@@ -210,7 +267,7 @@ class CSVBulkExport extends TabularBulkExport
|
||||
{
|
||||
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
|
||||
// and thus to convert field by field and not the whole row or file at once (see ticket #991)
|
||||
$aData[$idx] = iconv('UTF-8', $this->aStatusInfo['charset'].'//IGNORE//TRANSLIT', $aData[$idx]);
|
||||
$aData[$idx] = @iconv('UTF-8', $this->aStatusInfo['charset'].'//IGNORE//TRANSLIT', $aData[$idx]);
|
||||
}
|
||||
}
|
||||
$sData = implode($this->aStatusInfo['separator'], $aData)."\n";
|
||||
@@ -231,6 +288,17 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$sData = '';
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
|
||||
$oPrevDateTimeFormat = AttributeDateTime::GetFormat();
|
||||
$oPrevDateFormat = AttributeDate::GetFormat();
|
||||
if ($sExportDateTimeFormat !== (string)$oPrevDateTimeFormat)
|
||||
{
|
||||
// Change date & time formats
|
||||
$oDateTimeFormat = new DateTimeFormat($sExportDateTimeFormat);
|
||||
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
|
||||
AttributeDateTime::SetFormat($oDateTimeFormat);
|
||||
AttributeDate::SetFormat($oDateFormat);
|
||||
}
|
||||
while($aRow = $oSet->FetchAssoc())
|
||||
{
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
@@ -251,14 +319,14 @@ class CSVBulkExport extends TabularBulkExport
|
||||
break;
|
||||
|
||||
default:
|
||||
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput);
|
||||
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']);
|
||||
}
|
||||
}
|
||||
if ($this->aStatusInfo['charset'] != 'UTF-8')
|
||||
{
|
||||
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
|
||||
// and thus to convert field by field and not the whole row or file at once (see ticket #991)
|
||||
$aData[] = iconv('UTF-8', $this->aStatusInfo['charset'].'//IGNORE//TRANSLIT', $sField);
|
||||
$aData[] = @iconv('UTF-8', $this->aStatusInfo['charset'].'//IGNORE//TRANSLIT', $sField);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -268,6 +336,9 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$sData .= implode($this->aStatusInfo['separator'], $aData)."\n";
|
||||
$iCount++;
|
||||
}
|
||||
// Restore original date & time formats
|
||||
AttributeDateTime::SetFormat($oPrevDateTimeFormat);
|
||||
AttributeDate::SetFormat($oPrevDateFormat);
|
||||
set_time_limit($iPreviousTimeLimit);
|
||||
$this->aStatusInfo['position'] += $this->iChunkSize;
|
||||
if ($this->aStatusInfo['total'] == 0)
|
||||
|
||||
@@ -54,12 +54,14 @@ class CSVParser
|
||||
private $m_sCSVData;
|
||||
private $m_sSep;
|
||||
private $m_sTextQualifier;
|
||||
private $m_iTimeLimitPerRow;
|
||||
|
||||
public function __construct($sTxt, $sSep = ',', $sTextQualifier = '"')
|
||||
public function __construct($sTxt, $sSep = ',', $sTextQualifier = '"', $iTimeLimitPerRow = null)
|
||||
{
|
||||
$this->m_sCSVData = str_replace("\r\n", "\n", $sTxt);
|
||||
$this->m_sSep = $sSep;
|
||||
$this->m_sTextQualifier = $sTextQualifier;
|
||||
$this->m_iTimeLimitPerRow = $iTimeLimitPerRow;
|
||||
}
|
||||
|
||||
protected $m_sCurrCell = '';
|
||||
@@ -129,6 +131,12 @@ class CSVParser
|
||||
// blank line, skip silently
|
||||
}
|
||||
$this->m_aCurrRow = array();
|
||||
|
||||
// More time for the next row
|
||||
if ($this->m_iTimeLimitPerRow !== null)
|
||||
{
|
||||
set_time_limit($this->m_iTimeLimitPerRow);
|
||||
}
|
||||
}
|
||||
protected function __AddCellTrimmed($c = null, $aFieldMap = null)
|
||||
{
|
||||
@@ -181,6 +189,13 @@ class CSVParser
|
||||
$iDataLength = strlen($this->m_sCSVData);
|
||||
|
||||
$iState = stSTARTING;
|
||||
$iTimeLimit = null;
|
||||
if ($this->m_iTimeLimitPerRow !== null)
|
||||
{
|
||||
// Give some time for the first row
|
||||
$iTimeLimit = ini_get('max_execution_time');
|
||||
set_time_limit($this->m_iTimeLimitPerRow);
|
||||
}
|
||||
for($i = 0; $i <= $iDataLength ; $i++)
|
||||
{
|
||||
if ($i == $iDataLength)
|
||||
@@ -237,6 +252,11 @@ class CSVParser
|
||||
$iLineCount = count($this->m_aDataSet);
|
||||
if (($iMax > 0) && ($iLineCount >= $iMax)) break;
|
||||
}
|
||||
if ($iTimeLimit !== null)
|
||||
{
|
||||
// Restore the previous time limit
|
||||
set_time_limit($iTimeLimit);
|
||||
}
|
||||
return $this->m_aDataSet;
|
||||
}
|
||||
|
||||
|
||||
140
core/customfieldshandler.class.inc.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
use Combodo\iTop\Form\Form;
|
||||
use Combodo\iTop\Form\FormManager;
|
||||
|
||||
/**
|
||||
* Base class to implement a handler for AttributeCustomFields
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
abstract class CustomFieldsHandler
|
||||
{
|
||||
protected $sAttCode;
|
||||
protected $aValues;
|
||||
protected $oForm;
|
||||
|
||||
/**
|
||||
* This constructor's prototype must be frozen.
|
||||
* Any specific behavior must be implemented in BuildForm()
|
||||
*
|
||||
* @param $sAttCode
|
||||
*/
|
||||
final public function __construct($sAttCode)
|
||||
{
|
||||
$this->sAttCode = $sAttCode;
|
||||
$this->aValues = null;
|
||||
}
|
||||
|
||||
abstract public function BuildForm(DBObject $oHostObject, $sFormId);
|
||||
|
||||
/**
|
||||
*
|
||||
* @return \Combodo\iTop\Form\Form
|
||||
*/
|
||||
public function GetForm()
|
||||
{
|
||||
return $this->oForm;
|
||||
}
|
||||
|
||||
public function SetCurrentValues($aValues)
|
||||
{
|
||||
$this->aValues = $aValues;
|
||||
}
|
||||
|
||||
static public function GetPrerequisiteAttributes($sClass = null)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* List the available verbs for 'GetForTemplate'
|
||||
*/
|
||||
static public function EnumTemplateVerbs()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
|
||||
* @param $aValues array The current values
|
||||
* @param $sVerb string The verb specifying the representation of the value
|
||||
* @param $bLocalize bool Whether or not to localize the value
|
||||
* @return string
|
||||
*/
|
||||
abstract public function GetForTemplate($aValues, $sVerb, $bLocalize = true);
|
||||
|
||||
/**
|
||||
* @param $aValues
|
||||
* @param bool|true $bLocalize
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function GetAsHTML($aValues, $bLocalize = true);
|
||||
|
||||
/**
|
||||
* @param $aValues
|
||||
* @param bool|true $bLocalize
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function GetAsXML($aValues, $bLocalize = true);
|
||||
|
||||
/**
|
||||
* @param $aValues
|
||||
* @param string $sSeparator
|
||||
* @param string $sTextQualifier
|
||||
* @param bool|true $bLocalize
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function GetAsCSV($aValues, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true);
|
||||
|
||||
/**
|
||||
* @param DBObject $oHostObject
|
||||
* @return array Associative array id => value
|
||||
*/
|
||||
abstract public function ReadValues(DBObject $oHostObject);
|
||||
|
||||
/**
|
||||
* Record the data (currently in the processing of recording the host object)
|
||||
* It is assumed that the data has been checked prior to calling Write()
|
||||
* @param DBObject $oHostObject
|
||||
* @param array Associative array id => value
|
||||
*/
|
||||
abstract public function WriteValues(DBObject $oHostObject, $aValues);
|
||||
|
||||
/**
|
||||
* Cleanup data upon object deletion (object id still available here)
|
||||
* @param DBObject $oHostObject
|
||||
*/
|
||||
abstract public function DeleteValues(DBObject $oHostObject);
|
||||
|
||||
/**
|
||||
* @param $aValuesA
|
||||
* @param $aValuesB
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function CompareValues($aValuesA, $aValuesB);
|
||||
|
||||
/**
|
||||
* String representation of the value, must depend solely on the semantics
|
||||
* @return string
|
||||
*/
|
||||
abstract public function GetValueFingerprint();
|
||||
}
|
||||
428
core/datetimeformat.class.inc.php
Normal file
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Helper class to generate Date & Time formatting strings in the various conventions
|
||||
* from the PHP DateTime::createFromFormat convention.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $oFormat = new DateTimeFormat('m/d/Y H:i');
|
||||
* $oFormat->ToExcel();
|
||||
* >> 'MM/dd/YYYY HH:mm'
|
||||
*
|
||||
* @author Denis Flaven <denis.flaven@combodo.com>
|
||||
*
|
||||
*/
|
||||
class DateTimeFormat
|
||||
{
|
||||
protected $sPHPFormat;
|
||||
|
||||
/**
|
||||
* Constructs the DateTimeFormat object
|
||||
* @param string $sPHPFormat A format string using the PHP 'DateTime::createFromFormat' convention
|
||||
*/
|
||||
public function __construct($sPHPFormat)
|
||||
{
|
||||
$this->sPHPFormat = (string)$sPHPFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->sPHPFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping table for converting between various conventions for date/time formats
|
||||
*/
|
||||
protected static function GetFormatMapping()
|
||||
{
|
||||
return array(
|
||||
// Days
|
||||
'd' => array('regexpr' => '(0[1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'dd', 'excel' => 'dd', 'moment' => 'DD'), // Day of the month: 2 digits (with leading zero)
|
||||
'j' => array('regexpr' => '([1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'd', 'excel' => 'd', 'moment' => 'D'), // Day of the month: 1 or 2 digits (without leading zero)
|
||||
// Months
|
||||
'm' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'mm', 'excel' => 'MM', 'moment' => 'MM' ), // Month on 2 digits i.e. 01-12
|
||||
'n' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'm', 'excel' => 'm', 'moment' => 'M'), // Month on 1 or 2 digits 1-12
|
||||
// Years
|
||||
'Y' => array('regexpr' => '([0-9]{4})', 'datepicker' => 'yy', 'excel' => 'YYYY', 'moment' => 'YYYY'), // Year on 4 digits
|
||||
'y' => array('regexpr' => '([0-9]{2})', 'datepicker' => 'y', 'excel' => 'YY', 'moment' => 'YY'), // Year on 2 digits
|
||||
// Hours
|
||||
'H' => array('regexpr' => '([0-1][0-9]|2[0-3])', 'datepicker' => 'HH', 'excel' => 'HH', 'moment' => 'HH'), // Hour 00..23
|
||||
'h' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'hh', 'excel' => 'hh', 'moment' => 'hh'), // Hour 01..12
|
||||
'G' => array('regexpr' => '([1-9]|[1[0-9]|2[0-3])', 'datepicker' => 'H', 'excel' => 'H', 'moment' => 'H'), // Hour 0..23
|
||||
'g' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'h', 'excel' => 'h', 'moment' => 'h'), // Hour 1..12
|
||||
'a' => array('regexpr' => '(am|pm)', 'datepicker' => 'tt', 'excel' => 'am/pm', 'moment' => 'a'),
|
||||
'A' => array('regexpr' => '(AM|PM)', 'datepicker' => 'TT', 'excel' => 'AM/PM', 'moment' => 'A'),
|
||||
// Minutes
|
||||
'i' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'mm', 'excel' => 'mm', 'moment' => 'mm'),
|
||||
// Seconds
|
||||
's' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'ss', 'excel' => 'ss', 'moment' => 'ss'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the PHP format into the specified format, taking care of escaping the litteral characters
|
||||
* using the supplied escaping expression
|
||||
* @param string $sOutputFormatCode THe target format code: regexpr|datepicker|excel|moment
|
||||
* @param string $sEscapePattern The replacement string for escaping characters in the output string. %s is the source char.
|
||||
* @param string $bEscapeAll True to systematically escape all litteral characters
|
||||
* @param array $sSpecialChars A string containing the only characters to escape in the output
|
||||
* @return string The string in the requested format
|
||||
*/
|
||||
protected function Transform($sOutputFormatCode, $sEscapePattern, $bEscapeAll = false, $sSpecialChars = '')
|
||||
{
|
||||
$aMappings = static::GetFormatMapping();
|
||||
$sResult = '';
|
||||
|
||||
$bEscaping = false;
|
||||
for($i=0; $i < strlen($this->sPHPFormat); $i++)
|
||||
{
|
||||
if (($this->sPHPFormat[$i] == '\\'))
|
||||
{
|
||||
$bEscaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bEscaping)
|
||||
{
|
||||
if (($sSpecialChars === '') || (strpos($sSpecialChars, $this->sPHPFormat[$i]) !== false))
|
||||
{
|
||||
$sResult .= sprintf($sEscapePattern, $this->sPHPFormat[$i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sResult .= $this->sPHPFormat[$i];
|
||||
}
|
||||
|
||||
$bEscaping = false;
|
||||
}
|
||||
else if(array_key_exists($this->sPHPFormat[$i], $aMappings))
|
||||
{
|
||||
// Not a litteral value, must be replaced by its regular expression pattern
|
||||
$sResult .= $aMappings[$this->sPHPFormat[$i]][$sOutputFormatCode];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($bEscapeAll || (strpos($sSpecialChars, $this->sPHPFormat[$i]) !== false))
|
||||
{
|
||||
$sResult .= sprintf($sEscapePattern, $this->sPHPFormat[$i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal char with no special meaning, no need to escape it
|
||||
$sResult .= $this->sPHPFormat[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date into the supplied format string
|
||||
* @param mixed $date An int, string, DateTime object or null !!
|
||||
* @throws Exception
|
||||
* @return string The formatted date
|
||||
*/
|
||||
public function Format($date)
|
||||
{
|
||||
if ($date == null)
|
||||
{
|
||||
$sDate = '';
|
||||
}
|
||||
else if (($date === '0000-00-00') || ($date === '0000-00-00 00:00:00'))
|
||||
{
|
||||
$sDate = '';
|
||||
}
|
||||
else if ($date instanceof DateTime)
|
||||
{
|
||||
// Parameter is a DateTime
|
||||
$sDate = $date->format($this->sPHPFormat);
|
||||
}
|
||||
else if (is_int($date))
|
||||
{
|
||||
// Parameter is a Unix timestamp
|
||||
$oDate = new DateTime();
|
||||
$oDate->setTimestamp($date);
|
||||
$sDate = $oDate->format($this->sPHPFormat);
|
||||
}
|
||||
else if (is_string($date))
|
||||
{
|
||||
$oDate = new DateTime($date);
|
||||
$sDate = $oDate->format($this->sPHPFormat);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(__CLASS__."::Format: Unexpected date value: ".print_r($date, true));
|
||||
}
|
||||
return $sDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a date in the supplied format and return the date as a string in the internal format
|
||||
* @param string $sDate The string to parse
|
||||
* @param string $sFormat The format, in PHP createFromFormat convention
|
||||
* @throws Exception
|
||||
* @return DateTime|null
|
||||
*/
|
||||
public function Parse($sDate)
|
||||
{
|
||||
if (($sDate == null) || ($sDate == '0000-00-00 00:00:00') || ($sDate == '0000-00-00'))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sFormat = preg_replace('/\\?/', '', $this->sPHPFormat); // replace escaped characters by a wildcard for parsing
|
||||
$oDate = DateTime::createFromFormat($this->sPHPFormat, $sDate);
|
||||
if ($oDate === false)
|
||||
{
|
||||
throw new Exception(__CLASS__."::Parse: Unable to parse the date: '$sDate' using the format: '{$this->sPHPFormat}'");
|
||||
}
|
||||
return $oDate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date or datetime format string in the jQuery UI date picker format
|
||||
* @return string The format string using the date picker convention
|
||||
*/
|
||||
public function ToDatePicker()
|
||||
{
|
||||
return $this->Transform('datepicker', "'%s'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a date or datetime format string in the Excel format
|
||||
* @return string The format string using the Excel convention
|
||||
*/
|
||||
public function ToExcel()
|
||||
{
|
||||
return $this->Transform('excel', "%s");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a date or datetime format string in the moment.js format
|
||||
* @return string The format string using the moment.js convention
|
||||
*/
|
||||
public function ToMomentJS()
|
||||
{
|
||||
return $this->Transform('moment', "[%s]", true /* escape all */);
|
||||
}
|
||||
|
||||
public static function GetJSSQLToCustomFormat()
|
||||
{
|
||||
$aPHPToMoment = array();
|
||||
foreach(self::GetFormatMapping() as $sPHPCode => $aMapping)
|
||||
{
|
||||
$aPHPToMoment[$sPHPCode] = $aMapping['moment'];
|
||||
}
|
||||
$sJSMapping = json_encode($aPHPToMoment);
|
||||
|
||||
$sFunction =
|
||||
<<<EOF
|
||||
function PHPDateTimeFormatToSubFormat(sPHPFormat, sPlaceholders)
|
||||
{
|
||||
var iMax = 0;
|
||||
var iMin = 999;
|
||||
var bEscaping = false;
|
||||
for(var i=0; i<sPHPFormat.length; i++)
|
||||
{
|
||||
var c = sPHPFormat[i];
|
||||
if (c == '\\\\')
|
||||
{
|
||||
bEscaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bEscaping)
|
||||
{
|
||||
bEscaping = false;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sPlaceholders.search(c) != -1)
|
||||
{
|
||||
iMax = Math.max(iMax, i);
|
||||
iMin = Math.min(iMin, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sPHPFormat.substr(iMin, iMax - iMin + 1);
|
||||
}
|
||||
|
||||
function PHPDateTimeFormatToMomentFormat(sPHPFormat)
|
||||
{
|
||||
var aFormatMapping = $sJSMapping;
|
||||
var sMomentFormat = '';
|
||||
|
||||
var bEscaping = false;
|
||||
for(var i=0; i<sPHPFormat.length; i++)
|
||||
{
|
||||
var c = sPHPFormat[i];
|
||||
if (c == '\\\\')
|
||||
{
|
||||
bEscaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bEscaping)
|
||||
{
|
||||
sMomentFormat += '['+c+']';
|
||||
bEscaping = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (aFormatMapping[c] !== undefined)
|
||||
{
|
||||
sMomentFormat += aFormatMapping[c];
|
||||
}
|
||||
else
|
||||
{
|
||||
sMomentFormat += '['+c+']';
|
||||
}
|
||||
}
|
||||
}
|
||||
return sMomentFormat;
|
||||
}
|
||||
|
||||
function DateFormatFromPHP(sSQLDate, sPHPFormat)
|
||||
{
|
||||
if (sSQLDate === '') return '';
|
||||
var sPHPDateFormat = PHPDateTimeFormatToSubFormat(sPHPFormat, 'Yydjmn');
|
||||
var sMomentFormat = PHPDateTimeFormatToMomentFormat(sPHPDateFormat);
|
||||
return moment(sSQLDate).format(sMomentFormat);
|
||||
}
|
||||
|
||||
function DateTimeFormatFromPHP(sSQLDate, sPHPFormat)
|
||||
{
|
||||
if (sSQLDate === '') return '';
|
||||
var sMomentFormat = PHPDateTimeFormatToMomentFormat(sPHPFormat);
|
||||
return moment(sSQLDate).format(sMomentFormat);
|
||||
}
|
||||
EOF
|
||||
;
|
||||
return $sFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a placeholder text for a date or datetime format string
|
||||
* @return string The placeholder text (localized)
|
||||
*/
|
||||
public function ToPlaceholder()
|
||||
{
|
||||
$aMappings = static::GetFormatMapping();
|
||||
$sResult = '';
|
||||
|
||||
$bEscaping = false;
|
||||
for($i=0; $i < strlen($this->sPHPFormat); $i++)
|
||||
{
|
||||
if (($this->sPHPFormat[$i] == '\\'))
|
||||
{
|
||||
$bEscaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bEscaping)
|
||||
{
|
||||
$sResult .= $this->sPHPFormat[$i]; // No need to escape characters in the placeholder
|
||||
$bEscaping = false;
|
||||
}
|
||||
else if(array_key_exists($this->sPHPFormat[$i], $aMappings))
|
||||
{
|
||||
// Not a litteral value, must be replaced by Dict equivalent
|
||||
$sResult .= Dict::S('Core:DateTime:Placeholder_'.$this->sPHPFormat[$i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Normal char with no special meaning
|
||||
$sResult .= $this->sPHPFormat[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a subformat (Date or Time) by extracting the part of the whole DateTime format containing only the given placeholders
|
||||
* @return string
|
||||
*/
|
||||
protected function ToSubFormat($aPlaceholders)
|
||||
{
|
||||
$iStart = 999;
|
||||
$iEnd = 0;
|
||||
|
||||
foreach($aPlaceholders as $sChar)
|
||||
{
|
||||
$iPos = strpos($this->sPHPFormat, $sChar);
|
||||
if ($iPos !== false)
|
||||
{
|
||||
if (($iPos > 0) && ($this->sPHPFormat[$iPos-1] == '\\'))
|
||||
{
|
||||
// The placeholder is actually escaped, it's a litteral character, ignore it
|
||||
continue;
|
||||
}
|
||||
$iStart = min($iStart, $iPos);
|
||||
$iEnd = max($iEnd, $iPos);
|
||||
}
|
||||
}
|
||||
$sFormat = substr($this->sPHPFormat, $iStart, $iEnd - $iStart + 1);
|
||||
return $sFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the Date format string by extracting only the date part of the date and time format string
|
||||
* @return string
|
||||
*/
|
||||
public function ToDateFormat()
|
||||
{
|
||||
return $this->ToSubFormat(array('Y', 'y', 'd', 'j', 'm', 'n'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the Time format string by extracting only the time part of the date and time format string
|
||||
* @return string
|
||||
*/
|
||||
public function ToTimeFormat()
|
||||
{
|
||||
return $this->ToSubFormat(array('H', 'h', 'G', 'g', 'i', 's', 'a', 'A'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regular expression to (approximately) validate a date/time for the current format
|
||||
* The validation does not take into account the number of days in a month (i.e. June 31st will pass, as well as Feb 30th!)
|
||||
* @param string $sDelimiter Surround the regexp (and escape) if needed
|
||||
* @return string The regular expression in PCRE syntax
|
||||
*/
|
||||
public function ToRegExpr($sDelimiter = null)
|
||||
{
|
||||
$sRet = '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]:').'$';
|
||||
if ($sDelimiter !== null)
|
||||
{
|
||||
$sRet = $sDelimiter.str_replace($sDelimiter, '\\'.$sDelimiter, $sRet).$sDelimiter;
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -52,7 +52,7 @@ interface iDisplay
|
||||
/**
|
||||
* Class dbObject: the root of persistent classes
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -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->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue($this);
|
||||
$this->m_aOrigValues[$sAttCode] = null;
|
||||
if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
|
||||
{
|
||||
@@ -192,10 +192,14 @@ abstract class DBObject implements iDisplay
|
||||
return true;
|
||||
}
|
||||
|
||||
public function Reload()
|
||||
/**
|
||||
* @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/*, $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);
|
||||
@@ -291,16 +295,30 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
if (!$oAttDef->LoadInObject()) continue;
|
||||
|
||||
// Note: we assume that, for a given attribute, if it can be loaded,
|
||||
// then one column will be found with an empty suffix, the others have a suffix
|
||||
// Take care: the function isset will return false in case the value is null,
|
||||
// which is something that could happen on open joins
|
||||
$sAttRef = $sClassAlias.$sAttCode;
|
||||
|
||||
if (array_key_exists($sAttRef, $aRow))
|
||||
unset($value);
|
||||
$bIsDefined = false;
|
||||
if ($oAttDef->LoadFromDB())
|
||||
{
|
||||
$value = $oAttDef->FromSQLToValue($aRow, $sAttRef);
|
||||
// Note: we assume that, for a given attribute, if it can be loaded,
|
||||
// then one column will be found with an empty suffix, the others have a suffix
|
||||
// Take care: the function isset will return false in case the value is null,
|
||||
// which is something that could happen on open joins
|
||||
$sAttRef = $sClassAlias.$sAttCode;
|
||||
|
||||
if (array_key_exists($sAttRef, $aRow))
|
||||
{
|
||||
$value = $oAttDef->FromSQLToValue($aRow, $sAttRef);
|
||||
$bIsDefined = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = $oAttDef->ReadValue($this);
|
||||
$bIsDefined = true;
|
||||
}
|
||||
|
||||
if ($bIsDefined)
|
||||
{
|
||||
$this->m_aCurrValues[$sAttCode] = $value;
|
||||
if (is_object($value))
|
||||
{
|
||||
@@ -369,6 +387,7 @@ abstract class DBObject implements iDisplay
|
||||
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
{
|
||||
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
|
||||
$this->m_aLoadedAtt[$sCode] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -380,21 +399,25 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
{
|
||||
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue();
|
||||
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue($this);
|
||||
unset($this->m_aLoadedAtt[$sCode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!$oAttDef->IsScalar() && !is_object($value))
|
||||
{
|
||||
throw new CoreUnexpectedValue("scalar not allowed for attribute '$sAttCode', setting default value (empty list)");
|
||||
}
|
||||
if($oAttDef->IsLinkSet())
|
||||
{
|
||||
if((get_class($value) != 'DBObjectSet') && !is_subclass_of($value, 'DBObjectSet'))
|
||||
if (is_null($value))
|
||||
{
|
||||
throw new CoreUnexpectedValue("expecting a set of persistent objects (found a '".get_class($value)."'), setting default value (empty list)");
|
||||
// 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;
|
||||
@@ -412,7 +435,12 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_aCurrValues[$sAttCode] = $realvalue;
|
||||
$this->m_aTouchedAtt[$sAttCode] = true;
|
||||
unset($this->m_aModifiedAtt[$sAttCode]);
|
||||
|
||||
|
||||
foreach (MetaModel::ListMetaAttributes(get_class($this), $sAttCode) as $sMetaAttCode => $oMetaAttDef)
|
||||
{
|
||||
$this->Set($sMetaAttCode, $oMetaAttDef->MapValue($this));
|
||||
}
|
||||
|
||||
// The object has changed, reset caches
|
||||
$this->m_bCheckStatus = null;
|
||||
|
||||
@@ -479,13 +507,16 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
public function GetStrict($sAttCode)
|
||||
{
|
||||
if ($sAttCode == 'id')
|
||||
{
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
|
||||
if (!$oAttDef->LoadInObject())
|
||||
{
|
||||
$sParentAttCode = $oAttDef->GetParentAttCode();
|
||||
$parentValue = $this->GetStrict($sParentAttCode);
|
||||
$value = $oAttDef->GetValue($parentValue, $this);
|
||||
$value = $oAttDef->GetValue($this);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -541,7 +572,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue();
|
||||
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue($this);
|
||||
}
|
||||
$this->m_aLoadedAtt[$sCode] = true;
|
||||
}
|
||||
@@ -679,6 +710,17 @@ abstract class DBObject implements iDisplay
|
||||
if ($aCallInfo["function"] != "ComputeValues") continue;
|
||||
return; //skip!
|
||||
}
|
||||
|
||||
// Set the "null-not-allowed" datetimes (and dates) whose value is not initialized
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
|
||||
{
|
||||
// AttributeDate is derived from AttributeDateTime
|
||||
if (($oAttDef instanceof AttributeDateTime) && (!$oAttDef->IsNullAllowed()) && ($this->Get($sAttCode) == $oAttDef->GetNullValue()))
|
||||
{
|
||||
$this->Set($sAttCode, date($oAttDef->GetInternalFormat()));
|
||||
}
|
||||
}
|
||||
|
||||
$this->ComputeValues();
|
||||
}
|
||||
|
||||
@@ -699,8 +741,8 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLabel = $this->Get($sAttCode.'_friendlyname');
|
||||
return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sLabel);
|
||||
$sHtmlLabel = htmlentities($this->Get($sAttCode.'_friendlyname'), ENT_QUOTES, 'UTF-8');
|
||||
return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sHtmlLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,10 +791,10 @@ abstract class DBObject implements iDisplay
|
||||
return $oAtt->GetAsXML($this->Get($sAttCode), $this, $bLocalize);
|
||||
}
|
||||
|
||||
public function GetAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true)
|
||||
public function GetAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true, $bConvertToPlainText = false)
|
||||
{
|
||||
$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sTextQualifier, $this, $bLocalize);
|
||||
return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sTextQualifier, $this, $bLocalize, $bConvertToPlainText);
|
||||
}
|
||||
|
||||
public function GetOriginalAsHTML($sAttCode, $bLocalize = true)
|
||||
@@ -767,43 +809,51 @@ abstract class DBObject implements iDisplay
|
||||
return $oAtt->GetAsXML($this->GetOriginal($sAttCode), $this, $bLocalize);
|
||||
}
|
||||
|
||||
public function GetOriginalAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true)
|
||||
public function GetOriginalAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true, $bConvertToPlainText = false)
|
||||
{
|
||||
$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
return $oAtt->GetAsCSV($this->GetOriginal($sAttCode), $sSeparator, $sTextQualifier, $this, $bLocalize);
|
||||
return $oAtt->GetAsCSV($this->GetOriginal($sAttCode), $sSeparator, $sTextQualifier, $this, $bLocalize, $bConvertToPlainText);
|
||||
}
|
||||
|
||||
public static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)
|
||||
/**
|
||||
* @param $sObjClass
|
||||
* @param $sObjKey
|
||||
* @param string $sHtmlLabel Label with HTML entities escaped (< escaped as <)
|
||||
* @param null $sUrlMakerClass
|
||||
* @param bool|true $bWithNavigationContext
|
||||
* @return string
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public static function MakeHyperLink($sObjClass, $sObjKey, $sHtmlLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)
|
||||
{
|
||||
if ($sObjKey <= 0) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
|
||||
|
||||
// Safety net
|
||||
//
|
||||
if (empty($sLabel))
|
||||
if (empty($sHtmlLabel))
|
||||
{
|
||||
// If the object if not issued from a query but constructed programmatically
|
||||
// the label may be empty. In this case run a query to get the object's friendly name
|
||||
$oTmpObj = MetaModel::GetObject($sObjClass, $sObjKey, false);
|
||||
if (is_object($oTmpObj))
|
||||
{
|
||||
$sLabel = $oTmpObj->GetName();
|
||||
$sHtmlLabel = $oTmpObj->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
// May happen in case the target object is not in the list of allowed values for this attribute
|
||||
$sLabel = "<em>$sObjClass::$sObjKey</em>";
|
||||
$sHtmlLabel = "<em>$sObjClass::$sObjKey</em>";
|
||||
}
|
||||
//$sLabel = MetaModel::GetName($sObjClass)." #$sObjKey";
|
||||
}
|
||||
$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
|
||||
$sUrl = ApplicationContext::MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass, $bWithNavigationContext);
|
||||
if (strlen($sUrl) > 0)
|
||||
{
|
||||
return "<a href=\"$sUrl\" title=\"$sHint\">$sLabel</a>";
|
||||
return "<a href=\"$sUrl\" title=\"$sHint\">$sHtmlLabel</a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sLabel;
|
||||
return $sHtmlLabel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1100,6 +1150,10 @@ abstract class DBObject implements iDisplay
|
||||
return "Wrong format [$toCheck]";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $oAtt->CheckValue($this, $toCheck);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1396,6 +1450,19 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
// used both by insert/update
|
||||
private function WriteExternalAttributes()
|
||||
{
|
||||
foreach (MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (!$oAttDef->LoadInObject()) continue;
|
||||
if ($oAttDef->LoadFromDB()) continue;
|
||||
if (!array_key_exists($sAttCode, $this->m_aTouchedAtt)) continue;
|
||||
if (array_key_exists($sAttCode, $this->m_aModifiedAtt) && ($this->m_aModifiedAtt[$sAttCode] == false)) continue;
|
||||
$oAttDef->WriteValue($this, $this->m_aCurrValues[$sAttCode]);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this is experimental - it was designed to speed up the setup of iTop
|
||||
// Known limitations:
|
||||
// - does not work with multi-table classes (issue with the unique id to maintain in several tables)
|
||||
@@ -1582,6 +1649,8 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
$this->m_bIsInDB = true;
|
||||
$this->m_bDirty = false;
|
||||
|
||||
@@ -1803,11 +1872,15 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
$bHasANewExternalKeyValue = false;
|
||||
$aHierarchicalKeys = array();
|
||||
$aDBChanges = array();
|
||||
foreach($aChanges as $sAttCode => $valuecurr)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true;
|
||||
if (!$oAttDef->IsDirectField()) unset($aChanges[$sAttCode]);
|
||||
if ($oAttDef->IsDirectField())
|
||||
{
|
||||
$aDBChanges[$sAttCode] = $aChanges[$sAttCode];
|
||||
}
|
||||
if ($oAttDef->IsHierarchicalKey())
|
||||
{
|
||||
$aHierarchicalKeys[$sAttCode] = $oAttDef;
|
||||
@@ -1827,7 +1900,7 @@ abstract class DBObject implements iDisplay
|
||||
$iDelta =$iMyRight - $iMyLeft + 1;
|
||||
MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable);
|
||||
|
||||
if ($aChanges[$sAttCode] == 0)
|
||||
if ($aDBChanges[$sAttCode] == 0)
|
||||
{
|
||||
// No new parent, insert completely at the right of the tree
|
||||
$sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`";
|
||||
@@ -1844,39 +1917,44 @@ abstract class DBObject implements iDisplay
|
||||
else
|
||||
{
|
||||
// Insert at the right of the specified parent
|
||||
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aChanges[$sAttCode]);
|
||||
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aDBChanges[$sAttCode]);
|
||||
$iNewLeft = CMDBSource::QueryToScalar($sSQL);
|
||||
}
|
||||
|
||||
MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
|
||||
|
||||
$aHKChanges = array();
|
||||
$aHKChanges[$sAttCode] = $aChanges[$sAttCode];
|
||||
$aHKChanges[$sAttCode] = $aDBChanges[$sAttCode];
|
||||
$aHKChanges[$oAttDef->GetSQLLeft()] = $iNewLeft;
|
||||
$aHKChanges[$oAttDef->GetSQLRight()] = $iNewLeft + $iDelta - 1;
|
||||
$aChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below
|
||||
$aDBChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below
|
||||
}
|
||||
|
||||
// Update scalar attributes
|
||||
if (count($aChanges) != 0)
|
||||
if (count($aDBChanges) != 0)
|
||||
{
|
||||
$oFilter = new DBObjectSearch(get_class($this));
|
||||
$oFilter->AddCondition('id', $this->m_iKey, '=');
|
||||
$oFilter->AllowAllData();
|
||||
|
||||
$sSQL = $oFilter->MakeUpdateQuery($aChanges);
|
||||
$sSQL = $oFilter->MakeUpdateQuery($aDBChanges);
|
||||
CMDBSource::Query($sSQL);
|
||||
}
|
||||
}
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
|
||||
$this->AfterUpdate();
|
||||
|
||||
// Reload to get the external attributes
|
||||
if ($bHasANewExternalKeyValue)
|
||||
{
|
||||
$this->Reload();
|
||||
$this->Reload(true /* AllowAllData */);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1889,7 +1967,6 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_aOrigValues[$sAttCode] = is_object($value) ? clone $value : $value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (count($aChanges) != 0)
|
||||
@@ -1972,6 +2049,10 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
|
||||
}
|
||||
elseif (!$oAttDef->LoadFromDB())
|
||||
{
|
||||
$oAttDef->DeleteValue($this);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL) as $sParentClass)
|
||||
@@ -2067,13 +2148,21 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
/**
|
||||
* Designed as an action to be called when a stop watch threshold times out
|
||||
* or from within the framework
|
||||
*/
|
||||
* Designed as an action to be called when a stop watch threshold times out
|
||||
* or from within the framework
|
||||
* @param $sStimulusCode
|
||||
* @param bool|false $bDoNotWrite
|
||||
* @return bool
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
*/
|
||||
public function ApplyStimulus($sStimulusCode, $bDoNotWrite = false)
|
||||
{
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
|
||||
if (empty($sStateAttCode)) return false;
|
||||
if (empty($sStateAttCode))
|
||||
{
|
||||
throw new CoreException('No lifecycle for the class '.get_class($this));
|
||||
}
|
||||
|
||||
MyHelpers::CheckKeyInArray('object lifecycle stimulus', $sStimulusCode, MetaModel::EnumStimuli(get_class($this)));
|
||||
|
||||
@@ -2081,7 +2170,7 @@ abstract class DBObject implements iDisplay
|
||||
if (!array_key_exists($sStimulusCode, $aStateTransitions))
|
||||
{
|
||||
// This simulus has no effect in the current state... do nothing
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
$aTransitionDef = $aStateTransitions[$sStimulusCode];
|
||||
|
||||
@@ -2101,11 +2190,11 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
// Old (pre-2.1.0 modules) action definition without any parameter
|
||||
$aActionCallSpec = array($this, $actionHandler);
|
||||
$sActionDesc = get_class($this).'::'.$actionHandler;
|
||||
|
||||
if (!is_callable($aActionCallSpec))
|
||||
{
|
||||
throw new CoreException("Unable to call action: ".get_class($this)."::$actionHandler");
|
||||
return;
|
||||
}
|
||||
$bRet = call_user_func($aActionCallSpec, $sStimulusCode);
|
||||
}
|
||||
@@ -2113,6 +2202,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
// New syntax: 'verb' and typed parameters
|
||||
$sAction = $actionHandler['verb'];
|
||||
$sActionDesc = get_class($this).'::'.$sAction;
|
||||
$aParams = array();
|
||||
foreach($actionHandler['params'] as $aDefinition)
|
||||
{
|
||||
@@ -2145,7 +2235,12 @@ abstract class DBObject implements iDisplay
|
||||
$bRet = call_user_func_array($aCallSpec, $aParams);
|
||||
}
|
||||
// if one call fails, the whole is considered as failed
|
||||
if (!$bRet) $bSuccess = false;
|
||||
// (in case there is no returned value, null is obtained and means "ok")
|
||||
if ($bRet === false)
|
||||
{
|
||||
IssueLog::Info("Lifecycle action $sActionDesc returned false on object #".$this->GetKey());
|
||||
$bSuccess = false;
|
||||
}
|
||||
}
|
||||
if ($bSuccess)
|
||||
{
|
||||
@@ -2205,7 +2300,8 @@ abstract class DBObject implements iDisplay
|
||||
$oSW = $this->Get($sAttCode);
|
||||
$oSW->Reset($this, $oAttDef);
|
||||
$this->Set($sAttCode, $oSW);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle action: Recover the default value (aka when an object is being created)
|
||||
@@ -2213,7 +2309,7 @@ abstract class DBObject implements iDisplay
|
||||
public function Reset($sAttCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$this->Set($sAttCode, $oAttDef->GetDefaultValue());
|
||||
$this->Set($sAttCode, $oAttDef->GetDefaultValue($this));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2354,10 +2450,10 @@ abstract class DBObject implements iDisplay
|
||||
public function GetForTemplate($sPlaceholderAttCode)
|
||||
{
|
||||
$ret = null;
|
||||
if (($iPos = strpos($sPlaceholderAttCode, '->')) !== false)
|
||||
if (preg_match('/^([^-]+)-(>|>)(.+)$/', $sPlaceholderAttCode, $aMatches)) // Support both syntaxes: this->xxx or this->xxx for HTML compatibility
|
||||
{
|
||||
$sExtKeyAttCode = substr($sPlaceholderAttCode, 0, $iPos);
|
||||
$sRemoteAttCode = substr($sPlaceholderAttCode, $iPos + 2);
|
||||
$sExtKeyAttCode = $aMatches[1];
|
||||
$sRemoteAttCode = $aMatches[3];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sExtKeyAttCode))
|
||||
{
|
||||
throw new CoreException("Unknown attribute '$sExtKeyAttCode' for the class ".get_class($this));
|
||||
@@ -2388,20 +2484,12 @@ abstract class DBObject implements iDisplay
|
||||
$ret = $this->GetKey();
|
||||
break;
|
||||
|
||||
case 'hyperlink()':
|
||||
$ret = $this->GetHyperlink('iTopStandardURLMaker', false);
|
||||
break;
|
||||
|
||||
case 'hyperlink(portal)':
|
||||
$ret = $this->GetHyperlink('PortalURLMaker', false);
|
||||
break;
|
||||
|
||||
case 'name()':
|
||||
$ret = $this->GetName();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/^([^(]+)\\((.+)\\)$/', $sPlaceholderAttCode, $aMatches))
|
||||
if (preg_match('/^([^(]+)\\((.*)\\)$/', $sPlaceholderAttCode, $aMatches))
|
||||
{
|
||||
$sVerb = $aMatches[1];
|
||||
$sAttCode = $aMatches[2];
|
||||
@@ -2411,15 +2499,44 @@ abstract class DBObject implements iDisplay
|
||||
$sVerb = '';
|
||||
$sAttCode = $sPlaceholderAttCode;
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$ret = $oAttDef->GetForTemplate($this->Get($sAttCode), $sVerb, $this);
|
||||
}
|
||||
|
||||
if ($sVerb == 'hyperlink')
|
||||
{
|
||||
$sPortalId = ($sAttCode === '') ? 'console' : $sAttCode;
|
||||
if (!array_key_exists($sPortalId, self::$aPortalToURLMaker))
|
||||
{
|
||||
throw new Exception("Unknown portal id '$sPortalId' in placeholder '$sPlaceholderAttCode''");
|
||||
}
|
||||
$ret = $this->GetHyperlink(self::$aPortalToURLMaker[$sPortalId], false);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$ret = $oAttDef->GetForTemplate($this->Get($sAttCode), $sVerb, $this);
|
||||
}
|
||||
}
|
||||
if ($ret === null)
|
||||
{
|
||||
$ret = '';
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
static protected $aPortalToURLMaker = array('console' => 'iTopStandardURLMaker', 'portal' => 'PortalURLMaker');
|
||||
|
||||
/**
|
||||
* Associate a portal to a class that implements iDBObjectURLMaker,
|
||||
* and which will be invoked with placeholders like $this->org_id->hyperlink(portal)$
|
||||
*
|
||||
* @param $sPortalId Identifies the portal. Conventions: the main portal is 'console', The user requests portal is 'portal'.
|
||||
* @param $sUrlMakerClass
|
||||
*/
|
||||
static public function RegisterURLMakerClass($sPortalId, $sUrlMakerClass)
|
||||
{
|
||||
self::$aPortalToURLMaker[$sPortalId] = $sUrlMakerClass;
|
||||
}
|
||||
|
||||
// To be optionaly overloaded
|
||||
protected function OnInsert()
|
||||
{
|
||||
@@ -3008,5 +3125,355 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
return $sFingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a set of scripted actions onto the current object
|
||||
* See ExecAction for the syntax and features of the scripted actions
|
||||
*
|
||||
* @param $aActions array of statements (e.g. "set(name, Made after $source->name$)")
|
||||
* @param $aSourceObjects Array of Alias => Context objects (Convention: some statements require the 'source' element
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ExecActions($aActions, $aSourceObjects)
|
||||
{
|
||||
foreach($aActions as $sAction)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (preg_match('/^(\S*)\s*\((.*)\)$/ms', $sAction, $aMatches)) // multiline and newline matched by a dot
|
||||
{
|
||||
$sVerb = trim($aMatches[1]);
|
||||
$sParams = $aMatches[2];
|
||||
|
||||
// the coma is the separator for the parameters
|
||||
// comas can be escaped: \,
|
||||
$sParams = str_replace(array("\\\\", "\\,"), array("__backslash__", "__coma__"), $sParams);
|
||||
$sParams = trim($sParams);
|
||||
|
||||
if (strlen($sParams) == 0)
|
||||
{
|
||||
$aParams = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$aParams = explode(',', $sParams);
|
||||
foreach ($aParams as &$sParam)
|
||||
{
|
||||
$sParam = str_replace(array("__backslash__", "__coma__"), array("\\", ","), $sParam);
|
||||
$sParam = trim($sParam);
|
||||
}
|
||||
}
|
||||
$this->ExecAction($sVerb, $aParams, $aSourceObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Invalid syntax");
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new Exception('Action: '.$sAction.' - '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to copy an attribute between two objects (in memory)
|
||||
* Originally designed for ExecAction()
|
||||
*/
|
||||
public function CopyAttribute($oSourceObject, $sSourceAttCode, $sDestAttCode)
|
||||
{
|
||||
if ($sSourceAttCode == 'id')
|
||||
{
|
||||
$oSourceAttDef = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sDestAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sDestAttCode);
|
||||
}
|
||||
if (!MetaModel::IsValidAttCode(get_class($oSourceObject), $sSourceAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($oSourceObject)."::".$sSourceAttCode);
|
||||
}
|
||||
|
||||
$oSourceAttDef = MetaModel::GetAttributeDef(get_class($oSourceObject), $sSourceAttCode);
|
||||
}
|
||||
if (is_object($oSourceAttDef) && $oSourceAttDef->IsLinkSet())
|
||||
{
|
||||
// The copy requires that we create a new object set (the semantic of DBObject::Set is unclear about link sets)
|
||||
$oDestSet = DBObjectSet::FromScratch($oSourceAttDef->GetLinkedClass());
|
||||
$oSourceSet = $oSourceObject->Get($sSourceAttCode);
|
||||
$oSourceSet->Rewind();
|
||||
while ($oSourceLink = $oSourceSet->Fetch())
|
||||
{
|
||||
// Clone the link
|
||||
$sLinkClass = get_class($oSourceLink);
|
||||
$oLinkClone = MetaModel::NewObject($sLinkClass);
|
||||
foreach(MetaModel::ListAttributeDefs($sLinkClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
// As of now, ignore other attribute (do not attempt to recurse!)
|
||||
if ($oAttDef->IsScalar())
|
||||
{
|
||||
$oLinkClone->Set($sAttCode, $oSourceLink->Get($sAttCode));
|
||||
}
|
||||
}
|
||||
|
||||
// Not necessary - this will be handled by DBObject
|
||||
// $oLinkClone->Set($oSourceAttDef->GetExtKeyToMe(), 0);
|
||||
$oDestSet->AddObject($oLinkClone);
|
||||
}
|
||||
$this->Set($sDestAttCode, $oDestSet);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->Set($sDestAttCode, $oSourceObject->Get($sSourceAttCode));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a scripted action onto the current object
|
||||
* - clone (att1, att2, att3, ...)
|
||||
* - clone_scalars ()
|
||||
* - copy (source_att, dest_att)
|
||||
* - reset (att)
|
||||
* - nullify (att)
|
||||
* - set (att, value (placeholders $source->att$ or $current_date$, or $current_contact_id$, ...))
|
||||
* - append (att, value (placeholders $source->att$ or $current_date$, or $current_contact_id$, ...))
|
||||
* - add_to_list (source_key_att, dest_att)
|
||||
* - add_to_list (source_key_att, dest_att, lnk_att, lnk_att_value)
|
||||
* - apply_stimulus (stimulus)
|
||||
* - call_method (method_name)
|
||||
*
|
||||
* @param $sVerb string Any of the verb listed above (e.g. "set")
|
||||
* @param $aParams array of strings (e.g. array('name', 'copied from $source->name$')
|
||||
* @param $aSourceObjects Array of Alias => Context objects (Convention: some statements require the 'source' element
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ExecAction($sVerb, $aParams, $aSourceObjects)
|
||||
{
|
||||
switch($sVerb)
|
||||
{
|
||||
case 'clone':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
foreach($aParams as $sAttCode)
|
||||
{
|
||||
$this->CopyAttribute($oObjectToRead, $sAttCode, $sAttCode);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'clone_scalars':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if ($oAttDef->IsScalar())
|
||||
{
|
||||
$this->CopyAttribute($oObjectToRead, $sAttCode, $sAttCode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'copy':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: source attribute');
|
||||
}
|
||||
$sSourceAttCode = $aParams[0];
|
||||
if (!array_key_exists(1, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #2: target attribute');
|
||||
}
|
||||
$sDestAttCode = $aParams[1];
|
||||
$this->CopyAttribute($oObjectToRead, $sSourceAttCode, $sDestAttCode);
|
||||
break;
|
||||
|
||||
case 'reset':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: target attribute');
|
||||
}
|
||||
$sAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$this->Set($sAttCode, $oAttDef->GetDefaultValue());
|
||||
break;
|
||||
|
||||
case 'nullify':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: target attribute');
|
||||
}
|
||||
$sAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$this->Set($sAttCode, $oAttDef->GetNullValue());
|
||||
break;
|
||||
|
||||
case 'set':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: target attribute');
|
||||
}
|
||||
$sAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
if (!array_key_exists(1, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #2: value to set');
|
||||
}
|
||||
$sRawValue = $aParams[1];
|
||||
$aContext = array();
|
||||
foreach ($aSourceObjects as $sAlias => $oObject)
|
||||
{
|
||||
$aContext = array_merge($aContext, $oObject->ToArgs($sAlias));
|
||||
}
|
||||
$aContext['current_contact_id'] = UserRights::GetContactId();
|
||||
$aContext['current_contact_friendlyname'] = UserRights::GetUserFriendlyName();
|
||||
$aContext['current_date'] = date(AttributeDate::GetSQLFormat());
|
||||
$aContext['current_time'] = date(AttributeDateTime::GetSQLTimeFormat());
|
||||
$sValue = MetaModel::ApplyParams($sRawValue, $aContext);
|
||||
$this->Set($sAttCode, $sValue);
|
||||
break;
|
||||
|
||||
case 'append':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: target attribute');
|
||||
}
|
||||
$sAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
if (!array_key_exists(1, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #2: value to append');
|
||||
}
|
||||
$sRawAddendum = $aParams[1];
|
||||
$aContext = array();
|
||||
foreach ($aSourceObjects as $sAlias => $oObject)
|
||||
{
|
||||
$aContext = array_merge($aContext, $oObject->ToArgs($sAlias));
|
||||
}
|
||||
$aContext['current_contact_id'] = UserRights::GetContactId();
|
||||
$aContext['current_contact_friendlyname'] = UserRights::GetUserFriendlyName();
|
||||
$aContext['current_date'] = date(AttributeDate::GetSQLFormat());
|
||||
$aContext['current_time'] = date(AttributeDateTime::GetSQLTimeFormat());
|
||||
$sAddendum = MetaModel::ApplyParams($sRawAddendum, $aContext);
|
||||
$this->Set($sAttCode, $this->Get($sAttCode).$sAddendum);
|
||||
break;
|
||||
|
||||
case 'add_to_list':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: source attribute');
|
||||
}
|
||||
$sSourceKeyAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($oObjectToRead), $sSourceKeyAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($oObjectToRead)."::".$sSourceKeyAttCode);
|
||||
}
|
||||
if (!array_key_exists(1, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #2: target attribute (link set)');
|
||||
}
|
||||
$sTargetListAttCode = $aParams[1]; // indirect !!!
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sTargetListAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sTargetListAttCode);
|
||||
}
|
||||
if (isset($aParams[2]) && isset($aParams[3]))
|
||||
{
|
||||
$sRoleAttCode = $aParams[2];
|
||||
$sRoleValue = $aParams[3];
|
||||
}
|
||||
|
||||
$iObjKey = $oObjectToRead->Get($sSourceKeyAttCode);
|
||||
if ($iObjKey > 0)
|
||||
{
|
||||
$oLinkSet = $this->Get($sTargetListAttCode);
|
||||
|
||||
$oListAttDef = MetaModel::GetAttributeDef(get_class($this), $sTargetListAttCode);
|
||||
$oLnk = MetaModel::NewObject($oListAttDef->GetLinkedClass());
|
||||
$oLnk->Set($oListAttDef->GetExtKeyToRemote(), $iObjKey);
|
||||
if (isset($sRoleAttCode))
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode(get_class($oLnk), $sRoleAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($oLnk)."::".$sRoleAttCode);
|
||||
}
|
||||
$oLnk->Set($sRoleAttCode, $sRoleValue);
|
||||
}
|
||||
$oLinkSet->AddObject($oLnk);
|
||||
$this->Set($sTargetListAttCode, $oLinkSet);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'apply_stimulus':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: stimulus');
|
||||
}
|
||||
$sStimulus = $aParams[0];
|
||||
if (!in_array($sStimulus, MetaModel::EnumStimuli(get_class($this))))
|
||||
{
|
||||
throw new Exception("Unknown stimulus ".get_class($this)."::".$sStimulus);
|
||||
}
|
||||
$this->ApplyStimulus($sStimulus);
|
||||
break;
|
||||
|
||||
case 'call_method':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: method name');
|
||||
}
|
||||
$sMethod = $aParams[0];
|
||||
$aCallSpec = array($this, $sMethod);
|
||||
if (!is_callable($aCallSpec))
|
||||
{
|
||||
throw new Exception("Unknown method ".get_class($this)."::".$sMethod.'()');
|
||||
}
|
||||
// Note: $oObjectToRead has been preserved when adding $aSourceObjects, so as to remain backward compatible with methods having only 1 parameter ($oObjectToRead<61>
|
||||
call_user_func($aCallSpec, $oObjectToRead, $aSourceObjects);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Invalid verb");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Object set management
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -366,7 +366,7 @@ class DBObjectSet
|
||||
$oFilter = $this->m_oFilter->DeepClone();
|
||||
// Note: the arguments found within a set can be object (but not in a filter)
|
||||
// That's why PrepareQueryArguments must be invoked there
|
||||
$oFilter->SetInternalParams(array_merge($oFilter->GetInternalParams(), MetaModel::PrepareQueryArguments($this->m_aArgs)));
|
||||
$oFilter->SetInternalParams(array_merge($oFilter->GetInternalParams(), $this->m_aArgs));
|
||||
|
||||
if (count($this->m_aAddedIds) == 0)
|
||||
{
|
||||
@@ -445,8 +445,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 +461,34 @@ class DBObjectSet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
|
||||
*
|
||||
* @param hash $aAliases Format: alias => boolean (true = ascending, false = descending). If omitted, then it defaults to all the selected classes
|
||||
*/
|
||||
public function SetOrderByClasses($aAliases = null)
|
||||
{
|
||||
if ($aAliases === null)
|
||||
{
|
||||
$aAliases = array();
|
||||
foreach ($this->GetSelectedClasses() as $sAlias => $sClass)
|
||||
{
|
||||
$aAliases[$sAlias] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$aAttributes = array();
|
||||
foreach ($aAliases as $sAlias => $bClassDirection)
|
||||
{
|
||||
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClass($sAlias)) as $sAttCode => $bAttributeDirection)
|
||||
{
|
||||
$bDirection = $bClassDirection ? $bAttributeDirection : !$bAttributeDirection;
|
||||
$aAttributes[$sAlias.'.'.$sAttCode] = $bDirection;
|
||||
}
|
||||
}
|
||||
$this->SetOrderBy($aAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 'count' limit for loading the rows from the DB
|
||||
*
|
||||
@@ -1072,7 +1100,8 @@ class DBObjectSet
|
||||
*/
|
||||
public function ListConstantFields()
|
||||
{
|
||||
$aScalarArgs = $this->ExpandArgs();
|
||||
// The complete list of arguments will include magic arguments (e.g. current_user->attcode)
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($this->m_oFilter->GetInternalParams(), $this->m_aArgs);
|
||||
$aConst = $this->m_oFilter->ListConstantFields();
|
||||
|
||||
foreach($aConst as $sClassAlias => $aVals)
|
||||
@@ -1089,40 +1118,10 @@ class DBObjectSet
|
||||
return $aConst;
|
||||
}
|
||||
|
||||
protected function ExpandArgs()
|
||||
{
|
||||
$aScalarArgs = $this->m_oFilter->GetInternalParams();
|
||||
foreach($this->m_aArgs as $sArgName => $value)
|
||||
{
|
||||
if (MetaModel::IsValidObject($value))
|
||||
{
|
||||
if (strpos($sArgName, '->object()') === false)
|
||||
{
|
||||
// Lazy syntax - develop the object contextual parameters
|
||||
$aScalarArgs = array_merge($aScalarArgs, $value->ToArgsForQuery($sArgName));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Leave as is
|
||||
$aScalarArgs[$sArgName] = $value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_array($value)) // Sometimes ExtraParams contains a mix (like defaults[]) so non scalar parameters are ignored
|
||||
{
|
||||
$aScalarArgs[$sArgName] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$aScalarArgs['current_contact_id'] = UserRights::GetContactId();
|
||||
return $aScalarArgs;
|
||||
}
|
||||
|
||||
public function ApplyParameters()
|
||||
{
|
||||
$aScalarArgs = $this->ExpandArgs();
|
||||
$this->m_oFilter->ApplyParameters($aScalarArgs);
|
||||
$aAllArgs = MetaModel::PrepareQueryArguments($this->m_oFilter->GetInternalParams(), $this->m_aArgs);
|
||||
$this->m_oFilter->ApplyParameters($aAllArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,17 +20,6 @@
|
||||
require_once('dbobjectsearch.class.php');
|
||||
require_once('dbunionsearch.class.php');
|
||||
|
||||
|
||||
define('TREE_OPERATOR_EQUALS', 0);
|
||||
define('TREE_OPERATOR_BELOW', 1);
|
||||
define('TREE_OPERATOR_BELOW_STRICT', 2);
|
||||
define('TREE_OPERATOR_NOT_BELOW', 3);
|
||||
define('TREE_OPERATOR_NOT_BELOW_STRICT', 4);
|
||||
define('TREE_OPERATOR_ABOVE', 5);
|
||||
define('TREE_OPERATOR_ABOVE_STRICT', 6);
|
||||
define('TREE_OPERATOR_NOT_ABOVE', 7);
|
||||
define('TREE_OPERATOR_NOT_ABOVE_STRICT', 8);
|
||||
|
||||
/**
|
||||
* An object search
|
||||
*
|
||||
@@ -44,18 +33,17 @@ define('TREE_OPERATOR_NOT_ABOVE_STRICT', 8);
|
||||
* - do not provide a type-hint for function parameters defined in the modules
|
||||
* - leave the statements DBObjectSearch::FromOQL in the modules, though DBSearch is more relevant
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
abstract class DBSearch
|
||||
{
|
||||
protected $m_bDataFiltered = false;
|
||||
protected $m_aModifierProperties = array();
|
||||
const JOIN_POINTING_TO = 0;
|
||||
const JOIN_REFERENCED_BY = 1;
|
||||
|
||||
// By default, some information may be hidden to the current user
|
||||
// But it may happen that we need to disable that feature
|
||||
protected $m_bAllowAllData = false;
|
||||
protected $m_bNoContextParameters = false;
|
||||
protected $m_aModifierProperties = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -69,10 +57,11 @@ abstract class DBSearch
|
||||
return unserialize(serialize($this)); // Beware this serializes/unserializes the search and its parameters as well
|
||||
}
|
||||
|
||||
public function AllowAllData() {$this->m_bAllowAllData = true;}
|
||||
public function IsAllDataAllowed() {return $this->m_bAllowAllData;}
|
||||
public function IsDataFiltered() {return $this->m_bDataFiltered; }
|
||||
public function SetDataFiltered() {$this->m_bDataFiltered = true;}
|
||||
abstract public function AllowAllData();
|
||||
abstract public function IsAllDataAllowed();
|
||||
|
||||
public function NoContextParameters() {$this->m_bNoContextParameters = true;}
|
||||
public function HasContextParameters() {return $this->m_bNoContextParameters;}
|
||||
|
||||
public function SetModifierProperty($sPluginClass, $sProperty, $value)
|
||||
{
|
||||
@@ -102,6 +91,21 @@ abstract class DBSearch
|
||||
abstract public function ChangeClass($sNewClass, $sAlias = null);
|
||||
abstract public function GetSelectedClasses();
|
||||
|
||||
/**
|
||||
* @param array $aSelectedClasses array of aliases
|
||||
* @throws CoreException
|
||||
*/
|
||||
abstract public function SetSelectedClasses($aSelectedClasses);
|
||||
|
||||
/**
|
||||
* Change any alias of the query tree
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
* @return bool True if the alias has been found and changed
|
||||
*/
|
||||
abstract public function RenameAlias($sOldName, $sNewName);
|
||||
|
||||
abstract public function IsAny();
|
||||
|
||||
public function Describe(){return 'deprecated - use ToOQL() instead';}
|
||||
@@ -126,13 +130,71 @@ abstract class DBSearch
|
||||
abstract public function AddConditionAdvanced($sAttSpec, $value);
|
||||
abstract public function AddCondition_FullText($sFullText);
|
||||
|
||||
abstract public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS);
|
||||
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode);
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
* @throws CoreException
|
||||
* @throws CoreWarning
|
||||
*/
|
||||
abstract public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
*/
|
||||
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
|
||||
|
||||
abstract public function Intersect(DBSearch $oFilter);
|
||||
|
||||
/**
|
||||
* @param DBSearch $oFilter
|
||||
* @param integer $iDirection
|
||||
* @param string $sExtKeyAttCode
|
||||
* @param integer $iOperatorCode
|
||||
* @param array &$RealisasingMap Map of aliases from the attached query, that could have been renamed by the optimization process
|
||||
* @return DBSearch
|
||||
*/
|
||||
public function Join(DBSearch $oFilter, $iDirection, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
$oSourceFilter = $this->DeepClone();
|
||||
$oRet = null;
|
||||
|
||||
if ($oFilter instanceof DBUnionSearch)
|
||||
{
|
||||
$aSearches = array();
|
||||
foreach ($oFilter->GetSearches() as $oSearch)
|
||||
{
|
||||
$aSearches[] = $oSourceFilter->Join($oSearch, $iDirection, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
$oRet = new DBUnionSearch($aSearches);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iDirection === static::JOIN_POINTING_TO)
|
||||
{
|
||||
$oSourceFilter->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iOperatorCode !== TREE_OPERATOR_EQUALS)
|
||||
{
|
||||
throw new Exception('Only TREE_OPERATOR_EQUALS operator code is supported yet for AddCondition_ReferencedBy.');
|
||||
}
|
||||
$oSourceFilter->AddCondition_ReferencedBy($oFilter, $sExtKeyAttCode, TREE_OPERATOR_EQUALS, $aRealiasingMap);
|
||||
}
|
||||
$oRet = $oSourceFilter;
|
||||
}
|
||||
|
||||
return $oRet;
|
||||
}
|
||||
|
||||
abstract public function SetInternalParams($aParams);
|
||||
abstract public function GetInternalParams();
|
||||
abstract public function GetQueryParams();
|
||||
abstract public function GetQueryParams($bExcludeMagicParams = true);
|
||||
abstract public function ListConstantFields();
|
||||
|
||||
/**
|
||||
@@ -140,8 +202,8 @@ abstract class DBSearch
|
||||
* serialize a search
|
||||
*/
|
||||
abstract public function ApplyParameters($aArgs);
|
||||
|
||||
public function serialize($bDevelopParams = false, $aContextParams = null)
|
||||
|
||||
public function serialize($bDevelopParams = false, $aContextParams = null)
|
||||
{
|
||||
$sOql = $this->ToOql($bDevelopParams, $aContextParams);
|
||||
return base64_encode(serialize(array($sOql, $this->GetInternalParams(), $this->m_aModifierProperties)));
|
||||
@@ -160,7 +222,23 @@ abstract class DBSearch
|
||||
return $oRetFilter;
|
||||
}
|
||||
|
||||
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null);
|
||||
/**
|
||||
* Create a new DBObjectSearch from $oSearch with a new alias $sAlias
|
||||
*
|
||||
* Note : This has not be tested with UNION queries.
|
||||
*
|
||||
* @param DBSearch $oSearch
|
||||
* @param string $sAlias
|
||||
* @return DBObjectSearch
|
||||
*/
|
||||
static public function CloneWithAlias(DBSearch $oSearch, $sAlias)
|
||||
{
|
||||
$oSearchWithAlias = new DBObjectSearch($oSearch->GetClass(), $sAlias);
|
||||
$oSearchWithAlias = $oSearchWithAlias->Intersect($oSearch);
|
||||
return $oSearchWithAlias;
|
||||
}
|
||||
|
||||
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false);
|
||||
|
||||
static protected $m_aOQLQueries = array();
|
||||
|
||||
@@ -173,6 +251,12 @@ abstract class DBSearch
|
||||
return $oRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sQuery
|
||||
* @param array $aParams
|
||||
* @return DBSearch
|
||||
* @throws OQLException
|
||||
*/
|
||||
static public function FromOQL($sQuery, $aParams = null)
|
||||
{
|
||||
if (empty($sQuery)) return null;
|
||||
@@ -312,7 +396,7 @@ abstract class DBSearch
|
||||
$aAttToLoad = array();
|
||||
$oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr);
|
||||
|
||||
$aScalarArgs = array_merge(MetaModel::PrepareQueryArguments($aArgs), $this->GetInternalParams());
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
|
||||
try
|
||||
{
|
||||
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
@@ -330,8 +414,18 @@ abstract class DBSearch
|
||||
|
||||
|
||||
/**
|
||||
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
*/
|
||||
* @param array|hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
* @param array $aArgs
|
||||
* @param null $aAttToLoad
|
||||
* @param null $aExtendedDataSpec
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @param bool $bGetCount
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
* @throws MissingQueryArgument
|
||||
*/
|
||||
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
|
||||
{
|
||||
// Check the order by specification, and prefix with the class alias
|
||||
@@ -385,7 +479,16 @@ abstract class DBSearch
|
||||
|
||||
$oSQLQuery = $this->GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount);
|
||||
|
||||
$aScalarArgs = array_merge(MetaModel::PrepareQueryArguments($aArgs), $this->GetInternalParams());
|
||||
if ($this->m_bNoContextParameters)
|
||||
{
|
||||
// Only internal parameters
|
||||
$aScalarArgs = $this->GetInternalParams();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The complete list of arguments will include magic arguments (e.g. current_user->attcode)
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
|
||||
}
|
||||
try
|
||||
{
|
||||
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
@@ -408,129 +511,8 @@ abstract class DBSearch
|
||||
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null)
|
||||
{
|
||||
// Hide objects that are not visible to the current user
|
||||
//
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
|
||||
{
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false)
|
||||
{
|
||||
// Make sure this is a valid search object, saying NO for all
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass());
|
||||
}
|
||||
if (is_object($oVisibleObjects))
|
||||
{
|
||||
$oSearch = $this->Intersect($oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
else
|
||||
{
|
||||
// should be true at this point, meaning that no additional filtering
|
||||
// is required
|
||||
}
|
||||
}
|
||||
|
||||
// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
|
||||
//
|
||||
$aModifierProperties = MetaModel::MakeModifierProperties($oSearch);
|
||||
|
||||
// Create a unique cache id
|
||||
//
|
||||
if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries)
|
||||
{
|
||||
// Need to identify the query
|
||||
$sOqlQuery = $oSearch->ToOql();
|
||||
|
||||
if (count($aModifierProperties))
|
||||
{
|
||||
array_multisort($aModifierProperties);
|
||||
$sModifierProperties = json_encode($aModifierProperties);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sModifierProperties = '';
|
||||
}
|
||||
|
||||
$sRawId = $sOqlQuery.$sModifierProperties;
|
||||
if (!is_null($aAttToLoad))
|
||||
{
|
||||
$sRawId .= json_encode($aAttToLoad);
|
||||
}
|
||||
if (!is_null($aGroupByExpr))
|
||||
{
|
||||
foreach($aGroupByExpr as $sAlias => $oExpr)
|
||||
{
|
||||
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render();
|
||||
}
|
||||
}
|
||||
$sRawId .= $bGetCount;
|
||||
$sOqlId = md5($sRawId);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sOqlQuery = "SELECTING... ".$oSearch->GetClass();
|
||||
$sOqlId = "query id ? n/a";
|
||||
}
|
||||
|
||||
|
||||
// Query caching
|
||||
//
|
||||
if (self::$m_bQueryCacheEnabled)
|
||||
{
|
||||
// Warning: using directly the query string as the key to the hash array can FAIL if the string
|
||||
// is long and the differences are only near the end... so it's safer (but not bullet proof?)
|
||||
// to use a hash (like md5) of the string as the key !
|
||||
//
|
||||
// Example of two queries that were found as similar by the hash array:
|
||||
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTO' AND CustomerContract.customer_id = 2
|
||||
// and
|
||||
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2
|
||||
// the only difference is R instead or O at position 285 (TTR instead of TTO)...
|
||||
//
|
||||
if (array_key_exists($sOqlId, self::$m_aQueryStructCache))
|
||||
{
|
||||
// hit!
|
||||
|
||||
$oSQLQuery = unserialize(serialize(self::$m_aQueryStructCache[$sOqlId]));
|
||||
// Note: cloning is not enough because the subtree is made of objects
|
||||
}
|
||||
elseif (self::$m_bUseAPCCache)
|
||||
{
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
$sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-query-cache-'.$sOqlId;
|
||||
$oKPI = new ExecutionKPI();
|
||||
$result = apc_fetch($sOqlAPCCacheId);
|
||||
$oKPI->ComputeStats('Query APC (fetch)', $sOqlQuery);
|
||||
|
||||
if (is_object($result))
|
||||
{
|
||||
$oSQLQuery = $result;
|
||||
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($oSQLQuery))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oSQLQuery = $oSearch->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr);
|
||||
$oSQLQuery->SetSourceOQL($sOqlQuery);
|
||||
$oKPI->ComputeStats('MakeSQLQuery', $sOqlQuery);
|
||||
|
||||
if (self::$m_bQueryCacheEnabled)
|
||||
{
|
||||
if (self::$m_bUseAPCCache)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
apc_store($sOqlAPCCacheId, $oSQLQuery, self::$m_iQueryCacheTTL);
|
||||
$oKPI->ComputeStats('Query APC (store)', $sOqlQuery);
|
||||
}
|
||||
|
||||
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery->DeepClone();
|
||||
}
|
||||
}
|
||||
$oSQLQuery = $this->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
|
||||
$oSQLQuery->SetSourceOQL($this->ToOQL());
|
||||
|
||||
// Join to an additional table, if required...
|
||||
//
|
||||
@@ -540,7 +522,7 @@ abstract class DBSearch
|
||||
$aExtendedFields = array();
|
||||
foreach($aExtendedDataSpec['fields'] as $sColumn)
|
||||
{
|
||||
$sColRef = $oSearch->GetClassAlias().'_extdata_'.$sColumn;
|
||||
$sColRef = $this->GetClassAlias().'_extdata_'.$sColumn;
|
||||
$aExtendedFields[$sColRef] = new FieldExpressionResolved($sColumn, $sTableAlias);
|
||||
}
|
||||
$oSQLQueryExt = new SQLObjectQuery($aExtendedDataSpec['table'], $sTableAlias, $aExtendedFields);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* A union of DBObjectSearches
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -52,6 +52,30 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest common ancestor for each of the selected class
|
||||
*/
|
||||
protected function ComputeSelectedClasses()
|
||||
{
|
||||
// 1 - Collect all the column/classes
|
||||
$aColumnToClasses = array();
|
||||
foreach ($this->aSearches as $iPos => $oSearch)
|
||||
@@ -163,6 +187,39 @@ class DBUnionSearch extends DBSearch
|
||||
return $this->aSelectedClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aSelectedClasses array of aliases
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function SetSelectedClasses($aSelectedClasses)
|
||||
{
|
||||
// 1 - change for each search
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
// Throws an exception if not valid
|
||||
$oSearch->SetSelectedClasses($aSelectedClasses);
|
||||
}
|
||||
// 2 - update the lowest common ancestors
|
||||
$this->ComputeSelectedClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change any alias of the query tree
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
* @return bool True if the alias has been found and changed
|
||||
*/
|
||||
public function RenameAlias($sOldName, $sNewName)
|
||||
{
|
||||
$bRet = false;
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$bRet = $oSearch->RenameAlias($sOldName, $sNewName) || $bRet;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public function IsAny()
|
||||
{
|
||||
$bIsAny = true;
|
||||
@@ -258,19 +315,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)
|
||||
/**
|
||||
* @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);
|
||||
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,12 +373,12 @@ class DBUnionSearch extends DBSearch
|
||||
return $aParams;
|
||||
}
|
||||
|
||||
public function GetQueryParams()
|
||||
public function GetQueryParams($bExcludeMagicParams = true)
|
||||
{
|
||||
$aParams = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aParams = array_merge($oSearch->GetQueryParams(), $aParams);
|
||||
$aParams = array_merge($oSearch->GetQueryParams($bExcludeMagicParams), $aParams);
|
||||
}
|
||||
return $aParams;
|
||||
}
|
||||
@@ -333,17 +404,42 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new DBUnionSearch object where duplicates queries have been removed based on their OQLs
|
||||
*
|
||||
* @return \DBUnionSearch
|
||||
*/
|
||||
public function RemoveDuplicateQueries()
|
||||
{
|
||||
$aQueries = array();
|
||||
$aSearches = array();
|
||||
|
||||
foreach ($this->GetSearches() as $oTmpSearch)
|
||||
{
|
||||
$sQuery = $oTmpSearch->ToOQL(true);
|
||||
if (!in_array($sQuery, $aQueries))
|
||||
{
|
||||
$aQueries[] = $sQuery;
|
||||
$aSearches[] = $oTmpSearch;
|
||||
}
|
||||
}
|
||||
|
||||
$oNewSearch = new DBUnionSearch($aSearches);
|
||||
|
||||
return $oNewSearch;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Construction of the SQL queries
|
||||
@@ -360,11 +456,11 @@ class DBUnionSearch extends DBSearch
|
||||
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
|
||||
}
|
||||
|
||||
protected function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
protected function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
{
|
||||
if (count($this->aSearches) == 1)
|
||||
{
|
||||
return $this->aSearches[0]->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr);
|
||||
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
|
||||
}
|
||||
|
||||
$aSQLQueries = array();
|
||||
@@ -419,7 +515,7 @@ class DBUnionSearch extends DBSearch
|
||||
$aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
|
||||
}
|
||||
}
|
||||
$oSubQuery = $oSearch->MakeSQLQuery($aQueryAttToLoad, false, $aModifierProperties, $aQueryGroupByExpr, $aSearchSelectedClasses);
|
||||
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses);
|
||||
$aSQLQueries[] = $oSubQuery;
|
||||
}
|
||||
|
||||
|
||||
268
core/designdocument.class.inc.php
Normal file
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* Design document and associated nodes
|
||||
* @package Core
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop;
|
||||
|
||||
use \DOMDocument;
|
||||
use \DOMFormatException;
|
||||
|
||||
/**
|
||||
* Class \Combodo\iTop\DesignDocument
|
||||
*
|
||||
* A design document is the DOM tree that modelize behaviors. One of its
|
||||
* characteristics is that it can be altered by the mean of the same kind of document.
|
||||
*
|
||||
*/
|
||||
class DesignDocument extends DOMDocument
|
||||
{
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('1.0', 'UTF-8');
|
||||
$this->Init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloadable. Called prior to data loading.
|
||||
*/
|
||||
protected function Init()
|
||||
{
|
||||
$this->registerNodeClass('DOMElement', '\Combodo\iTop\DesignElement');
|
||||
|
||||
$this->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
|
||||
$this->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload of the standard API
|
||||
*/
|
||||
public function load($filename, $options = 0)
|
||||
{
|
||||
parent::load($filename, LIBXML_NOBLANKS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload of the standard API
|
||||
*/
|
||||
public function save($filename, $options = 0)
|
||||
{
|
||||
$this->documentElement->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
|
||||
return parent::save($filename, LIBXML_NOBLANKS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTML representation of the DOM, for debugging purposes
|
||||
* @param bool|false $bReturnRes Echoes or returns the HTML representation
|
||||
* @return mixed void or the HTML representation of the DOM
|
||||
*/
|
||||
public function Dump($bReturnRes = false)
|
||||
{
|
||||
$sXml = $this->saveXML();
|
||||
if ($bReturnRes)
|
||||
{
|
||||
return $sXml;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "<pre>\n";
|
||||
echo htmlentities($sXml);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote and escape strings for use within an XPath expression
|
||||
* Usage: DesignDocument::GetNodes('class[@id='.DesignDocument::XPathQuote($sId).']');
|
||||
* @param $sValue The value to be quoted
|
||||
* @return string to be used within an XPath
|
||||
*/
|
||||
public static function XPathQuote($sValue)
|
||||
{
|
||||
if (strpos($sValue, '"') !== false)
|
||||
{
|
||||
$aParts = explode('"', $sValue);
|
||||
$sRet = 'concat("'.implode('", \'"\', "', $aParts).'")';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = '"'.$sValue.'"';
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts some nodes from the DOM
|
||||
* @param string $sXPath A XPath expression
|
||||
* @param DesignNode|null $oContextNode The node to start the search from
|
||||
* @return \DOMNodeList
|
||||
*/
|
||||
public function GetNodes($sXPath, $oContextNode = null)
|
||||
{
|
||||
$oXPath = new \DOMXPath($this);
|
||||
if (is_null($oContextNode))
|
||||
{
|
||||
$oResult = $oXPath->query($sXPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oResult = $oXPath->query($sXPath, $oContextNode);
|
||||
}
|
||||
return $oResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative to getNodePath, that gives the id of nodes instead of the position within the children
|
||||
* @param $oNode The node to describe
|
||||
* @return string
|
||||
*/
|
||||
public static function GetItopNodePath($oNode)
|
||||
{
|
||||
if ($oNode instanceof \DOMDocument) return '';
|
||||
if (is_null($oNode)) return '';
|
||||
|
||||
$sId = $oNode->getAttribute('id');
|
||||
$sNodeDesc = ($sId != '') ? $oNode->nodeName.'['.$sId.']' : $oNode->nodeName;
|
||||
return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DesignElement: helper to read/change the DOM
|
||||
* @package ModelFactory
|
||||
*/
|
||||
class DesignElement extends \DOMElement
|
||||
{
|
||||
/**
|
||||
* Extracts some nodes from the DOM
|
||||
* @param string $sXPath A XPath expression
|
||||
* @return \DOMNodeList
|
||||
*/
|
||||
public function GetNodes($sXPath)
|
||||
{
|
||||
return $this->ownerDocument->GetNodes($sXPath, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTML representation of the DOM, for debugging purposes
|
||||
* @param bool|false $bReturnRes Echoes or returns the HTML representation
|
||||
* @return mixed void or the HTML representation of the DOM
|
||||
*/
|
||||
public function Dump($bReturnRes = false)
|
||||
{
|
||||
$oDoc = new DesignDocument();
|
||||
$oClone = $oDoc->importNode($this->cloneNode(true), true);
|
||||
$oDoc->appendChild($oClone);
|
||||
|
||||
$sXml = $oDoc->saveXML($oClone);
|
||||
if ($bReturnRes)
|
||||
{
|
||||
return $sXml;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "<pre>\n";
|
||||
echo htmlentities($sXml);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node directly under the given node
|
||||
* @param $sTagName
|
||||
* @param bool|true $bMustExist
|
||||
* @return null
|
||||
* @throws DOMFormatException
|
||||
*/
|
||||
public function GetUniqueElement($sTagName, $bMustExist = true)
|
||||
{
|
||||
$oNode = null;
|
||||
foreach($this->childNodes as $oChildNode)
|
||||
{
|
||||
if ($oChildNode->nodeName == $sTagName)
|
||||
{
|
||||
$oNode = $oChildNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($bMustExist && is_null($oNode))
|
||||
{
|
||||
throw new DOMFormatException('Missing unique tag: '.$sTagName);
|
||||
}
|
||||
return $oNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node directly under the current node, or null if missing
|
||||
* @param $sTagName
|
||||
* @return null
|
||||
* @throws DOMFormatException
|
||||
*/
|
||||
public function GetOptionalElement($sTagName)
|
||||
{
|
||||
return $this->GetUniqueElement($sTagName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TEXT of the current node (possibly from several child nodes)
|
||||
* @param null $sDefault
|
||||
* @return null|string
|
||||
*/
|
||||
public function GetText($sDefault = null)
|
||||
{
|
||||
$sText = null;
|
||||
foreach($this->childNodes as $oChildNode)
|
||||
{
|
||||
if ($oChildNode instanceof \DOMText)
|
||||
{
|
||||
if (is_null($sText)) $sText = '';
|
||||
$sText .= $oChildNode->wholeText;
|
||||
}
|
||||
}
|
||||
if (is_null($sText))
|
||||
{
|
||||
return $sDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TEXT value from a child node
|
||||
* @param string $sTagName
|
||||
* @param string|null $sDefault
|
||||
* @return string
|
||||
*/
|
||||
public function GetChildText($sTagName, $sDefault = null)
|
||||
{
|
||||
$sRet = $sDefault;
|
||||
if ($oChild = $this->GetOptionalElement($sTagName))
|
||||
{
|
||||
$sRet = $oChild->GetText($sDefault);
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -57,26 +57,13 @@ define('DICT_ERR_EXCEPTION', 2); // when a string is missing, throw an exception
|
||||
|
||||
class Dict
|
||||
{
|
||||
protected static $m_bTraceFiles = false;
|
||||
protected static $m_aEntryFiles = array();
|
||||
|
||||
protected static $m_iErrorMode = DICT_ERR_STRING;
|
||||
protected static $m_sDefaultLanguage = 'EN US';
|
||||
protected static $m_sCurrentLanguage = null; // No language selected by default
|
||||
|
||||
protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...)
|
||||
protected static $m_aData = array();
|
||||
|
||||
|
||||
public static function EnableTraceFiles()
|
||||
{
|
||||
self::$m_bTraceFiles = true;
|
||||
}
|
||||
|
||||
public static function GetEntryFiles()
|
||||
{
|
||||
return self::$m_aEntryFiles;
|
||||
}
|
||||
protected static $m_sApplicationPrefix = null;
|
||||
|
||||
public static function SetDefaultLanguage($sLanguageCode)
|
||||
{
|
||||
@@ -119,11 +106,20 @@ class Dict
|
||||
self::$m_iErrorMode = $iErrorMode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a localised string from the dictonary
|
||||
* @param string $sStringCode The code identifying the dictionary entry
|
||||
* @param string $sDefault Default value if there is no match in the dictionary
|
||||
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
|
||||
* @throws DictExceptionMissingString
|
||||
* @return unknown|Ambigous <>|string
|
||||
*/
|
||||
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
{
|
||||
// Attempt to find the string in the user language
|
||||
//
|
||||
self::InitLangIfNeeded(self::GetUserLanguage());
|
||||
|
||||
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
|
||||
{
|
||||
// It may happen, when something happens before the dictionnaries get loaded
|
||||
@@ -138,6 +134,8 @@ class Dict
|
||||
{
|
||||
// Attempt to find the string in the default language
|
||||
//
|
||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||
|
||||
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
|
||||
if (array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
@@ -145,6 +143,8 @@ class Dict
|
||||
}
|
||||
// Attempt to find the string in english
|
||||
//
|
||||
self::InitLangIfNeeded('EN US');
|
||||
|
||||
$aDefaultDictionary = self::$m_aData['EN US'];
|
||||
if (array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
@@ -175,6 +175,12 @@ class Dict
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats a localized string with numbered placeholders (%1$s...) for the additional arguments
|
||||
* See vsprintf for more information about the syntax of the placeholders
|
||||
* @param string $sFormatCode
|
||||
* @return string
|
||||
*/
|
||||
public static function Format($sFormatCode /*, ... arguments ....*/)
|
||||
{
|
||||
$sLocalizedFormat = self::S($sFormatCode);
|
||||
@@ -189,43 +195,95 @@ class Dict
|
||||
|
||||
return vsprintf($sLocalizedFormat, $aArguments);
|
||||
}
|
||||
|
||||
|
||||
// sLanguageCode: Code identifying the language i.e. FR-FR
|
||||
// sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France)
|
||||
// sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France)
|
||||
// aEntries: Hash array of dictionnary entries
|
||||
// ~~ or ~* can be used to indicate entries still to be translated.
|
||||
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
|
||||
|
||||
/**
|
||||
* Initialize a the entries for a given language (replaces the former Add() method)
|
||||
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
|
||||
* @param hash $aEntries Hash array of dictionnary entries
|
||||
*/
|
||||
public static function SetEntries($sLanguageCode, $aEntries)
|
||||
{
|
||||
if (self::$m_bTraceFiles)
|
||||
self::$m_aData[$sLanguageCode] = $aEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of available languages
|
||||
* @param hash $aLanguagesList
|
||||
*/
|
||||
public static function SetLanguagesList($aLanguagesList)
|
||||
{
|
||||
self::$m_aLanguages = $aLanguagesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a language from the language dictionary, if not already loaded
|
||||
* @param string $sLangCode Language code
|
||||
* @return boolean
|
||||
*/
|
||||
public static function InitLangIfNeeded($sLangCode)
|
||||
{
|
||||
if (array_key_exists($sLangCode, self::$m_aData)) return true;
|
||||
|
||||
$bResult = false;
|
||||
|
||||
if (function_exists('apc_fetch') && (self::$m_sApplicationPrefix !== null))
|
||||
{
|
||||
$aBacktrace = debug_backtrace();
|
||||
$sFile = $aBacktrace[0]["file"];
|
||||
|
||||
foreach($aEntries as $sKey => $sValue)
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
self::$m_aData[$sLangCode] = apc_fetch(self::$m_sApplicationPrefix.'-dict-'.$sLangCode);
|
||||
if (self::$m_aData[$sLangCode] === false)
|
||||
{
|
||||
self::$m_aEntryFiles[$sLanguageCode][$sKey] = array(
|
||||
'file' => $sFile,
|
||||
'value' => $sValue
|
||||
);
|
||||
unset(self::$m_aData[$sLangCode]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
if (!$bResult)
|
||||
{
|
||||
self::$m_aLanguages[$sLanguageCode] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
|
||||
self::$m_aData[$sLanguageCode] = array();
|
||||
}
|
||||
foreach($aEntries as $sCode => $sValue)
|
||||
{
|
||||
self::$m_aData[$sLanguageCode][$sCode] = self::FilterString($sValue);
|
||||
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
|
||||
require_once($sDictFile);
|
||||
|
||||
if (function_exists('apc_store') && (self::$m_sApplicationPrefix !== null))
|
||||
{
|
||||
apc_store(self::$m_sApplicationPrefix.'-dict-'.$sLangCode, self::$m_aData[$sLangCode]);
|
||||
}
|
||||
$bResult = true;
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable caching (cached using APC)
|
||||
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
|
||||
*/
|
||||
public static function EnableCache($sApplicationPrefix)
|
||||
{
|
||||
self::$m_sApplicationPrefix = $sApplicationPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cached entries (cached using APC)
|
||||
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
|
||||
*/
|
||||
public static function ResetCache($sApplicationPrefix)
|
||||
{
|
||||
if (function_exists('apc_delete'))
|
||||
{
|
||||
foreach(self::$m_aLanguages as $sLang => $void)
|
||||
{
|
||||
apc_delete($sApplicationPrefix.'-dict-'.$sLang);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Clone a string in every language (if it exists in that language)
|
||||
*/
|
||||
*/
|
||||
public static function CloneString($sSourceCode, $sDestCode)
|
||||
{
|
||||
foreach(self::$m_aLanguages as $sLanguageCode => $foo)
|
||||
@@ -236,14 +294,14 @@ class Dict
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
|
||||
{
|
||||
$aMissing = array(); // Strings missing for the target language
|
||||
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
|
||||
$aNotTranslated = array(); // Strings having the same value in both dictionaries
|
||||
$aOK = array(); // Strings having different values in both dictionaries
|
||||
|
||||
|
||||
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
|
||||
{
|
||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
|
||||
@@ -251,7 +309,7 @@ class Dict
|
||||
$aMissing[$sStringCode] = $sValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
|
||||
{
|
||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
|
||||
@@ -279,57 +337,23 @@ class Dict
|
||||
{
|
||||
MyHelpers::var_dump_html(self::$m_aData);
|
||||
}
|
||||
|
||||
public static function InCache($sApplicationPrefix)
|
||||
{
|
||||
if (function_exists('apc_fetch'))
|
||||
{
|
||||
$bResult = false;
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
self::$m_aData = apc_fetch($sApplicationPrefix.'-dict');
|
||||
if (is_bool(self::$m_aData) && (self::$m_aData === false))
|
||||
{
|
||||
self::$m_aData = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_aLanguages = apc_fetch($sApplicationPrefix.'-languages');
|
||||
if (is_bool(self::$m_aLanguages) && (self::$m_aLanguages === false))
|
||||
{
|
||||
self::$m_aLanguages = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function InitCache($sApplicationPrefix)
|
||||
{
|
||||
if (function_exists('apc_store'))
|
||||
{
|
||||
apc_store($sApplicationPrefix.'-languages', self::$m_aLanguages);
|
||||
apc_store($sApplicationPrefix.'-dict', self::$m_aData);
|
||||
}
|
||||
}
|
||||
|
||||
public static function ResetCache($sApplicationPrefix)
|
||||
// Only used by the setup to determine the list of languages to display in the initial setup screen
|
||||
// otherwise replaced by LoadModule by its own handler
|
||||
// sLanguageCode: Code identifying the language i.e. FR-FR
|
||||
// sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France)
|
||||
// sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France)
|
||||
// aEntries: Hash array of dictionnary entries
|
||||
// ~~ or ~* can be used to indicate entries still to be translated.
|
||||
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
|
||||
{
|
||||
if (function_exists('apc_delete'))
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
{
|
||||
apc_delete($sApplicationPrefix.'-languages');
|
||||
apc_delete($sApplicationPrefix.'-dict');
|
||||
self::$m_aLanguages[$sLanguageCode] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
|
||||
self::$m_aData[$sLanguageCode] = array();
|
||||
}
|
||||
}
|
||||
|
||||
protected static function FilterString($s)
|
||||
{
|
||||
return str_replace(array('~~', '~*'), '', $s);
|
||||
// No need to actually load the strings since it's only used to know the list of languages
|
||||
// at setup time !!
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -35,7 +35,7 @@ class DisplayableNode extends GraphNode
|
||||
* @param number $x Horizontal position
|
||||
* @param number $y Vertical position
|
||||
*/
|
||||
public function __construct(SimpleGraph $oGraph, $sId, $x = 0, $y = 0)
|
||||
public function __construct(SimpleGraph $oGraph, $sId, $x = null, $y = null)
|
||||
{
|
||||
parent::__construct($oGraph, $sId);
|
||||
$this->x = $x;
|
||||
@@ -236,6 +236,143 @@ class DisplayableNode extends GraphNode
|
||||
return is_object($this->GetProperty('object', null)) ? get_class($this->GetProperty('object', null)) : null;
|
||||
}
|
||||
|
||||
protected function AddToStats($oNode, &$aNodesPerClass)
|
||||
{
|
||||
$sClass = $oNode->GetObjectClass();
|
||||
if (!array_key_exists($sClass, $aNodesPerClass))
|
||||
{
|
||||
$aNodesPerClass[$sClass] = array(
|
||||
'reached' => array(
|
||||
'count' => 0,
|
||||
'nodes' => array(),
|
||||
'icon_url' => $oNode->GetProperty('icon_url'),
|
||||
),
|
||||
'not_reached' => array(
|
||||
'count' => 0,
|
||||
'nodes' => array(),
|
||||
'icon_url' => $oNode->GetProperty('icon_url'),
|
||||
)
|
||||
);
|
||||
}
|
||||
$sKey = $oNode->GetProperty('is_reached') ? 'reached' : 'not_reached';
|
||||
if (!array_key_exists($oNode->GetId(), $aNodesPerClass[$sClass][$sKey]['nodes']))
|
||||
{
|
||||
$aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode;
|
||||
$aNodesPerClass[$sClass][$sKey]['count'] += $oNode->GetObjectCount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of neighbour nodes, in the given direction: 'up' or 'down'
|
||||
* @param bool $bDirectionDown
|
||||
* @return multitype:NULL
|
||||
*/
|
||||
protected function GetNextNodes($bDirectionDown = true)
|
||||
{
|
||||
$aNextNodes = array();
|
||||
if ($bDirectionDown)
|
||||
{
|
||||
foreach($this->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
$aNextNodes[] = $oEdge->GetSinkNode();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($this->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$aNextNodes[] = $oEdge->GetSourceNode();
|
||||
}
|
||||
}
|
||||
return $aNextNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the next neighbour node (in the given direction: 'up' or 'down') by the supplied group node
|
||||
* preserving the connectivity of the graph
|
||||
* @param DisplayableGraph $oGraph
|
||||
* @param DisplayableNode $oNextNode
|
||||
* @param DisplayableGroupNode $oNewNode
|
||||
* @param bool $bDirectionDown
|
||||
*/
|
||||
protected function ReplaceNextNodeBy(DisplayableGraph $oGraph, DisplayableNode $oNextNode, DisplayableGroupNode $oNewNode, $bDirectionDown = true)
|
||||
{
|
||||
$sClass = $oNewNode->GetProperty('class');
|
||||
if ($bDirectionDown)
|
||||
{
|
||||
foreach($oNextNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
|
||||
{
|
||||
try
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// ignore this edge
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach($oNextNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
try
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// ignore this edge
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($oNextNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
if ($oEdge->GetSinkNode()->GetId() !== $this->GetId())
|
||||
{
|
||||
try
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// ignore this edge
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach($oNextNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
try
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// ignore this edge
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($oGraph->GetNode($oNextNode->GetId()))
|
||||
{
|
||||
$oGraph->_RemoveNode($oNextNode);
|
||||
if ($oNextNode instanceof DisplayableGroupNode)
|
||||
{
|
||||
// Copy all the objects of the previous group into the new group
|
||||
foreach($oNextNode->GetObjects() as $oObj)
|
||||
{
|
||||
$oNewNode->AddObject($oObj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oNewNode->AddObject($oNextNode->GetProperty('object'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group together (as a special kind of nodes) all the similar neighbours of the current node
|
||||
* @param DisplayableGraph $oGraph
|
||||
@@ -247,123 +384,65 @@ class DisplayableNode extends GraphNode
|
||||
{
|
||||
if ($this->GetProperty('grouped') === true) return;
|
||||
$this->SetProperty('grouped', true);
|
||||
|
||||
if ($bDirectionDown)
|
||||
|
||||
$aNodesPerClass = array();
|
||||
foreach($this->GetNextNodes($bDirectionDown) as $oNode)
|
||||
{
|
||||
$aNodesPerClass = array();
|
||||
foreach($this->GetOutgoingEdges() as $oEdge)
|
||||
$sClass = $oNode->GetObjectClass();
|
||||
if ($sClass !== null)
|
||||
{
|
||||
$oNode = $oEdge->GetSinkNode();
|
||||
$sClass = $oNode->GetObjectClass();
|
||||
if ($sClass !== null)
|
||||
{
|
||||
if (!array_key_exists($sClass, $aNodesPerClass))
|
||||
{
|
||||
$aNodesPerClass[$sClass] = array(
|
||||
'reached' => array(
|
||||
'count' => 0,
|
||||
'nodes' => array(),
|
||||
'icon_url' => $oNode->GetProperty('icon_url'),
|
||||
),
|
||||
'not_reached' => array(
|
||||
'count' => 0,
|
||||
'nodes' => array(),
|
||||
'icon_url' => $oNode->GetProperty('icon_url'),
|
||||
)
|
||||
);
|
||||
}
|
||||
$sKey = $oNode->GetProperty('is_reached') ? 'reached' : 'not_reached';
|
||||
if (!array_key_exists($oNode->GetId(), $aNodesPerClass[$sClass][$sKey]['nodes']))
|
||||
{
|
||||
$aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode;
|
||||
$aNodesPerClass[$sClass][$sKey]['count'] += $oNode->GetObjectCount();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
$this->AddToStats($oNode, $aNodesPerClass);
|
||||
}
|
||||
foreach($aNodesPerClass as $sClass => $aDefs)
|
||||
else
|
||||
{
|
||||
foreach($aDefs as $sStatus => $aGroupProps)
|
||||
$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
}
|
||||
foreach($aNodesPerClass as $sClass => $aDefs)
|
||||
{
|
||||
foreach($aDefs as $sStatus => $aGroupProps)
|
||||
{
|
||||
if (count($aGroupProps['nodes']) >= $iThresholdCount)
|
||||
{
|
||||
if (count($aGroupProps['nodes']) >= $iThresholdCount)
|
||||
$sNewId = $this->GetId().'::'.$sClass.'/'.(($sStatus == 'reached') ? '_reached': '');
|
||||
$oNewNode = $oGraph->GetNode($sNewId);
|
||||
if ($oNewNode == null)
|
||||
{
|
||||
$sNewId = $this->GetId().'::'.$sClass.'/'.(($sStatus == 'reached') ? '_reached': '');
|
||||
$oNewNode = $oGraph->GetNode($sNewId);
|
||||
if ($oNewNode == null)
|
||||
{
|
||||
$oNewNode = new DisplayableGroupNode($oGraph, $sNewId);
|
||||
$oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
|
||||
$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
|
||||
$oNewNode->SetProperty('class', $sClass);
|
||||
$oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
|
||||
$oNewNode->SetProperty('count', $aGroupProps['count']);
|
||||
}
|
||||
|
||||
try
|
||||
$oNewNode = new DisplayableGroupNode($oGraph, $sNewId);
|
||||
$oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
|
||||
$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
|
||||
$oNewNode->SetProperty('class', $sClass);
|
||||
$oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
|
||||
$oNewNode->SetProperty('count', $aGroupProps['count']);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if ($bDirectionDown)
|
||||
{
|
||||
$oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
|
||||
}
|
||||
catch(Exception $e)
|
||||
else
|
||||
{
|
||||
// Ignore this redundant egde
|
||||
}
|
||||
|
||||
foreach($aGroupProps['nodes'] as $oNode)
|
||||
{
|
||||
foreach($oNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
|
||||
{
|
||||
try
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// ignore this edge
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach($oNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
$aOutgoing[] = $oEdge->GetSinkNode();
|
||||
try
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oNewNode, $oEdge->GetSinkNode());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// ignore this edge
|
||||
}
|
||||
}
|
||||
if ($oGraph->GetNode($oNode->GetId()))
|
||||
{
|
||||
$oGraph->_RemoveNode($oNode);
|
||||
if ($oNode instanceof DisplayableGroupNode)
|
||||
{
|
||||
// Copy all the objects of the previous group into the new group
|
||||
foreach($oNode->GetObjects() as $oObj)
|
||||
{
|
||||
$oNewNode->AddObject($oObj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oNewNode->AddObject($oNode->GetProperty('object'));
|
||||
}
|
||||
}
|
||||
$oOutgoingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $oNewNode, $this);
|
||||
}
|
||||
$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
else
|
||||
catch(Exception $e)
|
||||
{
|
||||
foreach($aGroupProps['nodes'] as $oNode)
|
||||
{
|
||||
$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
// Ignore this redundant egde
|
||||
}
|
||||
|
||||
foreach($aGroupProps['nodes'] as $oNextNode)
|
||||
{
|
||||
$this->ReplaceNextNodeBy($oGraph, $oNextNode, $oNewNode, $bDirectionDown);
|
||||
}
|
||||
$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($aGroupProps['nodes'] as $oNode)
|
||||
{
|
||||
$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,64 +565,67 @@ class DisplayableRedundancyNode extends DisplayableNode
|
||||
public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
|
||||
{
|
||||
parent::GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
|
||||
|
||||
if ($bDirectionUp)
|
||||
{
|
||||
$aNodesPerClass = array();
|
||||
foreach($this->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$oNode = $oEdge->GetSourceNode();
|
||||
|
||||
|
||||
if (($oNode->GetObjectClass() !== null) && (!$oNode->GetProperty('is_reached')))
|
||||
{
|
||||
$sClass = $oNode->GetObjectClass();
|
||||
if (!array_key_exists($sClass, $aNodesPerClass))
|
||||
{
|
||||
$aNodesPerClass[$sClass] = array('reached' => array(), 'not_reached' => array());
|
||||
}
|
||||
$aNodesPerClass[$sClass][$oNode->GetProperty('is_reached') ? 'reached' : 'not_reached'][] = $oNode;
|
||||
{
|
||||
$this->AddToStats($oNode, $aNodesPerClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
//$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
foreach($aNodesPerClass as $sClass => $aDefs)
|
||||
{
|
||||
foreach($aDefs as $sStatus => $aNodes)
|
||||
foreach($aDefs as $sStatus => $aGroupProps)
|
||||
{
|
||||
if (count($aNodes) >= $iThresholdCount)
|
||||
if (count($aGroupProps['nodes']) >= $iThresholdCount)
|
||||
{
|
||||
$oNewNode = new DisplayableGroupNode($oGraph, '-'.$this->GetId().'::'.$sClass.'/'.$sStatus);
|
||||
$oNewNode->SetProperty('label', 'x'.count($aNodes));
|
||||
$oNewNode->SetProperty('icon_url', $aNodes[0]->GetProperty('icon_url'));
|
||||
$oNewNode->SetProperty('is_reached', $aNodes[0]->GetProperty('is_reached'));
|
||||
|
||||
$oOutgoingEdge = new DisplayableEdge($oGraph, '-'.$this->GetId().'-'.$oNewNode->GetId().'/'.$sStatus, $oNewNode, $this);
|
||||
|
||||
foreach($aNodes as $oNode)
|
||||
$oNewNode->SetProperty('label', 'x'.count($aGroupProps['nodes']));
|
||||
$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
|
||||
$oNewNode->SetProperty('is_reached', ($sStatus == 'is_reached'));
|
||||
$oNewNode->SetProperty('class', $sClass);
|
||||
$oNewNode->SetProperty('count', count($aGroupProps['nodes']));
|
||||
|
||||
|
||||
$sNewId = $this->GetId().'::'.$sClass.'/'.(($sStatus == 'reached') ? '_reached': '');
|
||||
$oNewNode = $oGraph->GetNode($sNewId);
|
||||
if ($oNewNode == null)
|
||||
{
|
||||
foreach($oNode->GetIncomingEdges() as $oEdge)
|
||||
{
|
||||
$oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
|
||||
}
|
||||
foreach($oNode->GetOutgoingEdges() as $oEdge)
|
||||
{
|
||||
if ($oEdge->GetSinkNode()->GetId() !== $this->GetId())
|
||||
{
|
||||
$aOutgoing[] = $oEdge->GetSinkNode();
|
||||
$oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass.'/'.$sStatus, $oNewNode, $oEdge->GetSinkNode());
|
||||
}
|
||||
}
|
||||
$oGraph->_RemoveNode($oNode);
|
||||
$oNewNode->AddObject($oNode->GetProperty('object'));
|
||||
$oNewNode = new DisplayableGroupNode($oGraph, $sNewId);
|
||||
$oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
|
||||
$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
|
||||
$oNewNode->SetProperty('class', $sClass);
|
||||
$oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
|
||||
$oNewNode->SetProperty('count', $aGroupProps['count']);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$oOutgoingEdge = new DisplayableEdge($oGraph, '-'.$this->GetId().'-'.$oNewNode->GetId().'/'.$sStatus, $oNewNode, $this);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// Ignore this redundant egde
|
||||
}
|
||||
|
||||
foreach($aGroupProps['nodes'] as $oNextNode)
|
||||
{
|
||||
$this->ReplaceNextNodeBy($oGraph, $oNextNode, $oNewNode, !$bDirectionUp);
|
||||
}
|
||||
//$oNewNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($aNodes as $oNode)
|
||||
foreach($aGroupProps['nodes'] as $oNode)
|
||||
{
|
||||
//$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown);
|
||||
}
|
||||
@@ -580,10 +662,21 @@ class DisplayableEdge extends GraphEdge
|
||||
{
|
||||
public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs)
|
||||
{
|
||||
$xStart = $this->GetSourceNode()->x * $fScale;
|
||||
$yStart = $this->GetSourceNode()->y * $fScale;
|
||||
$xEnd = $this->GetSinkNode()->x * $fScale;
|
||||
$yEnd = $this->GetSinkNode()->y * $fScale;
|
||||
$oSourceNode = $this->GetSourceNode();
|
||||
if (($oSourceNode->x == null) || ($oSourceNode->y == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
$xStart = $oSourceNode->x * $fScale;
|
||||
$yStart = $oSourceNode->y * $fScale;
|
||||
|
||||
$oSinkNode = $this->GetSinkNode();
|
||||
if (($oSinkNode->x == null) || ($oSinkNode->y == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
$xEnd = $oSinkNode->x * $fScale;
|
||||
$yEnd = $oSinkNode->y * $fScale;
|
||||
|
||||
$bReached = ($this->GetSourceNode()->GetProperty('is_reached') && $this->GetSinkNode()->GetProperty('is_reached'));
|
||||
|
||||
@@ -627,14 +720,17 @@ class DisplayableGroupNode extends DisplayableNode
|
||||
$this->aObjects = array();
|
||||
}
|
||||
|
||||
public function AddObject(DBObject $oObj)
|
||||
public function AddObject(DBObject $oObj = null)
|
||||
{
|
||||
$sPrevClass = $this->GetObjectClass();
|
||||
if (($sPrevClass !== null) && (get_class($oObj) !== $sPrevClass))
|
||||
if (is_object($oObj))
|
||||
{
|
||||
throw new Exception("Error: adding an object of class '".get_class($oObj)."' to a group of '$sPrevClass' objects.");
|
||||
$sPrevClass = $this->GetObjectClass();
|
||||
if (($sPrevClass !== null) && (get_class($oObj) !== $sPrevClass))
|
||||
{
|
||||
throw new Exception("Error: adding an object of class '".get_class($oObj)."' to a group of '$sPrevClass' objects.");
|
||||
}
|
||||
$this->aObjects[$oObj->GetKey()] = $oObj;
|
||||
}
|
||||
$this->aObjects[$oObj->GetKey()] = $oObj;
|
||||
}
|
||||
|
||||
public function GetObjects()
|
||||
@@ -863,9 +959,13 @@ class DisplayableGraph extends SimpleGraph
|
||||
foreach($oNodesIter as $oNode)
|
||||
{
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
if ($oNode->GetProperty('source'))
|
||||
if ($bDirectionDown && $oNode->GetProperty('source'))
|
||||
{
|
||||
$oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, true);
|
||||
$oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, $bDirectionDown);
|
||||
}
|
||||
else if (!$bDirectionDown && $oNode->GetProperty('sink'))
|
||||
{
|
||||
$oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, $bDirectionDown);
|
||||
}
|
||||
}
|
||||
// Groups numbering
|
||||
@@ -878,7 +978,7 @@ class DisplayableGraph extends SimpleGraph
|
||||
{
|
||||
if ($oNode->GetObjectCount() == 0)
|
||||
{
|
||||
// Remove emtpry groups
|
||||
// Remove empty groups
|
||||
$oNewGraph->_RemoveNode($oNode);
|
||||
}
|
||||
else
|
||||
@@ -945,8 +1045,15 @@ class DisplayableGraph extends SimpleGraph
|
||||
$yPos = $aMatches[3];
|
||||
|
||||
$oNode = $this->GetNode($sId);
|
||||
$oNode->x = (float)$xPos;
|
||||
$oNode->y = (float)$yPos;
|
||||
if ($oNode !== null)
|
||||
{
|
||||
$oNode->x = (float)$xPos;
|
||||
$oNode->y = (float)$yPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Warning("??? Position of the non-existing node '$sId', x=$xPos, y=$yPos");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1009,7 +1116,7 @@ class DisplayableGraph extends SimpleGraph
|
||||
{
|
||||
$aContextDefs = static::GetContextDefinitions($sContextKey, false);
|
||||
|
||||
$aData = array('nodes' => array(), 'edges' => array(), 'groups' => array());
|
||||
$aData = array('nodes' => array(), 'edges' => array(), 'groups' => array(), 'lists' => array());
|
||||
$iGroupIdx = 0;
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
foreach($oIterator as $sId => $oNode)
|
||||
@@ -1032,10 +1139,35 @@ class DisplayableGraph extends SimpleGraph
|
||||
$aData['groups'][$iGroupIdx] = array('class' => $sClass, 'keys' => $aKeys);
|
||||
$oNode->SetProperty('group_index', $iGroupIdx);
|
||||
$iGroupIdx++;
|
||||
|
||||
if ($oNode->GetProperty('is_reached'))
|
||||
{
|
||||
// Also add the objects from this group into the 'list' tab
|
||||
if (!array_key_exists($sClass, $aData['lists']))
|
||||
{
|
||||
$aData['lists'][$sClass] = $aKeys;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aData['lists'][$sClass] = array_merge($aData['lists'][$sClass], $aKeys);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (($oNode instanceof DisplayableNode) && $oNode->GetProperty('is_reached') && is_object($oNode->GetProperty('object')))
|
||||
{
|
||||
$sObjClass = get_class($oNode->GetProperty('object'));
|
||||
if (!array_key_exists($sObjClass, $aData['lists']))
|
||||
{
|
||||
$aData['lists'][$sObjClass] = array();
|
||||
}
|
||||
$aData['lists'][$sObjClass][] = $oNode->GetProperty('object')->GetKey();
|
||||
}
|
||||
$aData['nodes'][] = $oNode->GetForRaphael($aContextDefs);
|
||||
}
|
||||
|
||||
uksort($aData['lists'], array(get_class($this), 'SortOnClassLabel')); // sort on the localized names of the classes to provide a consistent and stable order
|
||||
|
||||
$oIterator = new RelationTypeIterator($this, 'Edge');
|
||||
foreach($oIterator as $sId => $oEdge)
|
||||
{
|
||||
@@ -1051,6 +1183,17 @@ class DisplayableGraph extends SimpleGraph
|
||||
return json_encode($aData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort class "codes" based on their localized name
|
||||
* @param string $sClass1
|
||||
* @param string $sClass2
|
||||
* @return number -1, 0 or 1
|
||||
*/
|
||||
public static function SortOnClassLabel($sClass1, $sClass2)
|
||||
{
|
||||
return strcasecmp(MetaModel::GetName($sClass1), MetaModel::GetName($sClass2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the graph in a PDF document: centered in the current page
|
||||
* @param PDFPage $oPage The PDFPage representing the PDF document to draw into
|
||||
@@ -1124,7 +1267,7 @@ class DisplayableGraph extends SimpleGraph
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
$oNode->RenderAsPDF($oPdf, $this, $fScale, $aContextDefs);
|
||||
}
|
||||
$oIterator = new RelationTypeIterator($this, 'Node');
|
||||
|
||||
$oPdf->SetAutoPageBreak(true, $fBreakMargin);
|
||||
$oPdf->SetAlpha(1);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Send an email (abstraction for synchronous/asynchronous modes)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -57,9 +57,6 @@ class EMail
|
||||
{
|
||||
$this->m_aData = array();
|
||||
$this->m_oMessage = Swift_Message::newInstance();
|
||||
|
||||
$oEncoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit');
|
||||
$this->m_oMessage->setEncoder($oEncoder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,6 +154,9 @@ class EMail
|
||||
|
||||
protected function SendSynchronous(&$aIssues, $oLog = null)
|
||||
{
|
||||
// If the body of the message is in HTML, embed all images based on attachments
|
||||
$this->EmbedInlineImages();
|
||||
|
||||
$this->LoadConfig();
|
||||
|
||||
$sTransport = self::$m_oConfig->Get('email_transport');
|
||||
@@ -194,6 +194,7 @@ class EMail
|
||||
$oMailer = Swift_Mailer::newInstance($oTransport);
|
||||
|
||||
$aFailedRecipients = array();
|
||||
$this->m_oMessage->setMaxLineLength(0);
|
||||
$iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients);
|
||||
if ($iSent === 0)
|
||||
{
|
||||
@@ -208,9 +209,46 @@ class EMail
|
||||
return EMAIL_SEND_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reprocess the body of the message (if it is an HTML message)
|
||||
* to replace the URL of images based on attachments by a link
|
||||
* to an embedded image (i.e. cid:....)
|
||||
*/
|
||||
protected function EmbedInlineImages()
|
||||
{
|
||||
if ($this->m_aData['body']['mimeType'] == 'text/html')
|
||||
{
|
||||
$oDOMDoc = new DOMDocument();
|
||||
$oDOMDoc->preserveWhitespace = true;
|
||||
@$oDOMDoc->loadHTML('<?xml encoding="UTF-8"?>'.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified
|
||||
|
||||
$oXPath = new DOMXPath($oDOMDoc);
|
||||
$sXPath = "//img[@data-img-id]";
|
||||
$oImagesList = $oXPath->query($sXPath);
|
||||
|
||||
if ($oImagesList->length != 0)
|
||||
{
|
||||
foreach($oImagesList as $oImg)
|
||||
{
|
||||
$iAttId = $oImg->getAttribute('data-img-id');
|
||||
$oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */);
|
||||
if ($oAttachment)
|
||||
{
|
||||
$oDoc = $oAttachment->Get('contents');
|
||||
$oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType());
|
||||
$sCid = $this->m_oMessage->embed($oSwiftImage);
|
||||
$oImg->setAttribute('src', $sCid);
|
||||
}
|
||||
}
|
||||
}
|
||||
$sHtmlBody = $oDOMDoc->saveHTML();
|
||||
$this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
|
||||
{
|
||||
{
|
||||
if ($bForceSynchronous)
|
||||
{
|
||||
return $this->SendSynchronous($aIssues, $oLog);
|
||||
@@ -265,8 +303,14 @@ class EMail
|
||||
$this->AddToHeader('References', $sReferences);
|
||||
}
|
||||
|
||||
public function SetBody($sBody, $sMimeType = 'text/html')
|
||||
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)
|
||||
{
|
||||
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
|
||||
{
|
||||
require_once(APPROOT.'lib/emogrifier/Classes/Emogrifier.php');
|
||||
$emogrifier = new \Pelago\Emogrifier($sBody, $sCustomStyles);
|
||||
$sBody = $emogrifier->emogrify(); // Adds html/body tags if not already present
|
||||
}
|
||||
$this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
|
||||
$this->m_oMessage->setBody($sBody, $sMimeType);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Bulk export: Excel (xlsx) export
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -46,12 +46,37 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
{
|
||||
$oP->p(" * xlsx format options:");
|
||||
$oP->p(" *\tfields: the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
|
||||
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
|
||||
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
|
||||
}
|
||||
|
||||
|
||||
public function ReadParameters()
|
||||
{
|
||||
parent::ReadParameters();
|
||||
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
|
||||
|
||||
$sDateFormatRadio = utils::ReadParam('excel_date_format_radio', '');
|
||||
switch($sDateFormatRadio)
|
||||
{
|
||||
case 'default':
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
{
|
||||
return array_merge(parent::EnumFormParts(), array('interactive_fields_xlsx' => array('interactive_fields_xlsx')));
|
||||
return array_merge(parent::EnumFormParts(), array('xlsx_options' => array('formatted_text') ,'interactive_fields_xlsx' => array('interactive_fields_xlsx')));
|
||||
}
|
||||
|
||||
public function DisplayFormPart(WebPage $oP, $sPartId)
|
||||
@@ -62,7 +87,42 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
$this->GetInteractiveFieldsWidget($oP, 'interactive_fields_xlsx');
|
||||
break;
|
||||
|
||||
default:
|
||||
case 'xlsx_options':
|
||||
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:XLSXOptions').'</legend>');
|
||||
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
|
||||
|
||||
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
|
||||
$oP->add('<input type="checkbox" id="xlsx_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="xlsx_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
|
||||
|
||||
$oP->add('</td><td style="vertical-align:top">');
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$oP->add('<input type="radio" id="excel_date_time_format_default" name="excel_date_format_radio" value="default"'.$sDefaultChecked.'><label for="excel_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="excel_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oP->add('<input type="radio" id="excel_date_time_format_custom" name="excel_date_format_radio" value="custom"'.$sCustomChecked.'><label for="excel_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
|
||||
|
||||
$oP->add('</td></tr></table>');
|
||||
|
||||
$oP->add('</fieldset>');
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#excel_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_xlsx_options').on('preview_updated', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
$('#excel_date_time_format_default').on('click', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
$('#excel_date_time_format_custom').on('click', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
$('#excel_custom_date_time_format').on('click', function() { $('#excel_date_time_format_custom').prop('checked', true); FormatDatesInPreview('excel', 'xlsx'); }).on('keyup', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
EOF
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
return parent:: DisplayFormPart($oP, $sPartId);
|
||||
}
|
||||
}
|
||||
@@ -88,6 +148,15 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
}
|
||||
return '<div class="text-preview">'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
|
||||
@@ -103,18 +172,51 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
$value = $oObj->Get($sAttCode);
|
||||
if ($value instanceOf ormCaseLog)
|
||||
{
|
||||
if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
|
||||
{
|
||||
$sText = $value->GetText();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sText = $value->GetAsPlainText();
|
||||
}
|
||||
// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
|
||||
$sRet = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText()));
|
||||
$sRet = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $sText));
|
||||
}
|
||||
else if ($value instanceOf DBObjectSet)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
|
||||
}
|
||||
else if ($value instanceOf ormCustomFieldsValue)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$sRet = $oAttDef->GetAsCSV($value, "\n", '', $oObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$sRet = $oAttDef->GetEditValue($value, $oObj);
|
||||
if ($oAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
// Date and times are formatted using the ISO encoding, not the localized format
|
||||
if ($oAttDef->IsNull($value))
|
||||
{
|
||||
// NOt a valid date
|
||||
$sRet = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = $value;
|
||||
}
|
||||
}
|
||||
else if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
|
||||
{
|
||||
$sRet = $oAttDef->GetEditValue($value, $oObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = $oAttDef->GetAsPlainText($value, $oObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sRet;
|
||||
@@ -143,7 +245,11 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
default:
|
||||
$oAttDef = MetaModel::GetAttributeDef($aFieldSpec['sClass'], $aFieldSpec['sAttCode']);
|
||||
$sType = 'string';
|
||||
if($oAttDef instanceof AttributeDateTime)
|
||||
if($oAttDef instanceof AttributeDate)
|
||||
{
|
||||
$sType = 'date';
|
||||
}
|
||||
else if($oAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$sType = 'datetime';
|
||||
}
|
||||
@@ -235,6 +341,10 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
|
||||
$fStartExcel = microtime(true);
|
||||
$writer = new XLSXWriter();
|
||||
$oDateTimeFormat = new DateTimeFormat($this->aStatusInfo['date_format']);
|
||||
$writer->setDateTimeFormat($oDateTimeFormat->ToExcel());
|
||||
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
|
||||
$writer->setDateFormat($oDateFormat->ToExcel());
|
||||
$writer->setAuthor(UserRights::GetUserFriendlyName());
|
||||
$aHeaderTypes = array();
|
||||
$aHeaderNames = array();
|
||||
|
||||
@@ -51,6 +51,15 @@ class HTMLBulkExport extends TabularBulkExport
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
}
|
||||
return $this->GetValue($oObj, $sAttCode);
|
||||
}
|
||||
|
||||
|
||||
349
core/htmlsanitizer.class.inc.php
Normal file
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
/**
|
||||
* Base class for all possible implementations of HTML Sanitization
|
||||
*/
|
||||
abstract class HTMLSanitizer
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Do nothing..
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given HTML document
|
||||
* @param string $sHTML
|
||||
* @return string
|
||||
*/
|
||||
abstract public function DoSanitize($sHTML);
|
||||
|
||||
/**
|
||||
* Sanitize an HTML string with the configured sanitizer, falling back to HTMLDOMSanitizer in case of Exception or invalid configuration
|
||||
* @param string $sHTML
|
||||
* @return string
|
||||
*/
|
||||
public static function Sanitize($sHTML)
|
||||
{
|
||||
$sSanitizerClass = MetaModel::GetConfig()->Get('html_sanitizer');
|
||||
if(!class_exists($sSanitizerClass))
|
||||
{
|
||||
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a valid class. Will use HTMLDOMSanitizer as the default sanitizer.');
|
||||
$sSanitizerClass = 'HTMLDOMSanitizer';
|
||||
}
|
||||
else if(!is_subclass_of($sSanitizerClass, 'HTMLSanitizer'))
|
||||
{
|
||||
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a subclass of HTMLSanitizer. Will use HTMLDOMSanitizer as the default sanitizer.');
|
||||
$sSanitizerClass = 'HTMLDOMSanitizer';
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$oSanitizer = new $sSanitizerClass();
|
||||
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
if($sSanitizerClass != 'HTMLDOMSanitizer')
|
||||
{
|
||||
IssueLog::Warning('Failed to sanitize an HTML string with "'.$sSanitizerClass.'". The following exception occured: '.$e->getMessage());
|
||||
IssueLog::Warning('Will try to sanitize with HTMLDOMSanitizer.');
|
||||
// try again with the HTMLDOMSanitizer
|
||||
$oSanitizer = new HTMLDOMSanitizer();
|
||||
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Error('Failed to sanitize an HTML string with "HTMLDOMSanitizer". The following exception occured: '.$e->getMessage());
|
||||
IssueLog::Error('The HTML will NOT be sanitized.');
|
||||
$sCleanHTML = $sHTML;
|
||||
}
|
||||
}
|
||||
return $sCleanHTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy HTMLSanitizer which does nothing at all!
|
||||
* Can be used if HTML Sanitization is not important
|
||||
* (for example when importing "safe" data during an on-boarding)
|
||||
* and performance is at stake
|
||||
*
|
||||
*/
|
||||
class HTMLNullSanitizer extends HTMLSanitizer
|
||||
{
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see HTMLSanitizer::Sanitize()
|
||||
*/
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
return $sHTML;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A standard-compliant HTMLSanitizer based on the HTMLPurifier library by Edward Z. Yang
|
||||
* Complete but quite slow
|
||||
* http://htmlpurifier.org
|
||||
*/
|
||||
/*
|
||||
class HTMLPurifierSanitizer extends HTMLSanitizer
|
||||
{
|
||||
protected static $oPurifier = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (self::$oPurifier == null)
|
||||
{
|
||||
$sLibPath = APPROOT.'lib/htmlpurifier/HTMLPurifier.auto.php';
|
||||
if (!file_exists($sLibPath))
|
||||
{
|
||||
throw new Exception("Missing library '$sLibPath', cannot use HTMLPurifierSanitizer.");
|
||||
}
|
||||
require_once($sLibPath);
|
||||
|
||||
$oPurifierConfig = HTMLPurifier_Config::createDefault();
|
||||
$oPurifierConfig->set('Core.Encoding', 'UTF-8'); // defaults to 'UTF-8'
|
||||
$oPurifierConfig->set('HTML.Doctype', 'XHTML 1.0 Strict'); // defaults to 'XHTML 1.0 Transitional'
|
||||
$oPurifierConfig->set('URI.AllowedSchemes', array (
|
||||
'http' => true,
|
||||
'https' => true,
|
||||
'data' => true, // This one is not present by default
|
||||
));
|
||||
$sPurifierCache = APPROOT.'data/HTMLPurifier';
|
||||
if (!is_dir($sPurifierCache))
|
||||
{
|
||||
mkdir($sPurifierCache);
|
||||
}
|
||||
if (!is_dir($sPurifierCache))
|
||||
{
|
||||
throw new Exception("Could not create the cache directory '$sPurifierCache'");
|
||||
}
|
||||
$oPurifierConfig->set('Cache.SerializerPath', $sPurifierCache); // no trailing slash
|
||||
self::$oPurifier = new HTMLPurifier($oPurifierConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$sCleanHtml = self::$oPurifier->purify($sHTML);
|
||||
return $sCleanHtml;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
{
|
||||
protected $oDoc;
|
||||
protected static $aTagsWhiteList = array(
|
||||
'html' => array(),
|
||||
'body' => array(),
|
||||
'a' => array('href', 'name', 'style', 'target'),
|
||||
'p' => array('style'),
|
||||
'br' => array(),
|
||||
'span' => array('style'),
|
||||
'div' => array('style'),
|
||||
'b' => array(),
|
||||
'i' => array(),
|
||||
'u' => array(),
|
||||
'em' => array(),
|
||||
'strong' => array(),
|
||||
'img' => array('src','style'),
|
||||
'ul' => array('style'),
|
||||
'ol' => array('style'),
|
||||
'li' => array('style'),
|
||||
'h1' => array('style'),
|
||||
'h2' => array('style'),
|
||||
'h3' => array('style'),
|
||||
'h4' => array('style'),
|
||||
'nav' => array('style'),
|
||||
'section' => array('style'),
|
||||
'code' => array('style'),
|
||||
'table' => array('style', 'width', 'summary', 'align', 'border', 'cellpadding', 'cellspacing'),
|
||||
'thead' => array('style'),
|
||||
'tbody' => array('style'),
|
||||
'tr' => array('style'),
|
||||
'td' => array('style', 'colspan'),
|
||||
'th' => array('style'),
|
||||
'fieldset' => array('style'),
|
||||
'legend' => array('style'),
|
||||
'font' => array('face', 'color', 'style', 'size'),
|
||||
'big' => array(),
|
||||
'small' => array(),
|
||||
'tt' => array(),
|
||||
'code' => array(),
|
||||
'kbd' => array(),
|
||||
'samp' => array(),
|
||||
'var' => array(),
|
||||
'del' => array(),
|
||||
's' => array(), // strikethrough
|
||||
'ins' => array(),
|
||||
'cite' => array(),
|
||||
'q' => array(),
|
||||
'hr' => array('style'),
|
||||
'pre' => array(),
|
||||
'center' => array(),
|
||||
'caption' => array(),
|
||||
);
|
||||
|
||||
protected static $aAttrsWhiteList = array(
|
||||
'src' => '/^(http:|https:|data:)/i',
|
||||
);
|
||||
|
||||
protected static $aStylesWhiteList = array(
|
||||
'background-color', 'color', 'float', 'font', 'font-style', 'font-size', 'font-family', 'padding', 'margin', 'border', 'cellpadding', 'cellspacing', 'bordercolor', 'border-collapse', 'width', 'height', 'text-align',
|
||||
);
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!array_key_exists('href', self::$aAttrsWhiteList))
|
||||
{
|
||||
$sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i';
|
||||
self::$aAttrsWhiteList['href'] = $sPattern;
|
||||
}
|
||||
}
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
|
||||
|
||||
$this->CleanNode($this->oDoc);
|
||||
|
||||
$oXPath = new DOMXPath($this->oDoc);
|
||||
$sXPath = "//body";
|
||||
$oNodesList = $oXPath->query($sXPath);
|
||||
|
||||
if ($oNodesList->length == 0)
|
||||
{
|
||||
// No body, save the whole document
|
||||
$sCleanHtml = $this->oDoc->saveHTML();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Export only the content of the body tag
|
||||
$sCleanHtml = $this->oDoc->saveHTML($oNodesList->item(0));
|
||||
// remove the body tag itself
|
||||
$sCleanHtml = str_replace( array('<body>', '</body>'), '', $sCleanHtml);
|
||||
}
|
||||
|
||||
return $sCleanHtml;
|
||||
}
|
||||
|
||||
protected function CleanNode(DOMNode $oElement)
|
||||
{
|
||||
$aAttrToRemove = array();
|
||||
// Gather the attributes to remove
|
||||
if ($oElement->hasAttributes())
|
||||
{
|
||||
foreach($oElement->attributes as $oAttr)
|
||||
{
|
||||
$sAttr = strtolower($oAttr->name);
|
||||
if (!in_array($sAttr, self::$aTagsWhiteList[strtolower($oElement->tagName)]))
|
||||
{
|
||||
// Forbidden (or unknown) attribute
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value))
|
||||
{
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else if ($sAttr == 'style')
|
||||
{
|
||||
// Special processing for style tags
|
||||
$sCleanStyle = $this->CleanStyle($oAttr->value);
|
||||
if ($sCleanStyle == '')
|
||||
{
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oElement->setAttribute($oAttr->name, $sCleanStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aAttrToRemove as $sName)
|
||||
{
|
||||
$oElement->removeAttribute($sName);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oElement->hasChildNodes())
|
||||
{
|
||||
$aChildElementsToRemove = array();
|
||||
// Gather the child noes to remove
|
||||
foreach($oElement->childNodes as $oNode)
|
||||
{
|
||||
if (($oNode instanceof DOMElement) && (!array_key_exists(strtolower($oNode->tagName), self::$aTagsWhiteList)))
|
||||
{
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
}
|
||||
else if ($oNode instanceof DOMComment)
|
||||
{
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recurse
|
||||
$this->CleanNode($oNode);
|
||||
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img'))
|
||||
{
|
||||
$this->ProcessImage($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aChildElementsToRemove as $oDomElement)
|
||||
{
|
||||
$oElement->removeChild($oDomElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an extra attribute data-img-id for images which are based on an actual InlineImage
|
||||
* so that we can later reconstruct the full "src" URL when needed
|
||||
* @param DOMNode $oElement
|
||||
*/
|
||||
protected function ProcessImage(DOMNode $oElement)
|
||||
{
|
||||
$sSrc = $oElement->getAttribute('src');
|
||||
$sDownloadUrl = str_replace(array('.', '?'), array('\.', '\?'), INLINEIMAGE_DOWNLOAD_URL); // Escape . and ?
|
||||
$sUrlPattern = '|'.$sDownloadUrl.'([0-9]+)&s=([0-9a-f]+)|';
|
||||
if (preg_match($sUrlPattern, $sSrc, $aMatches))
|
||||
{
|
||||
$oElement->setAttribute('data-img-id', $aMatches[1]);
|
||||
$oElement->setAttribute('data-img-secret', $aMatches[2]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function CleanStyle($sStyle)
|
||||
{
|
||||
$aAllowedStyles = array();
|
||||
$aItems = explode(';', $sStyle);
|
||||
{
|
||||
foreach($aItems as $sItem)
|
||||
{
|
||||
$aElements = explode(':', trim($sItem));
|
||||
if (in_array(trim(strtolower($aElements[0])), static::$aStylesWhiteList))
|
||||
{
|
||||
$aAllowedStyles[] = trim($sItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return implode(';', $aAllowedStyles);
|
||||
}
|
||||
|
||||
protected function IsValidAttributeContent($sAttributeName, $sValue)
|
||||
{
|
||||
if (array_key_exists($sAttributeName, self::$aAttrsWhiteList))
|
||||
{
|
||||
return preg_match(self::$aAttrsWhiteList[$sAttributeName], $sValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
547
core/inlineimage.class.inc.php
Normal file
@@ -0,0 +1,547 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
define('INLINEIMAGE_DOWNLOAD_URL', 'pages/ajax.document.php?operation=download_inlineimage&id=');
|
||||
|
||||
/**
|
||||
* Persistent classes (internal): store images referenced inside HTML formatted text fields
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class InlineImage extends DBObject
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
'category' => 'addon',
|
||||
'key_type' => 'autoincrement',
|
||||
'name_attcode' => array('item_class', 'temp_id'),
|
||||
'state_attcode' => '',
|
||||
'reconc_keys' => array(''),
|
||||
'db_table' => 'inline_image',
|
||||
'db_key_field' => 'id',
|
||||
'db_finalclass_field' => '',
|
||||
'indexes' => array(
|
||||
array('temp_id'),
|
||||
array('item_class', 'item_id'),
|
||||
array('item_org_id'),
|
||||
),
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("expire", array("allowed_values"=>null, "sql"=>'expire', "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("temp_id", array("allowed_values"=>null, "sql"=>'temp_id', "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("item_class", array("allowed_values"=>null, "sql"=>'item_class', "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeObjectKey("item_id", array("class_attcode"=>'item_class', "allowed_values"=>null, "sql"=>'item_id', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("item_org_id", array("allowed_values"=>null, "sql"=>'item_org_id', "default_value"=>'0', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("secret", array("allowed_values"=>null, "sql" => "secret", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
|
||||
|
||||
MetaModel::Init_SetZListItems('details', array('temp_id', 'item_class', 'item_id', 'item_org_id'));
|
||||
MetaModel::Init_SetZListItems('standard_search', array('temp_id', 'item_class', 'item_id'));
|
||||
MetaModel::Init_SetZListItems('list', array('temp_id', 'item_class', 'item_id' ));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps the given context parameter name to the appropriate filter/search code for this class
|
||||
* @param string $sContextParam Name of the context parameter, e.g. 'org_id'
|
||||
* @return string Filter code, e.g. 'customer_id'
|
||||
*/
|
||||
public static function MapContextParam($sContextParam)
|
||||
{
|
||||
if ($sContextParam == 'org_id')
|
||||
{
|
||||
return 'item_org_id';
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/Update all of the '_item' fields
|
||||
* @param DBObject $oItem Container item
|
||||
* @return void
|
||||
*/
|
||||
public function SetItem(DBObject $oItem, $bUpdateOnChange = false)
|
||||
{
|
||||
$sClass = get_class($oItem);
|
||||
$iItemId = $oItem->GetKey();
|
||||
|
||||
$this->Set('item_class', $sClass);
|
||||
$this->Set('item_id', $iItemId);
|
||||
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
|
||||
{
|
||||
$iOrgId = $oItem->Get($sAttCode);
|
||||
if ($iOrgId > 0)
|
||||
{
|
||||
if ($iOrgId != $this->Get('item_org_id'))
|
||||
{
|
||||
$this->Set('item_org_id', $iOrgId);
|
||||
if ($bUpdateOnChange)
|
||||
{
|
||||
$this->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a default value for item_org_id (if relevant...)
|
||||
* @return void
|
||||
*/
|
||||
public function SetDefaultOrgId()
|
||||
{
|
||||
// First check that the organization CAN be fetched from the target class
|
||||
//
|
||||
$sClass = $this->Get('item_class');
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
|
||||
{
|
||||
// Second: check that the organization CAN be fetched from the current user
|
||||
//
|
||||
if (MetaModel::IsValidClass('Person'))
|
||||
{
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
|
||||
{
|
||||
// OK - try it
|
||||
//
|
||||
$oCurrentPerson = MetaModel::GetObject('Person', UserRights::GetContactId(), false);
|
||||
if ($oCurrentPerson)
|
||||
{
|
||||
$this->Set('item_org_id', $oCurrentPerson->Get($sAttCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When posting a form, finalize the creation of the inline images
|
||||
* related to the specified object
|
||||
*
|
||||
* @param DBObject $oObject
|
||||
*/
|
||||
public static function FinalizeInlineImages(DBObject $oObject)
|
||||
{
|
||||
$iTransactionId = utils::ReadParam('transaction_id', null);
|
||||
if (!is_null($iTransactionId))
|
||||
{
|
||||
// Attach new (temporary) inline images
|
||||
|
||||
$sTempId = utils::GetUploadTempId($iTransactionId);
|
||||
// The object is being created from a form, check if there are pending inline images for this object
|
||||
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
|
||||
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
|
||||
while($oInlineImage = $oSet->Fetch())
|
||||
{
|
||||
$oInlineImage->SetItem($oObject);
|
||||
$oInlineImage->Set('temp_id', '');
|
||||
$oInlineImage->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the pending images if the form is not submitted
|
||||
* @param string $sTempId
|
||||
*/
|
||||
public static function OnFormCancel($sTempId)
|
||||
{
|
||||
// Delete all "pending" InlineImages for this form
|
||||
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
|
||||
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
|
||||
while($oInlineImage = $oSet->Fetch())
|
||||
{
|
||||
$oInlineImage->DBDelete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the supplied HTML fragment to rebuild the attribute src="" for images
|
||||
* that refer to an InlineImage (detected via the attribute data-img-id="") so that
|
||||
* the URL is consistent with the current URL of the application.
|
||||
* @param string $sHtml The HTML fragment to process
|
||||
* @return string The modified HTML
|
||||
*/
|
||||
public static function FixUrls($sHtml)
|
||||
{
|
||||
$aNeedles = array();
|
||||
$aReplacements = array();
|
||||
// Find img tags with an attribute data-img-id
|
||||
if (preg_match_all('/<img ([^>]*)data-img-id="([0-9]+)"([^>]*)>/i', $sHtml, $aMatches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
|
||||
{
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL;
|
||||
foreach($aMatches as $aImgInfo)
|
||||
{
|
||||
$sImgTag = $aImgInfo[0][0];
|
||||
$sSecret = '';
|
||||
if (preg_match('/data-img-secret="([0-9a-f]+)"/', $sImgTag, $aSecretMatches))
|
||||
{
|
||||
$sSecret = '&s='.$aSecretMatches[1];
|
||||
}
|
||||
$sAttId = $aImgInfo[2][0];
|
||||
|
||||
$sNewImgTag = preg_replace('/src="[^"]+"/', 'src="'.htmlentities($sUrl.$sAttId.$sSecret, ENT_QUOTES, 'UTF-8').'"', $sImgTag); // preserve other attributes, must convert & to & to be idempotent with CKEditor
|
||||
$aNeedles[] = $sImgTag;
|
||||
$aReplacements[] = $sNewImgTag;
|
||||
}
|
||||
$sHtml = str_replace($aNeedles, $aReplacements, $sHtml);
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the javascript fragment - to be added to "on document ready" - to adjust (on the fly) the width on Inline Images
|
||||
*/
|
||||
public static function FixImagesWidth()
|
||||
{
|
||||
$iMaxWidth = (int)MetaModel::GetConfig()->Get('inline_image_max_display_width', 0);
|
||||
$sJS = '';
|
||||
if ($iMaxWidth != 0)
|
||||
{
|
||||
$sJS =
|
||||
<<<EOF
|
||||
$('img[data-img-id]').each(function() {
|
||||
if ($(this).width() > $iMaxWidth)
|
||||
{
|
||||
$(this).css({'max-width': '{$iMaxWidth}px', width: '', height: '', 'max-height': ''});
|
||||
}
|
||||
$(this).addClass('inline-image').attr('href', $(this).attr('src'));
|
||||
}).magnificPopup({type: 'image', closeOnContentClick: true });
|
||||
EOF
|
||||
;
|
||||
}
|
||||
|
||||
return $sJS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an the given mimeType is an image that can be processed by the system
|
||||
* @param string $sMimeType
|
||||
* @return boolean
|
||||
*/
|
||||
public static function IsImage($sMimeType)
|
||||
{
|
||||
if (!function_exists('gd_info')) return false; // no image processing capability on this system
|
||||
|
||||
$bRet = false;
|
||||
$aInfo = gd_info(); // What are the capabilities
|
||||
switch($sMimeType)
|
||||
{
|
||||
case 'image/gif':
|
||||
return $aInfo['GIF Read Support'];
|
||||
break;
|
||||
|
||||
case 'image/jpeg':
|
||||
return $aInfo['JPEG Support'];
|
||||
break;
|
||||
|
||||
case 'image/png':
|
||||
return $aInfo['PNG Support'];
|
||||
break;
|
||||
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize an image so that it fits the maximum width/height defined in the config file
|
||||
* @param ormDocument $oImage The original image stored as an array (content / mimetype / filename)
|
||||
* @return ormDocument The resampled image (or the original one if it already fit)
|
||||
*/
|
||||
public static function ResizeImageToFit(ormDocument $oImage, &$aDimensions = null)
|
||||
{
|
||||
$img = false;
|
||||
switch($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
case 'image/jpeg':
|
||||
case 'image/png':
|
||||
$img = @imagecreatefromstring($oImage->GetData());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unsupported image type, return the image as-is
|
||||
$aDimensions = null;
|
||||
return $oImage;
|
||||
}
|
||||
if ($img === false)
|
||||
{
|
||||
$aDimensions = null;
|
||||
return $oImage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's scale the image, preserving the transparency for GIFs and PNGs
|
||||
$iWidth = imagesx($img);
|
||||
$iHeight = imagesy($img);
|
||||
$aDimensions = array('width' => $iWidth, 'height' => $iHeight);
|
||||
$iMaxImageSize = (int)MetaModel::GetConfig()->Get('inline_image_max_storage_width', 0);
|
||||
|
||||
if (($iMaxImageSize > 0) && ($iWidth <= $iMaxImageSize) && ($iHeight <= $iMaxImageSize))
|
||||
{
|
||||
// No need to resize
|
||||
return $oImage;
|
||||
}
|
||||
|
||||
$fScale = min($iMaxImageSize / $iWidth, $iMaxImageSize / $iHeight);
|
||||
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
|
||||
$aDimensions['width'] = $iNewWidth;
|
||||
$aDimensions['height'] = $iNewHeight;
|
||||
|
||||
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
|
||||
|
||||
// Preserve transparency
|
||||
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
|
||||
{
|
||||
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
|
||||
imagealphablending($new, false);
|
||||
imagesavealpha($new, true);
|
||||
}
|
||||
|
||||
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
|
||||
|
||||
ob_start();
|
||||
switch ($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
imagegif($new); // send image to output buffer
|
||||
break;
|
||||
|
||||
case 'image/jpeg':
|
||||
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
|
||||
break;
|
||||
|
||||
case 'image/png':
|
||||
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
|
||||
break;
|
||||
}
|
||||
$oNewImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
|
||||
@ob_end_clean();
|
||||
|
||||
imagedestroy($img);
|
||||
imagedestroy($new);
|
||||
|
||||
return $oNewImage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the (localized) textual representation of the max upload size
|
||||
* @return string
|
||||
*/
|
||||
public static function GetMaxUpload()
|
||||
{
|
||||
$iMaxUpload = ini_get('upload_max_filesize');
|
||||
if (!$iMaxUpload)
|
||||
{
|
||||
$sRet = Dict::S('Attachments:UploadNotAllowedOnThisSystem');
|
||||
}
|
||||
else
|
||||
{
|
||||
$iMaxUpload = utils::ConvertToBytes($iMaxUpload);
|
||||
if ($iMaxUpload > 1024*1024*1024)
|
||||
{
|
||||
$sRet = Dict::Format('Attachment:Max_Go', sprintf('%0.2f', $iMaxUpload/(1024*1024*1024)));
|
||||
}
|
||||
else if ($iMaxUpload > 1024*1024)
|
||||
{
|
||||
$sRet = Dict::Format('Attachment:Max_Mo', sprintf('%0.2f', $iMaxUpload/(1024*1024)));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = Dict::Format('Attachment:Max_Ko', sprintf('%0.2f', $iMaxUpload/(1024)));
|
||||
}
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fragment of javascript needed to complete the initialization of
|
||||
* CKEditor when creating/modifying an object
|
||||
*
|
||||
* @param DBObject $oObject The object being edited
|
||||
* @param string $sTempId The concatenation of session_id().'_'.$iTransactionId.
|
||||
* @return string The JS fragment to insert in "on document ready"
|
||||
*/
|
||||
public static function EnableCKEditorImageUpload(DBObject $oObject, $sTempId)
|
||||
{
|
||||
$sObjClass = get_class($oObject);
|
||||
$iObjKey = $oObject->GetKey();
|
||||
|
||||
$sAbsoluteUrlAppRoot = utils::GetAbsoluteUrlAppRoot();
|
||||
$sToggleFullScreen = htmlentities(Dict::S('UI:ToggleFullScreen'), ENT_QUOTES, 'UTF-8');
|
||||
$sAppRootUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
|
||||
return
|
||||
<<<EOF
|
||||
// Hook the file upload of all CKEditor instances
|
||||
$('.htmlEditor').each(function() {
|
||||
var oEditor = $(this).ckeditorGet();
|
||||
oEditor.config.extraPlugins = 'font,uploadimage';
|
||||
oEditor.config.uploadUrl = '$sAbsoluteUrlAppRoot'+'pages/ajax.render.php';
|
||||
oEditor.config.filebrowserBrowseUrl = '$sAbsoluteUrlAppRoot'+'pages/ajax.render.php?operation=cke_browse&temp_id=$sTempId&obj_class=$sObjClass&obj_key=$iObjKey';
|
||||
oEditor.on( 'fileUploadResponse', function( evt ) {
|
||||
var fileLoader = evt.data.fileLoader;
|
||||
var xhr = fileLoader.xhr;
|
||||
var data = evt.data;
|
||||
try {
|
||||
var response = JSON.parse( xhr.responseText );
|
||||
|
||||
// Error message does not need to mean that upload finished unsuccessfully.
|
||||
// It could mean that ex. file name was changes during upload due to naming collision.
|
||||
if ( response.error && response.error.message ) {
|
||||
data.message = response.error.message;
|
||||
}
|
||||
|
||||
// But !uploaded means error.
|
||||
if ( !response.uploaded ) {
|
||||
evt.cancel();
|
||||
} else {
|
||||
data.fileName = response.fileName;
|
||||
data.url = response.url;
|
||||
|
||||
// Do not call the default listener.
|
||||
evt.stop();
|
||||
}
|
||||
} catch ( err ) {
|
||||
// Response parsing error.
|
||||
data.message = fileLoader.lang.filetools.responseError;
|
||||
window.console && window.console.log( xhr.responseText );
|
||||
|
||||
evt.cancel();
|
||||
}
|
||||
} );
|
||||
|
||||
oEditor.on( 'fileUploadRequest', function( evt ) {
|
||||
evt.data.fileLoader.uploadUrl += '?operation=cke_img_upload&temp_id=$sTempId&obj_class=$sObjClass';
|
||||
}, null, null, 4 ); // Listener with priority 4 will be executed before priority 5.
|
||||
|
||||
oEditor.on( 'instanceReady', function() {
|
||||
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .editor_magnifier').length == 0)
|
||||
{
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="editor_magnifier" title="$sToggleFullScreen" style="display:block;width:12px;height:11px;border:1px #A6A6A6 solid;cursor:pointer; background-image:url(\\'$sAppRootUrl/images/full-screen.png\\')"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox .editor_magnifier').on('click', function() {
|
||||
oEditor.execCommand('maximize');
|
||||
if ($(this).closest('.cke_maximized').length != 0)
|
||||
{
|
||||
$('#'+oEditor.id+'_toolbar_collapser').trigger('click');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (oEditor.widgets.registered.uploadimage)
|
||||
{
|
||||
oEditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
|
||||
var oData = JSON.parse(upload.xhr.responseText);
|
||||
this.replaceWith( '<img src="' + upload.url + '" ' +
|
||||
'width="' + oData.width + '" ' +
|
||||
'height="' + oData.height + '">' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
EOF
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Garbage collector for cleaning "old" temporary InlineImages (and Attachments).
|
||||
* This background process runs every hour and deletes all temporary InlineImages and Attachments
|
||||
* whic are are older than one hour.
|
||||
*/
|
||||
class InlineImageGC implements iBackgroundProcess
|
||||
{
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return 3600; // Runs every 3600 seconds
|
||||
}
|
||||
|
||||
public function Process($iTimeLimit)
|
||||
{
|
||||
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time()); // Every temporary InlineImage/Attachment expired will be deleted
|
||||
|
||||
$iProcessed = 0;
|
||||
$sOQL = "SELECT InlineImage WHERE (item_id = 0) AND (expire < '$sDateLimit')";
|
||||
while (time() < $iTimeLimit)
|
||||
{
|
||||
// Next one ?
|
||||
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('expire' => true) /* order by*/, array(), null, 1 /* limit count */);
|
||||
$oSet->OptimizeColumnLoad(array());
|
||||
$oResult = $oSet->Fetch();
|
||||
if (is_null($oResult))
|
||||
{
|
||||
// Nothing to be done
|
||||
break;
|
||||
}
|
||||
$iProcessed++;
|
||||
$oResult->DBDelete();
|
||||
}
|
||||
|
||||
$iProcessed2 = 0;
|
||||
if (class_exists('Attachment'))
|
||||
{
|
||||
$sOQL = "SELECT Attachment WHERE (item_id = 0) AND (expire < '$sDateLimit')";
|
||||
while (time() < $iTimeLimit)
|
||||
{
|
||||
// Next one ?
|
||||
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('expire' => true) /* order by*/, array(), null, 1 /* limit count */);
|
||||
$oSet->OptimizeColumnLoad(array());
|
||||
$oResult = $oSet->Fetch();
|
||||
if (is_null($oResult))
|
||||
{
|
||||
// Nothing to be done
|
||||
break;
|
||||
}
|
||||
$iProcessed2++;
|
||||
$oResult->DBDelete();
|
||||
}
|
||||
}
|
||||
return "Cleaned $iProcessed old temporary InlineImage(s) and $iProcessed2 old temporary Attachment(s).";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -22,45 +22,20 @@ require_once(APPROOT.'core/querymodifier.class.inc.php');
|
||||
require_once(APPROOT.'core/metamodelmodifier.inc.php');
|
||||
require_once(APPROOT.'core/computing.inc.php');
|
||||
require_once(APPROOT.'core/relationgraph.class.inc.php');
|
||||
require_once(APPROOT.'core/apc-compat.php');
|
||||
|
||||
/**
|
||||
* Metamodel
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
// #@# todo: change into class const (see Doctrine)
|
||||
// Doctrine example
|
||||
// class toto
|
||||
// {
|
||||
// /**
|
||||
// * VERSION
|
||||
// */
|
||||
// const VERSION = '1.0.0';
|
||||
// }
|
||||
|
||||
/**
|
||||
* add some description here...
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
define('ENUM_CHILD_CLASSES_EXCLUDETOP', 1);
|
||||
/**
|
||||
* add some description here...
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
define('ENUM_CHILD_CLASSES_ALL', 2);
|
||||
/**
|
||||
* add some description here...
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
define('ENUM_PARENT_CLASSES_EXCLUDELEAF', 1);
|
||||
/**
|
||||
* add some description here...
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
define('ENUM_PARENT_CLASSES_ALL', 2);
|
||||
@@ -134,6 +109,7 @@ abstract class MetaModel
|
||||
|
||||
private static $m_bTraceSourceFiles = false;
|
||||
private static $m_aClassToFile = array();
|
||||
protected static $m_sEnvironment = 'production';
|
||||
|
||||
public static function GetClassFiles()
|
||||
{
|
||||
@@ -331,7 +307,7 @@ abstract class MetaModel
|
||||
return self::GetClassIcon($sParentClass, $bImgTag, $sMoreStyles);
|
||||
}
|
||||
}
|
||||
$sIcon = str_replace('/modules/', '/env-'.utils::GetCurrentEnvironment().'/', $sIcon); // Support of pre-2.0 modules
|
||||
$sIcon = str_replace('/modules/', '/env-'.self::$m_sEnvironment.'/', $sIcon); // Support of pre-2.0 modules
|
||||
if ($bImgTag && ($sIcon != ''))
|
||||
{
|
||||
$sIcon = "<img src=\"$sIcon\" style=\"vertical-align:middle;$sMoreStyles\"/>";
|
||||
@@ -499,7 +475,7 @@ abstract class MetaModel
|
||||
self::_check_subclass($sClass);
|
||||
return self::$m_aAttribOrigins[$sClass][$sAttCode];
|
||||
}
|
||||
final static public function GetPrequisiteAttributes($sClass, $sAttCode)
|
||||
final static public function GetPrerequisiteAttributes($sClass, $sAttCode)
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
$oAtt = self::GetAttributeDef($sClass, $sAttCode);
|
||||
@@ -511,7 +487,7 @@ abstract class MetaModel
|
||||
return $oAtt->GetPrerequisiteAttributes();
|
||||
}
|
||||
/**
|
||||
* Find all attributes that depend on the specified one (reverse of GetPrequisiteAttributes)
|
||||
* Find all attributes that depend on the specified one (reverse of GetPrerequisiteAttributes)
|
||||
* @param string $sClass Name of the class
|
||||
* @param string $sAttCode Code of the attributes
|
||||
* @return Array List of attribute codes that depend on the given attribute, empty array if none.
|
||||
@@ -522,7 +498,7 @@ abstract class MetaModel
|
||||
self::_check_subclass($sClass);
|
||||
foreach (self::ListAttributeDefs($sClass) as $sDependentAttCode=>$void)
|
||||
{
|
||||
$aPrerequisites = self::GetPrequisiteAttributes($sClass, $sDependentAttCode);
|
||||
$aPrerequisites = self::GetPrerequisiteAttributes($sClass, $sDependentAttCode);
|
||||
if (in_array($sAttCode, $aPrerequisites))
|
||||
{
|
||||
$aResults[] = $sDependentAttCode;
|
||||
@@ -645,7 +621,8 @@ abstract class MetaModel
|
||||
private static $m_aAttribDefs = array(); // array of ("classname" => array of attributes)
|
||||
private static $m_aAttribOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass"))
|
||||
private static $m_aExtKeyFriends = array(); // array of ("classname" => array of ("indirect ext key attcode"=> array of ("relative ext field")))
|
||||
private static $m_aIgnoredAttributes = array(); //array of ("classname" => array of ("attcode")
|
||||
private static $m_aIgnoredAttributes = array(); //array of ("classname" => array of ("attcode"))
|
||||
private static $m_aEnumToMeta = array(); // array of ("classname" => array of ("attcode" => array of ("metaattcode" => oMetaAttDef))
|
||||
|
||||
final static public function ListAttributeDefs($sClass)
|
||||
{
|
||||
@@ -860,6 +837,18 @@ abstract class MetaModel
|
||||
return self::$m_aTrackForwardCache[$sClass];
|
||||
}
|
||||
|
||||
final static public function ListMetaAttributes($sClass, $sAttCode)
|
||||
{
|
||||
if (isset(self::$m_aEnumToMeta[$sClass][$sAttCode]))
|
||||
{
|
||||
$aRet = self::$m_aEnumToMeta[$sClass][$sAttCode];
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRet = array();
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attribute label
|
||||
@@ -1632,7 +1621,7 @@ abstract class MetaModel
|
||||
$oExtensionInstance = null;
|
||||
foreach($aInterfaces as $sInterface)
|
||||
{
|
||||
if ($oRefClass->implementsInterface($sInterface))
|
||||
if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable())
|
||||
{
|
||||
if (is_null($oExtensionInstance))
|
||||
{
|
||||
@@ -1711,6 +1700,7 @@ abstract class MetaModel
|
||||
throw new CoreException("Class $sChildClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code");
|
||||
}
|
||||
$oCloned = clone $oClassAtt;
|
||||
$oCloned->SetHostClass($sChildClass);
|
||||
$oCloned->SetFixedValue($sChildClass);
|
||||
self::$m_aAttribDefs[$sChildClass]['finalclass'] = $oCloned;
|
||||
self::$m_aAttribOrigins[$sChildClass]['finalclass'] = $sRootClass;
|
||||
@@ -1778,10 +1768,10 @@ abstract class MetaModel
|
||||
$oFriendlyName = new AttributeExternalField($sFriendlyNameAttCode, array("allowed_values"=>null, "extkey_attcode"=>$sKeyAttCode, "target_attcode"=>$sRemoteAttCode, "depends_on"=>array()));
|
||||
$oFriendlyName->SetHostClass($sClass);
|
||||
self::$m_aAttribDefs[$sClass][$sFriendlyNameAttCode] = $oFriendlyName;
|
||||
self::$m_aAttribOrigins[$sClass][$sFriendlyNameAttCode] = $sRemoteClass;
|
||||
self::$m_aAttribOrigins[$sClass][$sFriendlyNameAttCode] = self::$m_aAttribOrigins[$sClass][$sKeyAttCode];
|
||||
$oFriendlyNameFlt = new FilterFromAttribute($oFriendlyName);
|
||||
self::$m_aFilterDefs[$sClass][$sFriendlyNameAttCode] = $oFriendlyNameFlt;
|
||||
self::$m_aFilterOrigins[$sClass][$sFriendlyNameAttCode] = $sRemoteClass;
|
||||
self::$m_aFilterOrigins[$sClass][$sFriendlyNameAttCode] = self::$m_aFilterOrigins[$sClass][$sKeyAttCode];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1790,10 +1780,10 @@ abstract class MetaModel
|
||||
$oFriendlyName = new AttributeFriendlyName($sFriendlyNameAttCode, $sAttCode);
|
||||
$oFriendlyName->SetHostClass($sClass);
|
||||
self::$m_aAttribDefs[$sClass][$sFriendlyNameAttCode] = $oFriendlyName;
|
||||
self::$m_aAttribOrigins[$sClass][$sFriendlyNameAttCode] = $sRemoteClass;
|
||||
self::$m_aAttribOrigins[$sClass][$sFriendlyNameAttCode] = self::$m_aAttribOrigins[$sClass][$sAttCode];
|
||||
$oFriendlyNameFlt = new FilterFromAttribute($oFriendlyName);
|
||||
self::$m_aFilterDefs[$sClass][$sFriendlyNameAttCode] = $oFriendlyNameFlt;
|
||||
self::$m_aFilterOrigins[$sClass][$sFriendlyNameAttCode] = $sRemoteClass;
|
||||
self::$m_aFilterOrigins[$sClass][$sFriendlyNameAttCode] = self::$m_aFilterOrigins[$sClass][$sAttCode];
|
||||
|
||||
if (self::HasChildrenClasses($sRemoteClass))
|
||||
{
|
||||
@@ -1808,11 +1798,11 @@ abstract class MetaModel
|
||||
));
|
||||
$oClassRecall->SetHostClass($sClass);
|
||||
self::$m_aAttribDefs[$sClass][$sClassRecallAttCode] = $oClassRecall;
|
||||
self::$m_aAttribOrigins[$sClass][$sClassRecallAttCode] = $sRemoteClass;
|
||||
self::$m_aAttribOrigins[$sClass][$sClassRecallAttCode] = self::$m_aAttribOrigins[$sClass][$sAttCode];
|
||||
|
||||
$oClassFlt = new FilterFromAttribute($oClassRecall);
|
||||
self::$m_aFilterDefs[$sClass][$sClassRecallAttCode] = $oClassFlt;
|
||||
self::$m_aFilterOrigins[$sClass][$sClassRecallAttCode] = $sRemoteClass;
|
||||
self::$m_aFilterOrigins[$sClass][$sClassRecallAttCode] = self::$m_aFilterOrigins[$sClass][$sAttCode];
|
||||
|
||||
// Add it to the ZLists where the external key is present
|
||||
//foreach(self::$m_aListData[$sClass] as $sListCode => $aAttributes)
|
||||
@@ -1859,6 +1849,15 @@ abstract class MetaModel
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($oAttDef instanceof AttributeMetaEnum)
|
||||
{
|
||||
$aMappingData = $oAttDef->GetMapRule($sClass);
|
||||
if ($aMappingData != null)
|
||||
{
|
||||
$sEnumAttCode = $aMappingData['attcode'];
|
||||
self::$m_aEnumToMeta[$sClass][$sEnumAttCode][$sAttCode] = $oAttDef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a 'id' filter
|
||||
@@ -2453,19 +2452,27 @@ abstract class MetaModel
|
||||
return $oReflection->isAbstract();
|
||||
}
|
||||
|
||||
public static function PrepareQueryArguments($aArgs)
|
||||
/**
|
||||
* Normalizes query arguments and adds magic parameters:
|
||||
* - current_contact_id
|
||||
* - current_contact (DBObject)
|
||||
* - current_user (DBObject)
|
||||
*
|
||||
* @param array $aArgs Context arguments (some can be persistent objects)
|
||||
* @param array $aMoreArgs Other query parameters
|
||||
* @return array
|
||||
*/
|
||||
public static function PrepareQueryArguments($aArgs, $aMoreArgs = array())
|
||||
{
|
||||
// Translate any object into scalars
|
||||
//
|
||||
$aScalarArgs = array();
|
||||
foreach($aArgs as $sArgName => $value)
|
||||
foreach(array_merge($aArgs, $aMoreArgs) as $sArgName => $value)
|
||||
{
|
||||
if (self::IsValidObject($value))
|
||||
{
|
||||
if (strpos($sArgName, '->object()') === false)
|
||||
{
|
||||
// Lazy syntax - develop the object contextual parameters
|
||||
$aScalarArgs = array_merge($aScalarArgs, $value->ToArgsForQuery($sArgName));
|
||||
// Normalize object arguments
|
||||
$aScalarArgs[$sArgName.'->object()'] = $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2483,11 +2490,27 @@ abstract class MetaModel
|
||||
{
|
||||
$aScalarArgs[$sArgName] = null;
|
||||
}
|
||||
elseif (is_array($value))
|
||||
{
|
||||
$aScalarArgs[$sArgName] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add standard contextual arguments
|
||||
// Add standard magic arguments
|
||||
//
|
||||
$aScalarArgs['current_contact_id'] = UserRights::GetContactId();
|
||||
$aScalarArgs['current_contact_id'] = UserRights::GetContactId(); // legacy
|
||||
|
||||
$oUser = UserRights::GetUserObject();
|
||||
if (!is_null($oUser))
|
||||
{
|
||||
$aScalarArgs['current_user->object()'] = $oUser;
|
||||
|
||||
$oContact = UserRights::GetContactObject();
|
||||
if (!is_null($oContact))
|
||||
{
|
||||
$aScalarArgs['current_contact->object()'] = $oContact;
|
||||
}
|
||||
}
|
||||
return $aScalarArgs;
|
||||
}
|
||||
|
||||
@@ -2697,7 +2720,77 @@ abstract class MetaModel
|
||||
CMDBSource::Query($sSQL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the meta enums
|
||||
* See Also AttributeMetaEnum::MapValue that must be aligned with the above implementation
|
||||
*
|
||||
* @param $bVerbose boolean Displays some information about what is done/what needs to be done
|
||||
*/
|
||||
public static function RebuildMetaEnums($bVerbose = false)
|
||||
{
|
||||
foreach (self::GetClasses() as $sClass)
|
||||
{
|
||||
if (!self::HasTable($sClass)) continue;
|
||||
|
||||
foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
|
||||
{
|
||||
// Check (once) all the attributes that are hierarchical keys
|
||||
if((self::GetAttributeOrigin($sClass, $sAttCode) == $sClass) && $oAttDef instanceof AttributeEnum)
|
||||
{
|
||||
if (isset(self::$m_aEnumToMeta[$sClass][$sAttCode]))
|
||||
{
|
||||
foreach (self::$m_aEnumToMeta[$sClass][$sAttCode] as $sMetaAttCode => $oMetaAttDef)
|
||||
{
|
||||
$aMetaValues = array(); // array of (metavalue => array of values)
|
||||
foreach ($oAttDef->GetAllowedValues() as $sCode => $sLabel)
|
||||
{
|
||||
$aMappingData = $oMetaAttDef->GetMapRule($sClass);
|
||||
if ($aMappingData == null)
|
||||
{
|
||||
$sMetaValue = $oMetaAttDef->GetDefaultValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (array_key_exists($sCode, $aMappingData['values']))
|
||||
{
|
||||
$sMetaValue = $aMappingData['values'][$sCode];
|
||||
}
|
||||
elseif ($oMetaAttDef->GetDefaultValue() != '')
|
||||
{
|
||||
$sMetaValue = $oMetaAttDef->GetDefaultValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception('MetaModel::RebuildMetaEnums(): mapping not found for value "'.$sCode.'"" in '.$sClass.', on attribute '.self::GetAttributeOrigin($sClass, $oMetaAttDef->GetCode()).'::'.$oMetaAttDef->GetCode());
|
||||
}
|
||||
}
|
||||
$aMetaValues[$sMetaValue][] = $sCode;
|
||||
}
|
||||
foreach ($aMetaValues as $sMetaValue => $aEnumValues)
|
||||
{
|
||||
$sMetaTable = self::DBGetTable($sClass, $sMetaAttCode);
|
||||
$sEnumTable = self::DBGetTable($sClass);
|
||||
$aColumns = array_keys($oMetaAttDef->GetSQLColumns());
|
||||
$sMetaColumn = reset($aColumns);
|
||||
$aColumns = array_keys($oAttDef->GetSQLColumns());
|
||||
$sEnumColumn = reset($aColumns);
|
||||
$sValueList = implode(', ', CMDBSource::Quote($aEnumValues));
|
||||
$sSql = "UPDATE `$sMetaTable` JOIN `$sEnumTable` ON `$sEnumTable`.id = `$sMetaTable`.id SET `$sMetaTable`.`$sMetaColumn` = '$sMetaValue' WHERE `$sEnumTable`.`$sEnumColumn` IN ($sValueList) AND `$sMetaTable`.`$sMetaColumn` != '$sMetaValue'";
|
||||
if ($bVerbose)
|
||||
{
|
||||
echo "Executing query: $sSql\n";
|
||||
}
|
||||
CMDBSource::Query($sSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function CheckDataSources($bDiagnostics, $bVerbose)
|
||||
{
|
||||
$sOQL = 'SELECT SynchroDataSource';
|
||||
@@ -3393,9 +3486,10 @@ abstract class MetaModel
|
||||
$aAlterTableItems = array(); // array of <table> => <alter specification>
|
||||
|
||||
foreach (self::GetClasses() as $sClass)
|
||||
{
|
||||
{
|
||||
if (!self::HasTable($sClass)) continue;
|
||||
|
||||
|
||||
// Check that the table exists
|
||||
//
|
||||
$sTable = self::DBGetTable($sClass);
|
||||
@@ -3452,7 +3546,6 @@ abstract class MetaModel
|
||||
{
|
||||
// Skip this attribute if not originaly defined in this class
|
||||
if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue;
|
||||
|
||||
foreach($oAttDef->GetSQLColumns(true) as $sField => $sDBFieldSpec)
|
||||
{
|
||||
// Keep track of columns used by iTop
|
||||
@@ -4035,11 +4128,13 @@ abstract class MetaModel
|
||||
}
|
||||
}
|
||||
|
||||
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false)
|
||||
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production')
|
||||
{
|
||||
self::$m_sEnvironment = $sEnvironment;
|
||||
|
||||
if (!defined('MODULESROOT'))
|
||||
{
|
||||
define('MODULESROOT', APPROOT.'env-'.utils::GetCurrentEnvironment().'/');
|
||||
define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/');
|
||||
|
||||
self::$m_bTraceSourceFiles = $bTraceSourceFiles;
|
||||
|
||||
@@ -4132,36 +4227,25 @@ abstract class MetaModel
|
||||
// needed when some error occur
|
||||
$sAppIdentity = 'itop-'.MetaModel::GetEnvironmentId();
|
||||
$bDictInitializedFromData = false;
|
||||
if (!self::$m_bUseAPCCache || !Dict::InCache($sAppIdentity))
|
||||
if (self::$m_bUseAPCCache)
|
||||
{
|
||||
$bDictInitializedFromData = true;
|
||||
foreach (self::$m_oConfig->GetDictionaries() as $sModule => $sToInclude)
|
||||
{
|
||||
self::IncludeModule('dictionaries', $sToInclude);
|
||||
}
|
||||
}
|
||||
// Set the language... after the dictionaries have been loaded!
|
||||
Dict::EnableCache($sAppIdentity);
|
||||
}
|
||||
require_once(APPROOT.'env-'.self::$m_sEnvironment.'/dictionaries/languages.php');
|
||||
|
||||
// Set the default language...
|
||||
Dict::SetDefaultLanguage(self::$m_oConfig->GetDefaultLanguage());
|
||||
|
||||
// Romain: this is the only way I've found to cope with the fact that
|
||||
// classes have to be derived from cmdbabstract (to be editable in the UI)
|
||||
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
|
||||
|
||||
foreach (self::$m_oConfig->GetAppModules() as $sModule => $sToInclude)
|
||||
{
|
||||
self::IncludeModule('application', $sToInclude);
|
||||
}
|
||||
foreach (self::$m_oConfig->GetDataModels() as $sModule => $sToInclude)
|
||||
{
|
||||
self::IncludeModule('business', $sToInclude);
|
||||
}
|
||||
foreach (self::$m_oConfig->GetWebServiceCategories() as $sModule => $sToInclude)
|
||||
{
|
||||
self::IncludeModule('webservice', $sToInclude);
|
||||
}
|
||||
require_once(APPROOT.'core/autoload.php');
|
||||
require_once(APPROOT.'env-'.self::$m_sEnvironment.'/autoload.php');
|
||||
|
||||
foreach (self::$m_oConfig->GetAddons() as $sModule => $sToInclude)
|
||||
{
|
||||
self::IncludeModule('addons', $sToInclude);
|
||||
self::IncludeModule($sToInclude, 'addons');
|
||||
}
|
||||
|
||||
$sServer = self::$m_oConfig->GetDBHost();
|
||||
@@ -4202,6 +4286,7 @@ abstract class MetaModel
|
||||
self::$m_aStimuli = $result['m_aStimuli'];
|
||||
self::$m_aTransitions = $result['m_aTransitions'];
|
||||
self::$m_aHighlightScales = $result['m_aHighlightScales'];
|
||||
self::$m_aEnumToMeta = $result['m_aEnumToMeta'];
|
||||
}
|
||||
$oKPI->ComputeAndReport('Metamodel APC (fetch + read)');
|
||||
}
|
||||
@@ -4239,15 +4324,11 @@ abstract class MetaModel
|
||||
$aCache['m_aStimuli'] = self::$m_aStimuli; // array of ("classname" => array of ("stimuluscode"=>array('label'=>...)))
|
||||
$aCache['m_aTransitions'] = self::$m_aTransitions; // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD)))
|
||||
$aCache['m_aHighlightScales'] = self::$m_aHighlightScales; // array of ("classname" => array of higlightcodes)))
|
||||
$aCache['m_aEnumToMeta'] = self::$m_aEnumToMeta;
|
||||
apc_store($sOqlAPCCacheId, $aCache);
|
||||
$oKPI->ComputeAndReport('Metamodel APC (store)');
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$m_bUseAPCCache && $bDictInitializedFromData)
|
||||
{
|
||||
Dict::InitCache($sAppIdentity);
|
||||
}
|
||||
|
||||
self::$m_sDBName = $sSource;
|
||||
self::$m_sTablePrefix = $sTablePrefix;
|
||||
@@ -4283,12 +4364,12 @@ abstract class MetaModel
|
||||
|
||||
public static function GetEnvironmentId()
|
||||
{
|
||||
return md5(APPROOT).'-'.utils::GetCurrentEnvironment();
|
||||
return md5(APPROOT).'-'.self::$m_sEnvironment;
|
||||
}
|
||||
|
||||
protected static $m_aExtensionClasses = array();
|
||||
|
||||
protected static function IncludeModule($sModuleType, $sToInclude)
|
||||
protected static function IncludeModule($sToInclude, $sModuleType = null)
|
||||
{
|
||||
$sFirstChar = substr($sToInclude, 0, 1);
|
||||
$sSecondChar = substr($sToInclude, 1, 1);
|
||||
@@ -4314,14 +4395,21 @@ abstract class MetaModel
|
||||
if (!file_exists($sFile))
|
||||
{
|
||||
$sConfigFile = self::$m_oConfig->GetLoadedFile();
|
||||
if (strlen($sConfigFile) > 0)
|
||||
if ($sModuleType == null)
|
||||
{
|
||||
throw new CoreException('Include: wrong file name in configuration file', array('config file' => $sConfigFile, 'section' => $sModuleType, 'filename' => $sFile));
|
||||
throw new CoreException("Include: unable to load the file '$sFile'");
|
||||
}
|
||||
else
|
||||
{
|
||||
// The configuration is in memory only
|
||||
throw new CoreException('Include: wrong file name in configuration file (in memory)', array('section' => $sModuleType, 'filename' => $sFile));
|
||||
if (strlen($sConfigFile) > 0)
|
||||
{
|
||||
throw new CoreException('Include: wrong file name in configuration file', array('config file' => $sConfigFile, 'section' => $sModuleType, 'filename' => $sFile));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The configuration is in memory only
|
||||
throw new CoreException('Include: wrong file name in configuration file (in memory)', array('section' => $sModuleType, 'filename' => $sFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4337,7 +4425,7 @@ abstract class MetaModel
|
||||
{
|
||||
if ($sPreviousContent != '')
|
||||
{
|
||||
IssueLog::Error("Spurious characters injected by $sModuleType/$sToInclude");
|
||||
IssueLog::Error("Spurious characters injected by '$sFile'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4398,7 +4486,7 @@ abstract class MetaModel
|
||||
{
|
||||
$oFilter->AllowAllData();
|
||||
}
|
||||
|
||||
$oFilter->NoContextParameters();
|
||||
$sSQL = $oFilter->MakeSelectQuery();
|
||||
self::$aQueryCacheGetObject[$sQuerySign] = $sSQL;
|
||||
self::$aQueryCacheGetObjectHits[$sQuerySign] = 0;
|
||||
@@ -4805,9 +4893,9 @@ abstract class MetaModel
|
||||
{
|
||||
// Expand the parameters for the object
|
||||
$sName = substr($sSearch, 0, $iPos);
|
||||
if (preg_match_all('/\\$'.$sName.'->([^\\$]+)\\$/', $sInput, $aMatches))
|
||||
if (preg_match_all('/\\$'.$sName.'-(>|>)([^\\$]+)\\$/', $sInput, $aMatches)) // Support both syntaxes: $this->xxx$ or $this->xxx$ for HTML compatibility
|
||||
{
|
||||
foreach($aMatches[1] as $sPlaceholderAttCode)
|
||||
foreach($aMatches[2] as $idx => $sPlaceholderAttCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -4815,7 +4903,7 @@ abstract class MetaModel
|
||||
if ($sReplacement !== null)
|
||||
{
|
||||
$aReplacements[] = $sReplacement;
|
||||
$aSearches[] = '$'.$sName.'->'.$sPlaceholderAttCode.'$';
|
||||
$aSearches[] = '$'.$sName.'-'.$aMatches[1][$idx].$sPlaceholderAttCode.'$';
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
@@ -4830,9 +4918,11 @@ abstract class MetaModel
|
||||
continue; // Ignore this non-scalar value
|
||||
}
|
||||
}
|
||||
|
||||
$aSearches[] = '$'.$sSearch.'$';
|
||||
$aReplacements[] = (string) $replace;
|
||||
else
|
||||
{
|
||||
$aSearches[] = '$'.$sSearch.'$';
|
||||
$aReplacements[] = (string) $replace;
|
||||
}
|
||||
}
|
||||
return str_replace($aSearches, $aReplacements, $sInput);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,21 @@
|
||||
* @copyright Copyright (C) 2013 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Exclude the parent class from the list
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
define('ENUM_CHILD_CLASSES_EXCLUDETOP', 1);
|
||||
/**
|
||||
* Include the parent class in the list
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
define('ENUM_CHILD_CLASSES_ALL', 2);
|
||||
|
||||
|
||||
abstract class ModelReflection
|
||||
{
|
||||
abstract public function GetClassIcon($sClass, $bImgTag = true);
|
||||
@@ -62,6 +76,9 @@ abstract class ModelReflection
|
||||
}
|
||||
|
||||
abstract public function GetIconSelectionField($sCode, $sLabel = '', $defaultValue = '');
|
||||
|
||||
abstract public function GetRootClass($sClass);
|
||||
abstract public function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP);
|
||||
}
|
||||
|
||||
abstract class QueryReflection
|
||||
@@ -234,6 +251,16 @@ class ModelReflectionRuntime extends ModelReflection
|
||||
{
|
||||
return new RunTimeIconSelectionField($sCode, $sLabel, $defaultValue);
|
||||
}
|
||||
|
||||
public function GetRootClass($sClass)
|
||||
{
|
||||
return MetaModel::GetRootClass($sClass);
|
||||
}
|
||||
|
||||
public function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP)
|
||||
{
|
||||
return MetaModel::EnumChildClasses($sClass, $iOption);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
120
core/moduledesign.class.inc.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* Module specific customizations:
|
||||
* The customizations are done in XML, within a module_design section (itop_design/module_designs/module_design)
|
||||
* The module reads the cusomtizations by the mean of the ModuleDesign API
|
||||
* @package Core
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'application/utils.inc.php');
|
||||
require_once(APPROOT.'core/designdocument.class.inc.php');
|
||||
|
||||
|
||||
/**
|
||||
* Class ModuleDesign
|
||||
*
|
||||
* Usage from within a module:
|
||||
*
|
||||
* // Fetch the design
|
||||
* $oDesign = new ModuleDesign('tagada');
|
||||
*
|
||||
* // Read data from the root node
|
||||
* $oRoot = $oDesign->documentElement;
|
||||
* $oProperties = $oRoot->GetUniqueElement('properties');
|
||||
* $prop1 = $oProperties->GetChildText('property1');
|
||||
* $prop2 = $oProperties->GetChildText('property2');
|
||||
*
|
||||
* // Read data by searching the entire DOM
|
||||
* foreach ($oDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode)
|
||||
* {
|
||||
* $sId = $oBrickNode->getAttribute('id');
|
||||
* $sType = $oBrickNode->getAttribute('xsi:type');
|
||||
* }
|
||||
*
|
||||
* // Search starting a given node
|
||||
* $oBricks = $oDesign->documentElement->GetUniqueElement('bricks');
|
||||
* foreach ($oBricks->GetNodes('brick') as $oBrickNode)
|
||||
* {
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
class ModuleDesign extends \Combodo\iTop\DesignDocument
|
||||
{
|
||||
/**
|
||||
* @param string|null $sDesignSourceId Identifier of the section module_design (generally a module name), null to build an empty design
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($sDesignSourceId = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (!is_null($sDesignSourceId))
|
||||
{
|
||||
$this->LoadFromCompiledDesigns($sDesignSourceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data where the compiler has left them...
|
||||
* @param $sDesignSourceId String Identifier of the section module_design (generally a module name)
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function LoadFromCompiledDesigns($sDesignSourceId)
|
||||
{
|
||||
$sDesignDir = APPROOT.'env-'.utils::GetCurrentEnvironment().'/core/module_designs/';
|
||||
$sFile = $sDesignDir.$sDesignSourceId.'.xml';
|
||||
if (!file_exists($sFile))
|
||||
{
|
||||
$aFiles = glob($sDesignDir.'/*.xml');
|
||||
if (count($aFiles) == 0)
|
||||
{
|
||||
$sAvailable = 'none!';
|
||||
}
|
||||
else
|
||||
{
|
||||
var_dump($aFiles);
|
||||
$aAvailable = array();
|
||||
foreach ($aFiles as $sFile)
|
||||
{
|
||||
$aAvailable[] = "'".basename($sFile, '.xml')."'";
|
||||
}
|
||||
$sAvailable = implode(', ', $aAvailable);
|
||||
}
|
||||
throw new Exception("Could not load module design '$sDesignSourceId'. Available designs: $sAvailable");
|
||||
}
|
||||
|
||||
// Silently keep track of errors
|
||||
libxml_use_internal_errors(true);
|
||||
libxml_clear_errors();
|
||||
$this->load($sFile);
|
||||
//$bValidated = $oDocument->schemaValidate(APPROOT.'setup/itop_design.xsd');
|
||||
$aErrors = libxml_get_errors();
|
||||
if (count($aErrors) > 0)
|
||||
{
|
||||
$aDisplayErrors = array();
|
||||
foreach($aErrors as $oXmlError)
|
||||
{
|
||||
$aDisplayErrors[] = 'Line '.$oXmlError->line.': '.$oXmlError->message;
|
||||
}
|
||||
|
||||
throw new Exception("Invalid XML in '$sFile'. Errors: ".implode(', ', $aDisplayErrors));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2013 Combodo SARL
|
||||
// Copyright (C) 2013-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -24,7 +24,7 @@
|
||||
* Relies on MySQL locks because the API sem_get is not always present in the
|
||||
* installed PHP.
|
||||
*
|
||||
* @copyright Copyright (C) 2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2013-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
class iTopMutex
|
||||
@@ -139,6 +139,36 @@ class iTopMutex
|
||||
}
|
||||
return ($res !== '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the mutex is locked WITHOUT TRYING TO ACQUIRE IT
|
||||
* @returns bool True if the mutex is in use, false otherwise
|
||||
*/
|
||||
public function IsLocked()
|
||||
{
|
||||
if ($this->bLocked)
|
||||
{
|
||||
return true; // Already acquired
|
||||
}
|
||||
if (self::$aAcquiredLocks[$this->sName] > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$res = $this->QueryToScalar("SELECT IS_FREE_LOCK('".$this->sName."')"); // IS_FREE_LOCK detects some error cases that IS_USED_LOCK do not detect
|
||||
if (is_null($res))
|
||||
{
|
||||
$sMsg = "MySQL Error, IS_FREE_LOCK('".$this->sName."') returned null. Error (".mysqli_errno($this->hDBLink).") = '".mysqli_error($this->hDBLink)."'";
|
||||
IssueLog::Error($sMsg);
|
||||
throw new Exception($sMsg);
|
||||
}
|
||||
else if ($res == '1')
|
||||
{
|
||||
// Lock is free
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the mutex
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
#!/bin/bash
|
||||
php PHP/LexerGenerator/cli.php oql-lexer.plex
|
||||
php PHP/ParserGenerator/cli.php oql-parser.y
|
||||
#
|
||||
# Rebuild the iTop Lexer / Parser
|
||||
# PEAR is required to build (really?)
|
||||
# Launch this batch from the core/oql/build directory
|
||||
# with ./build.bash
|
||||
#
|
||||
php PHP/LexerGenerator/cli.php ../oql-lexer.plex
|
||||
php PHP/ParserGenerator/cli.php ../oql-parser.y
|
||||
php -r "echo date('Y-m-d');" > ../version.txt
|
||||
|
||||
|
||||
@@ -2,4 +2,5 @@ rem must be run with current directory = the directory of the batch
|
||||
rem PEAR is required to build
|
||||
php -d include_path=".;C:\iTop\PHP\PEAR" ".\PHP\LexerGenerator\cli.php" ..\oql-lexer.plex
|
||||
php ".\PHP\ParserGenerator\cli.php" ..\oql-parser.y
|
||||
php -r "echo date('Y-m-d');" > ..\version.txt
|
||||
pause
|
||||
53
core/oql/check_oql.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal file (with all the needed includes) to check the validity of an OQL by verifying:
|
||||
* - The syntax (of the OQL query string)
|
||||
* - The consistency with a given data model (represented by an instance of ModelReflection)
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* require_once(APPROOT.'core/oql/check_oql.php');
|
||||
*
|
||||
* $sOQL = "SELECT Zerver WHERE status = 'production'";
|
||||
* $oModelReflection = new ModelReflectionRuntime();
|
||||
* $aResults = CheckOQL($sOQL, $oModelReflection);
|
||||
* if ($aResults['status'] == 'error')
|
||||
* {
|
||||
* echo "The query '$sOQL' is not a valid query. Reason: {$aResults['message']}";
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* echo "Ok, '$sOQL' is a valid query";
|
||||
* }
|
||||
*/
|
||||
if (!class_exists('CoreException', false))
|
||||
{
|
||||
class CoreException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
require_once(__DIR__.'/expression.class.inc.php');
|
||||
require_once(__DIR__.'/oqlquery.class.inc.php');
|
||||
require_once(__DIR__.'/oqlexception.class.inc.php');
|
||||
require_once(__DIR__.'/oql-parser.php');
|
||||
require_once(__DIR__.'/oql-lexer.php');
|
||||
require_once(__DIR__.'/oqlinterpreter.class.inc.php');
|
||||
|
||||
function CheckOQL($sOQL, ModelReflection $oModelReflection)
|
||||
{
|
||||
$aRes = array('status' => 'ok', 'message' => '');
|
||||
try
|
||||
{
|
||||
$oOql = new OqlInterpreter($sOQL);
|
||||
$oOqlQuery = $oOql->ParseQuery(); // Exceptions thrown in case of issue
|
||||
$oOqlQuery->Check($oModelReflection,$sOQL); // Exceptions thrown in case of issue
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$aRes['status'] = 'error';
|
||||
$aRes['message'] = $e->getMessage();
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
1408
core/oql/expression.class.inc.php
Normal file
@@ -24,6 +24,16 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
define('TREE_OPERATOR_EQUALS', 0);
|
||||
define('TREE_OPERATOR_BELOW', 1);
|
||||
define('TREE_OPERATOR_BELOW_STRICT', 2);
|
||||
define('TREE_OPERATOR_NOT_BELOW', 3);
|
||||
define('TREE_OPERATOR_NOT_BELOW_STRICT', 4);
|
||||
define('TREE_OPERATOR_ABOVE', 5);
|
||||
define('TREE_OPERATOR_ABOVE_STRICT', 6);
|
||||
define('TREE_OPERATOR_NOT_ABOVE', 7);
|
||||
define('TREE_OPERATOR_NOT_ABOVE_STRICT', 8);
|
||||
|
||||
// Position a string within an OQL query
|
||||
// This is a must if we want to be able to pinpoint an error at any stage of the query interpretation
|
||||
// In particular, the normalization phase requires this
|
||||
@@ -278,6 +288,23 @@ abstract class OqlQuery
|
||||
* @throws OqlNormalizeException
|
||||
*/
|
||||
abstract public function Check(ModelReflection $oModelReflection, $sSourceQuery);
|
||||
|
||||
/**
|
||||
* Determine the class
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
abstract public function GetClass(ModelReflection $oModelReflection);
|
||||
|
||||
/**
|
||||
* Determine the class alias
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
abstract public function GetClassAlias();
|
||||
}
|
||||
|
||||
class OqlObjectQuery extends OqlQuery
|
||||
@@ -303,10 +330,25 @@ class OqlObjectQuery extends OqlQuery
|
||||
{
|
||||
return $this->m_aSelect;
|
||||
}
|
||||
public function GetClass()
|
||||
|
||||
/**
|
||||
* Determine the class
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetClass(ModelReflection $oModelReflection)
|
||||
{
|
||||
return $this->m_oClass->GetValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the class alias
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetClassAlias()
|
||||
{
|
||||
return $this->m_oClassAlias->GetValue();
|
||||
@@ -339,7 +381,7 @@ class OqlObjectQuery extends OqlQuery
|
||||
*/
|
||||
public function Check(ModelReflection $oModelReflection, $sSourceQuery)
|
||||
{
|
||||
$sClass = $this->GetClass();
|
||||
$sClass = $this->GetClass($oModelReflection);
|
||||
$sClassAlias = $this->GetClassAlias();
|
||||
|
||||
if (!$oModelReflection->IsValidClass($sClass))
|
||||
@@ -490,7 +532,7 @@ class OqlObjectQuery extends OqlQuery
|
||||
*/
|
||||
public function ToDBSearch($sQuery)
|
||||
{
|
||||
$sClass = $this->GetClass();
|
||||
$sClass = $this->GetClass(new ModelReflectionRuntime());
|
||||
$sClassAlias = $this->GetClassAlias();
|
||||
|
||||
$oSearch = new DBObjectSearch($sClass, $sClassAlias);
|
||||
@@ -538,7 +580,7 @@ class OqlUnionQuery extends OqlQuery
|
||||
{
|
||||
$oQuery->Check($oModelReflection, $sSourceQuery);
|
||||
|
||||
$aAliasToClass = array($oQuery->GetClassAlias() => $oQuery->GetClass());
|
||||
$aAliasToClass = array($oQuery->GetClassAlias() => $oQuery->GetClass($oModelReflection));
|
||||
$aJoinSpecs = $oQuery->GetJoins();
|
||||
if (is_array($aJoinSpecs))
|
||||
{
|
||||
@@ -580,13 +622,13 @@ class OqlUnionQuery extends OqlQuery
|
||||
if ($iQuery == 0)
|
||||
{
|
||||
// Establish the reference
|
||||
$sRootClass = MetaModel::GetRootClass($aData['class']);
|
||||
$sRootClass = $oModelReflection->GetRootClass($aData['class']);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MetaModel::GetRootClass($aData['class']) != $sRootClass)
|
||||
if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass)
|
||||
{
|
||||
$aSubclasses = MetaModel::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL);
|
||||
$aSubclasses = $oModelReflection->EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL);
|
||||
throw new OqlNormalizeException('Incompatible classes: could not find a common ancestor', $sSourceQuery, $aData['class_name'], $aSubclasses);
|
||||
}
|
||||
}
|
||||
@@ -594,6 +636,100 @@ class OqlUnionQuery extends OqlQuery
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the class
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetClass(ModelReflection $oModelReflection)
|
||||
{
|
||||
$aFirstColClasses = array();
|
||||
foreach ($this->aQueries as $iQuery => $oQuery)
|
||||
{
|
||||
$aFirstColClasses[] = $oQuery->GetClass($oModelReflection);
|
||||
}
|
||||
$sClass = self::GetLowestCommonAncestor($oModelReflection, $aFirstColClasses);
|
||||
if (is_null($sClass))
|
||||
{
|
||||
throw new Exception('Could not determine the class of the union query. This issue should have been detected earlier by calling OqlQuery::Check()');
|
||||
}
|
||||
return $sClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the class alias
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetClassAlias()
|
||||
{
|
||||
$sAlias = $this->aQueries[0]->GetClassAlias();
|
||||
return $sAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param array $aClasses Flat list of classes
|
||||
* @return string the lowest common ancestor amongst classes, null if none has been found
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function GetLowestCommonAncestor(ModelReflection $oModelReflection, $aClasses)
|
||||
{
|
||||
$sAncestor = null;
|
||||
foreach($aClasses as $sClass)
|
||||
{
|
||||
if (is_null($sAncestor))
|
||||
{
|
||||
// first loop
|
||||
$sAncestor = $sClass;
|
||||
}
|
||||
elseif ($sClass == $sAncestor)
|
||||
{
|
||||
// remains the same
|
||||
}
|
||||
elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor))
|
||||
{
|
||||
$sAncestor = null;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sAncestor = self::LowestCommonAncestor($oModelReflection, $sAncestor, $sClass);
|
||||
}
|
||||
}
|
||||
return $sAncestor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: assumes that class A and B have a common ancestor
|
||||
*/
|
||||
protected static function LowestCommonAncestor(ModelReflection $oModelReflection, $sClassA, $sClassB)
|
||||
{
|
||||
if ($sClassA == $sClassB)
|
||||
{
|
||||
$sRet = $sClassA;
|
||||
}
|
||||
elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB)))
|
||||
{
|
||||
$sRet = $sClassB;
|
||||
}
|
||||
elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA)))
|
||||
{
|
||||
$sRet = $sClassA;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recurse
|
||||
$sRet = self::LowestCommonAncestor($oModelReflection, $sClassA, $oModelReflection->GetParentClass($sClassB));
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
/**
|
||||
* Make the relevant DBSearch instance (FromOQL)
|
||||
*/
|
||||
|
||||
1
core/oql/version.txt
Normal file
@@ -0,0 +1 @@
|
||||
2015-08-31
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 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-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
class ormCaseLog {
|
||||
@@ -43,9 +43,17 @@ class ormCaseLog {
|
||||
$this->m_bModified = false;
|
||||
}
|
||||
|
||||
public function GetText()
|
||||
public function GetText($bConvertToPlainText = false)
|
||||
{
|
||||
return $this->m_sLog;
|
||||
if ($bConvertToPlainText)
|
||||
{
|
||||
// Rebuild the log, but filtering any HTML markup for the all 'html' entries in the log
|
||||
return $this->GetAsPlainText();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->m_sLog;
|
||||
}
|
||||
}
|
||||
|
||||
public static function FromJSON($oJson)
|
||||
@@ -66,6 +74,31 @@ class ormCaseLog {
|
||||
* Return a value that will be further JSON encoded
|
||||
*/
|
||||
public function GetForJSON()
|
||||
{
|
||||
// Order by ascending date
|
||||
$aRet = array('entries' => array_reverse($this->GetAsArray()));
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the data, in a format that is suitable for programmatic usages:
|
||||
* -> dates not formatted
|
||||
* -> to preserve backward compatibility, to the returned structure must grow (new array entries)
|
||||
*
|
||||
* Format:
|
||||
* array (
|
||||
* array (
|
||||
* 'date' => <yyyy-mm-dd hh:mm:ss>,
|
||||
* 'user_login' => <user friendly name>
|
||||
* 'user_id' => OPTIONAL <id of the user account (caution: the object might have been deleted since)>
|
||||
* 'message' => <message as plain text (CR/LF), empty if message_html is given>
|
||||
* 'message_html' => <message with HTML markup, empty if message is given>
|
||||
* )
|
||||
*
|
||||
* @return array
|
||||
* @throws DictExceptionMissingString
|
||||
*/
|
||||
public function GetAsArray()
|
||||
{
|
||||
$aEntries = array();
|
||||
$iPos = 0;
|
||||
@@ -82,14 +115,14 @@ class ormCaseLog {
|
||||
if (is_int($this->m_aIndex[$index]['date']))
|
||||
{
|
||||
// Unix timestamp
|
||||
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$this->m_aIndex[$index]['date']);
|
||||
$sDate = date(AttributeDateTime::GetInternalFormat(),$this->m_aIndex[$index]['date']);
|
||||
}
|
||||
elseif (is_object($this->m_aIndex[$index]['date']))
|
||||
{
|
||||
if (version_compare(phpversion(), '5.3.0', '>='))
|
||||
{
|
||||
// DateTime
|
||||
$sDate = $this->m_aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
|
||||
$sDate = $this->m_aIndex[$index]['date']->format(AttributeDateTime::GetInternalFormat());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -97,11 +130,24 @@ class ormCaseLog {
|
||||
$sDate = '';
|
||||
}
|
||||
}
|
||||
$sFormat = array_key_exists('format', $this->m_aIndex[$index]) ? $this->m_aIndex[$index]['format'] : 'text';
|
||||
switch($sFormat)
|
||||
{
|
||||
case 'text':
|
||||
$sHtmlEntry = utils::TextToHtml($sTextEntry);
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
$sHtmlEntry = $sTextEntry;
|
||||
$sTextEntry = utils::HtmlToText($sHtmlEntry);
|
||||
break;
|
||||
}
|
||||
$aEntries[] = array(
|
||||
'date' => $sDate,
|
||||
'user_login' => $this->m_aIndex[$index]['user_name'],
|
||||
'user_id' => $this->m_aIndex[$index]['user_id'],
|
||||
'message' => $sTextEntry
|
||||
'message' => $sTextEntry,
|
||||
'message_html' => $sHtmlEntry,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,13 +159,29 @@ class ormCaseLog {
|
||||
$aEntries[] = array(
|
||||
'date' => '',
|
||||
'user_login' => '',
|
||||
'message' => $sTextEntry
|
||||
'message' => $sTextEntry,
|
||||
'message_html' => utils::TextToHtml($sTextEntry),
|
||||
);
|
||||
}
|
||||
|
||||
// Order by ascending date
|
||||
$aRet = array('entries' => array_reverse($aEntries));
|
||||
return $aRet;
|
||||
return $aEntries;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a "plain text" version of the log (equivalent to $this->m_sLog) where all the HTML markup from the 'html' entries have been removed
|
||||
* @return string
|
||||
*/
|
||||
public function GetAsPlainText()
|
||||
{
|
||||
$sPlainText = '';
|
||||
$aJSON = $this->GetForJSON();
|
||||
foreach($aJSON['entries'] as $aData)
|
||||
{
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $aData['date'], $aData['user_login'], $aData['user_id']);
|
||||
$sPlainText .= $sSeparator.$aData['message'];
|
||||
}
|
||||
return $sPlainText;
|
||||
}
|
||||
|
||||
public function GetIndex()
|
||||
@@ -152,7 +214,16 @@ class ormCaseLog {
|
||||
{
|
||||
$iPos += $aIndex[$index]['separator_length'];
|
||||
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
$sCSSClass = 'caselog_entry_html';
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
|
||||
{
|
||||
$sCSSClass = 'caselog_entry';
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sTextEntry = InlineImage::FixUrls($sTextEntry);
|
||||
}
|
||||
$iPos += $aIndex[$index]['text_length'];
|
||||
|
||||
$sEntry = '<div class="caselog_header" style="'.$sStyleCaseLogHeader.'">';
|
||||
@@ -163,14 +234,14 @@ class ormCaseLog {
|
||||
if (is_int($aIndex[$index]['date']))
|
||||
{
|
||||
// Unix timestamp
|
||||
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
|
||||
$sDate = date((string)AttributeDateTime::GetFormat(), $aIndex[$index]['date']);
|
||||
}
|
||||
elseif (is_object($aIndex[$index]['date']))
|
||||
{
|
||||
if (version_compare(phpversion(), '5.3.0', '>='))
|
||||
{
|
||||
// DateTime
|
||||
$sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
|
||||
$sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -180,7 +251,7 @@ class ormCaseLog {
|
||||
}
|
||||
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), '<span class="caselog_header_date">'.$sDate.'</span>', '<span class="caselog_header_user">'.$aIndex[$index]['user_name'].'</span>');
|
||||
$sEntry .= '</div>';
|
||||
$sEntry .= '<div class="caselog_entry" style="'.$sStyleCaseLogEntry.'">';
|
||||
$sEntry .= '<div class="'.$sCSSClass.'" style="'.$sStyleCaseLogEntry.'">';
|
||||
$sEntry .= $sTextEntry;
|
||||
$sEntry .= '</div>';
|
||||
$sHtml = $sHtml.$sEntry;
|
||||
@@ -215,7 +286,7 @@ class ormCaseLog {
|
||||
/**
|
||||
* Produces an HTML representation, aimed at being used to produce a PDF with TCPDF (no table)
|
||||
*/
|
||||
public function GetAsSimpleHtml()
|
||||
public function GetAsSimpleHtml($aTransfoHandler = null)
|
||||
{
|
||||
$sStyleCaseLogHeader = '';
|
||||
$sStyleCaseLogEntry = '';
|
||||
@@ -227,7 +298,24 @@ class ormCaseLog {
|
||||
{
|
||||
$iPos += $aIndex[$index]['separator_length'];
|
||||
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
$sCSSClass = 'case_log_simple_html_entry_html';
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
|
||||
{
|
||||
$sCSSClass = 'case_log_simple_html_entry';
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
if (!is_null($aTransfoHandler))
|
||||
{
|
||||
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_null($aTransfoHandler))
|
||||
{
|
||||
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry, true /* wiki "links" only */);
|
||||
}
|
||||
$sTextEntry = InlineImage::FixUrls($sTextEntry);
|
||||
}
|
||||
$iPos += $aIndex[$index]['text_length'];
|
||||
|
||||
$sEntry = '<li>';
|
||||
@@ -238,14 +326,14 @@ class ormCaseLog {
|
||||
if (is_int($aIndex[$index]['date']))
|
||||
{
|
||||
// Unix timestamp
|
||||
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
|
||||
$sDate = date((string)AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
|
||||
}
|
||||
elseif (is_object($aIndex[$index]['date']))
|
||||
{
|
||||
if (version_compare(phpversion(), '5.3.0', '>='))
|
||||
{
|
||||
// DateTime
|
||||
$sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
|
||||
$sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -254,7 +342,7 @@ class ormCaseLog {
|
||||
}
|
||||
}
|
||||
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), '<span class="caselog_header_date">'.$sDate.'</span>', '<span class="caselog_header_user">'.$aIndex[$index]['user_name'].'</span>');
|
||||
$sEntry .= '<div class="case_log_simple_html_entry" style="'.$sStyleCaseLogEntry.'">';
|
||||
$sEntry .= '<div class="'.$sCSSClass.'" style="'.$sStyleCaseLogEntry.'">';
|
||||
$sEntry .= $sTextEntry;
|
||||
$sEntry .= '</div>';
|
||||
$sEntry .= '</li>';
|
||||
@@ -317,10 +405,23 @@ class ormCaseLog {
|
||||
}
|
||||
$iPos += $aIndex[$index]['separator_length'];
|
||||
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
if (!is_null($aTransfoHandler))
|
||||
$sCSSClass= 'caselog_entry_html';
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
|
||||
{
|
||||
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
|
||||
$sCSSClass= 'caselog_entry';
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
if (!is_null($aTransfoHandler))
|
||||
{
|
||||
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_null($aTransfoHandler))
|
||||
{
|
||||
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry, true /* wiki "links" only */);
|
||||
}
|
||||
$sTextEntry = InlineImage::FixUrls($sTextEntry);
|
||||
}
|
||||
$iPos += $aIndex[$index]['text_length'];
|
||||
|
||||
@@ -332,14 +433,14 @@ class ormCaseLog {
|
||||
if (is_int($aIndex[$index]['date']))
|
||||
{
|
||||
// Unix timestamp
|
||||
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
|
||||
$sDate = date((string)AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
|
||||
}
|
||||
elseif (is_object($aIndex[$index]['date']))
|
||||
{
|
||||
if (version_compare(phpversion(), '5.3.0', '>='))
|
||||
{
|
||||
// DateTime
|
||||
$sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
|
||||
$sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -349,7 +450,7 @@ class ormCaseLog {
|
||||
}
|
||||
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
|
||||
$sEntry .= '</div>';
|
||||
$sEntry .= '<div class="caselog_entry"'.$sDisplay.'>';
|
||||
$sEntry .= '<div class="'.$sCSSClass.'"'.$sDisplay.'>';
|
||||
$sEntry .= $sTextEntry;
|
||||
$sEntry .= '</div>';
|
||||
$sHtml = $sHtml.$sEntry;
|
||||
@@ -358,6 +459,7 @@ class ormCaseLog {
|
||||
// Process the case of an eventual remainder (quick migration of AttributeText fields)
|
||||
if ($iPos < (strlen($this->m_sLog) - 1))
|
||||
{
|
||||
// In this case the format is always "text"
|
||||
$sTextEntry = substr($this->m_sLog, $iPos);
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
if (!is_null($aTransfoHandler))
|
||||
@@ -402,8 +504,9 @@ class ormCaseLog {
|
||||
*/
|
||||
public function AddLogEntry($sText, $sOnBehalfOf = '')
|
||||
{
|
||||
$sText = HTMLSanitizer::Sanitize($sText);
|
||||
$bMergeEntries = false;
|
||||
$sDate = date(Dict::S('UI:CaseLog:DateFormat'));
|
||||
$sDate = date(AttributeDateTime::GetInternalFormat());
|
||||
if ($sOnBehalfOf == '')
|
||||
{
|
||||
$sOnBehalfOf = UserRights::GetUserFriendlyName();
|
||||
@@ -439,7 +542,8 @@ class ormCaseLog {
|
||||
'user_id' => $iUserId,
|
||||
'date' => time(),
|
||||
'text_length' => $aLatestEntry['text_length'] + $iTextlength,
|
||||
'separator_length' => $iSepLength,
|
||||
'separator_length' => $iSepLength,
|
||||
'format' => 'html',
|
||||
);
|
||||
|
||||
}
|
||||
@@ -455,6 +559,7 @@ class ormCaseLog {
|
||||
'date' => time(),
|
||||
'text_length' => $iTextlength,
|
||||
'separator_length' => $iSepLength,
|
||||
'format' => 'html',
|
||||
);
|
||||
}
|
||||
$this->m_bModified = true;
|
||||
@@ -463,7 +568,7 @@ class ormCaseLog {
|
||||
|
||||
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
|
||||
{
|
||||
$sText = isset($oJson->message) ? $oJson->message : '';
|
||||
$sText = HTMLSanitizer::Sanitize(isset($oJson->message) ? $oJson->message : '');
|
||||
|
||||
if (isset($oJson->user_id))
|
||||
{
|
||||
@@ -505,7 +610,17 @@ class ormCaseLog {
|
||||
{
|
||||
$iDate = time();
|
||||
}
|
||||
$sDate = date(Dict::S('UI:CaseLog:DateFormat'), $iDate);
|
||||
if (isset($oJson->format))
|
||||
{
|
||||
$sFormat = $oJson->format;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: what is the default format ? text ?
|
||||
$sFormat = 'html';
|
||||
}
|
||||
|
||||
$sDate = date(AttributeDateTime::GetInternalFormat(), $iDate);
|
||||
|
||||
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
|
||||
$iSepLength = strlen($sSeparator);
|
||||
@@ -516,31 +631,58 @@ class ormCaseLog {
|
||||
'user_id' => $iUserId,
|
||||
'date' => $iDate,
|
||||
'text_length' => $iTextlength,
|
||||
'separator_length' => $iSepLength,
|
||||
'separator_length' => $iSepLength,
|
||||
'format' => $sFormat,
|
||||
);
|
||||
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
|
||||
public function GetModifiedEntry()
|
||||
public function GetModifiedEntry($sFormat = 'text')
|
||||
{
|
||||
$sModifiedEntry = '';
|
||||
if ($this->m_bModified)
|
||||
{
|
||||
$sModifiedEntry = $this->GetLatestEntry();
|
||||
$sModifiedEntry = $this->GetLatestEntry($sFormat);
|
||||
}
|
||||
return $sModifiedEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest entry from the log
|
||||
* @param string The expected output format text|html
|
||||
* @return string
|
||||
*/
|
||||
public function GetLatestEntry()
|
||||
public function GetLatestEntry($sFormat = 'text')
|
||||
{
|
||||
$sRes = '';
|
||||
$aLastEntry = end($this->m_aIndex);
|
||||
$sRes = substr($this->m_sLog, $aLastEntry['separator_length'], $aLastEntry['text_length']);
|
||||
$sRaw = substr($this->m_sLog, $aLastEntry['separator_length'], $aLastEntry['text_length']);
|
||||
switch($sFormat)
|
||||
{
|
||||
case 'text':
|
||||
if ($aLastEntry['format'] == 'text')
|
||||
{
|
||||
$sRes = $sRaw;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRes = utils::HtmlToText($sRaw);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
if ($aLastEntry['format'] == 'text')
|
||||
{
|
||||
$sRes = utils::TextToHtml($sRaw);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRes = $sRaw;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
|
||||
103
core/ormcustomfieldsvalue.class.inc.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?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/>
|
||||
|
||||
|
||||
/**
|
||||
* Base class to hold the value managed by CustomFieldsHandler
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class ormCustomFieldsValue
|
||||
{
|
||||
protected $oHostObject;
|
||||
protected $sAttCode;
|
||||
protected $aCurrentValues;
|
||||
|
||||
/**
|
||||
* @param DBObject $oHostObject
|
||||
* @param $sAttCode
|
||||
*/
|
||||
public function __construct(DBObject $oHostObject, $sAttCode, $aCurrentValues = null)
|
||||
{
|
||||
$this->oHostObject = $oHostObject;
|
||||
$this->sAttCode = $sAttCode;
|
||||
$this->aCurrentValues = $aCurrentValues;
|
||||
}
|
||||
|
||||
public function GetValues()
|
||||
{
|
||||
return $this->aCurrentValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper used when the only thing you have is the value...
|
||||
* @return \Combodo\iTop\Form\Form
|
||||
*/
|
||||
public function GetForm()
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
|
||||
return $oAttDef->GetForm($this->oHostObject);
|
||||
}
|
||||
|
||||
public function GetAsHTML($bLocalize = true)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
|
||||
$oHandler = $oAttDef->GetHandler($this->GetValues());
|
||||
return $oHandler->GetAsHTML($this->aCurrentValues, $bLocalize);
|
||||
}
|
||||
|
||||
public function GetAsXML($bLocalize = true)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
|
||||
$oHandler = $oAttDef->GetHandler($this->GetValues());
|
||||
return $oHandler->GetAsXML($this->aCurrentValues, $bLocalize);
|
||||
}
|
||||
|
||||
public function GetAsCSV($sSeparator = ',', $sTextQualifier = '"', $bLocalize = true)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
|
||||
$oHandler = $oAttDef->GetHandler($this->GetValues());
|
||||
return $oHandler->GetAsCSV($this->aCurrentValues, $sSeparator, $sTextQualifier, $bLocalize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
|
||||
* @param $value mixed The current value of the field
|
||||
* @param $sVerb string The verb specifying the representation of the value
|
||||
* @param $bLocalize bool Whether or not to localize the value
|
||||
*/
|
||||
public function GetForTemplate($sVerb, $bLocalize = true)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
|
||||
$oHandler = $oAttDef->GetHandler($this->GetValues());
|
||||
return $oHandler->GetForTemplate($this->aCurrentValues, $sVerb, $bLocalize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ormCustomFieldsValue $fellow
|
||||
* @return bool
|
||||
*/
|
||||
public function Equals(ormCustomFieldsValue $oReference)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
|
||||
$oHandler = $oAttDef->GetHandler($this->GetValues());
|
||||
return $oHandler->CompareValues($this->aCurrentValues, $oReference->aCurrentValues);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* ormDocument
|
||||
* encapsulate the behavior of a binary data set that will be stored an attribute of class AttributeBlob
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -90,12 +90,12 @@ class ormDocument
|
||||
{
|
||||
// If the filename is not empty, display it, this is used
|
||||
// by the creation wizard while the file has not yet been uploaded
|
||||
$sResult = $this->GetFileName();
|
||||
$sResult = htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
else
|
||||
{
|
||||
$data = $this->GetData();
|
||||
$sResult = $this->GetFileName().' [ '.$this->GetMimeType().', size: '.strlen($data).' byte(s) ]<br/>';
|
||||
$sResult = htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8').' [ '.$this->GetMimeType().', size: '.strlen($data).' byte(s) ]<br/>';
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ class ormDocument
|
||||
*/
|
||||
public function GetDisplayLink($sClass, $Id, $sAttCode)
|
||||
{
|
||||
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" target=\"_blank\" >".$this->GetFileName()."</a>\n";
|
||||
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" target=\"_blank\" >".htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8')."</a>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,21 +115,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\">".$this->GetFileName()."</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.render.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;
|
||||
@@ -144,5 +152,45 @@ class ormDocument
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a document to the browser, either as 'inline' or 'attachment'
|
||||
*
|
||||
* @param WebPage $oPage The web page for the output
|
||||
* @param string $sClass Class name of the object
|
||||
* @param mixed $id Identifier of the object
|
||||
* @param string $sAttCode Name of the attribute containing the document to download
|
||||
* @param string $sContentDisposition Either 'inline' or 'attachment'
|
||||
* @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
|
||||
* @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
|
||||
* @return none
|
||||
*/
|
||||
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, false);
|
||||
if (!is_object($oObj))
|
||||
{
|
||||
throw new Exception("Invalid id ($id) for class '$sClass' - the object does not exist or you are not allowed to view it");
|
||||
}
|
||||
if (($sSecretField != null) && ($oObj->Get($sSecretField) != $sSecretValue))
|
||||
{
|
||||
usleep(200);
|
||||
throw new Exception("Invalid secret for class '$sClass' - the object does not exist or you are not allowed to view it");
|
||||
}
|
||||
$oDocument = $oObj->Get($sAttCode);
|
||||
if (is_object($oDocument))
|
||||
{
|
||||
$oPage->TrashUnexpectedOutput();
|
||||
$oPage->SetContentType($oDocument->GetMimeType());
|
||||
$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
|
||||
$oPage->add($oDocument->GetData());
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$oPage->p($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -490,10 +490,11 @@ class CheckStopWatchThresholds implements iBackgroundProcess
|
||||
{
|
||||
$iPercent = $aThresholdData['percent']; // could be different than the index !
|
||||
|
||||
$sNow = date('Y-m-d H:i:s');
|
||||
$sNow = date(AttributeDateTime::GetSQLFormat());
|
||||
$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);
|
||||
|
||||
@@ -239,7 +239,7 @@ class iTopOwnershipLock
|
||||
{
|
||||
if ($sToken === $this->oToken->Get('token'))
|
||||
{
|
||||
$this->oToken->Set('last_seen', date('Y-m-d H:i:s'));
|
||||
$this->oToken->Set('last_seen', date(AttributeDateTime::GetSQLFormat()));
|
||||
$this->oToken->DBUpdate();
|
||||
$aResult['acquired'] = $this->oToken->Get('acquired');
|
||||
}
|
||||
@@ -327,9 +327,9 @@ class iTopOwnershipLock
|
||||
$this->oToken->Set('obj_class', $this->sObjClass);
|
||||
$this->oToken->Set('obj_key', $this->iObjKey);
|
||||
}
|
||||
$this->oToken->Set('acquired', date('Y-m-d H:i:s'));
|
||||
$this->oToken->Set('acquired', date(AttributeDateTime::GetSQLFormat()));
|
||||
$this->oToken->Set('user_id', UserRights::GetUserId());
|
||||
$this->oToken->Set('last_seen', date('Y-m-d H:i:s'));
|
||||
$this->oToken->Set('last_seen', date(AttributeDateTime::GetSQLFormat()));
|
||||
if ($sToken === null)
|
||||
{
|
||||
$sToken = sprintf('%X', microtime(true));
|
||||
@@ -342,7 +342,7 @@ class iTopOwnershipLock
|
||||
protected static function DeleteExpiredLocks()
|
||||
{
|
||||
$sOQL = "SELECT iTopOwnershipToken WHERE last_seen < :last_seen_limit";
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, array('last_seen_limit' => date('Y-m-d H:i:s', time() - MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay')))));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, array('last_seen_limit' => date(AttributeDateTime::GetSQLFormat(), time() - MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay')))));
|
||||
while($oToken = $oSet->Fetch())
|
||||
{
|
||||
$oToken->DBDelete();
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'application/pdfpage.class.inc.php');
|
||||
|
||||
class PDFBulkExport extends HTMLBulkExport
|
||||
{
|
||||
public function DisplayUsage(Page $oP)
|
||||
@@ -33,6 +31,7 @@ class PDFBulkExport extends HTMLBulkExport
|
||||
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
|
||||
$oP->p(" *\tpage_size: (optional) size of the page. One of A4, A3, Letter (default is 'A4').");
|
||||
$oP->p(" *\tpage_orientation: (optional) the orientation of the page. Either Portrait or Landscape (default is 'Portrait').");
|
||||
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
@@ -46,6 +45,8 @@ class PDFBulkExport extends HTMLBulkExport
|
||||
{
|
||||
case 'pdf_options':
|
||||
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:PDFOptions').'</legend>');
|
||||
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:PDFPageFormat').'</h3>');
|
||||
$oP->add('<table>');
|
||||
$oP->add('<tr>');
|
||||
$oP->add('<td>'.Dict::S('Core:BulkExport:PDFPageSize').'</td>');
|
||||
@@ -55,8 +56,33 @@ class PDFBulkExport extends HTMLBulkExport
|
||||
$oP->add('<td>'.$this->GetSelectCtrl('page_orientation', array('P', 'L'), 'Core:BulkExport:PageOrientation-', 'L').'</td>');
|
||||
$oP->add('</tr>');
|
||||
$oP->add('</table>');
|
||||
|
||||
|
||||
$oP->add('</td><td style="vertical-align:top">');
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$oP->add('<input type="radio" id="pdf_date_time_format_default" name="pdf_date_format_radio" value="default"'.$sDefaultChecked.'><label for="pdf_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="pdf_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oP->add('<input type="radio" id="pdf_date_time_format_custom" name="pdf_date_format_radio" value="custom"'.$sCustomChecked.'><label for="pdf_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
|
||||
|
||||
$oP->add('</td></tr></table>');
|
||||
|
||||
|
||||
$oP->add('</fieldset>');
|
||||
$sJSTooltip = json_encode('<div id="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#pdf_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_pdf_options').on('preview_updated', function() { FormatDatesInPreview('pdf', 'html'); });
|
||||
$('#pdf_date_time_format_default').on('click', function() { FormatDatesInPreview('pdf', 'html'); });
|
||||
$('#pdf_date_time_format_custom').on('click', function() { FormatDatesInPreview('pdf', 'html'); });
|
||||
$('#pdf_custom_date_time_format').on('click', function() { $('#pdf_date_time_format_custom').prop('checked', true); FormatDatesInPreview('pdf', 'html'); }).on('keyup', function() { FormatDatesInPreview('pdf', 'html'); });
|
||||
EOF
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -90,6 +116,24 @@ class PDFBulkExport extends HTMLBulkExport
|
||||
parent::ReadParameters();
|
||||
$this->aStatusInfo['page_size'] = utils::ReadParam('page_size', 'A4', true, 'raw_data');
|
||||
$this->aStatusInfo['page_orientation'] = utils::ReadParam('page_orientation', 'L', true);
|
||||
|
||||
$sDateFormatRadio = utils::ReadParam('pdf_date_format_radio', '');
|
||||
switch($sDateFormatRadio)
|
||||
{
|
||||
case 'default':
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
}
|
||||
|
||||
public function GetHeader()
|
||||
@@ -108,7 +152,14 @@ class PDFBulkExport extends HTMLBulkExport
|
||||
|
||||
public function GetNextChunk(&$aStatus)
|
||||
{
|
||||
$oPrevFormat = AttributeDateTime::GetFormat();
|
||||
$oPrevDateFormat = AttributeDate::GetFormat();
|
||||
$oDateTimeFormat = new DateTimeFormat($this->aStatusInfo['date_format']);
|
||||
AttributeDateTime::SetFormat($oDateTimeFormat);
|
||||
AttributeDate::SetFormat(new DateTimeFormat($oDateTimeFormat->ToDateFormat()));
|
||||
$sData = parent::GetNextChunk($aStatus);
|
||||
AttributeDateTime::SetFormat($oPrevFormat);
|
||||
AttributeDate::SetFormat($oPrevDateFormat);
|
||||
$hFile = @fopen($this->aStatusInfo['tmp_file'], 'ab');
|
||||
if ($hFile === false)
|
||||
{
|
||||
@@ -123,6 +174,10 @@ class PDFBulkExport extends HTMLBulkExport
|
||||
{
|
||||
$sData = parent::GetFooter();
|
||||
|
||||
// We need a lot of time for the PDF conversion
|
||||
set_time_limit(60*10); // 10 minutes max ???
|
||||
|
||||
require_once(APPROOT.'application/pdfpage.class.inc.php');
|
||||
$oPage = new PDFPage(Dict::Format('Core:BulkExportOf_Class', MetaModel::GetName($this->oSearch->GetClass())), $this->aStatusInfo['page_size'], $this->aStatusInfo['page_orientation']);
|
||||
$oPDF = $oPage->get_tcpdf();
|
||||
$oPDF->SetFont('dejavusans', '', 8, '', true);
|
||||
@@ -135,6 +190,60 @@ class PDFBulkExport extends HTMLBulkExport
|
||||
return $sPDF;
|
||||
}
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sRet = parent::GetValue($oObj, $sAttCode);
|
||||
break;
|
||||
|
||||
default:
|
||||
$value = $oObj->Get($sAttCode);
|
||||
if ($value instanceof ormDocument)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeImage)
|
||||
{
|
||||
// To limit the image size in the PDF output, we have to enforce the size as height/width because max-width/max-height have no effect
|
||||
//
|
||||
$iDefaultMaxWidthPx = 48;
|
||||
$iDefaultMaxHeightPx = 48;
|
||||
if ($value->IsEmpty())
|
||||
{
|
||||
$iNewWidth = $iDefaultMaxWidthPx;
|
||||
$iNewHeight = $iDefaultMaxHeightPx;
|
||||
|
||||
$sUrl = $oAttDef->Get('default_image');
|
||||
}
|
||||
else
|
||||
{
|
||||
list($iWidth, $iHeight) = utils::GetImageSize($value->GetData());
|
||||
$iMaxWidthPx = min($iDefaultMaxWidthPx, $oAttDef->Get('display_max_width'));
|
||||
$iMaxHeightPx = min($iDefaultMaxHeightPx, $oAttDef->Get('display_max_height'));
|
||||
|
||||
$fScale = min($iMaxWidthPx / $iWidth, $iMaxHeightPx / $iHeight);
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
|
||||
$sUrl = 'data:' . $value->GetMimeType() . ';base64,' . base64_encode($value->GetData());
|
||||
}
|
||||
$sRet = '<img src="' . $sUrl . '" style="width: ' . $iNewWidth . 'px; height: ' . $iNewHeight . 'px">';
|
||||
$sRet = '<div class="view-image">'.$sRet.'</div>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = parent::GetValue($oObj, $sAttCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = parent::GetValue($oObj, $sAttCode);
|
||||
}
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetSupportedFormats()
|
||||
{
|
||||
return array('pdf' => Dict::S('Core:BulkExport:PDFFormat'));
|
||||
|
||||
@@ -145,10 +145,10 @@ class RelationRedundancyNode extends GraphNode
|
||||
*/
|
||||
class RelationEdge extends GraphEdge
|
||||
{
|
||||
public function __construct(SimpleGraph $oGraph, GraphNode $oSourceNode, GraphNode $oSinkNode)
|
||||
public function __construct(SimpleGraph $oGraph, GraphNode $oSourceNode, GraphNode $oSinkNode, $bMustBeUnique = false)
|
||||
{
|
||||
$sId = $oSourceNode->GetId().'-to-'.$oSinkNode->GetId();
|
||||
parent::__construct($oGraph, $sId, $oSourceNode, $oSinkNode);
|
||||
parent::__construct($oGraph, $sId, $oSourceNode, $oSinkNode, $bMustBeUnique);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ class RelationGraph extends SimpleGraph
|
||||
$aAliasNames = array_keys($aAliases);
|
||||
$sRootCauseAlias = $aAliasNames[1]; // 1st column (=0) = object, second column = root cause
|
||||
$oSet = new DBObjectSet($aContextQuery['search'], array(), array('id' => $oObj->GetKey()));
|
||||
$oSet->OptimizeColumnLoad(array($aAliasNames[0] => array(), $aAliasNames[1] => array())); // Do not load any column... better do a reload than many joins
|
||||
while($aRow = $oSet->FetchAssoc())
|
||||
{
|
||||
if (!is_null($aRow[$sRootCauseAlias]))
|
||||
@@ -426,7 +427,7 @@ class RelationGraph extends SimpleGraph
|
||||
if (!$oRedundancyNode)
|
||||
{
|
||||
// Direct link (otherwise handled by ComputeRedundancy)
|
||||
$oEdge = new RelationEdge($this, $oSourceNode, $oSinkNode);
|
||||
new RelationEdge($this, $oSourceNode, $oSinkNode);
|
||||
}
|
||||
// Recurse
|
||||
$this->AddRelatedObjects($sRelCode, $bDown, $oRelatedNode, $iMaxDepth - 1, $bEnableRedundancy);
|
||||
|
||||
@@ -234,22 +234,24 @@ class GraphEdge extends GraphElement
|
||||
{
|
||||
protected $oSourceNode;
|
||||
protected $oSinkNode;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new directed edge inside the given graph
|
||||
* @param SimpleGraph $oGraph
|
||||
* @param string $sId The unique identifier of this edge in the graph
|
||||
* @param GraphNode $oSourceNode
|
||||
* @param GraphNode $oSinkNode
|
||||
* @param bool $bMustBeUnique
|
||||
* @throws SimpleGraphException
|
||||
*/
|
||||
public function __construct(SimpleGraph $oGraph, $sId, GraphNode $oSourceNode, GraphNode $oSinkNode)
|
||||
public function __construct(SimpleGraph $oGraph, $sId, GraphNode $oSourceNode, GraphNode $oSinkNode, $bMustBeUnique = false)
|
||||
{
|
||||
parent::__construct($sId);
|
||||
$this->oSourceNode = $oSourceNode;
|
||||
$this->oSinkNode = $oSinkNode;
|
||||
$oGraph->_AddEdge($this);
|
||||
$oGraph->_AddEdge($this, $bMustBeUnique);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the "source" node for this edge
|
||||
* @return GraphNode
|
||||
@@ -403,11 +405,22 @@ class SimpleGraph
|
||||
/**
|
||||
* INTERNAL USE ONLY
|
||||
* @param GraphEdge $oEdge
|
||||
* @param bool $bMustBeUnique
|
||||
* @throws SimpleGraphException
|
||||
*/
|
||||
public function _AddEdge(GraphEdge $oEdge)
|
||||
public function _AddEdge(GraphEdge $oEdge, $bMustBeUnique = false)
|
||||
{
|
||||
if (array_key_exists($oEdge->GetId(), $this->aEdges)) throw new SimpleGraphException('Cannot add edge (id='.$oEdge->GetId().') to the graph. An edge with the same id already exists in the graph.');
|
||||
if (array_key_exists($oEdge->GetId(), $this->aEdges))
|
||||
{
|
||||
if ($bMustBeUnique)
|
||||
{
|
||||
throw new SimpleGraphException('Cannot add edge (id=' . $oEdge->GetId() . ') to the graph. An edge with the same id already exists in the graph.');
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->aEdges[$oEdge->GetId()] = $oEdge;
|
||||
$oEdge->GetSourceNode()->_AddOutgoingEdge($oEdge);
|
||||
@@ -516,7 +529,7 @@ EOF
|
||||
@fwrite($rFile, $sDotDescription);
|
||||
@fclose($rFile);
|
||||
$aOutput = array();
|
||||
$CommandLine = "\"$sDotExecutable\" -v -Tpng < $sDotFilePath -o$sImageFilePath 2>&1";
|
||||
$CommandLine = "\"$sDotExecutable\" -v -Tpng < \"$sDotFilePath\" -o\"$sImageFilePath\" 2>&1";
|
||||
|
||||
exec($CommandLine, $aOutput, $iRetCode);
|
||||
if ($iRetCode != 0)
|
||||
@@ -572,7 +585,7 @@ EOF
|
||||
@fwrite($rFile, $sDotDescription);
|
||||
@fclose($rFile);
|
||||
$aOutput = array();
|
||||
$CommandLine = "\"$sDotExecutable\" -v -Tdot < $sDotFilePath -o$sXdotFilePath 2>&1";
|
||||
$CommandLine = "\"$sDotExecutable\" -v -Tdot < \"$sDotFilePath\" -o\"$sXdotFilePath\" 2>&1";
|
||||
|
||||
exec($CommandLine, $aOutput, $iRetCode);
|
||||
if ($iRetCode != 0)
|
||||
@@ -692,7 +705,7 @@ EOF
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge back to subgraphs into one
|
||||
* Merge back two subgraphs into one
|
||||
* @param SimpleGraph $oGraph
|
||||
*/
|
||||
public function Merge(SimpleGraph $oGraph)
|
||||
|
||||
@@ -30,6 +30,7 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$oP->p(" * spreadsheet format options:");
|
||||
$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'");
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
@@ -51,18 +52,75 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$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>');
|
||||
|
||||
$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');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$oP->add('<input type="radio" id="spreadsheet_date_time_format_default" name="spreadsheet_date_format_radio" value="default"'.$sDefaultChecked.'><label for="spreadsheet_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
|
||||
$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
|
||||
$('#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'); });
|
||||
EOF
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
return parent:: DisplayFormPart($oP, $sPartId);
|
||||
}
|
||||
}
|
||||
|
||||
public function ReadParameters()
|
||||
{
|
||||
parent::ReadParameters();
|
||||
|
||||
$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;
|
||||
|
||||
case 'custom':
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
}
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
}
|
||||
return $this->GetValue($oObj, $sAttCode);
|
||||
}
|
||||
|
||||
@@ -85,10 +143,18 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
{
|
||||
$sRet = $value->GetTimeSpent();
|
||||
}
|
||||
elseif ($value instanceof ormDocument)
|
||||
{
|
||||
$sRet = '';
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeString)
|
||||
{
|
||||
$sRet = $oObj->GetAsHTML($sAttCode);
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeCustomFields)
|
||||
{
|
||||
$sRet = $oObj->GetAsHTML($sAttCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->bLocalizeOutput)
|
||||
@@ -97,7 +163,7 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = htmlentities($value, ENT_QUOTES, 'UTF-8');
|
||||
$sRet = htmlentities((string)$value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,7 +230,13 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
|
||||
$this->OptimizeColumnLoad($oSet);
|
||||
|
||||
|
||||
$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
|
||||
// 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');
|
||||
@@ -199,10 +271,16 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
$oFinalAttDef = $oAttDef->GetFinalAttDef();
|
||||
if (get_class($oFinalAttDef) == 'AttributeDateTime')
|
||||
{
|
||||
$iDate = AttributeDateTime::GetAsUnixSeconds($oObj->Get($sAttCode));
|
||||
$sData .= '<td>'.date('Y-m-d', $iDate).'</td>'; // Add the first column directly
|
||||
$sField = date('H:i:s', $iDate); // Will add the second column below
|
||||
$sData .= "<td>$sField</td>";
|
||||
// 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)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 Combodo SARL
|
||||
// Copyright (C) 2015-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* SQLObjectQuery
|
||||
* build a mySQL compatible SQL query
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -196,7 +196,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
{
|
||||
$this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField, $sRightTable);
|
||||
}
|
||||
public function AddInnerJoinTree($oSQLQuery, $sLeftFieldLeft, $sLeftFieldRight, $sRightFieldLeft, $sRightFieldRight, $sRightTableAlias = '', $iOperatorCode = TREE_OPERATOR_BELOW)
|
||||
public function AddInnerJoinTree($oSQLQuery, $sLeftFieldLeft, $sLeftFieldRight, $sRightFieldLeft, $sRightFieldRight, $sRightTableAlias = '', $iOperatorCode = TREE_OPERATOR_BELOW, $bInvertOnClause = false)
|
||||
{
|
||||
assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__));
|
||||
if (empty($sRightTableAlias))
|
||||
@@ -211,7 +211,9 @@ class SQLObjectQuery extends SQLQuery
|
||||
"rightfield_left" => $sRightFieldLeft,
|
||||
"rightfield_right" => $sRightFieldRight,
|
||||
"righttablealias" => $sRightTableAlias,
|
||||
"tree_operator" => $iOperatorCode);
|
||||
"tree_operator" => $iOperatorCode,
|
||||
'invert_on_clause' => $bInvertOnClause
|
||||
);
|
||||
}
|
||||
public function AddLeftJoin($oSQLQuery, $sLeftField, $sRightField)
|
||||
{
|
||||
@@ -406,10 +408,20 @@ class SQLObjectQuery extends SQLQuery
|
||||
$aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond");
|
||||
break;
|
||||
case "inner_tree":
|
||||
$sNodeLeft = "`$sCallerAlias`.`{$aJoinData['leftfield']}`";
|
||||
$sNodeRight = "`$sCallerAlias`.`{$aJoinData['rightfield']}`";
|
||||
$sRootLeft = "`$sRightTableAlias`.`{$aJoinData['rightfield_left']}`";
|
||||
$sRootRight = "`$sRightTableAlias`.`{$aJoinData['rightfield_right']}`";
|
||||
if ($aJoinData['invert_on_clause'])
|
||||
{
|
||||
$sRootLeft = "`$sCallerAlias`.`{$aJoinData['leftfield']}`";
|
||||
$sRootRight = "`$sCallerAlias`.`{$aJoinData['rightfield']}`";
|
||||
$sNodeLeft = "`$sRightTableAlias`.`{$aJoinData['rightfield_left']}`";
|
||||
$sNodeRight = "`$sRightTableAlias`.`{$aJoinData['rightfield_right']}`";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sNodeLeft = "`$sCallerAlias`.`{$aJoinData['leftfield']}`";
|
||||
$sNodeRight = "`$sCallerAlias`.`{$aJoinData['rightfield']}`";
|
||||
$sRootLeft = "`$sRightTableAlias`.`{$aJoinData['rightfield_left']}`";
|
||||
$sRootRight = "`$sRightTableAlias`.`{$aJoinData['rightfield_right']}`";
|
||||
}
|
||||
switch($aJoinData['tree_operator'])
|
||||
{
|
||||
case TREE_OPERATOR_BELOW:
|
||||
|
||||
@@ -383,7 +383,7 @@ class TriggerOnThresholdReached extends TriggerOnObject
|
||||
MetaModel::Init_AddAttribute(new AttributeString("threshold_index", array("allowed_values"=>null, "sql"=>"threshold_index", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'stop_watch_code', 'threshold_index', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'stop_watch_code', 'threshold_index', 'filter', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('target_class', 'threshold_index', 'threshold_index')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* User rights management API
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -67,6 +67,18 @@ abstract class UserRightsAddOnAPI
|
||||
abstract public function IsPortalUser($oUser);
|
||||
abstract public function FlushPrivileges();
|
||||
|
||||
|
||||
/**
|
||||
* Default behavior for addons that do not support profiles
|
||||
*
|
||||
* @param $oUser User
|
||||
* @return array
|
||||
*/
|
||||
public function ListProfiles($oUser)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* ...
|
||||
*/
|
||||
@@ -176,15 +188,16 @@ abstract class User extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeString("login", array("allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql"=>"language", "default_value"=>"EN US", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values" => new ValueSetEnum('enabled,disabled'), "sql"=>"status", "default_value"=>"enabled", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profile_list", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"profileid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array())));
|
||||
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', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'login')); // Attributes to be displayed for a list
|
||||
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
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form
|
||||
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
|
||||
}
|
||||
|
||||
@@ -223,6 +236,23 @@ abstract class User extends cmdbAbstractObject
|
||||
return $this->Get('login');
|
||||
}
|
||||
|
||||
protected $oContactObject;
|
||||
|
||||
/**
|
||||
* Fetch and memoize the associated contact (if any)
|
||||
*/
|
||||
public function GetContactObject()
|
||||
{
|
||||
if (is_null($this->oContactObject))
|
||||
{
|
||||
if ($this->Get('contactid') != 0)
|
||||
{
|
||||
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
|
||||
}
|
||||
}
|
||||
return $this->oContactObject;
|
||||
}
|
||||
|
||||
/*
|
||||
* Overload the standard behavior
|
||||
*/
|
||||
@@ -400,13 +430,13 @@ abstract class UserInternal extends User
|
||||
MetaModel::Init_InheritAttributes();
|
||||
|
||||
// When set, this token allows for password reset
|
||||
MetaModel::Init_AddAttribute(new AttributeString("reset_pwd_token", array("allowed_values"=>null, "sql"=>"reset_pwd_token", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
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', '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')); // Attributes to be displayed for a list
|
||||
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
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form
|
||||
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
|
||||
}
|
||||
|
||||
@@ -562,7 +592,17 @@ class UserRights
|
||||
$oUser = self::FindUser($sName, $sAuthentication);
|
||||
if (is_null($oUser))
|
||||
{
|
||||
return self::CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
|
||||
// Check if the user does not exist at all or if it is just disabled
|
||||
if (self::FindUser($sName, $sAuthentication, true) == null)
|
||||
{
|
||||
// User does not exist at all
|
||||
return self::CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
|
||||
}
|
||||
else
|
||||
{
|
||||
// User is actually disabled
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$oUser->CheckCredentials($sPassword))
|
||||
@@ -740,6 +780,18 @@ class UserRights
|
||||
return $oUser->Get('contactid');
|
||||
}
|
||||
|
||||
public static function GetContactObject()
|
||||
{
|
||||
if (is_null(self::$m_oUser))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::$m_oUser->GetContactObject();
|
||||
}
|
||||
}
|
||||
|
||||
// Render the user name in best effort mode
|
||||
public static function GetUserFriendlyName($sName = '')
|
||||
{
|
||||
@@ -821,7 +873,6 @@ class UserRights
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function IsActionAllowed($sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
|
||||
{
|
||||
// When initializing, we need to let everything pass trough
|
||||
@@ -929,7 +980,7 @@ class UserRights
|
||||
return self::$m_oAddOn->IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet);
|
||||
}
|
||||
|
||||
static $m_aAdmins = array();
|
||||
protected static $m_aAdmins = array();
|
||||
public static function IsAdministrator($oUser = null)
|
||||
{
|
||||
if (!self::CheckLogin()) return false;
|
||||
@@ -946,7 +997,7 @@ class UserRights
|
||||
return self::$m_aAdmins[$iUser];
|
||||
}
|
||||
|
||||
static $m_aPortalUsers = array();
|
||||
protected static $m_aPortalUsers = array();
|
||||
public static function IsPortalUser($oUser = null)
|
||||
{
|
||||
if (!self::CheckLogin()) return false;
|
||||
@@ -963,6 +1014,69 @@ class UserRights
|
||||
return self::$m_aPortalUsers[$iUser];
|
||||
}
|
||||
|
||||
public static function GetAllowedPortals()
|
||||
{
|
||||
$aAllowedPortals = array();
|
||||
$aPortalsConf = PortalDispatcherData::GetData();
|
||||
$aDispatchers = array();
|
||||
foreach ($aPortalsConf as $sPortalId => $aConf)
|
||||
{
|
||||
$sHandlerClass = $aConf['handler'];
|
||||
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
|
||||
}
|
||||
|
||||
foreach ($aDispatchers as $sPortalId => $oDispatcher)
|
||||
{
|
||||
if ($oDispatcher->IsUserAllowed())
|
||||
{
|
||||
$aAllowedPortals[] = array(
|
||||
'id' => $sPortalId,
|
||||
'label' => $oDispatcher->GetLabel(),
|
||||
'url' => $oDispatcher->GetUrl(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return $aAllowedPortals;
|
||||
}
|
||||
|
||||
public static function ListProfiles($oUser = null)
|
||||
{
|
||||
if (is_null($oUser))
|
||||
{
|
||||
$oUser = self::$m_oUser;
|
||||
}
|
||||
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))
|
||||
{
|
||||
$aProfiles = $_SESSION['profile_list'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($aProfiles))
|
||||
{
|
||||
$aProfiles = self::$m_oAddOn->ListProfiles($oUser);
|
||||
}
|
||||
return $aProfiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sProfileName Profile name to search for
|
||||
* @param $oUser User|null
|
||||
* @return bool
|
||||
*/
|
||||
public static function HasProfile($sProfileName, $oUser = null)
|
||||
{
|
||||
$bRet = in_array($sProfileName, self::ListProfiles($oUser));
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset cached data
|
||||
* @param Bool Reset admin cache as well
|
||||
@@ -975,7 +1089,9 @@ class UserRights
|
||||
if ($bResetAdminCache)
|
||||
{
|
||||
self::$m_aAdmins = array();
|
||||
self::$m_aPortalUsers = array();
|
||||
}
|
||||
self::_ResetSessionCache();
|
||||
return self::$m_oAddOn->FlushPrivileges();
|
||||
}
|
||||
|
||||
@@ -984,9 +1100,10 @@ class UserRights
|
||||
* Find a user based on its login and its type of authentication
|
||||
* @param string $sLogin Login/identifier of the user
|
||||
* @param string $sAuthentication Type of authentication used: internal|external|any
|
||||
* @param bool $bAllowDisabledUsers Whether or not to retrieve disabled users (status != enabled)
|
||||
* @return User The found user or null
|
||||
*/
|
||||
protected static function FindUser($sLogin, $sAuthentication = 'any')
|
||||
protected static function FindUser($sLogin, $sAuthentication = 'any', $bAllowDisabledUsers = false)
|
||||
{
|
||||
if ($sAuthentication == 'any')
|
||||
{
|
||||
@@ -1020,6 +1137,10 @@ class UserRights
|
||||
assert(false); // should never happen
|
||||
}
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sBaseClass WHERE login = :login");
|
||||
if (!$bAllowDisabledUsers)
|
||||
{
|
||||
$oSearch->AddCondition('status', 'enabled');
|
||||
}
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('login' => $sLogin));
|
||||
$oUser = $oSet->fetch();
|
||||
self::$m_aCacheUsers[$sAuthentication][$sLogin] = $oUser;
|
||||
@@ -1033,6 +1154,23 @@ class UserRights
|
||||
{
|
||||
return self::$m_oAddOn->MakeSelectFilter($sClass, $aAllowedOrgs, $aSettings, $sAttCode);
|
||||
}
|
||||
|
||||
public static function _InitSessionCache()
|
||||
{
|
||||
// Cache data about the current user into the session
|
||||
if (isset($_SESSION))
|
||||
{
|
||||
$_SESSION['profile_list'] = self::ListProfiles();
|
||||
}
|
||||
}
|
||||
|
||||
public static function _ResetSessionCache()
|
||||
{
|
||||
if (isset($_SESSION['profile_list']))
|
||||
{
|
||||
unset($_SESSION['profile_list']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
1
css/c3.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-title{font:14px sans-serif}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}
|
||||
@@ -4,5 +4,5 @@ $complement-color: #1c94c4;
|
||||
$complement-light: #d6e8ef;
|
||||
$frame-background-color: #F1F1F1;
|
||||
$text-color: #000;
|
||||
// Beware the version number MUST beging with a letter otherwise it may be truncated...
|
||||
$version: v2.2.0;
|
||||
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
|
||||
$version: "v2.3.0";
|
||||
12
css/email.css
Normal file
@@ -0,0 +1,12 @@
|
||||
/* Note: only CSS1 is supported here (see the limitations of emogrifier: https://github.com/jjriv/emogrifier/) */
|
||||
.caselog_header {
|
||||
padding: 3px;
|
||||
border-top: 1px solid #fff;
|
||||
background-color: #ddd;
|
||||
padding-left: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
.caselog_header_date {
|
||||
}
|
||||
.caselog_header_user {
|
||||
}
|
||||
BIN
css/font-combodo/combodo-webfont.woff
Normal file
BIN
css/font-combodo/combodo-webfont.woff2
Normal file
724
css/font-combodo/combodo.sfd
Normal file
@@ -0,0 +1,724 @@
|
||||
SplineFontDB: 3.0
|
||||
FontName: Combodo
|
||||
FullName: Combodo
|
||||
FamilyName: Combodo
|
||||
Weight: Regular
|
||||
Copyright: Copyright (c) 2016, Combodo
|
||||
UComments: "2016-5-20: Created with FontForge (http://fontforge.org)"
|
||||
Version: 001.100
|
||||
ItalicAngle: 0
|
||||
UnderlinePosition: -102
|
||||
UnderlineWidth: 51
|
||||
Ascent: 819
|
||||
Descent: 205
|
||||
LayerCount: 3
|
||||
Layer: 0 0 "Arri+AOgA-re" 1
|
||||
Layer: 1 0 "Avant" 0
|
||||
Layer: 2 0 "Arri+AOgA-re 2" 1
|
||||
XUID: [1021 788 735880319 2265]
|
||||
FSType: 0
|
||||
OS2Version: 0
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 1
|
||||
CreationTime: 1463745065
|
||||
ModificationTime: 1464178421
|
||||
OS2TypoAscent: 0
|
||||
OS2TypoAOffset: 1
|
||||
OS2TypoDescent: 0
|
||||
OS2TypoDOffset: 1
|
||||
OS2TypoLinegap: 92
|
||||
OS2WinAscent: 0
|
||||
OS2WinAOffset: 1
|
||||
OS2WinDescent: 0
|
||||
OS2WinDOffset: 1
|
||||
HheadAscent: 0
|
||||
HheadAOffset: 1
|
||||
HheadDescent: 0
|
||||
HheadDOffset: 1
|
||||
OS2Vendor: 'PfEd'
|
||||
MarkAttachClasses: 1
|
||||
DEI: 91125
|
||||
Encoding: ISO8859-1
|
||||
UnicodeInterp: none
|
||||
NameList: Adobe Glyph List
|
||||
DisplaySize: -48
|
||||
AntiAlias: 1
|
||||
FitToEm: 0
|
||||
WinInfo: 0 31 10
|
||||
BeginPrivate: 0
|
||||
EndPrivate
|
||||
BeginChars: 256 8
|
||||
|
||||
StartChar: zero
|
||||
Encoding: 48 48 0
|
||||
Width: 1024
|
||||
VWidth: 0
|
||||
Flags: W
|
||||
HStem: -14 54<392.945 631.055> 147 53<436.58 587.42> 520 54<436.58 587.42> 680 54<392.945 631.055>
|
||||
VStem: 138 54<240.945 479.055> 298 54<284.58 435.42> 672 54<284.58 435.42> 832 54<242.481 479.055>
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
367 704 m 0
|
||||
413 724 461 734 512 734 c 0
|
||||
563 734 611 724 657 704 c 0
|
||||
703 684 743 658 776 625 c 0
|
||||
809 592 836 551 856 505 c 0
|
||||
876 459 886 411 886 360 c 0
|
||||
886 309 876 261 856 215 c 0
|
||||
836 169 809 129 776 96 c 0
|
||||
743 63 703 36 657 16 c 0
|
||||
611 -4 563 -14 512 -14 c 0
|
||||
461 -14 413 -4 367 16 c 0
|
||||
321 36 280 63 247 96 c 0
|
||||
214 129 188 169 168 215 c 0
|
||||
148 261 138 309 138 360 c 0
|
||||
138 411 148 459 168 505 c 0
|
||||
188 551 214 592 247 625 c 0
|
||||
280 658 321 684 367 704 c 0
|
||||
229 209 m 1
|
||||
310 290 l 1
|
||||
302 313 298 336 298 360 c 0
|
||||
298 384 302 407 310 430 c 1
|
||||
229 511 l 1
|
||||
204 463 192 413 192 360 c 0
|
||||
192 307 204 257 229 209 c 1
|
||||
399 247 m 0
|
||||
430 216 468 200 512 200 c 0
|
||||
556 200 594 216 625 247 c 0
|
||||
656 278 672 316 672 360 c 0
|
||||
672 404 656 442 625 473 c 0
|
||||
594 504 556 520 512 520 c 0
|
||||
468 520 430 504 399 473 c 0
|
||||
368 442 352 404 352 360 c 0
|
||||
352 316 368 278 399 247 c 0
|
||||
512 680 m 0
|
||||
459 680 409 668 361 643 c 1
|
||||
442 562 l 1
|
||||
465 570 488 574 512 574 c 0
|
||||
536 574 559 570 582 562 c 1
|
||||
663 643 l 1
|
||||
615 668 565 680 512 680 c 0
|
||||
512 40 m 0
|
||||
565 40 615 52 663 77 c 1
|
||||
582 158 l 1
|
||||
559 150 536 147 512 147 c 0
|
||||
488 147 465 150 442 158 c 1
|
||||
361 77 l 1
|
||||
409 52 459 40 512 40 c 0
|
||||
714 290 m 1
|
||||
795 210 l 1
|
||||
820 258 832 307 832 360 c 0
|
||||
832 413 820 463 795 511 c 1
|
||||
714 430 l 1
|
||||
722 407 726 384 726 360 c 0
|
||||
726 336 722 313 714 290 c 1
|
||||
EndSplineSet
|
||||
Validated: 1
|
||||
EndChar
|
||||
|
||||
StartChar: one
|
||||
Encoding: 49 49 1
|
||||
Width: 1024
|
||||
VWidth: 0
|
||||
Flags: W
|
||||
HStem: -6 148<444.866 547.081> 501 134<444.866 539.817>
|
||||
VStem: 137 179<274.173 371.715> 674 213<269.376 371.715>
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
887 315 m 0
|
||||
887 216 821 127 732 61 c 1
|
||||
766 -50 l 1
|
||||
645 17 l 1
|
||||
600 6 556 -6 512 -6 c 0
|
||||
302 -6 137 138 137 315 c 0
|
||||
137 492 302 635 512 635 c 0
|
||||
711 635 887 492 887 315 c 0
|
||||
674 282 m 1
|
||||
674 361 l 2
|
||||
674 369 666 376 656 376 c 2
|
||||
549 376 l 1
|
||||
549 483 l 2
|
||||
549 493 543 501 535 501 c 2
|
||||
455 501 l 2
|
||||
447 501 440 493 440 483 c 2
|
||||
440 376 l 1
|
||||
333 376 l 2
|
||||
323 376 316 369 316 361 c 2
|
||||
316 282 l 2
|
||||
316 274 323 267 333 267 c 2
|
||||
440 267 l 1
|
||||
440 160 l 2
|
||||
440 150 447 142 455 142 c 2
|
||||
535 142 l 2
|
||||
543 142 549 150 549 160 c 2
|
||||
549 267 l 1
|
||||
656 267 l 2
|
||||
666 267 674 274 674 282 c 1
|
||||
EndSplineSet
|
||||
Validated: 1
|
||||
EndChar
|
||||
|
||||
StartChar: two
|
||||
Encoding: 50 50 2
|
||||
Width: 1024
|
||||
VWidth: 0
|
||||
HStem: -12 242<523.008 709.336> 89 500<229.64 345> 307 234<523.008 816.383>
|
||||
VStem: 38 266<252.887 377.623> 346 138<235.474 301.49> 570 58<236.004 301.036> 714 59<236.004 301.036> 859 135<236.561 300.551>
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
304 272 m 4x5f
|
||||
304 205 329 141 372 91 c 5
|
||||
359 89 345 89 331 89 c 4
|
||||
297 89 263 97 228 106 c 5
|
||||
133 55 l 5
|
||||
159 141 l 5
|
||||
90 193 38 261 38 339 c 4
|
||||
38 477 176 589 331 589 c 4
|
||||
394 589 453 572 500 544 c 5
|
||||
384 494 304 392 304 272 c 4x5f
|
||||
994 265 m 4
|
||||
994 179 937 102 860 45 c 5
|
||||
889 -50 l 5
|
||||
785 7 l 5
|
||||
747 -2 708 -12 670 -12 c 4
|
||||
489 -12 346 113 346 265 c 4
|
||||
346 417 489 541 670 541 c 4xbf
|
||||
841 541 994 417 994 265 c 4
|
||||
522 230 m 4
|
||||
551 230 570 249 570 269 c 4
|
||||
570 288 551 307 522 307 c 4
|
||||
503 307 484 288 484 269 c 4
|
||||
484 249 503 230 522 230 c 4
|
||||
666 230 m 4
|
||||
695 230 714 249 714 269 c 4
|
||||
714 288 695 307 666 307 c 4
|
||||
647 307 628 288 628 269 c 4
|
||||
628 249 647 230 666 230 c 4
|
||||
811 230 m 4
|
||||
840 230 859 249 859 269 c 4
|
||||
859 288 840 307 811 307 c 4
|
||||
792 307 773 288 773 269 c 4
|
||||
773 249 792 230 811 230 c 4
|
||||
EndSplineSet
|
||||
Validated: 1
|
||||
EndChar
|
||||
|
||||
StartChar: three
|
||||
Encoding: 51 51 3
|
||||
Width: 1022
|
||||
VWidth: 0
|
||||
Flags: MO
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
992 263 m 0
|
||||
992 178 935 103 859 46 c 1
|
||||
888 -49 l 1
|
||||
784 8 l 1
|
||||
746 -1 708 -11 670 -11 c 0
|
||||
490 -11 348 111 348 263 c 0
|
||||
348 415 490 538 670 538 c 0
|
||||
840 538 992 415 992 263 c 0
|
||||
795 176 m 2
|
||||
800 181 799 189 793 195 c 2
|
||||
728 260 l 1
|
||||
793 325 l 2
|
||||
799 331 800 340 795 345 c 2
|
||||
747 393 l 2
|
||||
742 398 733 397 727 391 c 2
|
||||
662 326 l 1
|
||||
598 391 l 2
|
||||
592 397 583 398 578 393 c 2
|
||||
530 345 l 2
|
||||
525 340 525 331 531 325 c 2
|
||||
596 260 l 1
|
||||
531 195 l 2
|
||||
525 189 525 181 530 176 c 2
|
||||
578 127 l 2
|
||||
583 122 592 123 598 129 c 2
|
||||
662 194 l 1
|
||||
727 129 l 2
|
||||
733 123 742 122 747 127 c 2
|
||||
795 176 l 2
|
||||
302 273 m 0
|
||||
302 206 327 144 370 94 c 1
|
||||
357 92 344 92 330 92 c 0
|
||||
296 92 260 101 226 109 c 1
|
||||
132 58 l 1
|
||||
158 144 l 1
|
||||
89 195 38 264 38 341 c 0
|
||||
38 478 176 589 330 589 c 0
|
||||
393 589 450 573 497 545 c 1
|
||||
382 496 302 392 302 273 c 0
|
||||
EndSplineSet
|
||||
Validated: 1
|
||||
EndChar
|
||||
|
||||
StartChar: C
|
||||
Encoding: 67 67 4
|
||||
Width: 1080
|
||||
VWidth: 0
|
||||
Flags: HW
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
641 -116 m 4x9b20
|
||||
637 -116 633 -115 630 -113 c 4
|
||||
624 -110 616 -102 616 -88 c 4
|
||||
616 -84 617 -80 618 -75 c 4
|
||||
621 -65 626 -52 632 -40 c 4
|
||||
640 -23 648 -4 651 13 c 4
|
||||
651 14 651 16 651 17 c 4
|
||||
651 31 641 52 624 75 c 4
|
||||
609 94 595 108 586 115 c 5
|
||||
562 107 523 97 500 97 c 6
|
||||
499 97 l 6xdb20
|
||||
479 97 468 105 460 111 c 4
|
||||
457 114 455 115 453 116 c 4
|
||||
450 116 438 112 423 104 c 4
|
||||
412 98 405 94 401 90 c 5
|
||||
411 80 436 61 448 52 c 4
|
||||
460 43 468 37 473 32 c 4
|
||||
485 20 488 -4 488 -20 c 4
|
||||
488 -27 487 -33 487 -36 c 4
|
||||
485 -55 477 -90 452 -105 c 4
|
||||
444 -110 435 -112 426 -112 c 4
|
||||
388 -112 356 -68 351 -51 c 4
|
||||
347 -39 346 -18 345 -6 c 5
|
||||
338 0 325 9 313 21 c 4
|
||||
284 48 272 68 272 85 c 4
|
||||
272 86 272 86 272 87 c 4
|
||||
274 119 301 173 354 215 c 4
|
||||
367 225 387 232 404 236 c 5
|
||||
397 242 388 247 377 249 c 4
|
||||
375 249 373 250 372 250 c 4
|
||||
366 250 361 247 356 244 c 4
|
||||
354 243 352 241 350 240 c 4
|
||||
321 226 296 206 277 189 c 4
|
||||
260 174 248 164 236 161 c 4
|
||||
234 161 232 161 230 161 c 4
|
||||
221 161 206 165 168 192 c 4
|
||||
159 198 151 204 145 209 c 5
|
||||
127 190 l 5
|
||||
126 189 l 6
|
||||
123 187 109 178 93 178 c 4
|
||||
79 178 67 185 61 198 c 4
|
||||
54 213 53 244 53 268 c 4
|
||||
53 281 53 292 53 296 c 6
|
||||
53 298 l 5
|
||||
54 299 l 6
|
||||
55 305 61 333 82 345 c 4
|
||||
86 347 89 348 94 348 c 4
|
||||
104 348 120 344 172 314 c 4
|
||||
189 304 205 294 215 288 c 5
|
||||
309 342 l 5
|
||||
312 366 l 5
|
||||
304 373 290 386 276 400 c 4
|
||||
244 432 226 456 221 475 c 4
|
||||
219 484 218 493 218 504 c 4
|
||||
218 526 222 550 231 575 c 4
|
||||
237 593 255 636 284 651 c 4
|
||||
293 656 305 658 320 658 c 4xb9a0
|
||||
342 658 367 653 389 644 c 4
|
||||
416 633 437 617 452 597 c 4
|
||||
472 571 476 537 476 508 c 4
|
||||
476 497 476 487 475 479 c 5
|
||||
482 479 l 5
|
||||
505 499 l 5
|
||||
543 519 l 5
|
||||
538 529 l 5
|
||||
495 537 l 5
|
||||
492 547 l 6
|
||||
492 548 483 571 477 598 c 4
|
||||
474 610 472 624 472 637 c 4
|
||||
472 655 476 671 490 680 c 4
|
||||
498 685 508 687 520 687 c 4
|
||||
554 687 597 666 602 664 c 6
|
||||
607 661 l 5
|
||||
644 593 l 6
|
||||
649 589 659 581 669 571 c 4
|
||||
688 552 697 537 697 523 c 4
|
||||
697 520 697 517 696 515 c 4
|
||||
692 499 674 481 643 452 c 4
|
||||
623 433 601 412 593 399 c 4
|
||||
591 396 590 392 590 389 c 4
|
||||
590 376 604 363 614 355 c 5
|
||||
625 367 641 385 651 397 c 4
|
||||
661 409 680 426 700 441 c 4
|
||||
727 461 749 472 765 473 c 4
|
||||
766 473 767 473 768 473 c 4
|
||||
787 473 808 460 831 446 c 4
|
||||
845 437 865 424 873 424 c 4
|
||||
874 424 l 6x9d60
|
||||
875 424 880 425 884 426 c 4
|
||||
897 429 915 433 930 433 c 4
|
||||
956 433 965 421 969 412 c 4
|
||||
971 408 972 402 972 397 c 4
|
||||
972 381 965 362 959 347 c 4
|
||||
954 334 939 303 921 292 c 4
|
||||
916 289 911 288 905 288 c 4
|
||||
888 288 863 299 835 311 c 4
|
||||
810 322 780 335 766 335 c 4
|
||||
765 335 764 335 764 335 c 4
|
||||
763 335 757 331 748 315 c 4
|
||||
741 301 734 283 727 264 c 4
|
||||
721 248 715 231 708 216 c 5
|
||||
738 185 756 132 756 80 c 4
|
||||
756 69 755 58 753 47 c 4
|
||||
748 19 727 -22 708 -51 c 4
|
||||
697 -68 687 -82 677 -93 c 4
|
||||
663 -109 652 -116 641 -116 c 4x9b20
|
||||
308 85 m 5
|
||||
308 83 312 72 340 46 c 4
|
||||
357 30 374 18 374 18 c 6
|
||||
380 13 l 5
|
||||
381 4 l 6
|
||||
382 -9 383 -32 386 -40 c 4
|
||||
387 -42 393 -53 402 -62 c 4
|
||||
411 -71 419 -76 426 -76 c 4
|
||||
429 -76 430 -75 433 -73 c 4
|
||||
439 -69 445 -60 448 -46 c 4
|
||||
450 -37 451 -28 451 -20 c 4
|
||||
451 -7 449 3 447 6 c 4
|
||||
443 9 435 17 426 23 c 4
|
||||
383 55 362 72 362 90 c 4
|
||||
362 92 362 93 362 95 c 4
|
||||
363 100 366 113 402 134 c 4
|
||||
410 138 435 153 453 153 c 4xb9a0
|
||||
455 153 458 152 460 152 c 4
|
||||
470 150 476 145 482 141 c 4
|
||||
488 136 491 133 499 133 c 6
|
||||
500 133 l 6xd920
|
||||
521 133 562 145 581 153 c 6
|
||||
589 156 l 5
|
||||
596 153 l 6xb920
|
||||
612 146 637 119 654 97 c 4
|
||||
670 75 689 44 689 16 c 4
|
||||
689 13 689 10 688 7 c 4
|
||||
686 -7 681 -21 675 -35 c 5
|
||||
677 -32 679 -28 681 -25 c 4
|
||||
700 6 713 36 716 53 c 4
|
||||
717 63 718 73 718 83 c 4
|
||||
718 101 716 119 711 136 c 4
|
||||
703 163 690 185 674 198 c 6
|
||||
663 207 l 5
|
||||
670 221 l 6
|
||||
678 237 686 257 693 277 c 4
|
||||
710 322 724 361 753 370 c 4
|
||||
757 371 761 372 766 372 c 4
|
||||
788 372 818 359 850 345 c 4
|
||||
868 337 893 326 903 325 c 5
|
||||
907 329 916 340 924 359 c 4
|
||||
932 377 934 390 934 396 c 4
|
||||
933 396 932 396 930 396 c 4xdb20
|
||||
919 396 902 392 892 390 c 4
|
||||
886 389 881 388 878 388 c 4
|
||||
876 388 875 387 873 387 c 4
|
||||
855 387 833 400 811 414 c 4
|
||||
797 423 776 437 768 437 c 4
|
||||
762 436 745 429 722 412 c 4
|
||||
703 398 687 381 680 373 c 4
|
||||
664 353 631 319 630 318 c 6
|
||||
620 307 l 5
|
||||
607 314 l 6
|
||||
606 314 589 325 574 341 c 4
|
||||
560 357 553 373 553 389 c 4
|
||||
553 399 556 408 561 417 c 4
|
||||
571 435 595 458 618 479 c 4
|
||||
633 493 656 515 660 523 c 4
|
||||
659 525 656 533 640 548 c 4
|
||||
629 559 619 566 619 566 c 6
|
||||
615 569 l 5
|
||||
580 633 l 5
|
||||
566 639 539 650 520 650 c 4x9d60
|
||||
514 650 512 650 511 649 c 4
|
||||
510 648 509 644 509 637 c 4
|
||||
509 629 510 618 514 602 c 4
|
||||
517 589 520 577 523 569 c 5
|
||||
562 561 l 5
|
||||
593 505 l 5
|
||||
526 468 l 5
|
||||
495 442 l 5
|
||||
431 443 l 5
|
||||
435 465 l 6
|
||||
437 474 439 491 439 510 c 4
|
||||
439 533 436 558 423 575 c 4
|
||||
400 605 354 621 320 621 c 4x99a0
|
||||
310 621 304 619 301 618 c 4
|
||||
281 608 255 548 255 504 c 4
|
||||
255 497 255 490 257 484 c 4
|
||||
262 463 311 415 344 388 c 6
|
||||
352 381 l 5
|
||||
342 318 l 5
|
||||
214 245 l 5
|
||||
205 251 l 6
|
||||
205 251 181 266 155 281 c 4
|
||||
119 302 104 308 98 310 c 5
|
||||
95 306 91 298 90 293 c 4
|
||||
90 286 90 279 90 272 c 4
|
||||
90 248 91 224 94 215 c 4
|
||||
97 216 100 217 103 219 c 6
|
||||
142 260 l 5
|
||||
155 249 l 6
|
||||
185 224 218 201 229 198 c 4
|
||||
234 201 245 210 253 217 c 4
|
||||
273 234 300 258 334 274 c 4
|
||||
335 274 336 275 338 276 c 4
|
||||
345 280 356 287 372 287 c 4
|
||||
376 287 380 286 384 285 c 4
|
||||
431 276 455 235 456 233 c 6
|
||||
469 209 l 5
|
||||
442 206 l 6
|
||||
424 204 390 196 377 186 c 4
|
||||
330 149 310 105 308 85 c 5
|
||||
EndSplineSet
|
||||
EndChar
|
||||
|
||||
StartChar: I
|
||||
Encoding: 73 73 5
|
||||
Width: 1024
|
||||
VWidth: 0
|
||||
Flags: HW
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
51 -154 m 1
|
||||
51 768 l 1
|
||||
973 768 l 1
|
||||
973 -154 l 1
|
||||
51 -154 l 1
|
||||
497 469 m 2
|
||||
502 469 507 470 512 470 c 0
|
||||
517 470 522 469 527 469 c 1
|
||||
527 521 l 1
|
||||
610 604 l 1
|
||||
512 702 l 1
|
||||
414 604 l 1
|
||||
497 521 l 1
|
||||
497 469 l 1
|
||||
497 469 l 2
|
||||
653 417 m 1
|
||||
681 445 l 1
|
||||
798 445 l 1
|
||||
798 584 l 1
|
||||
659 584 l 1
|
||||
659 467 l 1
|
||||
633 440 l 1
|
||||
643 433 649 425 653 417 c 1
|
||||
366 282 m 1
|
||||
366 313 l 1
|
||||
288 313 l 1
|
||||
206 396 l 1
|
||||
108 298 l 1
|
||||
206 200 l 1
|
||||
288 282 l 1
|
||||
366 282 l 1
|
||||
343 445 m 1
|
||||
371 417 l 1
|
||||
375 425 381 433 391 440 c 1
|
||||
365 467 l 1
|
||||
365 584 l 1
|
||||
226 584 l 1
|
||||
226 445 l 1
|
||||
343 445 l 1
|
||||
371 178 m 1
|
||||
343 151 l 1
|
||||
226 151 l 1
|
||||
226 12 l 1
|
||||
365 12 l 1
|
||||
365 129 l 1
|
||||
391 155 l 1
|
||||
381 162 375 170 371 178 c 1
|
||||
818 396 m 1
|
||||
735 313 l 1
|
||||
658 313 l 1
|
||||
658 282 l 1
|
||||
735 282 l 1
|
||||
818 200 l 1
|
||||
916 298 l 1
|
||||
818 396 l 1
|
||||
653 178 m 1
|
||||
649 170 643 162 633 155 c 1
|
||||
659 129 l 1
|
||||
659 12 l 1
|
||||
798 12 l 1
|
||||
798 151 l 1
|
||||
681 151 l 1
|
||||
653 178 l 1
|
||||
527 126 m 1
|
||||
522 126 517 126 512 126 c 0
|
||||
507 126 502 126 497 126 c 1
|
||||
497 74 l 1
|
||||
414 -8 l 1
|
||||
512 -106 l 1
|
||||
610 -8 l 1
|
||||
527 74 l 1
|
||||
527 126 l 1
|
||||
527 126 l 1
|
||||
610 348 m 0
|
||||
584 337 549 330 512 330 c 0
|
||||
475 330 441 337 414 348 c 0
|
||||
408 351 402 353 397 356 c 1
|
||||
397 192 l 2
|
||||
397 184 408 174 426 167 c 0
|
||||
449 157 479 152 512 152 c 0
|
||||
545 152 575 157 598 167 c 0
|
||||
616 174 627 184 627 192 c 2
|
||||
627 356 l 1
|
||||
622 353 616 351 610 348 c 0
|
||||
512 443 m 1
|
||||
479 443 449 438 426 428 c 0
|
||||
408 421 397 410 397 402 c 0
|
||||
397 394 408 384 426 377 c 0
|
||||
449 367 479 362 512 362 c 0
|
||||
545 362 575 367 598 377 c 0
|
||||
616 384 627 394 627 402 c 0
|
||||
627 410 616 421 598 428 c 0
|
||||
575 438 545 443 512 443 c 1
|
||||
512 443 l 1
|
||||
EndSplineSet
|
||||
Validated: 5
|
||||
EndChar
|
||||
|
||||
StartChar: four
|
||||
Encoding: 52 52 6
|
||||
Width: 1024
|
||||
VWidth: 0
|
||||
Flags: H
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
801 -67 m 1
|
||||
643 19 l 1
|
||||
601 9 557 -2 513 -2 c 0
|
||||
407 -2 307 33 233 97 c 0
|
||||
197 128 168 165 148 206 c 0
|
||||
127 248 117 293 117 339 c 0
|
||||
117 385 127 431 148 473 c 0
|
||||
168 514 197 550 233 581 c 0
|
||||
307 645 407 680 513 680 c 0
|
||||
564 680 614 672 662 654 c 0
|
||||
709 637 751 612 788 581 c 0
|
||||
825 550 855 513 876 473 c 0
|
||||
898 430 909 385 909 339 c 0
|
||||
909 288 892 238 861 189 c 0
|
||||
835 149 801 111 757 77 c 1
|
||||
801 -67 l 1
|
||||
801 -67 l 1
|
||||
649 63 m 1
|
||||
733 17 l 1
|
||||
710 93 l 1
|
||||
721 102 l 2
|
||||
816 173 868 256 868 339 c 0
|
||||
868 502 705 639 513 639 c 0
|
||||
317 639 158 504 158 339 c 0
|
||||
158 174 317 39 513 39 c 0
|
||||
552 39 592 49 634 59 c 2
|
||||
649 63 l 1
|
||||
649 63 l 1
|
||||
675 306 m 1
|
||||
675 298 667 292 657 292 c 2
|
||||
550 292 l 1
|
||||
550 185 l 2
|
||||
550 175 543 167 535 167 c 2
|
||||
456 167 l 2
|
||||
448 167 441 175 441 185 c 2
|
||||
441 292 l 1
|
||||
334 292 l 2
|
||||
324 292 316 298 316 306 c 2
|
||||
316 386 l 2
|
||||
316 394 324 401 334 401 c 2
|
||||
441 401 l 1
|
||||
441 508 l 2
|
||||
441 518 448 525 456 525 c 2
|
||||
535 525 l 2
|
||||
543 525 550 518 550 508 c 2
|
||||
550 401 l 1
|
||||
657 401 l 2
|
||||
667 401 675 394 675 386 c 2
|
||||
675 306 l 1
|
||||
675 306 l 1
|
||||
EndSplineSet
|
||||
Validated: 5
|
||||
EndChar
|
||||
|
||||
StartChar: D
|
||||
Encoding: 68 68 7
|
||||
Width: 1080
|
||||
VWidth: 0
|
||||
Flags: HW
|
||||
LayerCount: 3
|
||||
Fore
|
||||
SplineSet
|
||||
469 -14 m 4
|
||||
469 -45 459 -89 425 -89 c 4
|
||||
397 -89 372 -53 369 -41 c 4
|
||||
365 -27 363 7 363 7 c 5
|
||||
363 7 292 63 292 88 c 4
|
||||
292 113 317 164 366 202 c 4
|
||||
388 219 439 225 439 225 c 5
|
||||
439 225 414 269 373 269 c 4
|
||||
359 269 350 261 343 258 c 4
|
||||
289 232 252 185 234 181 c 4
|
||||
233 181 233 181 232 181 c 4
|
||||
212 181 147 235 147 235 c 5
|
||||
119 206 l 5
|
||||
119 206 108 198 97 198 c 4
|
||||
78 198 76 234 76 261 c 4
|
||||
76 268 76 274 76 279 c 4
|
||||
76 288 76 295 76 295 c 6
|
||||
76 295 82 320 96 328 c 4
|
||||
97 328 98 329 99 329 c 4
|
||||
121 329 217 268 217 268 c 6
|
||||
327 330 l 5
|
||||
333 372 l 5
|
||||
333 372 250 441 241 477 c 4
|
||||
239 484 238 493 238 501 c 4
|
||||
238 550 266 615 294 630 c 4
|
||||
300 633 310 635 321 635 c 4
|
||||
356 635 409 619 437 582 c 4
|
||||
453 560 457 530 457 505 c 4
|
||||
457 479 453 459 453 459 c 5
|
||||
488 459 l 5
|
||||
514 481 l 5
|
||||
566 509 l 5
|
||||
548 542 l 5
|
||||
507 550 l 5
|
||||
507 550 489 598 489 631 c 4
|
||||
489 653 498 664 519 664 c 4
|
||||
549 664 591 642 591 642 c 5
|
||||
627 577 l 5
|
||||
627 577 674 541 674 519 c 4
|
||||
674 495 592 439 574 407 c 4
|
||||
570 401 569 394 569 388 c 4
|
||||
569 355 614 330 614 330 c 5
|
||||
614 330 646 364 662 384 c 4
|
||||
678 404 734 453 763 453 c 4
|
||||
790 453 841 404 867 404 c 4
|
||||
878 404 902 413 924 413 c 4
|
||||
938 413 945 409 945 396 c 4
|
||||
945 371 922 318 905 307 c 4
|
||||
903 306 902 306 899 306 c 4
|
||||
872 306 795 353 761 353 c 4
|
||||
758 353 756 353 754 352 c 4
|
||||
723 342 708 263 682 213 c 5
|
||||
713 187 733 135 733 85 c 4
|
||||
733 75 732 64 730 54 c 4
|
||||
722 9 658 -92 638 -92 c 4
|
||||
633 -92 632 -87 632 -83 c 4
|
||||
632 -64 660 -22 666 14 c 4
|
||||
666 16 667 19 667 21 c 4
|
||||
667 64 605 129 586 138 c 5
|
||||
565 130 522 118 499 118 c 4
|
||||
475 118 470 137 453 137 c 4
|
||||
437 137 380 108 380 93 c 4
|
||||
380 77 446 36 460 23 c 4
|
||||
465 18 469 3 469 -14 c 4
|
||||
EndSplineSet
|
||||
Validated: 1
|
||||
EndChar
|
||||
EndChars
|
||||
EndSplineFont
|
||||
BIN
css/font-combodo/combodo.ttf
Normal file
7
css/font-combodo/exclude.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# The following source files are not re-distributed with the "build" of the application
|
||||
# since they are used solely for constructing other files during the build process
|
||||
#
|
||||
glyphs
|
||||
combodo.sfd
|
||||
test.html
|
||||
199
css/font-combodo/font-combodo.css
Normal file
@@ -0,0 +1,199 @@
|
||||
@font-face {
|
||||
font-family: 'CombodoRegular';
|
||||
src: url('combodo-webfont.woff2?v=1.0') format('woff2'),
|
||||
url('combodo-webfont.woff?v=1.0') format('woff'),
|
||||
url('combodo-webfont.ttf?v=1.0') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
}
|
||||
|
||||
.fc {
|
||||
display: inline-block;
|
||||
font: normal normal normal 14px/1 CombodoRegular;
|
||||
font-size: inherit;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
/* makes the font 33% larger relative to the icon container */
|
||||
.fc-lg {
|
||||
font-size: 1.33333333em;
|
||||
line-height: 0.75em;
|
||||
vertical-align: -15%;
|
||||
}
|
||||
.fc-1-5x {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.fc-1-6x {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
.fc-2x {
|
||||
font-size: 2em;
|
||||
}
|
||||
.fc-3x {
|
||||
font-size: 3em;
|
||||
}
|
||||
.fc-4x {
|
||||
font-size: 4em;
|
||||
}
|
||||
.fc-5x {
|
||||
font-size: 5em;
|
||||
}
|
||||
.fc-border {
|
||||
padding: .2em .25em .15em;
|
||||
border: solid 0.08em #eeeeee;
|
||||
border-radius: .1em;
|
||||
}
|
||||
.fc-ul {
|
||||
padding-left: 0;
|
||||
margin-left: 2.2em;
|
||||
list-style-type: none;
|
||||
}
|
||||
.fc-ul > li {
|
||||
position: relative;
|
||||
}
|
||||
.fc-li {
|
||||
position: absolute;
|
||||
left: -2.2em;
|
||||
width: 2.2em;
|
||||
top: 0.15em;
|
||||
text-align: center;
|
||||
}
|
||||
.fc-li.fa-lg {
|
||||
left: -1.9em;
|
||||
}
|
||||
.fc-pull-left {
|
||||
float: left;
|
||||
}
|
||||
.fc-pull-right {
|
||||
float: right;
|
||||
}
|
||||
.fc.fc-pull-left {
|
||||
margin-right: .3em;
|
||||
}
|
||||
.fc.fa-pull-right {
|
||||
margin-left: .3em;
|
||||
}
|
||||
.fc-fw {
|
||||
width: 1.3em;
|
||||
text-align: center;
|
||||
}
|
||||
.fc-rotate-90 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
|
||||
-webkit-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.fc-rotate-180 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
|
||||
-webkit-transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.fc-rotate-270 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
|
||||
-webkit-transform: rotate(270deg);
|
||||
-ms-transform: rotate(270deg);
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
.fc-flip-horizontal {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
.fc-flip-vertical {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
|
||||
-webkit-transform: scale(1, -1);
|
||||
-ms-transform: scale(1, -1);
|
||||
transform: scale(1, -1);
|
||||
}
|
||||
.fc-spin {
|
||||
-webkit-animation: fc-spin 2s infinite linear;
|
||||
animation: fc-spin 2s infinite linear;
|
||||
}
|
||||
.fc-pulse {
|
||||
-webkit-animation: fc-spin 1s infinite steps(8);
|
||||
animation: fc-spin 1s infinite steps(8);
|
||||
}
|
||||
.fc-rotate {
|
||||
-webkit-animation: fc-rotate 2s infinite linear;
|
||||
animation: fc-rotate 2s infinite linear;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fc-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
@keyframes fc-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes fc-rotate {
|
||||
0% {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=0)";
|
||||
-webkit-transform: scale(1, 1);
|
||||
-ms-transform: scale(1, 1);
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
100% {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
@keyframes fc-rotate {
|
||||
0% {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=0)";
|
||||
-webkit-transform: scale(1, 1);
|
||||
-ms-transform: scale(1, 1);
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
100% {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* icons */
|
||||
.fc-life-saver:before {
|
||||
content: "0";
|
||||
}
|
||||
.fc-new-request:before {
|
||||
content: "1";
|
||||
}
|
||||
.fc-new-request-o:before {
|
||||
content: "4";
|
||||
}
|
||||
.fc-ongoing-request:before {
|
||||
content: "2";
|
||||
}
|
||||
.fc-closed-request:before {
|
||||
content: "3";
|
||||
}
|
||||
.fc-combodo-icon-o:before {
|
||||
content: "C";
|
||||
}
|
||||
.fc-combodo-icon:before {
|
||||
content: "D";
|
||||
}
|
||||
.fc-itop-icon:before {
|
||||
content: "I";
|
||||
}
|
||||
|
||||
26
css/font-combodo/glyphs/0.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<svg version="1.1" baseProfile="basic" id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px"
|
||||
viewBox="0 0 1000 1000" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#231F20" d="M358.179,112.114c45.101-19.253,92.379-28.928,141.835-28.928c49.429,0,96.705,9.675,141.757,28.928
|
||||
c45.031,19.253,83.89,45.208,116.459,77.777c32.56,32.559,58.514,71.398,77.855,116.548
|
||||
c19.174,45.041,28.928,92.319,28.928,141.736s-9.754,96.705-28.928,141.737c-19.342,45.129-45.296,83.979-77.855,116.547
|
||||
c-32.569,32.559-71.418,58.514-116.459,77.776c-45.052,19.254-92.328,28.929-141.757,28.929c-49.456,0-96.735-9.675-141.835-28.929
|
||||
c-45.031-19.263-83.88-45.218-116.498-77.776c-32.559-32.559-58.465-71.408-77.767-116.547
|
||||
c-19.302-45.032-28.928-92.32-28.928-141.737s9.626-96.696,28.928-141.736c19.302-45.14,45.208-83.979,77.767-116.548
|
||||
C274.309,157.322,313.149,131.366,358.179,112.114z M223.803,595.22l79.013-79.014c-7.605-22.236-11.412-44.962-11.412-68.022
|
||||
s3.807-45.796,11.412-68.032l-79.013-79.004c-24.435,46.435-36.691,95.46-36.691,147.036S199.368,548.776,223.803,595.22z
|
||||
M389.395,558.774c30.537,30.578,67.404,45.866,110.63,45.866c43.146,0,80.014-15.288,110.591-45.866
|
||||
c30.499-30.497,45.797-67.364,45.797-110.59c0-43.235-15.288-80.103-45.797-110.67c-30.577-30.498-67.444-45.796-110.591-45.796
|
||||
c-43.226,0-80.093,15.279-110.63,45.796c-30.538,30.567-45.836,67.435-45.836,110.67
|
||||
C343.558,491.41,358.857,528.267,389.395,558.774z M500.015,135.35c-51.616,0-100.621,12.227-147.114,36.681l79.042,79.013
|
||||
c22.247-7.595,44.972-11.402,68.063-11.402c22.981,0,45.786,3.808,67.944,11.402l79.092-79.013
|
||||
C600.519,147.577,551.592,135.35,500.015,135.35z M500.015,761.018c51.577,0,100.504-12.228,147.036-36.69l-79.092-79.004
|
||||
c-22.158,7.605-44.963,11.403-67.944,11.403c-23.1,0-45.826-3.798-68.052-11.403l-79.052,79.004
|
||||
C399.404,748.791,448.409,761.018,500.015,761.018z M697.155,516.197l79.014,79.014c24.385-46.444,36.691-95.46,36.691-147.036
|
||||
s-12.316-100.602-36.691-147.036l-79.014,79.004c7.605,22.236,11.413,44.972,11.413,68.032S704.75,493.961,697.155,516.197z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
13
css/font-combodo/glyphs/1.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<svg version="1.1" baseProfile="basic" id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px"
|
||||
viewBox="0 0 1000 1000" xml:space="preserve">
|
||||
<path fill="#231F20" d="M866.498,492.629c0-172.569-172.61-312.947-366.54-312.947c-205.265,0-366.456,140.378-366.456,312.947
|
||||
c0,172.511,161.191,312.948,366.456,312.948c42.846,0,86.248-11.319,129.707-22.049l118.354,64.845L715.315,740.69
|
||||
C802.134,675.854,866.498,589.597,866.498,492.629z M658.162,524.678c0,7.801-7.768,14.133-17.22,14.133H536.297v104.612
|
||||
c0,9.542-6.373,17.252-14.182,17.252h-77.89c-7.851,0-14.183-7.71-14.183-17.252V538.811H325.448c-9.501,0-17.22-6.332-17.22-14.133
|
||||
V446.78c0-7.892,7.719-14.224,17.22-14.224h104.596V327.953c0-9.452,6.332-17.178,14.183-17.178h77.89
|
||||
c7.809,0,14.182,7.726,14.182,17.178v104.603h104.646c9.452,0,17.22,6.332,17.22,14.224V524.678L658.162,524.678z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
18
css/font-combodo/glyphs/2.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<svg version="1.1" baseProfile="basic" id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px"
|
||||
viewBox="0 0 1000 1000" xml:space="preserve">
|
||||
<path fill="#231F20" d="M296.416,534.64c0-116.722,78.171-217.884,191.547-266.479c-46.256-27.633-102.769-43.899-164.408-43.899
|
||||
c-151.479,0-286.188,109.67-286.188,244.378c0,75.69,50.255,143.041,118.02,193.622l-25.533,84.137l92.469-50.59
|
||||
c33.882,8.341,67.755,17.184,101.232,17.184c13.53,0,26.777-0.847,39.761-2.355C321.305,661.336,296.416,600.473,296.416,534.64z"/>
|
||||
<path fill="#231F20" d="M970.632,541.419c0-148.892-148.893-270.098-316.318-270.098c-177.153,0-316.258,121.214-316.258,270.098
|
||||
c0,148.884,139.114,270.089,316.258,270.089c37.007,0,74.445-9.779,111.903-18.993l102.212,55.895l-28.234-92.972
|
||||
C915.1,699.525,970.632,625.08,970.632,541.419z M509.305,574.939c-18.552,0-37.087-18.543-37.087-37.589
|
||||
c0-18.632,18.535-37.166,37.087-37.166c28.375,0,46.953,18.544,46.953,37.166C556.258,556.405,537.68,574.939,509.305,574.939z
|
||||
M650.668,574.983c-18.533,0-37.077-18.631-37.077-37.677c0-18.588,18.544-37.166,37.077-37.166
|
||||
c28.366,0,47.007,18.588,47.007,37.166C697.675,556.361,679.026,574.983,650.668,574.983z M792.182,574.939
|
||||
c-18.649,0-37.175-18.543-37.175-37.589c0-18.632,18.535-37.166,37.175-37.166c27.846,0,46.865,18.544,46.865,37.166
|
||||
C839.038,556.405,820.036,574.939,792.182,574.939z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
17
css/font-combodo/glyphs/3.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<svg version="1.1" baseProfile="basic" id="Layer_2"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px"
|
||||
viewBox="0 0 1000 1000" xml:space="preserve">
|
||||
<path fill="#231F20" d="M968.382,542.585c0-147.978-147.97-268.432-314.358-268.432c-176.063,0-314.305,120.454-314.305,268.432
|
||||
C339.719,690.537,477.978,811,654.024,811c36.769,0,73.984-9.719,111.201-18.885l101.578,55.557l-28.05-92.404
|
||||
C913.185,699.703,968.382,625.726,968.382,542.585z M776.583,628.103l-47.11,47.215c-4.771,4.764-13.385,3.939-19.068-1.842
|
||||
l-63.441-63.433l-63.433,63.433c-5.78,5.693-14.306,6.605-19.017,1.842l-47.259-47.215c-4.815-4.859-3.981-13.288,1.78-19.068
|
||||
l63.398-63.433l-63.398-63.442c-5.736-5.78-6.552-14.209-1.78-18.98l47.259-47.303c4.711-4.772,13.245-3.851,19.017,1.833
|
||||
l63.433,63.433l63.441-63.433c5.684-5.771,14.297-6.605,19.068-1.833l47.11,47.303c4.771,4.771,3.946,13.209-1.824,18.893
|
||||
l-63.442,63.529l63.442,63.433C780.617,614.815,781.355,623.332,776.583,628.103z"/>
|
||||
<path fill="#231F20" d="M294.81,532.718c0-115.999,77.686-216.533,190.36-264.827c-45.97-27.462-102.131-43.628-163.389-43.628
|
||||
c-150.539,0-284.413,108.99-284.413,242.864c0,75.222,49.943,142.163,117.288,192.422l-25.375,83.623l91.895-50.275
|
||||
c33.672,8.279,67.336,17.077,100.605,17.077c13.446,0,26.612-0.833,39.514-2.342C319.545,658.636,294.81,598.133,294.81,532.718z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
20
css/font-combodo/glyphs/4.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<svg version="1.1" baseProfile="basic" id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px"
|
||||
viewBox="0 0 1000 1000" xml:space="preserve">
|
||||
<path d="M782.403,865.469l-154.508-84.653c-41.386,10.247-84.155,20.761-126.938,20.761c-103.692,0-200.729-34.312-273.236-96.612
|
||||
c-35.467-30.476-63.322-66.095-82.789-105.866c-20.192-41.251-30.43-85.147-30.43-130.469c0-45.334,10.238-89.239,30.43-130.493
|
||||
c19.467-39.773,47.321-75.391,82.79-105.864c72.499-62.287,169.535-96.59,273.235-96.59c49.85,0,98.952,8.694,145.943,25.842
|
||||
c45.657,16.661,87.014,40.417,122.923,70.61c36.458,30.654,65.213,66.204,85.467,105.661
|
||||
c21.372,41.634,32.208,85.653,32.208,130.834c0,49.914-15.553,99.344-46.225,146.918c-24.909,38.634-59.118,75.12-101.793,108.597
|
||||
L782.403,865.469L782.403,865.469z M633.438,738.242l82.198,45.035l-22.539-74.211l11.251-8.401
|
||||
c92.312-68.938,143.15-151.343,143.15-232.036c0-158.793-158.696-292.947-346.541-292.947
|
||||
c-191.036,0-346.455,131.416-346.455,292.947s155.419,292.947,346.455,292.947c38.218,0,77.086-9.624,118.235-19.813
|
||||
L633.438,738.242L633.438,738.242z"/>
|
||||
<path d="M658.965,500.679c0,7.801-7.768,14.133-17.219,14.133H537.102v104.612c0,9.542-6.373,17.252-14.184,17.252h-77.889
|
||||
c-7.852,0-14.184-7.71-14.184-17.252V514.812H326.252c-9.501,0-17.22-6.332-17.22-14.133V422.78c0-7.892,7.719-14.224,17.22-14.224
|
||||
h104.597V303.953c0-9.451,6.331-17.178,14.183-17.178h77.889c7.811,0,14.184,7.727,14.184,17.178v104.604h104.645
|
||||
c9.453,0,17.221,6.332,17.221,14.224v77.898H658.965z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
65
css/font-combodo/glyphs/C.svg
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<svg version="1.1" baseProfile="basic" id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px"
|
||||
viewBox="0 0 1000 1000" xml:space="preserve">
|
||||
<path fill="#010101" d="M626.01,912.824c-3.839,0-7.464-0.894-10.775-2.654c-7.166-3.806-18.222-13.761-11.633-37.6
|
||||
c2.681-9.688,7.889-21.117,13.407-33.22c7.747-16.992,16.526-36.251,19.114-52.397c2.104-13.062-7.717-35.54-26.272-60.147
|
||||
c-14.192-18.821-29.013-33.055-37.457-39.536c-23.266,8.18-60.974,17.937-83.818,18.217l-0.822,0.005
|
||||
c-19.333,0.001-30.751-8.556-38.305-14.218c-3.396-2.546-5.573-4.135-7.489-4.795c-3.329,0.364-13.864,3.459-28.673,11.547
|
||||
c-10.319,5.635-17.318,10.728-21.348,14.225c9.669,10.246,33.908,28.146,45.247,36.52c11.281,8.332,20.191,14.912,25.046,19.598
|
||||
c17.505,16.855,13.923,58.696,13.027,66.911c-2.021,18.544-9.063,51.887-33.495,66.841c-8.156,4.99-16.938,7.518-26.104,7.518
|
||||
c-37.301,0-67.472-43.222-72.642-59.651c-3.555-11.314-5.598-32.092-6.533-44.069c-7.101-5.721-19.086-15.689-31.088-27.095
|
||||
c-29.089-27.642-41.487-47.405-40.193-64.08c2.387-31.286,28.692-83.744,80.798-124.937c12.665-10.002,32.123-16.555,48.9-20.662
|
||||
c-7.086-5.52-16.09-10.549-26.88-12.719c-1.526-0.309-3.006-0.463-4.412-0.463c-5.852,0-10.735,2.765-15.904,5.694
|
||||
c-2.03,1.149-4.128,2.338-6.329,3.376c-28.714,13.508-52.119,33.866-70.925,50.225c-17.052,14.834-28.318,24.632-40.378,27.201
|
||||
c-1.722,0.374-3.601,0.569-5.525,0.57c-0.003,0-0.007,0-0.01,0c-8.992,0-23.197-3.622-60.349-30.442
|
||||
c-8.514-6.148-16.478-12.251-22.737-17.169l-17.281,18.383l-1.544,1.073c-2.684,1.868-16.848,11.184-32.191,11.186
|
||||
c-13.706,0.002-24.83-7.259-30.523-19.921c-10.257-22.775-8.457-83.479-7.993-95.417l0.06-1.485l0.3-1.458
|
||||
c1.125-5.441,7.759-33.159,27.945-44.569c3.507-1.979,7.543-2.978,12.009-2.978c9.292,0,25.593,3.898,76.053,32.807
|
||||
c16.842,9.649,32.412,19.195,41.832,25.067l91.185-51.828l3.857-23.849c-8.177-7.053-21.649-19.009-35.692-32.882
|
||||
c-31.73-31.35-49.341-55.531-53.84-73.927c-6.359-25.939-2.63-61.543,10.235-97.67c6.171-17.33,23.536-59.211,51.421-73.771
|
||||
c8.874-4.639,20.872-6.992,35.652-6.992c21.307,0,44.994,4.871,66.696,13.717c26.152,10.658,47.653,26.479,62.183,45.754
|
||||
c26.064,34.583,25.061,86.052,21.877,114.888l7.091,0.138l22.506-18.698l36.82-20.278l-4.926-9.063l-41.675-8.174l-3.501-9.488
|
||||
c-0.365-0.989-9-24.507-14.485-50.54c-5.625-26.7-10.367-63.969,13.306-79.276c7.723-4.994,17.518-7.524,29.114-7.524
|
||||
c33.263,0,75.393,20.77,80.087,23.14l5.021,2.533l35.934,66.072c4.951,3.875,14.762,11.859,24.305,21.408
|
||||
c21.854,21.867,30.233,39.52,26.377,55.556c-3.766,15.762-21.216,32.516-51.736,60.771c-19.577,18.125-41.766,38.667-49.108,51.768
|
||||
c-9.224,16.444,8.018,33.826,20.604,43.624c10.608-11.457,26.778-29.194,36.398-41.075c9.482-11.754,28.798-29.333,48.047-43.721
|
||||
c25.969-19.412,47.112-29.904,62.843-31.188c1.025-0.084,2.08-0.127,3.137-0.127c18.719,0,39.658,13.263,61.824,27.304
|
||||
c13.215,8.37,33.184,21.019,40.699,21.019c0.027-0.001,0.038-0.001,0.042,0l0.597-0.106l0.711-0.068
|
||||
c1.407-0.136,6.155-1.147,9.97-1.96c12.402-2.642,29.387-6.258,44.346-6.258c25.01,0,34.521,10.99,38.092,20.21
|
||||
c6.593,17.001-1.824,44.056-10.05,63.758c-5.206,12.473-19.181,42.651-36.454,53.481c-4.435,2.77-9.75,4.167-15.825,4.168
|
||||
c-0.002,0-0.004,0-0.006,0c-16.253,0-40.188-10.529-67.902-22.722c-24.064-10.586-54.012-23.76-67.958-23.76
|
||||
c-1.34,0-1.9,0.149-1.906,0.15c-1.445,0.449-6.93,3.197-15.321,19.279c-7.022,13.462-13.506,31.092-20.368,49.755
|
||||
c-5.829,15.854-11.812,32.129-18.596,47.053c34.784,37.289,53.174,105.168,43.337,165.17c-4.443,27.182-25.34,67.129-43.413,95.448
|
||||
c-10.502,16.453-21.042,30.734-30.482,41.297c-13.905,15.563-24.424,22.205-35.168,22.21
|
||||
C626.018,912.824,626.014,912.824,626.01,912.824L626.01,912.824z M301.15,716.984c0.413,1.587,4.053,12.336,31.011,37.584
|
||||
c16.329,15.291,32.524,27.744,32.686,27.867l6.522,5.001l0.497,8.21c0.748,12.439,2.903,35.26,5.503,43.535
|
||||
c0.714,2.162,5.75,11.524,14.752,20.787c8.434,8.68,17.014,13.656,23.539,13.656c2.508,0,4.83-0.708,7.31-2.224
|
||||
c5.863-3.589,11.203-13.631,14.281-26.858c5.466-23.499,2.169-45.649-0.566-50.771c-3.594-3.264-12.379-9.751-20.891-16.037
|
||||
c-46.36-34.235-66.267-51.283-62.343-70.823c0.904-4.503,3.657-18.211,38.713-38.33c7.419-4.258,32.974-18.149,50.34-18.149
|
||||
c2.255,0,4.416,0.221,6.42,0.659c9.861,2.178,16.659,7.272,22.121,11.365c6.042,4.529,9.371,7.023,16.708,7.023l0.384-0.002
|
||||
c20.516-0.251,60.798-11.345,79.517-18.871l7.094-2.854l6.981,3.126c15.828,7.087,40.592,32.653,56.874,54.245
|
||||
c17.734,23.52,37.869,57.801,33.073,87.545c-2.193,13.68-7.158,27.646-12.763,41.006c1.915-2.937,3.882-6.039,5.901-9.31
|
||||
c18.39-29.797,32.021-59.753,34.73-76.313c4.198-25.614,2.213-54.415-5.593-81.104c-7.73-26.43-20.359-47.85-35.567-60.316
|
||||
l-11.302-9.265l6.744-12.967c8.293-15.949,15.626-35.894,22.717-55.181c16.201-44.063,30.193-82.118,58.847-91.011
|
||||
c3.775-1.167,7.992-1.757,12.545-1.757c21.518,0,51.12,13.022,82.458,26.81c17.82,7.838,41.856,18.412,51.791,19.569
|
||||
c3.893-3.911,11.81-14.803,19.825-33.427c7.422-17.241,10.025-29.765,10.46-35.848c-1.111-0.125-2.566-0.218-4.431-0.218
|
||||
c-11.169,0-26.62,3.291-36.847,5.469c-5.727,1.22-9.971,2.123-13.311,2.508c-1.782,0.277-3.632,0.416-5.511,0.416
|
||||
c-17.961,0-38.365-12.925-59.967-26.607c-13.65-8.647-34.28-21.714-42.554-21.714c-0.072,0-0.143,0.001-0.211,0.007
|
||||
c-6.169,0.503-21.451,7.126-44.211,24.139c-18.746,14.012-35.118,29.476-41.599,37.511c-16.048,19.821-47.398,53.004-48.725,54.407
|
||||
l-9.697,10.255l-12.274-6.968c-0.733-0.417-18.14-10.388-32.508-26.236c-21.859-24.11-26.552-50.48-13.216-74.252
|
||||
c10.117-18.052,33.47-39.673,56.055-60.581c14.814-13.717,36.946-34.207,40.932-42.293c-0.587-2.08-3.642-9.708-18.976-24.506
|
||||
c-10.635-10.264-21.05-18.011-21.152-18.089l-3.195-2.368l-34.189-62.867c-13.597-6.252-40.612-16.921-58.942-16.921
|
||||
c-5.382,0-8.05,0.962-9.127,1.507c-1.668,2.911-3.995,15.773,2.813,45.84c2.839,12.529,6.404,24.296,8.938,32.082l38.169,7.485
|
||||
l30.114,55.409l-65.187,35.9l-30.181,25.075l-63.004-1.229l4.712-21.44c4.257-19.601,10.387-77.069-12.131-106.948
|
||||
c-22.336-29.63-67.277-45.133-100.124-45.133c-9.904,0-16.298,1.497-18.979,2.898c-22.114,11.548-53.948,87.747-43.353,130.96
|
||||
c5.065,20.708,52.987,67.619,85.4,94.317l7.977,6.571l-9.886,61.117l-125.008,71.053l-9.157-5.862
|
||||
c-0.226-0.145-22.931-14.664-48.237-29.213c-34.989-20.116-50.335-26.187-56.376-28.019c-3.153,4.022-6.154,11.394-7.477,16.741
|
||||
c-1.009,28.676,0.349,64.309,4.313,76.066c2.499-0.563,5.934-2.098,8.592-3.68l37.485-39.871l13.028,10.832
|
||||
c28.844,23.984,61.46,46.256,72.363,49.584c4.873-2.66,14.944-11.422,23.195-18.599c19.407-16.881,45.987-40.002,79.21-55.631
|
||||
c0.969-0.457,2.408-1.272,3.931-2.135c7.292-4.13,18.311-10.371,33.652-10.371c3.793,0,7.668,0.394,11.522,1.171
|
||||
c45.986,9.249,69.087,49.411,70.049,51.117l13.311,23.62l-26.934,3.106c-17.707,2.063-50.544,9.073-63.231,19.095
|
||||
C322.512,654.276,302.914,697.664,301.15,716.984L301.15,716.984z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
23
css/font-combodo/glyphs/D.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<svg version="1.1" baseProfile="basic" id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px"
|
||||
viewBox="0 0 1000 1000" xml:space="preserve">
|
||||
<path d="M431.95,881.456c-32.777,20.049-67-28.318-71.182-41.602c-4.181-13.314-6.211-47.199-6.211-47.199
|
||||
s-71.674-54.916-69.768-79.485c1.875-24.567,24.261-72.135,72.996-110.647c21.216-16.758,71.336-22.554,71.336-22.554
|
||||
s-19.218-34.085-57.131-41.726c-17.987-3.628-29.149,5.98-37.359,9.854c-53.01,24.938-88.432,71.337-105.897,75.058
|
||||
c-17.035,3.674-85.389-53.164-85.389-53.164l-27.028,28.749c0,0-26.597,18.495-35.515-1.322
|
||||
c-8.917-19.816-6.334-86.126-6.334-86.126s5.012-24.245,18.911-32.101c13.929-7.857,118.903,59.375,118.903,59.375l106.575-60.575
|
||||
l6.764-41.817c0,0-81.545-67.17-90.185-102.5c-11.653-47.537,19.925-132.495,51.78-149.13
|
||||
c24.476-12.791,103.53-1.507,139.875,46.707c32.132,42.633,15.128,119.98,15.128,119.98l33.885,0.661l25.982-21.584l50.305-27.704
|
||||
l-17.28-31.794l-39.512-7.749c0,0-33.454-90.631-8.179-106.974s90.061,16.343,90.061,16.343l34.592,63.603
|
||||
c0,0,51.72,38.344,46.523,59.96c-5.166,21.616-80.007,74.918-97.688,106.466c-22.63,40.341,38.375,74.979,38.375,74.979
|
||||
s31.578-33.393,47.137-52.611c15.527-19.248,70.045-65.155,97.012-67.354c26.966-2.198,81.053,51.734,105.805,47.337
|
||||
c11.593-1.122,65.74-17.833,72.873,0.569c7.134,18.403-20.201,82.543-38.742,94.167c-18.541,11.592-116.968-52.887-147.285-43.509
|
||||
c-30.318,9.409-44.677,86.803-69.952,135.416c37.206,30.486,56.146,99.426,46.86,156.063
|
||||
c-7.503,45.907-75.58,150.269-92.337,141.352c-16.789-8.918,23.061-61.035,29.795-103.039
|
||||
c6.765-41.972-58.668-111.954-78.439-120.81c-20.109,8.087-62.173,19.617-84.835,19.894c-22.63,0.277-27.919-15.281-42.679-18.525
|
||||
c-14.79-3.229-70.045,26.645-72.966,41.188c-2.921,14.544,64.295,57.101,77.548,69.892
|
||||
C462.33,790.227,461.991,863.071,431.95,881.456z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
36
css/font-combodo/glyphs/I.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<svg version="1.1" baseProfile="basic" id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px"
|
||||
viewBox="0 0 1000 1000" xml:space="preserve">
|
||||
<g>
|
||||
<rect x="50.013" y="50.022" fill="#E87D25" width="899.955" height="899.955"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M484.99,341.368c4.958-0.236,9.966-0.368,15.017-0.368c5.04,0,10.036,0.132,14.983,0.366v-50.652
|
||||
l80.77-80.748L499.99,114.17l-95.787,95.796l80.786,80.748V341.368L484.99,341.368z"/>
|
||||
<path fill="#FFFFFF" d="M638.014,392.131l26.871-26.871h114.258V229.796h-135.44v114.223l-25.951,25.95
|
||||
C627.125,376.584,633.957,384.055,638.014,392.131z"/>
|
||||
<polygon fill="#FFFFFF" points="357.698,523.971 357.74,493.971 281.732,493.971 200.96,413.193 105.191,508.971 200.96,604.734
|
||||
281.731,523.971 "/>
|
||||
<path fill="#FFFFFF" d="M335.065,365.26l26.892,26.893c4.055-8.076,10.884-15.552,20.258-22.168l-25.936-25.937V229.796H220.805
|
||||
l0.019,135.464H335.065z"/>
|
||||
<path fill="#FFFFFF" d="M361.941,625.778l-26.918,26.918H220.805l0.019,135.436h135.438l0.016-114.264l25.911-25.91
|
||||
C372.814,641.337,365.993,633.857,361.941,625.778z"/>
|
||||
<polygon fill="#FFFFFF" points="799.004,413.194 718.236,493.971 642.31,493.971 642.335,523.971 718.236,523.971 799.004,604.732
|
||||
894.79,508.971 "/>
|
||||
<path fill="#FFFFFF" d="M638.029,625.797c-4.057,8.08-10.877,15.557-20.252,22.174l25.926,25.926v114.234h135.44V652.696H664.926
|
||||
L638.029,625.797z"/>
|
||||
<path fill="#FFFFFF" d="M514.99,676.575c-4.947,0.233-9.943,0.366-14.983,0.366c-5.051,0-10.059-0.133-15.017-0.366v50.653
|
||||
l-80.786,80.748l95.787,95.795l95.769-95.795l-80.77-80.746V676.575L514.99,676.575z"/>
|
||||
</g>
|
||||
<path fill="#FFFFFF" d="M595.973,460.415c-25.874,11.007-59.951,17.067-95.956,17.067c-36.01,0-70.096-6.062-95.976-17.067
|
||||
c-5.955-2.532-11.368-5.297-16.236-8.259l-0.225,159.741c0,7.579,11.064,17.194,28.189,24.476
|
||||
c22.256,9.461,52.172,14.672,84.237,14.672c32.058,0,61.966-5.211,84.214-14.672c17.119-7.279,28.18-16.896,28.18-24.497
|
||||
l-0.127-159.764C607.39,455.092,601.956,457.87,595.973,460.415z"/>
|
||||
<path fill="#FFFFFF" d="M500.007,367.609c-32.063,0-61.979,5.213-84.235,14.679c-17.126,7.284-28.191,16.901-28.191,24.504
|
||||
c0,7.612,11.065,17.237,28.191,24.521c22.252,9.463,52.167,14.676,84.235,14.676c32.061,0,61.968-5.213,84.212-14.676
|
||||
c17.12-7.281,28.182-16.907,28.182-24.521c0-7.604-11.062-17.222-28.182-24.504C561.969,372.822,532.061,367.609,500.007,367.609
|
||||
L500.007,367.609z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
81
css/font-combodo/test.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Combodo Font test page</title>
|
||||
<link rel="stylesheet" type="text/css" href="./font-combodo.css" />
|
||||
<style>
|
||||
body {
|
||||
font-size: 10pt;
|
||||
font-family: Verdana, Arial, Helvetica;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
aIcons = {
|
||||
'new-request': 'New request',
|
||||
'new-request-o': 'New request (outline)',
|
||||
'ongoing-request': 'On-going request',
|
||||
'closed-request': 'Closed request',
|
||||
'combodo-icon': 'Combodo icon',
|
||||
'combodo-icon-o': 'Combodo icon (outline)',
|
||||
'itop-icon': 'iTop icon',
|
||||
}
|
||||
|
||||
function GenerateTable() {
|
||||
var sHtml = '';
|
||||
for(var k in aIcons)
|
||||
{
|
||||
var sClass = 'fc fc-'+k;
|
||||
var sColor = document.getElementById('color').value;
|
||||
var sSize = document.getElementById('size').value;
|
||||
if (sSize != '')
|
||||
{
|
||||
sClass += ' fc-'+sSize;
|
||||
}
|
||||
var sRotation = document.getElementById('rotation').value;
|
||||
if (sRotation != '')
|
||||
{
|
||||
sClass += ' fc-rotate-'+sRotation;
|
||||
}
|
||||
var sFlip = document.getElementById('flip').value;
|
||||
if (sFlip != '')
|
||||
{
|
||||
sClass += ' fc-flip-'+sFlip;
|
||||
}
|
||||
var sAnimation = document.getElementById('animation').value;
|
||||
if (sAnimation != '')
|
||||
{
|
||||
sClass += ' fc-'+sAnimation;
|
||||
}
|
||||
var sStyle= 'color: '+sColor;
|
||||
var sTitle = sClass;
|
||||
sHtml += '<div title="'+sTitle+'"><span class="'+sClass+'" style="'+sStyle+'"></span> '+aIcons[k]+'</div>';
|
||||
}
|
||||
var oElement = document.getElementById('dump');
|
||||
oElement.innerHTML = sHtml;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Combodo Font test page</h1>
|
||||
<h2>Parameters</h2>
|
||||
<div>
|
||||
<p>Text color: <input type="text" id="color" value="#CC8800"></p>
|
||||
<p>Icon size: <select id="size"><option value="">Normal (1x)</option><option value="2x" selected>2x</option><option value="3x">3x</option><option value="4x">4x</option><option value="5x" selected>5x</option></select></p>
|
||||
<p>Rotation: <select id="rotation"><option value="" selected>None</option><option value="90">90°</option><option value="180">180°</option><option value="270">270°</option></select></p>
|
||||
<p>Flip: <select id="flip"><option value="" selected>None</option><option value="vertical">Vertical</option><option value="horizontal">Horizontal</option></select> (NB: flip and rotation cannot be combined)</p>
|
||||
<p>Animation: <select id="animation"><option value="" selected>None</option><option value="spin">Spin</option><option value="Rotate">Rotate (Experimental!)</option></select></p>
|
||||
<button type="button" onclick="GenerateTable()">Redraw !</button>
|
||||
</div>
|
||||
<h2>Icons</h2>
|
||||
<div id="dump"></div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// your page initialization code here
|
||||
// the DOM will be available here
|
||||
GenerateTable();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
30
css/jquery-ui-timepicker-addon.css
Normal file
@@ -0,0 +1,30 @@
|
||||
.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
|
||||
.ui-timepicker-div dl { text-align: left; }
|
||||
.ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; }
|
||||
.ui-timepicker-div dl dd { margin: 0 10px 10px 40%; }
|
||||
.ui-timepicker-div td { font-size: 90%; }
|
||||
.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
|
||||
.ui-timepicker-div .ui_tpicker_unit_hide{ display: none; }
|
||||
|
||||
.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input { background: none; color: inherit; border: none; outline: none; border-bottom: solid 1px #555; width: 95%; }
|
||||
.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input:focus { border-bottom-color: #aaa; }
|
||||
|
||||
.ui-timepicker-rtl{ direction: rtl; }
|
||||
.ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; }
|
||||
.ui-timepicker-rtl dl dt{ float: right; clear: right; }
|
||||
.ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; }
|
||||
|
||||
/* Shortened version style */
|
||||
.ui-timepicker-div.ui-timepicker-oneLine { padding-right: 2px; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dt { display: none; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label { display: block; padding-top: 2px; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl { text-align: right; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd > div { display:inline-block; margin:0; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before { content:':'; display:inline-block; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before { content:'.'; display:inline-block; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{ display: none; }
|
||||