Compare commits
1057
Commits
7.x-3.5+37-dev
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49a0944412 | ||
|
|
d6e525465b | ||
|
|
91e0ff102e | ||
|
|
be549a75f6 | ||
|
|
1e27fcca51 | ||
|
|
ab5d43a397 | ||
|
|
094d6ec86f | ||
|
|
6475b0c126 | ||
|
|
a7f0bb7650 | ||
|
|
50d08c7efd | ||
|
|
4e780cb6d4 | ||
|
|
26fadc74e4 | ||
|
|
9f043d25e9 | ||
|
|
72e8abe604 | ||
|
|
637ff76ca8 | ||
|
|
ad25acf851 | ||
|
|
0f3912f6fd | ||
|
|
2553869978 | ||
|
|
d1290e31bc | ||
|
|
598f09bba5 | ||
|
|
fb11eac920 | ||
|
|
0973d0afe5 | ||
|
|
785eea77db | ||
|
|
2e69e3fd4c | ||
|
|
dc39ddbbea | ||
|
|
c97e0f8ba1 | ||
|
|
9adc940a67 | ||
|
|
58cd990c8c | ||
|
|
e08a2639c6 | ||
|
|
2ffad14939 | ||
|
|
33210e10f2 | ||
|
|
472762edfa | ||
|
|
89287879bc | ||
|
|
3411804c42 | ||
|
|
de0f246fb3 | ||
|
|
a180696fc7 | ||
|
|
ccd47590d5 | ||
|
|
5dd6874488 | ||
|
|
514f3bd89e | ||
|
|
0aea7a0db1 | ||
|
|
b2fe2731c2 | ||
|
|
f6e72bbe19 | ||
|
|
6fb9e4806d | ||
|
|
b764ef206e | ||
|
|
0615369680 | ||
|
|
954fc9ead1 | ||
|
|
79db3e3896 | ||
|
|
5510ac0abf | ||
|
|
2d23821da4 | ||
|
|
fca4395934 | ||
|
|
31cfa90501 | ||
|
|
ccab226e12 | ||
|
|
1b53514906 | ||
|
|
25b41d2a43 | ||
|
|
eb20184257 | ||
|
|
c285c6ccb8 | ||
|
|
fa07acdd31 | ||
|
|
9ad0aa199f | ||
|
|
7ab8742528 | ||
|
|
8ec6b54b6c | ||
|
|
f21817dec1 | ||
|
|
4cb8f6aee1 | ||
|
|
b3e863e943 | ||
|
|
4aed87ebd0 | ||
|
|
fafd439b26 | ||
|
|
226561ffd4 | ||
|
|
85772b4ca7 | ||
|
|
57376251fa | ||
|
|
8a05ca54b2 | ||
|
|
9344a61b61 | ||
|
|
18f4aba146 | ||
|
|
1eb61fe020 | ||
|
|
a11650f2b4 | ||
|
|
f09e9563b2 | ||
|
|
c914e7e348 | ||
|
|
b5f3769416 | ||
|
|
3133fd480b | ||
|
|
b20732f9a8 | ||
|
|
8b486ffc60 | ||
|
|
7d5bedcb0c | ||
|
|
268c8b619b | ||
|
|
bbeb5f01ac | ||
|
|
bd1a1de792 | ||
|
|
af9630060a | ||
|
|
383ca8b173 | ||
|
|
afc654c704 | ||
|
|
106129c27c | ||
|
|
81810c5af4 | ||
|
|
12c076c349 | ||
|
|
9d45086951 | ||
|
|
8777adbe2e | ||
|
|
82a22114d3 | ||
|
|
098f083b8d | ||
|
|
e8586eb4da | ||
|
|
1e1a762b9b | ||
|
|
e9146c29ce | ||
|
|
042e111bc4 | ||
|
|
2f56697bb7 | ||
|
|
55700094a2 | ||
|
|
9bfbd861ca | ||
|
|
5f26b3e160 | ||
|
|
1b9f8e3ab4 | ||
|
|
c90cfa63e4 | ||
|
|
7bc3c13ad6 | ||
|
|
1ee4e95491 | ||
|
|
bf5f1a63ff | ||
|
|
38648c1670 | ||
|
|
1d4fcf0d89 | ||
|
|
73417de740 | ||
|
|
0edc6fd23a | ||
|
|
abb106d7c2 | ||
|
|
be2da7afe0 | ||
|
|
243f5b689c | ||
|
|
301d403887 | ||
|
|
08e5ed7202 | ||
|
|
4ca87255ab | ||
|
|
4b2ab455c9 | ||
|
|
b9c809d2c7 | ||
|
|
ed483507e5 | ||
|
|
8feebe3d1e | ||
|
|
5c7f02554f | ||
|
|
58161d2b57 | ||
|
|
ce192b080e | ||
|
|
aafe1c5460 | ||
|
|
8539255353 | ||
|
|
5706aea93b | ||
|
|
9369b73118 | ||
|
|
ab9f1d3cc4 | ||
|
|
8d0f110c50 | ||
|
|
cedbce376b | ||
|
|
6e2a36fe0c | ||
|
|
d9f3677b98 | ||
|
|
5b82dfb691 | ||
|
|
728f8e6d31 | ||
|
|
9acef9d37e | ||
|
|
542ac42fca | ||
|
|
dbd7b88639 | ||
|
|
28c7a0965a | ||
|
|
b5d76ec5e6 | ||
|
|
83cbcefff9 | ||
|
|
0d940c99db | ||
|
|
2334eb741b | ||
|
|
ddc28956c4 | ||
|
|
abbcb19838 | ||
|
|
024afdfd3d | ||
|
|
b9f2b16d24 | ||
|
|
3f9ba49255 | ||
|
|
f01fdbdd9c | ||
|
|
7563311a19 | ||
|
|
669341f1d6 | ||
|
|
13aafca7e4 | ||
|
|
e24eec56e7 | ||
|
|
eba58a57bf | ||
|
|
e190f5f47d | ||
|
|
ec6f23e149 | ||
|
|
fdd842fe19 | ||
|
|
73ec6d3c1e | ||
|
|
dee5085c89 | ||
|
|
c4ab89f177 | ||
|
|
d1b069ad8b | ||
|
|
c56a523286 | ||
|
|
8b5d721bfd | ||
|
|
7ed9dc54bf | ||
|
|
adee2f54e3 | ||
|
|
41e3e6dfb2 | ||
|
|
5f6a9ac5d1 | ||
|
|
16d8adc672 | ||
|
|
138b68ac98 | ||
|
|
026a9bc241 | ||
|
|
adb1437a44 | ||
|
|
5d17730143 | ||
|
|
1ec80dc60e | ||
|
|
5423f6d3bc | ||
|
|
1ccc5d07d6 | ||
|
|
597e6c9159 | ||
|
|
b26db27098 | ||
|
|
b9ac935de6 | ||
|
|
091c01ce81 | ||
|
|
039f49aded | ||
|
|
cc295255a1 | ||
|
|
fedd8f4216 | ||
|
|
73f871cbcc | ||
|
|
0f286a2d78 | ||
|
|
ac87a57196 | ||
|
|
b00f897952 | ||
|
|
99f92edb37 | ||
|
|
d0566a6155 | ||
|
|
0bf7b4a1c8 | ||
|
|
2f02bb6485 | ||
|
|
a477dbb8ec | ||
|
|
d4fee57337 | ||
|
|
c97d1d7f75 | ||
|
|
c7e2de9e8b | ||
|
|
cc000ce3d6 | ||
|
|
7abf64be00 | ||
|
|
e8cadc31e1 | ||
|
|
7011e6c691 | ||
|
|
c70a4bed11 | ||
|
|
252abe9b0e | ||
|
|
773e7fbddd | ||
|
|
8d24211bf5 | ||
|
|
86c993f638 | ||
|
|
08127672d6 | ||
|
|
66fa1d419d | ||
|
|
0521608bb7 | ||
|
|
fdefc824d8 | ||
|
|
2b028cf376 | ||
|
|
d1963312a6 | ||
|
|
3b6c7b914d | ||
|
|
0bb339fc99 | ||
|
|
3413d81bb8 | ||
|
|
7aeabebddf | ||
|
|
2e0abeed03 | ||
|
|
82bb633c3e | ||
|
|
104fcaf970 | ||
|
|
34649ab98b | ||
|
|
9c43ba2fc6 | ||
|
|
1f780c974e | ||
|
|
2ed8b48636 | ||
|
|
d712e5881a | ||
|
|
253524c527 | ||
|
|
ae7349e811 | ||
|
|
c64b25b261 | ||
|
|
992c9a552a | ||
|
|
a2db826449 | ||
|
|
b11abbed59 | ||
|
|
f6f7fd575f | ||
|
|
610760bedf | ||
|
|
54399b169a | ||
|
|
598f3e8292 | ||
|
|
0347c5cb99 | ||
|
|
1f5092fb45 | ||
|
|
31d638cff7 | ||
|
|
31409daed2 | ||
|
|
42347006ff | ||
|
|
dd95b24f5b | ||
|
|
507402d07b | ||
|
|
e310490c7f | ||
|
|
918b4cd5e1 | ||
|
|
deae7e0b59 | ||
|
|
c64551f50c | ||
|
|
73793c9ba9 | ||
|
|
d38e929be5 | ||
|
|
266fb10083 | ||
|
|
6fee348360 | ||
|
|
74f8c52af3 | ||
|
|
9c97849d78 | ||
|
|
2ca9694d84 | ||
|
|
422322c96d | ||
|
|
aea9683c77 | ||
|
|
e2fde76aff | ||
|
|
fb0666538c | ||
|
|
3a55dcea49 | ||
|
|
39dc81ad14 | ||
|
|
7b11f2794e | ||
|
|
c23706f745 | ||
|
|
c47d792cd1 | ||
|
|
d7697f0f76 | ||
|
|
b27aabe359 | ||
|
|
8fb9c70e42 | ||
|
|
1658e609f8 | ||
|
|
e36279d8b1 | ||
|
|
0b6e0c10cb | ||
|
|
ba1ec7113e | ||
|
|
deb2bff572 | ||
|
|
538c791bae | ||
|
|
df107a0443 | ||
|
|
c9b16289ef | ||
|
|
b4acd7d8bb | ||
|
|
3af02617b4 | ||
|
|
0fdb4abd71 | ||
|
|
90be34eef1 | ||
|
|
3e984dc6c5 | ||
|
|
a0cb0b2171 | ||
|
|
f6346b0195 | ||
|
|
300f698989 | ||
|
|
8cbb5f8c4c | ||
|
|
a106b98209 | ||
|
|
8fe92dbcc7 | ||
|
|
60f2c5c7fa | ||
|
|
d96c17454b | ||
|
|
ed6ae84181 | ||
|
|
35e914df58 | ||
|
|
6b46869a16 | ||
|
|
892dba801f | ||
|
|
50dfd2d89b | ||
|
|
9a18821b7e | ||
|
|
82bf36a5eb | ||
|
|
3eb20676ca | ||
|
|
2155a66eb1 | ||
|
|
62fef652fc | ||
|
|
951c9114a2 | ||
|
|
edba04f103 | ||
|
|
26ab9aa56b | ||
|
|
5716acf5bb | ||
|
|
12fe570fbd | ||
|
|
66ee9c6657 | ||
|
|
ca5231f1fe | ||
|
|
b9a8db8140 | ||
|
|
d25e7428bc | ||
|
|
3027b3985b | ||
|
|
d214a44291 | ||
|
|
fc7753f7a0 | ||
|
|
7c51b255fb | ||
|
|
5e099d48de | ||
|
|
bf84ac06cd | ||
|
|
87beabee92 | ||
|
|
f6ee366a71 | ||
|
|
b216e30f28 | ||
|
|
0a863fc1bb | ||
|
|
a5edc41323 | ||
|
|
1cc2c9af75 | ||
|
|
cc653b73be | ||
|
|
73993a539b | ||
|
|
d7e7410950 | ||
|
|
7298235d4f | ||
|
|
fc0a779214 | ||
|
|
248c5ed3e1 | ||
|
|
ace9de07e2 | ||
|
|
7ad1f5dc67 | ||
|
|
d5628cc78e | ||
|
|
81371521c7 | ||
|
|
eb699f528d | ||
|
|
0ba0c21bb9 | ||
|
|
50c3e1c7d7 | ||
|
|
f0cb6175c1 | ||
|
|
e004b7a238 | ||
|
|
f7cd9c0858 | ||
|
|
5d30d9bcee | ||
|
|
c4f2e62697 | ||
|
|
a99f2d7aa6 | ||
|
|
4e8346e5e3 | ||
|
|
5b281024a5 | ||
|
|
96903cab02 | ||
|
|
ce20bb3f0f | ||
|
|
19d3d90743 | ||
|
|
62fec82217 | ||
|
|
f0295847f9 | ||
|
|
cc657eaec4 | ||
|
|
54f496acad | ||
|
|
feef7951cd | ||
|
|
8def2961ac | ||
|
|
1c38e1b72a | ||
|
|
0446c17ad1 | ||
|
|
459335fee1 | ||
|
|
408251fd28 | ||
|
|
98d1b5cb03 | ||
|
|
141d1f4b5c | ||
|
|
100dce022a | ||
|
|
3220ed8e4b | ||
|
|
22146c2778 | ||
|
|
f4b8bfa249 | ||
|
|
4c32c73321 | ||
|
|
09d1f06552 | ||
|
|
ade4a41f76 | ||
|
|
19b1d49a22 | ||
|
|
4264a271b4 | ||
|
|
de040b46ea | ||
|
|
aa323869c3 | ||
|
|
22e93d650e | ||
|
|
2664d2fcfd | ||
|
|
feb93503b6 | ||
|
|
2d5be00cf1 | ||
|
|
a84cd67456 | ||
|
|
8a1f5cd116 | ||
|
|
65c7ab92b6 | ||
|
|
1c1de98039 | ||
|
|
2e4a069036 | ||
|
|
cc8306bc14 | ||
|
|
d107702f86 | ||
|
|
f9144db774 | ||
|
|
1ca1912fe2 | ||
|
|
1657634e1a | ||
|
|
a908bb02ca | ||
|
|
4eadc1b771 | ||
|
|
ce2f811278 | ||
|
|
1c42c93c54 | ||
|
|
71744db508 | ||
|
|
3add8f6b96 | ||
|
|
678343b0e9 | ||
|
|
5485f80dae | ||
|
|
6df0ebfa53 | ||
|
|
9521fc1ce5 | ||
|
|
1f1247a226 | ||
|
|
b05c9a53c5 | ||
|
|
b2d9b7dbf7 | ||
|
|
98bc1dfef8 | ||
|
|
d35191a96e | ||
|
|
2fd09f23e9 | ||
|
|
181a28cd85 | ||
|
|
d5155b3b31 | ||
|
|
32f26b4bf5 | ||
|
|
1b9752f495 | ||
|
|
afd3a90538 | ||
|
|
96aef8c128 | ||
|
|
73741839ca | ||
|
|
8a8ed4f929 | ||
|
|
e12a664fa5 | ||
|
|
6ceb7d6534 | ||
|
|
3ffbc09c10 | ||
|
|
49004d7fee | ||
|
|
706c96d663 | ||
|
|
c9f8dc21ed | ||
|
|
59cec97bcf | ||
|
|
c2a05d8ab3 | ||
|
|
f75cb5562c | ||
|
|
342655bf2b | ||
|
|
f9f0ce54ec | ||
|
|
0c29476b3f | ||
|
|
c183a0b0dd | ||
|
|
ea423f2146 | ||
|
|
580c97dc49 | ||
|
|
95cbb13821 | ||
|
|
ddae683ff3 | ||
|
|
c90787212b | ||
|
|
81ea02bb3e | ||
|
|
d9f6370962 | ||
|
|
40e4643644 | ||
|
|
76c59dc2a9 | ||
|
|
ae3b3d3895 | ||
|
|
d858f00e17 | ||
|
|
f28719a994 | ||
|
|
922a2da1e5 | ||
|
|
26bb2de5b8 | ||
|
|
e4b76f4756 | ||
|
|
2686166df2 | ||
|
|
1f211d5348 | ||
|
|
b3c85e79c6 | ||
|
|
2ffa37fefa | ||
|
|
70151f23e1 | ||
|
|
79bd95aeec | ||
|
|
3a53525bf9 | ||
|
|
54787232fb | ||
|
|
69c5b7ee5c | ||
|
|
8d141a61e0 | ||
|
|
19a68f970e | ||
|
|
de8f5e4ccc | ||
|
|
442d89c948 | ||
|
|
3adb83ccf7 | ||
|
|
68b11d7a9b | ||
|
|
58e171be01 | ||
|
|
da4d135279 | ||
|
|
1313e1f0e2 | ||
|
|
e4885c3a5d | ||
|
|
f8a18606ba | ||
|
|
23b96ab26f | ||
|
|
8c920e81df | ||
|
|
202993e68e | ||
|
|
8589ce7c28 | ||
|
|
17580e8eba | ||
|
|
ca9530466a | ||
|
|
31ede7287a | ||
|
|
be2054c163 | ||
|
|
a20d86efbf | ||
|
|
0795a2a514 | ||
|
|
c92d7ed737 | ||
|
|
923b407498 | ||
|
|
ceeefdd109 | ||
|
|
a1fd56580b | ||
|
|
23c085213e | ||
|
|
0ba300fe22 | ||
|
|
7763d08111 | ||
|
|
e1819172e9 | ||
|
|
08b4b2dbbb | ||
|
|
42940e2cff | ||
|
|
9dfbaeb05a | ||
|
|
d69e29e3be | ||
|
|
b85dc130d6 | ||
|
|
d8626dbb2d | ||
|
|
4d3a184a81 | ||
|
|
eeb0df349d | ||
|
|
785dee0639 | ||
|
|
9afc105c7e | ||
|
|
0cd6215a32 | ||
|
|
c5028bffd3 | ||
|
|
ca9413af4d | ||
|
|
d3dcf44a1e | ||
|
|
6737f9faa1 | ||
|
|
322dd995bf | ||
|
|
cde7b73a73 | ||
|
|
2121a356b3 | ||
|
|
55b23a2cec | ||
|
|
134c8b8338 | ||
|
|
7c85261e56 | ||
|
|
37fbabab56 | ||
|
|
6a8d30db08 | ||
|
|
d5a03170e4 | ||
|
|
36b5937b9e | ||
|
|
49f9128c96 | ||
|
|
46b448c2a2 | ||
|
|
73186732c3 | ||
|
|
f71bfe281e | ||
|
|
9de02bab38 | ||
|
|
6719ca32c7 | ||
|
|
e89afbf603 | ||
|
|
a1856277e5 | ||
|
|
7becdeff92 | ||
|
|
f55c541343 | ||
|
|
1f82c4ef9b | ||
|
|
c0af9037e9 | ||
|
|
b3221c71e2 | ||
|
|
802ec0c6f3 | ||
|
|
6de56c702c | ||
|
|
f2b8ace9b7 | ||
|
|
7b6bd36fea | ||
|
|
8dc0b20c89 | ||
|
|
87f453cf20 | ||
|
|
5594bacfa9 | ||
|
|
8d74b0bb06 | ||
|
|
b285de8b3a | ||
|
|
775d25dbcc | ||
|
|
a181091a14 | ||
|
|
72cf8934c3 | ||
|
|
627216cb77 | ||
|
|
8ff8fcd8da | ||
|
|
178cfe05d0 | ||
|
|
879cd137d5 | ||
|
|
7b975a5cab | ||
|
|
ad76968844 | ||
|
|
c2252c8f9a | ||
|
|
1be1c89407 | ||
|
|
aceb299147 | ||
|
|
404757203b | ||
|
|
f0531608b7 | ||
|
|
28db07faf7 | ||
|
|
88c1012c48 | ||
|
|
a494bb4627 | ||
|
|
894a4849e0 | ||
|
|
886114f49b | ||
|
|
ae12eada47 | ||
|
|
51cc1bb7b2 | ||
|
|
f63556f674 | ||
|
|
29079eeb84 | ||
|
|
bf88fdf0b9 | ||
|
|
265c4ab336 | ||
|
|
c36797f2bb | ||
|
|
e5b63e455f | ||
|
|
ab1e9ae0ff | ||
|
|
6b3f29142b | ||
|
|
77450f1aad | ||
|
|
11617ec99c | ||
|
|
d1b96a77a6 | ||
|
|
077d83613e | ||
|
|
d2cffdeddd | ||
|
|
4237cb8edb | ||
|
|
88d4cf09dc | ||
|
|
1cc9a63c46 | ||
|
|
0bba2d0657 | ||
|
|
8e138e1153 | ||
|
|
ccb121e83a | ||
|
|
8f54fcd944 | ||
|
|
98f738f951 | ||
|
|
ef50154a49 | ||
|
|
982122065e | ||
|
|
80e491f1d8 | ||
|
|
c5f5eb4f64 | ||
|
|
7d410e8393 | ||
|
|
afce4662bd | ||
|
|
ab40bcf861 | ||
|
|
497a0108ae | ||
|
|
9f3444676a | ||
|
|
0545fabe49 | ||
|
|
37ac75fc59 | ||
|
|
6fdb4e0dc9 | ||
|
|
124e1a7bc2 | ||
|
|
7ee6c9dc12 | ||
|
|
e9a136db4c | ||
|
|
f515e148f4 | ||
|
|
aed0b98da2 | ||
|
|
ccd86a08a4 | ||
|
|
8f32d4e98b | ||
|
|
e82e3250b3 | ||
|
|
2e1e0cc05b | ||
|
|
488d4c19aa | ||
|
|
ce18a79a80 | ||
|
|
41e7f80383 | ||
|
|
8aabfe2592 | ||
|
|
847eb41741 | ||
|
|
6c4d1ed8f5 | ||
|
|
854a6cc680 | ||
|
|
4691968631 | ||
|
|
10c50db9e2 | ||
|
|
a645ef87b4 | ||
|
|
d214a86eeb | ||
|
|
5cbbf7cbf5 | ||
|
|
3310c6fad6 | ||
|
|
4756fdaf77 | ||
|
|
7580376050 | ||
|
|
acaa381d3d | ||
|
|
c3011cef61 | ||
|
|
5835745cc2 | ||
|
|
bf41479abc | ||
|
|
f4ae3c9055 | ||
|
|
28deeac474 | ||
|
|
0c71144833 | ||
|
|
f881f8b638 | ||
|
|
1eb8bb9de8 | ||
|
|
5b26e36355 | ||
|
|
9df43555ac | ||
|
|
e2183f0a0d | ||
|
|
c4d8c34e1a | ||
|
|
748548f8f4 | ||
|
|
5d0c233fec | ||
|
|
05a41ebf3a | ||
|
|
a08a5287fc | ||
|
|
ae72f70488 | ||
|
|
c8a37f4255 | ||
|
|
df548917f6 | ||
|
|
40fe76b2cf | ||
|
|
5b7b67573f | ||
|
|
a613426482 | ||
|
|
e371e746ff | ||
|
|
148e2db56e | ||
|
|
332c17c25c | ||
|
|
a64d6e34d8 | ||
|
|
6394330226 | ||
|
|
76efcb0778 | ||
|
|
f6d5c40ae2 | ||
|
|
9d2fee4ddd | ||
|
|
d7dddd5764 | ||
|
|
4d0605604f | ||
|
|
100abc9a3a | ||
|
|
094eb50691 | ||
|
|
7b99c14d96 | ||
|
|
c06be9a44e | ||
|
|
6ccf2ddb91 | ||
|
|
3757a19ce7 | ||
|
|
891166f86e | ||
|
|
85251183d6 | ||
|
|
54ee5c7b3a | ||
|
|
6402fc7ab8 | ||
|
|
d7303905a0 | ||
|
|
cf03e9ca52 | ||
|
|
60cc747699 | ||
|
|
d9298146fa | ||
|
|
1cc99f26e1 | ||
|
|
5cde537e0a | ||
|
|
0799729429 | ||
|
|
3fe3dde8c2 | ||
|
|
5d6f96ed0e | ||
|
|
983f51e711 | ||
|
|
85be86a9e4 | ||
|
|
ffff23584e | ||
|
|
770e2155bb | ||
|
|
90c6d5e6e9 | ||
|
|
e1a04d9b8e | ||
|
|
3d36e980af | ||
|
|
acb87f238a | ||
|
|
7230dafd5f | ||
|
|
9af55b03bd | ||
|
|
10de06dd70 | ||
|
|
572f88b950 | ||
|
|
52d0c3b77e | ||
|
|
794ccba61a | ||
|
|
925324448b | ||
|
|
64ebb28c01 | ||
|
|
d35f79e2cf | ||
|
|
c3c0c71945 | ||
|
|
91c826322e | ||
|
|
ab51102cd0 | ||
|
|
77414c7cb9 | ||
|
|
7aa6dfe191 | ||
|
|
ca6c5bd4fa | ||
|
|
e960755e20 | ||
|
|
09d80d2c63 | ||
|
|
5daabaaf5a | ||
|
|
8909491a12 | ||
|
|
051455cd71 | ||
|
|
235e57d8e9 | ||
|
|
ca63b9bbca | ||
|
|
cb791f03ff | ||
|
|
7a46e22394 | ||
|
|
3bdbc68690 | ||
|
|
33ce5d0e40 | ||
|
|
8e478340a2 | ||
|
|
78fa0756a9 | ||
|
|
176b1c05d9 | ||
|
|
acbf965c77 | ||
|
|
bd98c3d7f6 | ||
|
|
1c56076189 | ||
|
|
e0ab0fd0d5 | ||
|
|
b58a00a4c8 | ||
|
|
4d81ea318d | ||
|
|
77cb87b49d | ||
|
|
5203118606 | ||
|
|
39759425ce | ||
|
|
62b7436183 | ||
|
|
d8237ffb99 | ||
|
|
a30917d1d2 | ||
|
|
e6aed1cba0 | ||
|
|
16ed68abf6 | ||
|
|
128640cd15 | ||
|
|
7e3ef32d18 | ||
|
|
d4d5acf837 | ||
|
|
2b45f07449 | ||
|
|
9faad4a27d | ||
|
|
32e4448773 | ||
|
|
e539e701f8 | ||
|
|
db5f70501a | ||
|
|
f47c978520 | ||
|
|
4362f6dc23 | ||
|
|
eeccd6a4d2 | ||
|
|
8672e5eb9f | ||
|
|
f1c9615e4a | ||
|
|
9eb6fe8360 | ||
|
|
2d18c524eb | ||
|
|
afd4de26e7 | ||
|
|
7c486da7c5 | ||
|
|
0ccaf2eeeb | ||
|
|
5fd46660b9 | ||
|
|
70b4f2279f | ||
|
|
2f95c828e6 | ||
|
|
1923036d97 | ||
|
|
49db4b90be | ||
|
|
6c85be6c6d | ||
|
|
ee27274830 | ||
|
|
84542b241d | ||
|
|
e99ac67b73 | ||
|
|
5a4cadabf6 | ||
|
|
5658794f17 | ||
|
|
df0ec71ac7 | ||
|
|
93c278c5e8 | ||
|
|
13d6d3b95b | ||
|
|
e0ae80791b | ||
|
|
dfe36ba913 | ||
|
|
caf3bf662c | ||
|
|
66e87e0bf4 | ||
|
|
b2873ad5b1 | ||
|
|
f43260082d | ||
|
|
bef27e8258 | ||
|
|
443e4fc138 | ||
|
|
09ac2a24ac | ||
|
|
789d8708bf | ||
|
|
578adab53b | ||
|
|
d5097a4bc6 | ||
|
|
661765bde9 | ||
|
|
fde4f26165 | ||
|
|
8763bf01dd | ||
|
|
10c9d53c31 | ||
|
|
29f7711833 | ||
|
|
9cc5ba4cfa | ||
|
|
644b7db597 | ||
|
|
779d42d9bb | ||
|
|
78b816920d | ||
|
|
f5e39e1209 | ||
|
|
c9912105d5 | ||
|
|
6a402551d7 | ||
|
|
5b845da277 | ||
|
|
763ce99b95 | ||
|
|
f207e56b2d | ||
|
|
06a4d254c2 | ||
|
|
49fb38b169 | ||
|
|
de022d62e2 | ||
|
|
d9d092295b | ||
|
|
8c47736b71 | ||
|
|
de59738907 | ||
|
|
a575bce275 | ||
|
|
2fdd8fb95c | ||
|
|
e573564b93 | ||
|
|
454adf9478 | ||
|
|
b181133e60 | ||
|
|
e7d0e34651 | ||
|
|
f0f9635e8b | ||
|
|
7a3ee63d15 | ||
|
|
8f492c8b4e | ||
|
|
108f77c73d | ||
|
|
0db5022c47 | ||
|
|
d01d0a2a7e | ||
|
|
2b137c6a19 | ||
|
|
cb6963919c | ||
|
|
39db0ed16d | ||
|
|
1aa3c4e9eb | ||
|
|
1f662a2b40 | ||
|
|
83cf488d4a | ||
|
|
1a145b8470 | ||
|
|
8c09d74e68 | ||
|
|
8e432ea544 | ||
|
|
2d4301ad40 | ||
|
|
662012976d | ||
|
|
cc526dd563 | ||
|
|
87edb46954 | ||
|
|
f9a60fe62d | ||
|
|
c60696ba2b | ||
|
|
90adf0ad52 | ||
|
|
2506c8b8ac | ||
|
|
d6bc2debdf | ||
|
|
438ebb75ad | ||
|
|
cb5ba1c633 | ||
|
|
432b8ee65e | ||
|
|
72e2d8a7f4 | ||
|
|
2fff41d01d | ||
|
|
83fbaa4669 | ||
|
|
184a808558 | ||
|
|
63465d9076 | ||
|
|
ca2ebbf0c8 | ||
|
|
a59e01d581 | ||
|
|
5a82c8394d | ||
|
|
1f9bef33b9 | ||
|
|
1e40a74e1a | ||
|
|
b8b7044eb3 | ||
|
|
739f52c1df | ||
|
|
64bf6b3fa2 | ||
|
|
df986ba09a | ||
|
|
1377500ccc | ||
|
|
7bd9dd6d80 | ||
|
|
e1d0639eee | ||
|
|
618d68bd70 | ||
|
|
1bcda42052 | ||
|
|
6d0af35191 | ||
|
|
6a94814eaa | ||
|
|
1b6b74ea40 | ||
|
|
00df84c69c | ||
|
|
89e7b9de6e | ||
|
|
4caa0c442b | ||
|
|
6c0f932603 | ||
|
|
18eea497c3 | ||
|
|
c3f366a045 | ||
|
|
fc0253d9c2 | ||
|
|
a6f0f199b5 | ||
|
|
12045fe181 | ||
|
|
feae2e1f34 | ||
|
|
b0f5c3eae0 | ||
|
|
bcf209570a | ||
|
|
b7ab35d6aa | ||
|
|
3b2eb43c8f | ||
|
|
4ef59776b0 | ||
|
|
f276e09115 | ||
|
|
60ca2f9e72 | ||
|
|
aa45d84855 | ||
|
|
5396b3e2b5 | ||
|
|
3eaa5817e0 | ||
|
|
975d758599 | ||
|
|
0600bf6674 | ||
|
|
1cd7c773a5 | ||
|
|
2601c718c2 | ||
|
|
61c83b5a75 | ||
|
|
a2b9816e52 | ||
|
|
f3be35fe5e | ||
|
|
707403f8e7 | ||
|
|
d2285db42a | ||
|
|
7dafe10be1 | ||
|
|
d97c0a2e45 | ||
|
|
63df1c8f3a | ||
|
|
6793ae61a6 | ||
|
|
d3b84b7733 | ||
|
|
0e49c6cedf | ||
|
|
948913bc82 | ||
|
|
b1bf1c9387 | ||
|
|
1d11663ec4 | ||
|
|
df2c715315 | ||
|
|
d77d7e5ef7 | ||
|
|
8e956c857b | ||
|
|
ef943eba75 | ||
|
|
b750ae41c4 | ||
|
|
761ffba48d | ||
|
|
e218aeb4da | ||
|
|
493d3bf780 | ||
|
|
f8af96a85b | ||
|
|
2248bfd12e | ||
|
|
65ea77dea2 | ||
|
|
edc3e1f685 | ||
|
|
7022567394 | ||
|
|
4b1685af04 | ||
|
|
7884444ec6 | ||
|
|
aff5ecb650 | ||
|
|
9a18131bcf | ||
|
|
ce04398f78 | ||
|
|
6022c4547f | ||
|
|
a981ba0fcd | ||
|
|
a3196f9486 | ||
|
|
4240ab47e8 | ||
|
|
c259bc4543 | ||
|
|
04d628c609 | ||
|
|
3faebabce1 | ||
|
|
a7b1a91c6f | ||
|
|
900155d078 | ||
|
|
aa8698232d | ||
|
|
2543d32599 | ||
|
|
e5028b4522 | ||
|
|
3b4f1e024f | ||
|
|
476ccd94fe | ||
|
|
86d0eb03f5 | ||
|
|
789a40d106 | ||
|
|
2697ba8675 | ||
|
|
c811ffcb26 | ||
|
|
3a3a620cac | ||
|
|
4f6a8d389b | ||
|
|
95098773d5 | ||
|
|
c952bd330f | ||
|
|
6c13226969 | ||
|
|
3ca910cf34 | ||
|
|
40b0b8bae2 | ||
|
|
0370c6233d | ||
|
|
fb30fe966e | ||
|
|
68126583b1 | ||
|
|
2030a6f810 | ||
|
|
256475e716 | ||
|
|
5e63575d94 | ||
|
|
ad7809415c | ||
|
|
0685b2dc63 | ||
|
|
0ed294ff52 | ||
|
|
addf98f5ab | ||
|
|
89159da2cf | ||
|
|
4cf9d7b2c6 | ||
|
|
35a5eb3a60 | ||
|
|
c0a9653975 | ||
|
|
b920153c13 | ||
|
|
18982b6700 | ||
|
|
7ce8d94ed9 | ||
|
|
45c41b319d | ||
|
|
461c93d48a | ||
|
|
47348748c4 | ||
|
|
e31dc0d4fc | ||
|
|
9e85e7a105 | ||
|
|
b861d2da15 | ||
|
|
2a9aca944c | ||
|
|
1896d11e80 | ||
|
|
b4b346b8a5 | ||
|
|
93b7db54fe | ||
|
|
760047e9fc | ||
|
|
94bf329b91 | ||
|
|
93b353922e | ||
|
|
df91706e57 | ||
|
|
4eda04b2f0 | ||
|
|
f4fc36431c | ||
|
|
292f704f37 | ||
|
|
420f792a08 | ||
|
|
4a958da4df | ||
|
|
5b16446d2f | ||
|
|
f62d12ba55 | ||
|
|
8b5f7c5ccb | ||
|
|
11d4a60bfc | ||
|
|
6a13522171 | ||
|
|
9d97a0b7b7 | ||
|
|
746109f079 | ||
|
|
20ad82694b | ||
|
|
49bdef396b | ||
|
|
9d10e145b4 | ||
|
|
e0eae18f29 | ||
|
|
6ab6fc916b | ||
|
|
dcd5bf483e | ||
|
|
0a062a5297 | ||
|
|
f50849a3a3 | ||
|
|
7f1b21c912 | ||
|
|
fbaa166db8 | ||
|
|
60518fcfc2 | ||
|
|
747fef9a0a | ||
|
|
0c65f95fbb | ||
|
|
4fc8e542c3 | ||
|
|
8690f4a165 | ||
|
|
26591bf350 | ||
|
|
66ff837039 | ||
|
|
40f34e2191 | ||
|
|
f83a0b9aee | ||
|
|
626ec7eabc | ||
|
|
c22eb6bc11 | ||
|
|
01aecdfd57 | ||
|
|
d84e570b3c | ||
|
|
7351bc66a0 | ||
|
|
691ecd7d61 | ||
|
|
930913e764 | ||
|
|
09bea7c003 | ||
|
|
d23f6ab12d | ||
|
|
aa857c3a40 | ||
|
|
ad30a381f9 | ||
|
|
951be207ab | ||
|
|
8ec280d5fa | ||
|
|
072342016d | ||
|
|
5427717666 | ||
|
|
a19cf5aae0 | ||
|
|
000cc37f2f | ||
|
|
4d9851e410 | ||
|
|
9cdd779930 | ||
|
|
e3e87a93d7 | ||
|
|
ea30728ec8 | ||
|
|
8a187e010b | ||
|
|
03cc8469dc | ||
|
|
4430806082 | ||
|
|
d9f856a075 | ||
|
|
b50c953eeb | ||
|
|
5109e619cc | ||
|
|
d5ce155335 | ||
|
|
de47d2f980 | ||
|
|
b8566401fd | ||
|
|
8008cecb85 | ||
|
|
2281eac5c1 | ||
|
|
202280fa6b | ||
|
|
c198b6f75f | ||
|
|
13e221324c | ||
|
|
f3409f422e | ||
|
|
1e627725ac | ||
|
|
18f4b720a8 | ||
|
|
b21bbdb14f | ||
|
|
0dbc7d7df4 | ||
|
|
b2b3f7f0b4 | ||
|
|
5c42f3c952 | ||
|
|
cc52ea5af6 | ||
|
|
a570618597 | ||
|
|
b6333de34e | ||
|
|
cc43fb6fa3 | ||
|
|
53a2bce0bd | ||
|
|
a42aa98302 | ||
|
|
c98cff88e7 | ||
|
|
c3abc04ce2 | ||
|
|
d8f75a77e1 | ||
|
|
23a553dc8b | ||
|
|
d45e0979fa | ||
|
|
f0db5b9d05 | ||
|
|
2f6f865e33 | ||
|
|
ebdc82203b | ||
|
|
f55e651074 | ||
|
|
5ef33d2fa2 | ||
|
|
f3935ae423 | ||
|
|
0dd46f5214 | ||
|
|
5c6bec1d8a | ||
|
|
67bf2e51ad | ||
|
|
71f74b5565 | ||
|
|
f9b542c391 | ||
|
|
57a7629b29 | ||
|
|
ebb1ab6ce1 | ||
|
|
3d8955fb48 | ||
|
|
36d7d5cf37 | ||
|
|
778c49b92e | ||
|
|
31259736e3 | ||
|
|
08fe6616f9 | ||
|
|
3ce126eb48 | ||
|
|
d32378cf35 | ||
|
|
2ca8cc7e56 | ||
|
|
53f9178ff0 | ||
|
|
a5276dfe41 | ||
|
|
21563d09ce | ||
|
|
4d03934230 | ||
|
|
ec97ad2402 | ||
|
|
ccd2494fb7 | ||
|
|
6b915cda88 | ||
|
|
eb09ab5342 | ||
|
|
cd38617398 | ||
|
|
659a21255a | ||
|
|
4687549e89 | ||
|
|
ba3b8e51d1 | ||
|
|
6085946d1d | ||
|
|
a5022b38fc | ||
|
|
831fbf4e0f | ||
|
|
7bbcc73ae8 | ||
|
|
6f6aef2e67 | ||
|
|
cb22790246 | ||
|
|
cca7f982e4 | ||
|
|
50807fe40f | ||
|
|
60cc6ae1a3 | ||
|
|
1ed6151591 | ||
|
|
0c661bcbd4 | ||
|
|
1251927952 | ||
|
|
5ca8a4fc6c | ||
|
|
44e1fc9296 | ||
|
|
24bb9a57bb | ||
|
|
911791b362 | ||
|
|
44264836be |
Executable
+31
@@ -0,0 +1,31 @@
|
||||
# Ignore configuration files that may contain sensitive information.
|
||||
sites/*/settings*.php
|
||||
sites/*/drushrc*
|
||||
|
||||
# Ignore paths that contain user-generated content.
|
||||
#sites/*
|
||||
sites/*/files
|
||||
sites/*/private
|
||||
sites/all/translations/*
|
||||
|
||||
_base_d7.tmproj
|
||||
/_materio.sublime-project
|
||||
/_materio.sublime-workspace
|
||||
/_solr.sublime-project
|
||||
/_solr.sublime-workspace
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
*.sublime-project*
|
||||
|
||||
node_modules
|
||||
|
||||
.sass-cache
|
||||
typescript
|
||||
.directory
|
||||
|
||||
*.old_patched
|
||||
|
||||
sync_uploads
|
||||
|
||||
vendor
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
#
|
||||
# Apache/PHP/Drupal settings:
|
||||
#
|
||||
|
||||
# Protect files and directories from prying eyes.
|
||||
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$">
|
||||
Order allow,deny
|
||||
</FilesMatch>
|
||||
|
||||
# Don't show directory listings for URLs which map to a directory.
|
||||
Options -Indexes
|
||||
|
||||
# Follow symbolic links in this directory.
|
||||
Options +FollowSymLinks
|
||||
|
||||
# Make Drupal handle any 404 errors.
|
||||
ErrorDocument 404 /index.php
|
||||
|
||||
# Set the default handler.
|
||||
DirectoryIndex index.php index.html index.htm
|
||||
|
||||
# Override PHP settings that cannot be changed at runtime. See
|
||||
# sites/default/default.settings.php and drupal_environment_initialize() in
|
||||
# includes/bootstrap.inc for settings that can be changed at runtime.
|
||||
|
||||
# PHP 5, Apache 1 and 2.
|
||||
<IfModule mod_php5.c>
|
||||
php_flag magic_quotes_gpc off
|
||||
php_flag magic_quotes_sybase off
|
||||
php_flag register_globals off
|
||||
php_flag session.auto_start off
|
||||
php_value mbstring.http_input pass
|
||||
php_value mbstring.http_output pass
|
||||
php_flag mbstring.encoding_translation off
|
||||
</IfModule>
|
||||
|
||||
# Requires mod_expires to be enabled.
|
||||
<IfModule mod_expires.c>
|
||||
# Enable expirations.
|
||||
ExpiresActive On
|
||||
|
||||
# Cache all files for 2 weeks after access (A).
|
||||
ExpiresDefault A1209600
|
||||
|
||||
<FilesMatch \.php$>
|
||||
# Do not allow PHP scripts to be cached unless they explicitly send cache
|
||||
# headers themselves. Otherwise all scripts would have to overwrite the
|
||||
# headers set by mod_expires if they want another caching behavior. This may
|
||||
# fail if an error occurs early in the bootstrap process, and it may cause
|
||||
# problems if a non-Drupal PHP file is installed in a subdirectory.
|
||||
ExpiresActive Off
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# Various rewrite rules.
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
|
||||
# Set "protossl" to "s" if we were accessed via https://. This is used later
|
||||
# if you enable "www." stripping or enforcement, in order to ensure that
|
||||
# you don't bounce between http and https.
|
||||
RewriteRule ^ - [E=protossl]
|
||||
RewriteCond %{HTTPS} on
|
||||
RewriteRule ^ - [E=protossl:s]
|
||||
|
||||
# Make sure Authorization HTTP header is available to PHP
|
||||
# even when running as CGI or FastCGI.
|
||||
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Block access to "hidden" directories whose names begin with a period. This
|
||||
# includes directories used by version control systems such as Subversion or
|
||||
# Git to store control files. Files whose names begin with a period, as well
|
||||
# as the control files used by CVS, are protected by the FilesMatch directive
|
||||
# above.
|
||||
#
|
||||
# NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is
|
||||
# not possible to block access to entire directories from .htaccess, because
|
||||
# <DirectoryMatch> is not allowed here.
|
||||
#
|
||||
# If you do not have mod_rewrite installed, you should remove these
|
||||
# directories from your webroot or otherwise protect them from being
|
||||
# downloaded.
|
||||
RewriteRule "(^|/)\." - [F]
|
||||
|
||||
# If your site can be accessed both with and without the 'www.' prefix, you
|
||||
# can use one of the following settings to redirect users to your preferred
|
||||
# URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
|
||||
#
|
||||
# To redirect all users to access the site WITH the 'www.' prefix,
|
||||
# (http://example.com/... will be redirected to http://www.example.com/...)
|
||||
# uncomment the following:
|
||||
# RewriteCond %{HTTP_HOST} .
|
||||
# RewriteCond %{HTTP_HOST} !^www\. [NC]
|
||||
# RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# To redirect all users to access the site WITHOUT the 'www.' prefix,
|
||||
# (http://www.example.com/... will be redirected to http://example.com/...)
|
||||
# uncomment the following:
|
||||
# RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
# RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# redirect all materiau domains to materio.com
|
||||
RewriteCond %{HTTP_HOST} ^basebeta\.materio\.com [OR]
|
||||
RewriteCond %{HTTP_HOST} ^www\.materio\.fr [OR]
|
||||
RewriteCond %{HTTP_HOST} ^materio\.fr [OR]
|
||||
RewriteCond %{HTTP_HOST} ^www\.materio\.net [OR]
|
||||
RewriteCond %{HTTP_HOST} ^materio\.net [OR]
|
||||
RewriteCond %{HTTP_HOST} ^www\.materio\.eu [OR]
|
||||
RewriteCond %{HTTP_HOST} ^materio\.eu [OR]
|
||||
RewriteCond %{HTTP_HOST} ^www\.materio\.com
|
||||
# RewriteRule ^ http%{ENV:protossl}://materio.com%{REQUEST_URI} [L,R=301]
|
||||
RewriteRule ^ https://materio.com%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Redirect to HTTPS
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteCond %{HTTP:X-Forwarded-Proto} !https
|
||||
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Modify the RewriteBase if you are using Drupal in a subdirectory or in a
|
||||
# VirtualDocumentRoot and the rewrite rules are not working properly.
|
||||
# For example if your site is at http://example.com/drupal uncomment and
|
||||
# modify the following line:
|
||||
# RewriteBase /drupal
|
||||
#
|
||||
# If your site is running in a VirtualDocumentRoot at http://example.com/,
|
||||
# uncomment the following line:
|
||||
RewriteBase /
|
||||
|
||||
# Pass all requests not referring directly to files in the filesystem to
|
||||
# index.php. Clean URLs are handled in drupal_environment_initialize().
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} !=/favicon.ico
|
||||
RewriteRule ^ index.php [L]
|
||||
|
||||
# Rules to correctly serve gzip compressed CSS and JS files.
|
||||
# Requires both mod_rewrite and mod_headers to be enabled.
|
||||
<IfModule mod_headers.c>
|
||||
# Serve gzip compressed CSS files if they exist and the client accepts gzip.
|
||||
RewriteCond %{HTTP:Accept-encoding} gzip
|
||||
RewriteCond %{REQUEST_FILENAME}\.gz -s
|
||||
RewriteRule ^(.*)\.css $1\.css\.gz [QSA]
|
||||
|
||||
# Serve gzip compressed JS files if they exist and the client accepts gzip.
|
||||
RewriteCond %{HTTP:Accept-encoding} gzip
|
||||
RewriteCond %{REQUEST_FILENAME}\.gz -s
|
||||
RewriteRule ^(.*)\.js $1\.js\.gz [QSA]
|
||||
|
||||
# Serve correct content types, and prevent mod_deflate double gzip.
|
||||
RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1]
|
||||
RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1]
|
||||
|
||||
<FilesMatch "(\.js\.gz|\.css\.gz)$">
|
||||
# Serve correct encoding type.
|
||||
Header set Content-Encoding gzip
|
||||
# Force proxies to cache gzipped & non-gzipped css/js files separately.
|
||||
Header append Vary Accept-Encoding
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# Add headers to all responses.
|
||||
<IfModule mod_headers.c>
|
||||
# Disable content sniffing, since it's an attack vector.
|
||||
Header always set X-Content-Type-Options nosniff
|
||||
</IfModule>
|
||||
|
||||
# from boiler plate V5.02
|
||||
|
||||
# ######################################################################
|
||||
# # MEDIA TYPES AND CHARACTER ENCODINGS #
|
||||
# ######################################################################
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Media types |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Serve resources with the proper media types (f.k.a. MIME types).
|
||||
#
|
||||
# https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addtype
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
|
||||
# Data interchange
|
||||
|
||||
AddType application/atom+xml atom
|
||||
AddType application/json json map topojson
|
||||
AddType application/ld+json jsonld
|
||||
AddType application/rss+xml rss
|
||||
AddType application/vnd.geo+json geojson
|
||||
AddType application/xml rdf xml
|
||||
|
||||
|
||||
# JavaScript
|
||||
|
||||
# Normalize to standard type.
|
||||
# https://tools.ietf.org/html/rfc4329#section-7.2
|
||||
|
||||
AddType application/javascript js
|
||||
|
||||
|
||||
# Manifest files
|
||||
|
||||
AddType application/manifest+json webmanifest
|
||||
AddType application/x-web-app-manifest+json webapp
|
||||
AddType text/cache-manifest appcache
|
||||
|
||||
|
||||
# Media files
|
||||
|
||||
AddType audio/mp4 f4a f4b m4a
|
||||
AddType audio/ogg oga ogg opus
|
||||
AddType image/bmp bmp
|
||||
AddType image/svg+xml svg svgz
|
||||
AddType image/webp webp
|
||||
AddType video/mp4 f4v f4p m4v mp4
|
||||
AddType video/ogg ogv
|
||||
AddType video/webm webm
|
||||
AddType video/x-flv flv
|
||||
|
||||
# Serving `.ico` image files with a different media type
|
||||
# prevents Internet Explorer from displaying then as images:
|
||||
# https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee
|
||||
|
||||
AddType image/x-icon cur ico
|
||||
|
||||
|
||||
# Web fonts
|
||||
|
||||
AddType application/font-woff woff
|
||||
AddType application/font-woff2 woff2
|
||||
AddType application/vnd.ms-fontobject eot
|
||||
|
||||
# Browsers usually ignore the font media types and simply sniff
|
||||
# the bytes to figure out the font type.
|
||||
# https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
|
||||
#
|
||||
# However, Blink and WebKit based browsers will show a warning
|
||||
# in the console if the following font types are served with any
|
||||
# other media types.
|
||||
|
||||
AddType application/x-font-ttf ttc ttf
|
||||
AddType font/opentype otf
|
||||
|
||||
|
||||
# Other
|
||||
|
||||
AddType application/octet-stream safariextz
|
||||
AddType application/x-bb-appworld bbaw
|
||||
AddType application/x-chrome-extension crx
|
||||
AddType application/x-opera-extension oex
|
||||
AddType application/x-xpinstall xpi
|
||||
AddType text/vcard vcard vcf
|
||||
AddType text/vnd.rim.location.xloc xloc
|
||||
AddType text/vtt vtt
|
||||
AddType text/x-component htc
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Character encodings |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Serve all resources labeled as `text/html` or `text/plain`
|
||||
# with the media type `charset` parameter set to `UTF-8`.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
|
||||
|
||||
AddDefaultCharset utf-8
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Serve the following file types with the media type `charset`
|
||||
# parameter set to `UTF-8`.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
AddCharset utf-8 .atom \
|
||||
.bbaw \
|
||||
.css \
|
||||
.geojson \
|
||||
.js \
|
||||
.json \
|
||||
.jsonld \
|
||||
.manifest \
|
||||
.rdf \
|
||||
.rss \
|
||||
.topojson \
|
||||
.vtt \
|
||||
.webapp \
|
||||
.webmanifest \
|
||||
.xloc \
|
||||
.xml
|
||||
</IfModule>
|
||||
@@ -0,0 +1,73 @@
|
||||
From 1f82c4ef9b474a12d160813f767128d645f524cc Mon Sep 17 00:00:00 2001
|
||||
From: Bachir Soussi Chiadmi <bachir@g-u-i.me>
|
||||
Date: Sun, 19 Apr 2015 21:04:36 +0200
|
||||
Subject: [PATCH] repatched entityrefeerence module
|
||||
|
||||
---
|
||||
...ityReference_SelectionHandler_Generic.class.php | 43 +++++++++++++++++++---
|
||||
1 file changed, 38 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php b/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php
|
||||
index 444a74c..660fe3d 100644
|
||||
--- a/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php
|
||||
+++ b/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php
|
||||
@@ -370,6 +370,28 @@ class EntityReference_SelectionHandler_Generic_node extends EntityReference_Sele
|
||||
* This only exists to workaround core bugs.
|
||||
*/
|
||||
class EntityReference_SelectionHandler_Generic_user extends EntityReference_SelectionHandler_Generic {
|
||||
+
|
||||
+ /**
|
||||
+ * Implements EntityReferenceHandler::settingsForm().
|
||||
+ */
|
||||
+ public static function settingsForm($field, $instance) {
|
||||
+ $settings = $field['settings']['handler_settings'];
|
||||
+ $form = parent::settingsForm($field, $instance);
|
||||
+ $form['referenceable_roles'] = array(
|
||||
+ '#type' => 'checkboxes',
|
||||
+ '#title' => t('User roles that can be referenced'),
|
||||
+ '#default_value' => isset($settings['referenceable_roles']) ? array_filter($settings['referenceable_roles']) : array(),
|
||||
+ '#options' => user_roles(TRUE),
|
||||
+ );
|
||||
+ $form['referenceable_status'] = array(
|
||||
+ '#type' => 'checkboxes',
|
||||
+ '#title' => t('User status that can be referenced'),
|
||||
+ '#default_value' => isset($settings['referenceable_status']) ? array_filter($settings['referenceable_status']) : array('active' => 'active'),
|
||||
+ '#options' => array('active' => t('Active'), 'blocked' => t('Blocked')),
|
||||
+ );
|
||||
+ return $form;
|
||||
+ }
|
||||
+
|
||||
public function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityFieldQuery($match, $match_operator);
|
||||
|
||||
@@ -378,11 +400,22 @@ class EntityReference_SelectionHandler_Generic_user extends EntityReference_Sele
|
||||
$query->propertyCondition('name', $match, $match_operator);
|
||||
}
|
||||
|
||||
- // Adding the 'user_access' tag is sadly insufficient for users: core
|
||||
- // requires us to also know about the concept of 'blocked' and
|
||||
- // 'active'.
|
||||
- if (!user_access('administer users')) {
|
||||
- $query->propertyCondition('status', 1);
|
||||
+ $field = $this->field;
|
||||
+ $settings = $field['settings']['handler_settings'];
|
||||
+ $referenceable_roles = isset($settings['referenceable_roles']) ? array_filter($settings['referenceable_roles']) : array();
|
||||
+ $referenceable_status = isset($settings['referenceable_status']) ? array_filter($settings['referenceable_status']) : array('active' => 'active');
|
||||
+
|
||||
+ // If this filter is not filled, use the users access permissions.
|
||||
+ if (empty($referenceable_status)) {
|
||||
+ // Adding the 'user_access' tag is sadly insufficient for users: core
|
||||
+ // requires us to also know about the concept of 'blocked' and 'active'.
|
||||
+ if (!user_access('administer users')) {
|
||||
+ $query->propertyCondition('status', 1);
|
||||
+ }
|
||||
+ }
|
||||
+ elseif (count($referenceable_status) == 1) {
|
||||
+ $values = array('active' => 1, 'blocked' => 0);
|
||||
+ $query->propertyCondition('status', $values[key($referenceable_status)]);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
--
|
||||
2.3.5
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
From 7becdeff922748fb65a320903b0ee37268895dd1 Mon Sep 17 00:00:00 2001
|
||||
From: Bachir Soussi Chiadmi <bachir@g-u-i.me>
|
||||
Date: Sun, 19 Apr 2015 21:13:38 +0200
|
||||
Subject: [PATCH] repatched views module
|
||||
|
||||
---
|
||||
sites/all/modules/contrib/views/views/handlers/views_handler_field.inc | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/sites/all/modules/contrib/views/views/handlers/views_handler_field.inc b/sites/all/modules/contrib/views/views/handlers/views_handler_field.inc
|
||||
index 65210d9..c04fd46 100644
|
||||
--- a/sites/all/modules/contrib/views/views/handlers/views_handler_field.inc
|
||||
+++ b/sites/all/modules/contrib/views/views/handlers/views_handler_field.inc
|
||||
@@ -372,7 +372,7 @@ class views_handler_field extends views_handler {
|
||||
* Optional name of the field where the value is stored.
|
||||
*/
|
||||
function get_value($values, $field = NULL) {
|
||||
- $alias = isset($field) ? $this->aliases[$field] : $this->field_alias;
|
||||
+ $alias = isset($field) && isset($this->aliases[$field]) ? $this->aliases[$field] : $this->field_alias;
|
||||
if (isset($values->{$alias})) {
|
||||
return $values->{$alias};
|
||||
}
|
||||
--
|
||||
2.3.5
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
diff --git a/modules/user/user.module b/modules/user/user.module
|
||||
index bdfd36f..d33a528 100644
|
||||
--- a/modules/user/user.module
|
||||
+++ b/modules/user/user.module
|
||||
@@ -2328,14 +2328,26 @@ function user_external_login_register($name, $module) {
|
||||
* following properties:
|
||||
* - uid: The user ID number.
|
||||
* - login: The UNIX timestamp of the user's last login.
|
||||
+ * @param array $options
|
||||
+ * (optional) A keyed array of settings. Supported options are:
|
||||
+ * - langcode: A language code to be used when generating locale-sensitive
|
||||
+ * urls. If langcode is NULL the users preferred language is used.
|
||||
*
|
||||
* @return
|
||||
* A unique URL that provides a one-time log in for the user, from which
|
||||
* they can change their password.
|
||||
*/
|
||||
-function user_pass_reset_url($account) {
|
||||
+function user_pass_reset_url($account, $options = array()) {
|
||||
$timestamp = REQUEST_TIME;
|
||||
- return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
|
||||
+ $url_options = array('absolute' => TRUE);
|
||||
+ if (isset($options['langcode'])) {
|
||||
+ $languages = language_list();
|
||||
+ $url_options['language'] = $languages[$options['langcode']];
|
||||
+ }
|
||||
+ else {
|
||||
+ $url_options['language'] = user_preferred_language($account);
|
||||
+ }
|
||||
+ return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), $url_options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2347,6 +2359,10 @@ function user_pass_reset_url($account) {
|
||||
* - uid: The user ID number.
|
||||
* - pass: The hashed user password string.
|
||||
* - login: The UNIX timestamp of the user's last login.
|
||||
+ * @param array $options
|
||||
+ * (optional) A keyed array of settings. Supported options are:
|
||||
+ * - langcode: A language code to be used when generating locale-sensitive
|
||||
+ * urls. If langcode is NULL the users preferred language is used.
|
||||
*
|
||||
* @return
|
||||
* A unique URL that may be used to confirm the cancellation of the user
|
||||
@@ -2355,9 +2371,17 @@ function user_pass_reset_url($account) {
|
||||
* @see user_mail_tokens()
|
||||
* @see user_cancel_confirm()
|
||||
*/
|
||||
-function user_cancel_url($account) {
|
||||
+function user_cancel_url($account, $options = array()) {
|
||||
$timestamp = REQUEST_TIME;
|
||||
- return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
|
||||
+ $url_options = array('absolute' => TRUE);
|
||||
+ if (isset($options['langcode'])) {
|
||||
+ $languages = language_list();
|
||||
+ $url_options['language'] = $languages[$options['langcode']];
|
||||
+ }
|
||||
+ else {
|
||||
+ $url_options['language'] = user_preferred_language($account);
|
||||
+ }
|
||||
+ return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), $url_options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2843,7 +2867,7 @@ Your account on [site:name] has been canceled.
|
||||
if ($replace) {
|
||||
// We do not sanitize the token replacement, since the output of this
|
||||
// replacement is intended for an e-mail message, not a web browser.
|
||||
- return token_replace($text, $variables, array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
|
||||
+ return token_replace($text, $variables, array('language' => $language, 'langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
|
||||
}
|
||||
|
||||
return $text;
|
||||
@@ -2870,8 +2894,8 @@ Your account on [site:name] has been canceled.
|
||||
*/
|
||||
function user_mail_tokens(&$replacements, $data, $options) {
|
||||
if (isset($data['user'])) {
|
||||
- $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user']);
|
||||
- $replacements['[user:cancel-url]'] = user_cancel_url($data['user']);
|
||||
+ $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options);
|
||||
+ $replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/modules/user/user.test b/modules/user/user.test
|
||||
index 07be4c2..4536e0c 100644
|
||||
--- a/modules/user/user.test
|
||||
+++ b/modules/user/user.test
|
||||
@@ -2145,6 +2145,26 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
+ public function setUp() {
|
||||
+ parent::setUp('locale');
|
||||
+
|
||||
+ $account = $this->drupalCreateUser(array('access administration pages', 'administer languages'));
|
||||
+ $this->drupalLogin($account);
|
||||
+
|
||||
+ // Add language.
|
||||
+ $edit = array('langcode' => 'de');
|
||||
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
+
|
||||
+ // Enable URL language detection and selection.
|
||||
+ $edit = array('language[enabled][locale-url]' => 1);
|
||||
+ $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
|
||||
+
|
||||
+ // Reset static caching.
|
||||
+ drupal_static_reset('language_list');
|
||||
+ drupal_static_reset('locale_url_outbound_alter');
|
||||
+ drupal_static_reset('locale_language_url_rewrite_url');
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* Creates a user, then tests the tokens generated from it.
|
||||
*/
|
||||
@@ -2195,6 +2215,39 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase {
|
||||
$output = token_replace($input, array('user' => $account), array('language' => $language, 'sanitize' => FALSE));
|
||||
$this->assertEqual($output, $expected, format_string('Unsanitized user token %token replaced.', array('%token' => $input)));
|
||||
}
|
||||
+
|
||||
+ $languages = language_list();
|
||||
+
|
||||
+ // Generate login and cancel link.
|
||||
+ $tests = array();
|
||||
+ $tests['[user:one-time-login-url]'] = user_pass_reset_url($account);
|
||||
+ $tests['[user:cancel-url]'] = user_cancel_url($account);
|
||||
+
|
||||
+ // Generate tokens with interface language.
|
||||
+ $link = url('user', array('absolute' => TRUE));
|
||||
+ foreach ($tests as $input => $expected) {
|
||||
+ $output = token_replace($input, array('user' => $account), array('langcode' => $language->language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
|
||||
+ $this->assertTrue(strpos($output, $link) === 0, 'Generated URL is in interface language.');
|
||||
+ }
|
||||
+
|
||||
+ // Generate tokens with the user's preferred language.
|
||||
+ $edit['language'] = 'de';
|
||||
+ $account = user_save($account, $edit);
|
||||
+ $link = url('user', array('language' => $languages[$account->language], 'absolute' => TRUE));
|
||||
+ foreach ($tests as $input => $expected) {
|
||||
+ $output = token_replace($input, array('user' => $account), array('callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
|
||||
+ $this->assertTrue(strpos($output, $link) === 0, "Generated URL is in the user's preferred language.");
|
||||
+ }
|
||||
+
|
||||
+ // Generate tokens with one specific language.
|
||||
+ $link = url('user', array('language' => $languages['de'], 'absolute' => TRUE));
|
||||
+ foreach ($tests as $input => $expected) {
|
||||
+ foreach (array($user1, $user2) as $account) {
|
||||
+ $output = token_replace($input, array('user' => $account), array('langcode' => 'de', 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
|
||||
+ $this->assertTrue(strpos($output, $link) === 0, "Generated URL in in the requested language.");
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
}
|
||||
}
|
||||
|
||||
+2414
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
All Drupal code is Copyright 2001 - 2013 by the original authors.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or (at
|
||||
your option) any later version.
|
||||
|
||||
This program 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 General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program as the file LICENSE.txt; if not, please see
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
|
||||
|
||||
Drupal is a registered trademark of Dries Buytaert.
|
||||
|
||||
Drupal includes works under other copyright notices and distributed
|
||||
according to the terms of the GNU General Public License or a compatible
|
||||
license, including:
|
||||
|
||||
Javascript
|
||||
|
||||
Farbtastic - Copyright (c) 2010 Matt Farina
|
||||
|
||||
jQuery - Copyright (c) 2010 John Resig
|
||||
|
||||
jQuery BBQ - Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
|
||||
jQuery Cookie - Copyright (c) 2006 Klaus Hartl
|
||||
|
||||
jQuery Form - Copyright (c) 2010 Mike Alsup
|
||||
|
||||
jQuery Once - Copyright (c) 2009 Konstantin K�fer
|
||||
|
||||
jQuery UI - Copyright (c) 2010 by the original authors
|
||||
(http://jqueryui.com/about)
|
||||
|
||||
Sizzle.js - Copyright (c) 2010 The Dojo Foundation (http://sizzlejs.com/)
|
||||
|
||||
PHP
|
||||
|
||||
ArchiveTar - Copyright (c) 1997 - 2008 Vincent Blavet
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
CREATE THE MySQL DATABASE
|
||||
--------------------------
|
||||
|
||||
This step is only necessary if you don't already have a database set up (e.g.,
|
||||
by your host). In the following examples, 'username' is an example MySQL user
|
||||
which has the CREATE and GRANT privileges. Use the appropriate user name for
|
||||
your system.
|
||||
|
||||
First, you must create a new database for your Drupal site (here, 'databasename'
|
||||
is the name of the new database):
|
||||
|
||||
mysqladmin -u username -p create databasename
|
||||
|
||||
MySQL will prompt for the 'username' database password and then create the
|
||||
initial database files. Next you must log in and set the access database rights:
|
||||
|
||||
mysql -u username -p
|
||||
|
||||
Again, you will be asked for the 'username' database password. At the MySQL
|
||||
prompt, enter the following command:
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
|
||||
CREATE TEMPORARY TABLES ON databasename.*
|
||||
TO 'username'@'localhost' IDENTIFIED BY 'password';
|
||||
|
||||
where:
|
||||
|
||||
'databasename' is the name of your database
|
||||
'username' is the username of your MySQL account
|
||||
'localhost' is the web server host where Drupal is installed
|
||||
'password' is the password required for that username
|
||||
|
||||
Note: Unless the database user/host combination for your Drupal installation
|
||||
has all of the privileges listed above (except possibly CREATE TEMPORARY TABLES,
|
||||
which is currently only used by Drupal core automated tests and some
|
||||
contributed modules), you will not be able to install or run Drupal.
|
||||
|
||||
If successful, MySQL will reply with:
|
||||
|
||||
Query OK, 0 rows affected
|
||||
|
||||
If the InnoDB storage engine is available, it will be used for all database
|
||||
tables. InnoDB provides features over MyISAM such as transaction support,
|
||||
row-level locks, and consistent non-locking reads.
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
CREATE THE PostgreSQL DATABASE
|
||||
------------------------------
|
||||
|
||||
Note that the database must be created with UTF-8 (Unicode) encoding.
|
||||
|
||||
1. CREATE DATABASE USER
|
||||
|
||||
This step is only necessary if you don't already have a user set up (e.g., by
|
||||
your host), or want to create a new user for use with Drupal only. The
|
||||
following command creates a new user named 'username' and asks for a password
|
||||
for that user:
|
||||
|
||||
createuser --pwprompt --encrypted --no-createrole --no-createdb username
|
||||
|
||||
If there are no errors, then the command was successful.
|
||||
|
||||
2. CREATE DRUPAL DATABASE
|
||||
|
||||
This step is only necessary if you don't already have a database set up
|
||||
(e.g., by your host) or want to create a new database for use with Drupal
|
||||
only. The following command creates a new database named 'databasename',
|
||||
which is owned by the previously created 'username':
|
||||
|
||||
createdb --encoding=UTF8 --owner=username databasename
|
||||
|
||||
If there are no errors, then the command was successful.
|
||||
|
||||
3. CREATE SCHEMA OR SCHEMAS (Optional advanced step)
|
||||
|
||||
Drupal will run across different schemas within your database if you so wish.
|
||||
By default, Drupal runs inside the 'public' schema but you can use $db_prefix
|
||||
inside settings.php to define a schema for Drupal to run inside of, or
|
||||
specify tables that are shared inside of a separate schema. Drupal will not
|
||||
create schemas for you. In fact, the user that Drupal runs as should not be
|
||||
allowed to do this. You'll need to execute the SQL below as a superuser,
|
||||
replace 'username' with the username that Drupal uses to connect to
|
||||
PostgreSQL, and replace 'schema_name' with a schema name you wish to use,
|
||||
such as 'shared':
|
||||
|
||||
CREATE SCHEMA schema_name AUTHORIZATION username;
|
||||
|
||||
Do this for as many schemas as you need. See default.settings.php for
|
||||
instructions on how to set which tables use which schemas.
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
SQLITE REQUIREMENTS
|
||||
-------------------
|
||||
|
||||
To use SQLite with your Drupal installation, the following requirements must be
|
||||
met: Server has PHP 5.2 or later with PDO, and the PDO SQLite driver must be
|
||||
enabled.
|
||||
|
||||
SQLITE DATABASE CREATION
|
||||
------------------------
|
||||
|
||||
The Drupal installer will create the SQLite database for you. The only
|
||||
requirement is that the installer must have write permissions to the directory
|
||||
where the database file resides. This directory (not just the database file) also
|
||||
has to remain writeable by the web server going forward for SQLite to continue to
|
||||
be able to operate.
|
||||
|
||||
On the "Database configuration" form in the "Database file" field, you must
|
||||
supply the exact path to where you wish your database file to reside. It is
|
||||
strongly suggested that you choose a path that is outside of the webroot, yet
|
||||
ensure that the directory is writeable by the web server.
|
||||
|
||||
If you must place your database file in your webroot, you could try using the
|
||||
following in your "Database file" field:
|
||||
|
||||
sites/default/files/.ht.sqlite
|
||||
|
||||
Note: The .ht in the name will tell Apache to prevent the database from being
|
||||
downloaded. Please check that the file is, indeed, protected by your webserver.
|
||||
If not, please consult the documentation of your webserver on how to protect a
|
||||
file from downloading.
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
|
||||
CONTENTS OF THIS FILE
|
||||
---------------------
|
||||
|
||||
* Requirements and notes
|
||||
* Optional server requirements
|
||||
* Installation
|
||||
* Building and customizing your site
|
||||
* Multisite configuration
|
||||
* More information
|
||||
|
||||
REQUIREMENTS AND NOTES
|
||||
----------------------
|
||||
|
||||
Drupal requires:
|
||||
|
||||
- A web server. Apache (version 2.0 or greater) is recommended.
|
||||
- PHP 5.2.4 (or greater) (http://www.php.net/).
|
||||
- One of the following databases:
|
||||
- MySQL 5.0.15 (or greater) (http://www.mysql.com/).
|
||||
- MariaDB 5.1.44 (or greater) (http://mariadb.org/). MariaDB is a fully
|
||||
compatible drop-in replacement for MySQL.
|
||||
- Percona Server 5.1.70 (or greater) (http://www.percona.com/). Percona
|
||||
Server is a backwards-compatible replacement for MySQL.
|
||||
- PostgreSQL 8.3 (or greater) (http://www.postgresql.org/).
|
||||
- SQLite 3.3.7 (or greater) (http://www.sqlite.org/).
|
||||
|
||||
For more detailed information about Drupal requirements, including a list of
|
||||
PHP extensions and configurations that are required, see "System requirements"
|
||||
(http://drupal.org/requirements) in the Drupal.org online documentation.
|
||||
|
||||
For detailed information on how to configure a test server environment using a
|
||||
variety of operating systems and web servers, see "Local server setup"
|
||||
(http://drupal.org/node/157602) in the Drupal.org online documentation.
|
||||
|
||||
Note that all directories mentioned in this document are always relative to the
|
||||
directory of your Drupal installation, and commands are meant to be run from
|
||||
this directory (except for the initial commands that create that directory).
|
||||
|
||||
OPTIONAL SERVER REQUIREMENTS
|
||||
----------------------------
|
||||
|
||||
- If you want to use Drupal's "Clean URLs" feature on an Apache web server, you
|
||||
will need the mod_rewrite module and the ability to use local .htaccess
|
||||
files. For Clean URLs support on IIS, see "Clean URLs with IIS"
|
||||
(http://drupal.org/node/3854) in the Drupal.org online documentation.
|
||||
|
||||
- If you plan to use XML-based services such as RSS aggregation, you will need
|
||||
PHP's XML extension. This extension is enabled by default on most PHP
|
||||
installations.
|
||||
|
||||
- To serve gzip compressed CSS and JS files on an Apache web server, you will
|
||||
need the mod_headers module and the ability to use local .htaccess files.
|
||||
|
||||
- Some Drupal functionality (e.g., checking whether Drupal and contributed
|
||||
modules need updates, RSS aggregation, etc.) require that the web server be
|
||||
able to go out to the web and download information. If you want to use this
|
||||
functionality, you need to verify that your hosting provider or server
|
||||
configuration allows the web server to initiate outbound connections. Most web
|
||||
hosting setups allow this.
|
||||
|
||||
INSTALLATION
|
||||
------------
|
||||
|
||||
1. Download and extract Drupal.
|
||||
|
||||
You can obtain the latest Drupal release from http://drupal.org -- the files
|
||||
are available in .tar.gz and .zip formats and can be extracted using most
|
||||
compression tools.
|
||||
|
||||
To download and extract the files, on a typical Unix/Linux command line, use
|
||||
the following commands (assuming you want version x.y of Drupal in .tar.gz
|
||||
format):
|
||||
|
||||
wget http://drupal.org/files/projects/drupal-x.y.tar.gz
|
||||
tar -zxvf drupal-x.y.tar.gz
|
||||
|
||||
This will create a new directory drupal-x.y/ containing all Drupal files and
|
||||
directories. Then, to move the contents of that directory into a directory
|
||||
within your web server's document root or your public HTML directory,
|
||||
continue with this command:
|
||||
|
||||
mv drupal-x.y/* drupal-x.y/.htaccess /path/to/your/installation
|
||||
|
||||
2. Optionally, download a translation.
|
||||
|
||||
By default, Drupal is installed in English, and further languages may be
|
||||
installed later. If you prefer to install Drupal in another language
|
||||
initially:
|
||||
|
||||
- Download a translation file for the correct Drupal version and language
|
||||
from the translation server: http://localize.drupal.org/translate/downloads
|
||||
|
||||
- Place the file into your installation profile's translations directory.
|
||||
For instance, if you are using the Standard installation profile,
|
||||
move the .po file into the directory:
|
||||
|
||||
profiles/standard/translations/
|
||||
|
||||
For detailed instructions, visit http://drupal.org/localize
|
||||
|
||||
3. Create the Drupal database.
|
||||
|
||||
Because Drupal stores all site information in a database, you must create
|
||||
this database in order to install Drupal, and grant Drupal certain database
|
||||
privileges (such as the ability to create tables). For details, consult
|
||||
INSTALL.mysql.txt, INSTALL.pgsql.txt, or INSTALL.sqlite.txt. You may also
|
||||
need to consult your web hosting provider for instructions specific to your
|
||||
web host.
|
||||
|
||||
Take note of the username, password, database name, and hostname as you
|
||||
create the database. You will enter this information during the install.
|
||||
|
||||
4. Run the install script.
|
||||
|
||||
To run the install script, point your browser to the base URL of your
|
||||
website (e.g., http://www.example.com).
|
||||
|
||||
You will be guided through several screens to set up the database, add the
|
||||
site maintenance account (the first user, also known as user/1), and provide
|
||||
basic web site settings.
|
||||
|
||||
During installation, several files and directories need to be created, which
|
||||
the install script will try to do automatically. However, on some hosting
|
||||
environments, manual steps are required, and the install script will tell
|
||||
you that it cannot proceed until you fix certain issues. This is normal and
|
||||
does not indicate a problem with your server.
|
||||
|
||||
The most common steps you may need to perform are:
|
||||
|
||||
a. Missing files directory.
|
||||
|
||||
The install script will attempt to create a file storage directory in
|
||||
the default location at sites/default/files (the location of the files
|
||||
directory may be changed after Drupal is installed).
|
||||
|
||||
If auto-creation fails, you can make it work by changing permissions on
|
||||
the sites/default directory so that the web server can create the files
|
||||
directory within it for you. (If you are creating a multisite
|
||||
installation, substitute the correct sites directory for sites/default;
|
||||
see the Multisite Configuration section of this file, below.)
|
||||
|
||||
For example, on a Unix/Linux command line, you can grant everyone
|
||||
(including the web server) permission to write to the sites/default
|
||||
directory with this command:
|
||||
|
||||
chmod a+w sites/default
|
||||
|
||||
Be sure to set the permissions back after the installation is finished!
|
||||
Sample command:
|
||||
|
||||
chmod go-w sites/default
|
||||
|
||||
Alternatively, instead of allowing the web server to create the files
|
||||
directory for you as described above, you can create it yourself. Sample
|
||||
commands from a Unix/Linux command line:
|
||||
|
||||
mkdir sites/default/files
|
||||
chmod a+w sites/default/files
|
||||
|
||||
b. Missing settings file.
|
||||
|
||||
Drupal will try to automatically create a settings.php configuration file,
|
||||
which is normally in the directory sites/default (to avoid problems when
|
||||
upgrading, Drupal is not packaged with this file). If auto-creation fails,
|
||||
you will need to create this file yourself, using the file
|
||||
sites/default/default.settings.php as a template.
|
||||
|
||||
For example, on a Unix/Linux command line, you can make a copy of the
|
||||
default.settings.php file with the command:
|
||||
|
||||
cp sites/default/default.settings.php sites/default/settings.php
|
||||
|
||||
Next, grant write privileges to the file to everyone (including the web
|
||||
server) with the command:
|
||||
|
||||
chmod a+w sites/default/settings.php
|
||||
|
||||
Be sure to set the permissions back after the installation is finished!
|
||||
Sample command:
|
||||
|
||||
chmod go-w sites/default/settings.php
|
||||
|
||||
c. Write permissions after install.
|
||||
|
||||
The install script will attempt to write-protect the settings.php file and
|
||||
the sites/default directory after saving your configuration. If this
|
||||
fails, you will be notified, and you can do it manually. Sample commands
|
||||
from a Unix/Linux command line:
|
||||
|
||||
chmod go-w sites/default/settings.php
|
||||
chmod go-w sites/default
|
||||
|
||||
5. Verify that the site is working.
|
||||
|
||||
When the install script finishes, you will be logged in with the site
|
||||
maintenance account on a "Welcome" page. If the default Drupal theme is not
|
||||
displaying properly and links on the page result in "Page Not Found" errors,
|
||||
you may be experiencing problems with clean URLs. Visit
|
||||
http://drupal.org/getting-started/clean-urls to troubleshoot.
|
||||
|
||||
6. Change file system storage settings (optional).
|
||||
|
||||
The files directory created in step 4 is the default file system path used to
|
||||
store all uploaded files, as well as some temporary files created by
|
||||
Drupal. After installation, you can modify the file system path to store
|
||||
uploaded files in a different location.
|
||||
|
||||
It is not necessary to modify this path, but you may wish to change it if:
|
||||
|
||||
- Your site runs multiple Drupal installations from a single codebase (modify
|
||||
the file system path of each installation to a different directory so that
|
||||
uploads do not overlap between installations).
|
||||
|
||||
- Your site runs on a number of web servers behind a load balancer or reverse
|
||||
proxy (modify the file system path on each server to point to a shared file
|
||||
repository).
|
||||
|
||||
- You want to restrict access to uploaded files.
|
||||
|
||||
To modify the file system path:
|
||||
|
||||
a. Ensure that the new location for the path exists and is writable by the
|
||||
web server. For example, to create a new directory named uploads and grant
|
||||
write permissions, use the following commands on a Unix/Linux command
|
||||
line:
|
||||
|
||||
mkdir uploads
|
||||
chmod a+w uploads
|
||||
|
||||
b. Navigate to Administration > Configuration > Media > File system, and
|
||||
enter the desired path. Note that if you want to use private file storage,
|
||||
you need to first enter the path for private files and save the
|
||||
configuration, and then change the "Default download method" setting and
|
||||
save again.
|
||||
|
||||
Changing the file system path after files have been uploaded may cause
|
||||
unexpected problems on an existing site. If you modify the file system path
|
||||
on an existing site, remember to copy all files from the original location
|
||||
to the new location.
|
||||
|
||||
7. Revoke documentation file permissions (optional).
|
||||
|
||||
Some administrators suggest making the documentation files, especially
|
||||
CHANGELOG.txt, non-readable so that the exact version of Drupal you are
|
||||
running is slightly more difficult to determine. If you wish to implement
|
||||
this optional security measure, from a Unix/Linux command line you can use
|
||||
the following command:
|
||||
|
||||
chmod a-r CHANGELOG.txt
|
||||
|
||||
Note that the example only affects CHANGELOG.txt. To completely hide all
|
||||
documentation files from public view, repeat this command for each of the
|
||||
Drupal documentation files in the installation directory, substituting the
|
||||
name of each file for CHANGELOG.txt in the example.
|
||||
|
||||
For more information on setting file permissions, see "Modifying Linux,
|
||||
Unix, and Mac file permissions" (http://drupal.org/node/202483) or
|
||||
"Modifying Windows file permissions" (http://drupal.org/node/202491) in the
|
||||
Drupal.org online documentation.
|
||||
|
||||
8. Set up independent "cron" maintenance jobs.
|
||||
|
||||
Many Drupal modules have tasks that must be run periodically, including the
|
||||
Search module (building and updating the index used for keyword searching),
|
||||
the Aggregator module (retrieving feeds from other sites), and the System
|
||||
module (performing routine maintenance and pruning of database tables). These
|
||||
tasks are known as "cron maintenance tasks", named after the Unix/Linux
|
||||
"cron" utility.
|
||||
|
||||
When you install Drupal, its built-in cron feature is enabled, which
|
||||
automatically runs the cron tasks periodically, triggered by people visiting
|
||||
pages of your site. You can configure the built-in cron feature by navigating
|
||||
to Administration > Configuration > System > Cron.
|
||||
|
||||
It is also possible to run the cron tasks independent of site visits; this is
|
||||
recommended for most sites. To do this, you will need to set up an automated
|
||||
process to visit the page cron.php on your site, which executes the cron
|
||||
tasks.
|
||||
|
||||
The URL of the cron.php page requires a "cron key" to protect against
|
||||
unauthorized access. Your site's cron key is automatically generated during
|
||||
installation and is specific to your site. The full URL of the page, with the
|
||||
cron key, is available in the "Cron maintenance tasks" section of the Status
|
||||
report page at Administration > Reports > Status report.
|
||||
|
||||
As an example for how to set up this automated process, you can use the
|
||||
crontab utility on Unix/Linux systems. The following crontab line uses the
|
||||
wget command to visit the cron.php page, and runs each hour, on the hour:
|
||||
|
||||
0 * * * * wget -O - -q -t 1 http://example.com/cron.php?cron_key=YOURKEY
|
||||
|
||||
Replace the text "http://example.com/cron.php?cron_key=YOURKEY" in the
|
||||
example with the full URL displayed under "Cron maintenance tasks" on the
|
||||
"Status report" page.
|
||||
|
||||
More information about cron maintenance tasks is available at
|
||||
http://drupal.org/cron, and sample cron shell scripts can be found in the
|
||||
scripts/ directory. (Note that these scripts must be customized like the
|
||||
above example, to add your site-specific cron key and domain name.)
|
||||
|
||||
BUILDING AND CUSTOMIZING YOUR SITE
|
||||
----------------------------------
|
||||
|
||||
A new installation of Drupal defaults to a very basic configuration. To extend
|
||||
your site, you use "modules" and "themes". A module is a plugin that adds
|
||||
functionality to Drupal, while a theme changes the look of your site. The core
|
||||
of Drupal provides several optional modules and themes, and you can download
|
||||
more at http://drupal.org/project/modules and http://drupal.org/project/themes
|
||||
|
||||
Do not mix downloaded or custom modules and themes with Drupal's core modules
|
||||
and themes. Drupal's modules and themes are located in the top-level modules and
|
||||
themes directories, while the modules and themes you add to Drupal are normally
|
||||
placed in the sites/all/modules and sites/all/themes directories. If you run a
|
||||
multisite installation, you can also place modules and themes in the
|
||||
site-specific directories -- see the Multisite Configuration section, below.
|
||||
|
||||
Never edit Drupal's core modules and themes; instead, use the hooks available in
|
||||
the Drupal API. To modify the behavior of Drupal, develop a module as described
|
||||
at http://drupal.org/developing/modules. To modify the look of Drupal, create a
|
||||
subtheme as described at http://drupal.org/node/225125, or a completely new
|
||||
theme as described at http://drupal.org/documentation/theme
|
||||
|
||||
MULTISITE CONFIGURATION
|
||||
-----------------------
|
||||
|
||||
A single Drupal installation can host several Drupal-powered sites, each with
|
||||
its own individual configuration.
|
||||
|
||||
Additional site configurations are created in subdirectories within the 'sites'
|
||||
directory. Each subdirectory must have a 'settings.php' file, which specifies
|
||||
the configuration settings. The easiest way to create additional sites is to
|
||||
copy the 'default' directory and modify the 'settings.php' file as appropriate.
|
||||
The new directory name is constructed from the site's URL. The configuration for
|
||||
www.example.com could be in 'sites/example.com/settings.php' (note that 'www.'
|
||||
should be omitted if users can access your site at http://example.com/).
|
||||
|
||||
Sites do not have to have a different domain. You can also use subdomains and
|
||||
subdirectories for Drupal sites. For example, example.com, sub.example.com, and
|
||||
sub.example.com/site3 can all be defined as independent Drupal sites. The setup
|
||||
for a configuration such as this would look like the following:
|
||||
|
||||
sites/default/settings.php
|
||||
sites/example.com/settings.php
|
||||
sites/sub.example.com/settings.php
|
||||
sites/sub.example.com.site3/settings.php
|
||||
|
||||
When searching for a site configuration (for example www.sub.example.com/site3),
|
||||
Drupal will search for configuration files in the following order, using the
|
||||
first configuration it finds:
|
||||
|
||||
sites/www.sub.example.com.site3/settings.php
|
||||
sites/sub.example.com.site3/settings.php
|
||||
sites/example.com.site3/settings.php
|
||||
sites/www.sub.example.com/settings.php
|
||||
sites/sub.example.com/settings.php
|
||||
sites/example.com/settings.php
|
||||
sites/default/settings.php
|
||||
|
||||
If you are installing on a non-standard port, the port number is treated as the
|
||||
deepest subdomain. For example: http://www.example.com:8080/ could be loaded
|
||||
from sites/8080.www.example.com/. The port number will be removed according to
|
||||
the pattern above if no port-specific configuration is found, just like a real
|
||||
subdomain.
|
||||
|
||||
Each site configuration can have its own site-specific modules and themes in
|
||||
addition to those installed in the standard 'modules' and 'themes' directories.
|
||||
To use site-specific modules or themes, simply create a 'modules' or 'themes'
|
||||
directory within the site configuration directory. For example, if
|
||||
sub.example.com has a custom theme and a custom module that should not be
|
||||
accessible to other sites, the setup would look like this:
|
||||
|
||||
sites/sub.example.com/
|
||||
settings.php
|
||||
themes/custom_theme
|
||||
modules/custom_module
|
||||
|
||||
NOTE: for more information about multiple virtual hosts or the configuration
|
||||
settings, consult http://drupal.org/getting-started/6/install/multi-site
|
||||
|
||||
For more information on configuring Drupal's file system path in a multisite
|
||||
configuration, see step 6 above.
|
||||
|
||||
MORE INFORMATION
|
||||
----------------
|
||||
|
||||
- See the Drupal.org online documentation:
|
||||
http://drupal.org/documentation
|
||||
|
||||
- For a list of security announcements, see the "Security advisories" page at
|
||||
http://drupal.org/security (available as an RSS feed). This page also
|
||||
describes how to subscribe to these announcements via e-mail.
|
||||
|
||||
- For information about the Drupal security process, or to find out how to
|
||||
report a potential security issue to the Drupal security team, see the
|
||||
"Security team" page at http://drupal.org/security-team
|
||||
|
||||
- For information about the wide range of available support options, visit
|
||||
http://drupal.org and click on Community and Support in the top or bottom
|
||||
navigation.
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
|
||||
Drupal core is built and maintained by the Drupal project community. Everyone is
|
||||
encouraged to submit issues and changes (patches) to improve Drupal, and to
|
||||
contribute in other ways -- see https://www.drupal.org/contribute to find out
|
||||
how.
|
||||
|
||||
Branch maintainers
|
||||
------------------
|
||||
|
||||
The Drupal Core branch maintainers oversee the development of Drupal as a whole.
|
||||
The branch maintainers for Drupal 7 are:
|
||||
|
||||
- Dries Buytaert 'dries' https://www.drupal.org/u/dries
|
||||
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
|
||||
- (provisional) Drew Webber 'mcdruid' https://www.drupal.org/u/mcdruid
|
||||
|
||||
|
||||
Component maintainers
|
||||
---------------------
|
||||
|
||||
The Drupal Core component maintainers oversee the development of Drupal
|
||||
subsystems. See https://www.drupal.org/contribute/core-maintainers for more
|
||||
information on their responsibilities, and to find out how to become a component
|
||||
maintainer. Current component maintainers for Drupal 7:
|
||||
|
||||
Ajax system
|
||||
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
|
||||
- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos
|
||||
|
||||
Base system
|
||||
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
|
||||
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
|
||||
|
||||
Batch system
|
||||
- Yves Chedemois 'yched' https://www.drupal.org/u/yched
|
||||
|
||||
Cache system
|
||||
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
|
||||
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
|
||||
|
||||
Cron system
|
||||
- Derek Wright 'dww' https://www.drupal.org/u/dww
|
||||
|
||||
Database system
|
||||
- ?
|
||||
|
||||
- MySQL driver
|
||||
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
|
||||
|
||||
- PostgreSQL driver
|
||||
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
|
||||
- Josh Waihi 'fiasco' https://www.drupal.org/u/josh-waihi
|
||||
|
||||
- Sqlite driver
|
||||
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
|
||||
|
||||
Database update system
|
||||
- Ashok Modi 'BTMash' https://www.drupal.org/u/btmash
|
||||
|
||||
Entity system
|
||||
- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago
|
||||
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
|
||||
- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
|
||||
|
||||
File system
|
||||
- Andrew Morton 'drewish' https://www.drupal.org/u/drewish
|
||||
- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron
|
||||
|
||||
Form system
|
||||
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
|
||||
- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago
|
||||
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
|
||||
- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
|
||||
|
||||
Image system
|
||||
- Andrew Morton 'drewish' https://www.drupal.org/u/drewish
|
||||
- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch
|
||||
|
||||
Install system
|
||||
- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
|
||||
|
||||
JavaScript
|
||||
- Théodore Biadala 'nod_' https://www.drupal.org/u/nod_
|
||||
- Steve De Jonghe 'seutje' https://www.drupal.org/u/seutje
|
||||
|
||||
Language system
|
||||
- Francesco Placella 'plach' https://www.drupal.org/u/plach
|
||||
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
|
||||
|
||||
Lock system
|
||||
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
|
||||
|
||||
Mail system
|
||||
- ?
|
||||
|
||||
Markup
|
||||
- Jacine Luisi 'Jacine' https://www.drupal.org/u/jacine
|
||||
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
|
||||
|
||||
Menu system
|
||||
- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
|
||||
|
||||
Path system
|
||||
- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
|
||||
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
|
||||
|
||||
Render system
|
||||
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
|
||||
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
|
||||
- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
|
||||
|
||||
Theme system
|
||||
- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos
|
||||
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
|
||||
- Joon Park 'dvessel' https://www.drupal.org/u/dvessel
|
||||
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
|
||||
|
||||
Token system
|
||||
- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
|
||||
|
||||
XML-RPC system
|
||||
- Frederic G. Marand 'fgm' https://www.drupal.org/u/fgm
|
||||
|
||||
|
||||
Topic coordinators
|
||||
------------------
|
||||
|
||||
Accessibility
|
||||
- Everett Zufelt 'Everett Zufelt' https://www.drupal.org/u/everett-zufelt
|
||||
- Brandon Bowersox-Johnson 'bowersox' https://www.drupal.org/u/bowersox
|
||||
|
||||
Documentation
|
||||
- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon
|
||||
|
||||
Translations
|
||||
- Gerhard Killesreiter 'killes' https://www.drupal.org/u/gerhard-killesreiter
|
||||
|
||||
User experience and usability
|
||||
- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy
|
||||
- Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan
|
||||
|
||||
Node Access
|
||||
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
|
||||
- Ken Rickard 'agentrickard' https://www.drupal.org/u/agentrickard
|
||||
|
||||
|
||||
Security team
|
||||
-----------------
|
||||
|
||||
To report a security issue, see: https://www.drupal.org/security-team/report-issue
|
||||
|
||||
The Drupal security team provides Security Advisories for vulnerabilities,
|
||||
assists developers in resolving security issues, and provides security
|
||||
documentation. See https://www.drupal.org/security-team for more information.
|
||||
The security team lead is:
|
||||
|
||||
- Michael Hess 'mlhess' https://www.drupal.org/u/mlhess
|
||||
|
||||
|
||||
Module maintainers
|
||||
------------------
|
||||
|
||||
Aggregator module
|
||||
- ?
|
||||
|
||||
Block module
|
||||
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
|
||||
|
||||
Blog module
|
||||
- ?
|
||||
|
||||
Book module
|
||||
- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
|
||||
|
||||
Color module
|
||||
- ?
|
||||
|
||||
Comment module
|
||||
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
|
||||
|
||||
Contact module
|
||||
- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
|
||||
|
||||
Contextual module
|
||||
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
|
||||
|
||||
Dashboard module
|
||||
- ?
|
||||
|
||||
Database logging module
|
||||
- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey
|
||||
|
||||
Field module
|
||||
- Yves Chedemois 'yched' https://www.drupal.org/u/yched
|
||||
- Barry Jaspan 'bjaspan' https://www.drupal.org/u/bjaspan
|
||||
|
||||
Field UI module
|
||||
- Yves Chedemois 'yched' https://www.drupal.org/u/yched
|
||||
|
||||
File module
|
||||
- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron
|
||||
|
||||
Filter module
|
||||
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
|
||||
|
||||
Forum module
|
||||
- Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan
|
||||
|
||||
Help module
|
||||
- ?
|
||||
|
||||
Image module
|
||||
- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch
|
||||
|
||||
Locale module
|
||||
- Gábor Hojtsy 'Gábor Hojtsy' https://www.drupal.org/u/gábor-hojtsy
|
||||
|
||||
Menu module
|
||||
- ?
|
||||
|
||||
Node module
|
||||
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
|
||||
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
|
||||
|
||||
OpenID module
|
||||
- Vojtech Kusy 'wojtha' https://www.drupal.org/u/wojtha
|
||||
- Christian Schmidt 'c960657' https://www.drupal.org/u/c960657
|
||||
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
|
||||
|
||||
Overlay module
|
||||
- Katherine Senzee 'ksenzee' https://www.drupal.org/u/ksenzee
|
||||
|
||||
Path module
|
||||
- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
|
||||
|
||||
PHP module
|
||||
- ?
|
||||
|
||||
Poll module
|
||||
- Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu
|
||||
|
||||
Profile module
|
||||
- ?
|
||||
|
||||
RDF module
|
||||
- Stéphane Corlosquet 'scor' https://www.drupal.org/u/scor
|
||||
|
||||
Search module
|
||||
- Doug Green 'douggreen' https://www.drupal.org/u/douggreen
|
||||
|
||||
Shortcut module
|
||||
- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
|
||||
|
||||
Simpletest module
|
||||
- Jimmy Berry 'boombatower' https://www.drupal.org/u/boombatower
|
||||
|
||||
Statistics module
|
||||
- Tim Millwood 'timmillwood' https://www.drupal.org/u/timmillwood
|
||||
|
||||
Syslog module
|
||||
- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey
|
||||
|
||||
System module
|
||||
- ?
|
||||
|
||||
Taxonomy module
|
||||
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
|
||||
- Benjamin Doherty 'bangpound' https://www.drupal.org/u/bangpound
|
||||
|
||||
Toolbar module
|
||||
- ?
|
||||
|
||||
Tracker module
|
||||
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
|
||||
|
||||
Translation module
|
||||
- Francesco Placella 'plach' https://www.drupal.org/u/plach
|
||||
|
||||
Trigger module
|
||||
- ?
|
||||
|
||||
Update module
|
||||
- Derek Wright 'dww' https://www.drupal.org/u/dww
|
||||
|
||||
User module
|
||||
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
|
||||
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
|
||||
|
||||
|
||||
Theme maintainers
|
||||
-----------------
|
||||
|
||||
Bartik theme
|
||||
- Jen Simmons 'jensimmons' https://www.drupal.org/u/jensimmons
|
||||
- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz
|
||||
|
||||
Garland theme
|
||||
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
|
||||
|
||||
Seven theme
|
||||
- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz
|
||||
|
||||
Stark theme
|
||||
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
|
||||
@@ -0,0 +1,31 @@
|
||||
# Core Patches :
|
||||
- Implement language aware tokens for one time login link and cancel link
|
||||
https://www.drupal.org/node/1754162
|
||||
|
||||
//- est-ce que je dois faire ce patch ???
|
||||
//includes/entity.inc
|
||||
//see https://www.drupal.org/node/1003788
|
||||
|
||||
# Modules :
|
||||
Redis
|
||||
- https://www.drupal.org/project/redis/issues/3074189
|
||||
cer
|
||||
contentadminrelink
|
||||
entityreference
|
||||
feedback
|
||||
filter_path_alias
|
||||
flag_lists
|
||||
- https://www.drupal.org/project/flag_lists/issues/2114731
|
||||
login_tobogan (pd with field permission et donc field collection)
|
||||
- https://www.drupal.org/node/1365764#comment-10286257
|
||||
- logintoboggan-exempting_lt_preauth_role_from_user_permissions_js-1365764-52.patch
|
||||
- interdiff-1365764-23-52-do-not-test.diff
|
||||
node_export :
|
||||
- https://www.drupal.org/node/1869918
|
||||
- https://www.drupal.org/node/1911638
|
||||
termreferencetree
|
||||
?? views
|
||||
views_rss_media
|
||||
- https://www.drupal.org/node/2472409
|
||||
- https://www.drupal.org/project/views_rss_media/issues/2149287
|
||||
wysiwyg
|
||||
+116
-12
@@ -1,19 +1,123 @@
|
||||
|
||||
Welcome to Views 3. Please see the advanced help for more information.
|
||||
CONTENTS OF THIS FILE
|
||||
---------------------
|
||||
|
||||
If you're having trouble installing this module, please ensure that your
|
||||
tar program is not flattening the directory tree, truncating filenames
|
||||
or losing files.
|
||||
* About Drupal
|
||||
* Configuration and features
|
||||
* Installation profiles
|
||||
* Appearance
|
||||
* Developing for Drupal
|
||||
|
||||
Installing Views:
|
||||
ABOUT DRUPAL
|
||||
------------
|
||||
|
||||
Place the entirety of this directory in sites/all/modules/views
|
||||
You must also install the CTools module (http://www.drupal.org/project/ctools) to use Views.
|
||||
Drupal is an open source content management platform supporting a variety of
|
||||
websites ranging from personal weblogs to large community-driven websites. For
|
||||
more information, see the Drupal website at http://drupal.org/, and join the
|
||||
Drupal community at http://drupal.org/community.
|
||||
|
||||
Navigate to administer >> build >> modules. Enable Views and Views UI.
|
||||
Legal information about Drupal:
|
||||
* Know your rights when using Drupal:
|
||||
See LICENSE.txt in the same directory as this document.
|
||||
* Learn about the Drupal trademark and logo policy:
|
||||
http://drupal.com/trademark
|
||||
|
||||
If you're new to Views, try the Simple Views module which can create some
|
||||
often used Views for you, this might save you some time.
|
||||
CONFIGURATION AND FEATURES
|
||||
--------------------------
|
||||
|
||||
Here you can find many modules extending the functionality of Views:
|
||||
http://drupal.org/taxonomy/term/89
|
||||
Drupal core (what you get when you download and extract a drupal-x.y.tar.gz or
|
||||
drupal-x.y.zip file from http://drupal.org/project/drupal) has what you need to
|
||||
get started with your website. It includes several modules (extensions that add
|
||||
functionality) for common website features, such as managing content, user
|
||||
accounts, image uploading, and search. Core comes with many options that allow
|
||||
site-specific configuration. In addition to the core modules, there are
|
||||
thousands of contributed modules (for functionality not included with Drupal
|
||||
core) available for download.
|
||||
|
||||
More about configuration:
|
||||
* Install, upgrade, and maintain Drupal:
|
||||
See INSTALL.txt and UPGRADE.txt in the same directory as this document.
|
||||
* Learn about how to use Drupal to create your site:
|
||||
http://drupal.org/documentation
|
||||
* Download contributed modules to sites/all/modules to extend Drupal's
|
||||
functionality:
|
||||
http://drupal.org/project/modules
|
||||
* See also: "Developing for Drupal" for writing your own modules, below.
|
||||
|
||||
INSTALLATION PROFILES
|
||||
---------------------
|
||||
|
||||
Installation profiles define additional steps (such as enabling modules,
|
||||
defining content types, etc.) that run after the base installation provided
|
||||
by core when Drupal is first installed. There are two basic installation
|
||||
profiles provided with Drupal core.
|
||||
|
||||
Installation profiles from the Drupal community modify the installation process
|
||||
to provide a website for a specific use case, such as a CMS for media
|
||||
publishers, a web-based project tracking tool, or a full-fledged CRM for
|
||||
non-profit organizations raising money and accepting donations. They can be
|
||||
distributed as bare installation profiles or as "distributions". Distributions
|
||||
include Drupal core, the installation profile, and all other required
|
||||
extensions, such as contributed and custom modules, themes, and third-party
|
||||
libraries. Bare installation profiles require you to download Drupal Core and
|
||||
the required extensions separately; place the downloaded profile in the
|
||||
/profiles directory before you start the installation process. Note that the
|
||||
contents of this directory may be overwritten during updates of Drupal core;
|
||||
it is advised to keep code backups or use a version control system.
|
||||
|
||||
Additionally, modules and themes may be placed inside subdirectories in a
|
||||
specific installation profile such as profiles/your_site_profile/modules and
|
||||
profiles/your_site_profile/themes respectively to restrict their usage to only
|
||||
sites that were installed with that specific profile.
|
||||
|
||||
More about installation profiles and distributions:
|
||||
* Read about the difference between installation profiles and distributions:
|
||||
http://drupal.org/node/1089736
|
||||
* Download contributed installation profiles and distributions:
|
||||
http://drupal.org/project/distributions
|
||||
* Develop your own installation profile or distribution:
|
||||
http://drupal.org/developing/distributions
|
||||
|
||||
APPEARANCE
|
||||
----------
|
||||
|
||||
In Drupal, the appearance of your site is set by the theme (themes are
|
||||
extensions that set fonts, colors, and layout). Drupal core comes with several
|
||||
themes. More themes are available for download, and you can also create your own
|
||||
custom theme.
|
||||
|
||||
More about themes:
|
||||
* Download contributed themes to sites/all/themes to modify Drupal's
|
||||
appearance:
|
||||
http://drupal.org/project/themes
|
||||
* Develop your own theme:
|
||||
http://drupal.org/documentation/theme
|
||||
|
||||
DEVELOPING FOR DRUPAL
|
||||
---------------------
|
||||
|
||||
Drupal contains an extensive API that allows you to add to and modify the
|
||||
functionality of your site. The API consists of "hooks", which allow modules to
|
||||
react to system events and customize Drupal's behavior, and functions that
|
||||
standardize common operations such as database queries and form generation. The
|
||||
flexible hook architecture means that you should never need to directly modify
|
||||
the files that come with Drupal core to achieve the functionality you want;
|
||||
instead, functionality modifications take the form of modules.
|
||||
|
||||
When you need new functionality for your Drupal site, search for existing
|
||||
contributed modules. If you find a module that matches except for a bug or an
|
||||
additional needed feature, change the module and contribute your improvements
|
||||
back to the project in the form of a "patch". Create new custom modules only
|
||||
when nothing existing comes close to what you need.
|
||||
|
||||
More about developing:
|
||||
* Search for existing contributed modules:
|
||||
http://drupal.org/project/modules
|
||||
* Contribute a patch:
|
||||
http://drupal.org/patch/submit
|
||||
* Develop your own module:
|
||||
http://drupal.org/developing/modules
|
||||
* Follow best practices:
|
||||
http://drupal.org/best-practices
|
||||
* Refer to the API documentation:
|
||||
http://api.drupal.org/api/drupal/7
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
INTRODUCTION
|
||||
------------
|
||||
This document describes how to:
|
||||
|
||||
* Update your Drupal site from one minor 7.x version to another minor 7.x
|
||||
version; for example, from 7.8 to 7.9, or from 7.6 to 7.10.
|
||||
|
||||
* Upgrade your Drupal site's major version from 6.x to 7.x.
|
||||
|
||||
First steps and definitions:
|
||||
|
||||
* If you are upgrading to Drupal version x.y, then x is known as the major
|
||||
version number, and y is known as the minor version number. The download
|
||||
file will be named drupal-x.y.tar.gz (or drupal-x.y.zip).
|
||||
|
||||
* All directories mentioned in this document are relative to the directory of
|
||||
your Drupal installation.
|
||||
|
||||
* Make a full backup of all files, directories, and your database(s) before
|
||||
starting, and save it outside your Drupal installation directory.
|
||||
Instructions may be found at http://drupal.org/upgrade/backing-up-the-db
|
||||
|
||||
* It is wise to try an update or upgrade on a test copy of your site before
|
||||
applying it to your live site. Even minor updates can cause your site's
|
||||
behavior to change.
|
||||
|
||||
* Each new release of Drupal has release notes, which explain the changes made
|
||||
since the previous version and any special instructions needed to update or
|
||||
upgrade to the new version. You can find a link to the release notes for the
|
||||
version you are upgrading or updating to on the Drupal project page
|
||||
(http://drupal.org/project/drupal).
|
||||
|
||||
UPGRADE PROBLEMS
|
||||
----------------
|
||||
If you encounter errors during this process,
|
||||
|
||||
* Note any error messages you see.
|
||||
|
||||
* Restore your site to its previous state, using the file and database backups
|
||||
you created before you started the upgrade process. Do not attempt to do
|
||||
further upgrades on a site that had update problems.
|
||||
|
||||
* Consult one of the support options listed on http://drupal.org/support
|
||||
|
||||
More in-depth information on upgrading can be found at http://drupal.org/upgrade
|
||||
|
||||
MINOR VERSION UPDATES
|
||||
---------------------
|
||||
To update from one minor 7.x version of Drupal to any later 7.x version, after
|
||||
following the instructions in the INTRODUCTION section at the top of this file:
|
||||
|
||||
1. Log in as a user with the permission "Administer software updates".
|
||||
|
||||
2. Go to Administration > Configuration > Development > Maintenance mode.
|
||||
Enable the "Put site into maintenance mode" checkbox and save the
|
||||
configuration.
|
||||
|
||||
3. Remove all old core files and directories, except for the 'sites' directory
|
||||
and any custom files you added elsewhere.
|
||||
|
||||
If you made modifications to files like .htaccess or robots.txt, you will
|
||||
need to re-apply them from your backup, after the new files are in place.
|
||||
|
||||
Sometimes an update includes changes to default.settings.php (this will be
|
||||
noted in the release notes). If that's the case, follow these steps:
|
||||
|
||||
- Locate your settings.php file in the /sites/* directory. (Typically
|
||||
sites/default.)
|
||||
|
||||
- Make a backup copy of your settings.php file, with a different file name.
|
||||
|
||||
- Make a copy of the new default.settings.php file, and name the copy
|
||||
settings.php (overwriting your previous settings.php file).
|
||||
|
||||
- Copy the custom and site-specific entries from the backup you made into the
|
||||
new settings.php file. You will definitely need the lines giving the
|
||||
database information, and you will also want to copy in any other
|
||||
customizations you have added.
|
||||
|
||||
You can find the release notes for your version at
|
||||
https://www.drupal.org/project/drupal. At bottom of the project page under
|
||||
"Downloads" use the link for your version of Drupal to view the release
|
||||
notes. If your version is not listed, use the 'View all releases' link. From
|
||||
this page you can scroll down or use the filter to find your version and its
|
||||
release notes.
|
||||
|
||||
4. Download the latest Drupal 7.x release from http://drupal.org to a
|
||||
directory outside of your web root. Extract the archive and copy the files
|
||||
into your Drupal directory.
|
||||
|
||||
On a typical Unix/Linux command line, use the following commands to download
|
||||
and extract:
|
||||
|
||||
wget http://drupal.org/files/projects/drupal-x.y.tar.gz
|
||||
tar -zxvf drupal-x.y.tar.gz
|
||||
|
||||
This creates a new directory drupal-x.y/ containing all Drupal files and
|
||||
directories. Copy the files into your Drupal installation directory:
|
||||
|
||||
cp -R drupal-x.y/* drupal-x.y/.htaccess /path/to/your/installation
|
||||
|
||||
If you do not have command line access to your server, download the archive
|
||||
from http://drupal.org using your web browser, extract it, and then use an
|
||||
FTP client to upload the files to your web root.
|
||||
|
||||
5. Re-apply any modifications to files such as .htaccess or robots.txt.
|
||||
|
||||
6. Run update.php by visiting http://www.example.com/update.php (replace
|
||||
www.example.com with your domain name). This will update the core database
|
||||
tables.
|
||||
|
||||
If you are unable to access update.php do the following:
|
||||
|
||||
- Open settings.php with a text editor.
|
||||
|
||||
- Find the line that says:
|
||||
$update_free_access = FALSE;
|
||||
|
||||
- Change it into:
|
||||
$update_free_access = TRUE;
|
||||
|
||||
- Once the upgrade is done, $update_free_access must be reverted to FALSE.
|
||||
|
||||
7. Go to Administration > Reports > Status report. Verify that everything is
|
||||
working as expected.
|
||||
|
||||
8. Ensure that $update_free_access is FALSE in settings.php.
|
||||
|
||||
9. Go to Administration > Configuration > Development > Maintenance mode.
|
||||
Disable the "Put site into maintenance mode" checkbox and save the
|
||||
configuration.
|
||||
|
||||
MAJOR VERSION UPGRADE
|
||||
---------------------
|
||||
To upgrade from a previous major version of Drupal to Drupal 7.x, after
|
||||
following the instructions in the INTRODUCTION section at the top of this file:
|
||||
|
||||
1. Check on the Drupal 7 status of your contributed and custom modules and
|
||||
themes. See http://drupal.org/node/948216 for information on upgrading
|
||||
contributed modules and themes. See http://drupal.org/node/895314 for a list
|
||||
of modules that have been moved into core for Drupal 7, and instructions on
|
||||
how to update them. See http://drupal.org/update/modules for information on
|
||||
how to update your custom modules, and http://drupal.org/update/theme for
|
||||
custom themes.
|
||||
|
||||
You may decide at this point that you cannot upgrade your site, because
|
||||
needed modules or themes are not ready for Drupal 7.
|
||||
|
||||
2. Update to the latest available version of Drupal 6.x (if your current version
|
||||
is Drupal 5.x, you have to upgrade to 6.x first). If you need to update,
|
||||
download Drupal 6.x and follow the instructions in its UPGRADE.txt. This
|
||||
document only applies for upgrades from 6.x to 7.x.
|
||||
|
||||
3. In addition to updating to the latest available version of Drupal 6.x core,
|
||||
you must also upgrade all of your contributed modules for Drupal to their
|
||||
latest Drupal 6.x versions.
|
||||
|
||||
4. Log in as user ID 1 (the site maintenance user).
|
||||
|
||||
5. Go to Administer > Site configuration > Site maintenance. Select
|
||||
"Off-line" and save the configuration.
|
||||
|
||||
6. Go to Administer > Site building > Themes. Enable "Garland" and select it as
|
||||
the default theme.
|
||||
|
||||
7. Go to Administer > Site building > Modules. Disable all modules that are not
|
||||
listed under "Core - required" or "Core - optional". It is possible that some
|
||||
modules cannot be disabled, because others depend on them. Repeat this step
|
||||
until all non-core modules are disabled.
|
||||
|
||||
If you know that you will not re-enable some modules for Drupal 7.x and you
|
||||
no longer need their data, then you can uninstall them under the Uninstall
|
||||
tab after disabling them.
|
||||
|
||||
8. On the command line or in your FTP client, remove the file
|
||||
|
||||
sites/default/default.settings.php
|
||||
|
||||
9. Remove all old core files and directories, except for the 'sites' directory
|
||||
and any custom files you added elsewhere.
|
||||
|
||||
If you made modifications to files like .htaccess or robots.txt, you will
|
||||
need to re-apply them from your backup, after the new files are in place.
|
||||
|
||||
10. If you uninstalled any modules, remove them from the sites/all/modules and
|
||||
other sites/*/modules directories. Leave other modules in place, even though
|
||||
they are incompatible with Drupal 7.x.
|
||||
|
||||
11. Download the latest Drupal 7.x release from http://drupal.org to a
|
||||
directory outside of your web root. Extract the archive and copy the files
|
||||
into your Drupal directory.
|
||||
|
||||
On a typical Unix/Linux command line, use the following commands to download
|
||||
and extract:
|
||||
|
||||
wget http://drupal.org/files/projects/drupal-x.y.tar.gz
|
||||
tar -zxvf drupal-x.y.tar.gz
|
||||
|
||||
This creates a new directory drupal-x.y/ containing all Drupal files and
|
||||
directories. Copy the files into your Drupal installation directory:
|
||||
|
||||
cp -R drupal-x.y/* drupal-x.y/.htaccess /path/to/your/installation
|
||||
|
||||
If you do not have command line access to your server, download the archive
|
||||
from http://drupal.org using your web browser, extract it, and then use an
|
||||
FTP client to upload the files to your web root.
|
||||
|
||||
12. Re-apply any modifications to files such as .htaccess or robots.txt.
|
||||
|
||||
13. Make your settings.php file writeable, so that the update process can
|
||||
convert it to the format of Drupal 7.x. settings.php is usually located in
|
||||
|
||||
sites/default/settings.php
|
||||
|
||||
14. Run update.php by visiting http://www.example.com/update.php (replace
|
||||
www.example.com with your domain name). This will update the core database
|
||||
tables.
|
||||
|
||||
If you are unable to access update.php do the following:
|
||||
|
||||
- Open settings.php with a text editor.
|
||||
|
||||
- Find the line that says:
|
||||
$update_free_access = FALSE;
|
||||
|
||||
- Change it into:
|
||||
$update_free_access = TRUE;
|
||||
|
||||
- Once the upgrade is done, $update_free_access must be reverted to FALSE.
|
||||
|
||||
15. Backup your database after the core upgrade has run.
|
||||
|
||||
16. Replace and update your non-core modules and themes, following the
|
||||
procedures at http://drupal.org/node/948216
|
||||
|
||||
17. Go to Administration > Reports > Status report. Verify that everything is
|
||||
working as expected.
|
||||
|
||||
18. Ensure that $update_free_access is FALSE in settings.php.
|
||||
|
||||
19. Go to Administration > Configuration > Development > Maintenance mode.
|
||||
Disable the "Put site into maintenance mode" checkbox and save the
|
||||
configuration.
|
||||
|
||||
To get started with Drupal 7 administration, visit
|
||||
http://drupal.org/getting-started/7/admin
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Administrative script for running authorized file operations.
|
||||
*
|
||||
* Using this script, the site owner (the user actually owning the files on the
|
||||
* webserver) can authorize certain file-related operations to proceed with
|
||||
* elevated privileges, for example to deploy and upgrade modules or themes.
|
||||
* Users should not visit this page directly, but instead use an administrative
|
||||
* user interface which knows how to redirect the user to this script as part of
|
||||
* a multistep process. This script actually performs the selected operations
|
||||
* without loading all of Drupal, to be able to more gracefully recover from
|
||||
* errors. Access to the script is controlled by a global killswitch in
|
||||
* settings.php ('allow_authorize_operations') and via the 'administer software
|
||||
* updates' permission.
|
||||
*
|
||||
* There are helper functions for setting up an operation to run via this
|
||||
* system in modules/system/system.module. For more information, see:
|
||||
* @link authorize Authorized operation helper functions @endlink
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the root directory of the Drupal installation.
|
||||
*/
|
||||
define('DRUPAL_ROOT', getcwd());
|
||||
|
||||
/**
|
||||
* Global flag to identify update.php and authorize.php runs.
|
||||
*
|
||||
* Identifies update.php and authorize.php runs, avoiding unwanted operations
|
||||
* such as hook_init() and hook_exit() invokes, css/js preprocessing and
|
||||
* translation, and solves some theming issues. The flag is checked in other
|
||||
* places in Drupal code (not just authorize.php).
|
||||
*/
|
||||
define('MAINTENANCE_MODE', 'update');
|
||||
|
||||
/**
|
||||
* Renders a 403 access denied page for authorize.php.
|
||||
*/
|
||||
function authorize_access_denied_page() {
|
||||
drupal_add_http_header('Status', '403 Forbidden');
|
||||
watchdog('access denied', 'authorize.php', NULL, WATCHDOG_WARNING);
|
||||
drupal_set_title('Access denied');
|
||||
return t('You are not allowed to access this page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is allowed to run authorize.php.
|
||||
*
|
||||
* The killswitch in settings.php overrides all else, otherwise, the user must
|
||||
* have access to the 'administer software updates' permission.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the current user can run authorize.php, and FALSE if not.
|
||||
*/
|
||||
function authorize_access_allowed() {
|
||||
return variable_get('allow_authorize_operations', TRUE) && user_access('administer software updates');
|
||||
}
|
||||
|
||||
// *** Real work of the script begins here. ***
|
||||
|
||||
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/common.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/file.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/module.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/ajax.inc';
|
||||
|
||||
// We prepare only a minimal bootstrap. This includes the database and
|
||||
// variables, however, so we have access to the class autoloader registry.
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
|
||||
|
||||
// This must go after drupal_bootstrap(), which unsets globals!
|
||||
global $conf;
|
||||
|
||||
// We have to enable the user and system modules, even to check access and
|
||||
// display errors via the maintenance theme.
|
||||
$module_list['system']['filename'] = 'modules/system/system.module';
|
||||
$module_list['user']['filename'] = 'modules/user/user.module';
|
||||
module_list(TRUE, FALSE, FALSE, $module_list);
|
||||
drupal_load('module', 'system');
|
||||
drupal_load('module', 'user');
|
||||
|
||||
// We also want to have the language system available, but we do *NOT* want to
|
||||
// actually call drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE), since that would
|
||||
// also force us through the DRUPAL_BOOTSTRAP_PAGE_HEADER phase, which loads
|
||||
// all the modules, and that's exactly what we're trying to avoid.
|
||||
drupal_language_initialize();
|
||||
|
||||
// Initialize the maintenance theme for this administrative script.
|
||||
drupal_maintenance_theme();
|
||||
|
||||
$output = '';
|
||||
$show_messages = TRUE;
|
||||
|
||||
if (authorize_access_allowed()) {
|
||||
// Load both the Form API and Batch API.
|
||||
require_once DRUPAL_ROOT . '/includes/form.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/batch.inc';
|
||||
// Load the code that drives the authorize process.
|
||||
require_once DRUPAL_ROOT . '/includes/authorize.inc';
|
||||
|
||||
// For the sake of Batch API and a few other low-level functions, we need to
|
||||
// initialize the URL path into $_GET['q']. However, we do not want to raise
|
||||
// our bootstrap level, nor do we want to call drupal_initialize_path(),
|
||||
// since that is assuming that modules are loaded and invoking hooks.
|
||||
// However, all we really care is if we're in the middle of a batch, in which
|
||||
// case $_GET['q'] will already be set, we just initialize it to an empty
|
||||
// string if it's not already defined.
|
||||
if (!isset($_GET['q'])) {
|
||||
$_GET['q'] = '';
|
||||
}
|
||||
|
||||
if (isset($_SESSION['authorize_operation']['page_title'])) {
|
||||
drupal_set_title($_SESSION['authorize_operation']['page_title']);
|
||||
}
|
||||
else {
|
||||
drupal_set_title(t('Authorize file system changes'));
|
||||
}
|
||||
|
||||
// See if we've run the operation and need to display a report.
|
||||
if (isset($_SESSION['authorize_results']) && $results = $_SESSION['authorize_results']) {
|
||||
|
||||
// Clear the session out.
|
||||
unset($_SESSION['authorize_results']);
|
||||
unset($_SESSION['authorize_operation']);
|
||||
unset($_SESSION['authorize_filetransfer_info']);
|
||||
|
||||
if (!empty($results['page_title'])) {
|
||||
drupal_set_title($results['page_title']);
|
||||
}
|
||||
if (!empty($results['page_message'])) {
|
||||
drupal_set_message($results['page_message']['message'], $results['page_message']['type']);
|
||||
}
|
||||
|
||||
$output = theme('authorize_report', array('messages' => $results['messages']));
|
||||
|
||||
$links = array();
|
||||
if (is_array($results['tasks'])) {
|
||||
$links += $results['tasks'];
|
||||
}
|
||||
else {
|
||||
$links = array_merge($links, array(
|
||||
l(t('Administration pages'), 'admin'),
|
||||
l(t('Front page'), '<front>'),
|
||||
));
|
||||
}
|
||||
|
||||
$output .= theme('item_list', array('items' => $links, 'title' => t('Next steps')));
|
||||
}
|
||||
// If a batch is running, let it run.
|
||||
elseif (isset($_GET['batch'])) {
|
||||
$output = _batch_page();
|
||||
}
|
||||
else {
|
||||
if (empty($_SESSION['authorize_operation']) || empty($_SESSION['authorize_filetransfer_info'])) {
|
||||
$output = t('It appears you have reached this page in error.');
|
||||
}
|
||||
elseif (!$batch = batch_get()) {
|
||||
// We have a batch to process, show the filetransfer form.
|
||||
$elements = drupal_get_form('authorize_filetransfer_form');
|
||||
$output = drupal_render($elements);
|
||||
}
|
||||
}
|
||||
// We defer the display of messages until all operations are done.
|
||||
$show_messages = !(($batch = batch_get()) && isset($batch['running']));
|
||||
}
|
||||
else {
|
||||
$output = authorize_access_denied_page();
|
||||
}
|
||||
|
||||
if (!empty($output)) {
|
||||
print theme('update_page', array('content' => $output, 'show_messages' => $show_messages));
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"require": {
|
||||
"drush/drush": "8.*"
|
||||
},
|
||||
"extra": {
|
||||
"installer-paths": {
|
||||
"core": ["type:drupal-core"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1354
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Handles incoming requests to fire off regularly-scheduled tasks (cron jobs).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Root directory of Drupal installation.
|
||||
*/
|
||||
define('DRUPAL_ROOT', getcwd());
|
||||
|
||||
include_once DRUPAL_ROOT . '/includes/bootstrap.inc';
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
|
||||
|
||||
if (!isset($_GET['cron_key']) || variable_get('cron_key', 'drupal') != $_GET['cron_key']) {
|
||||
watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE);
|
||||
drupal_access_denied();
|
||||
}
|
||||
elseif (variable_get('maintenance_mode', 0)) {
|
||||
watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE);
|
||||
drupal_access_denied();
|
||||
}
|
||||
else {
|
||||
drupal_cron_run();
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
/**
|
||||
* The .bartik.css file is intended to contain styles that override declarations
|
||||
* in the Bartik theme.
|
||||
*/
|
||||
|
||||
/* @group Lists */
|
||||
|
||||
.views-display-top .secondary .action-list {
|
||||
padding-left: 0; /* LTR */
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group Attachment details tabs
|
||||
*
|
||||
* The tabs that switch between sections
|
||||
*/
|
||||
|
||||
.views-displays .region-content .secondary,
|
||||
.views-displays .region-content .secondary {
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.views-displays .secondary a {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.views-displays .secondary > li a {
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.views-displays .secondary > li.open a {
|
||||
-moz-border-radius: 5px 5px 0 0;
|
||||
-webkit-border-bottom-left-radius: 0;
|
||||
-webkit-border-bottom-right-radius: 0;
|
||||
-webkit-border-top-left-radius: 5px;
|
||||
-webkit-border-top-right-radius: 5px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.views-displays .secondary .open > a:hover {
|
||||
color: #0071B3;
|
||||
}
|
||||
|
||||
.views-displays .secondary input.form-submit {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group Modal dialog box
|
||||
*
|
||||
* The contents of the popup dialog on the views edit form.
|
||||
*/
|
||||
|
||||
.views-filterable-options .even .form-type-checkbox {
|
||||
background-color: #F9F9F9;
|
||||
}
|
||||
|
||||
.views-ui-dialog .ui-dialog-titlebar-close,
|
||||
.views-ui-dialog #views-ajax-title,
|
||||
.views-ui-dialog .views-override,
|
||||
.views-ui-dialog .form-buttons {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.views-ui-dialog a {
|
||||
color: #0071b3;
|
||||
}
|
||||
|
||||
.views-ui-dialog a:hover,
|
||||
.views-ui-dialog a:focus {
|
||||
color: #018fe2;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group CTools */
|
||||
|
||||
/* @group Buttons */
|
||||
|
||||
.ctools-button-processed {
|
||||
background-image:
|
||||
-moz-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
background-image:
|
||||
-webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
color-stop(0.0, rgba(255, 255, 255, 1.0)),
|
||||
color-stop(1.0, rgba(249, 249, 249, 1.0))
|
||||
);
|
||||
background-image:
|
||||
-webkit-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
padding-bottom: 1px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.ctools-button-processed:hover {
|
||||
background-image:
|
||||
-moz-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f1f1f1 100%);
|
||||
background-image:
|
||||
-webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
color-stop(0.0, rgba(255, 255, 255, 1.0)),
|
||||
color-stop(1.0, rgba(241, 241, 241, 1.0))
|
||||
);
|
||||
background-image:
|
||||
-webkit-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f1f1f1 100%);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f1f1f1 100%);
|
||||
}
|
||||
|
||||
.ctools-button-processed li a,
|
||||
.views-ui-display-tab-actions .ctools-button-processed input {
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
}
|
||||
|
||||
.ctools-content ul.actions {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.ctools-dropbutton-processed.open:hover {
|
||||
background-image:
|
||||
-moz-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
background-image:
|
||||
-webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
color-stop(0.0, rgba(255, 255, 255, 1.0)),
|
||||
color-stop(1.0, rgba(249, 249, 249, 1.0))
|
||||
);
|
||||
background-image:
|
||||
-webkit-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
}
|
||||
|
||||
.ctools-dropbutton-processed.open {
|
||||
-moz-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
|
||||
-webkit-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
|
||||
box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
.ctools-twisty {
|
||||
top: 0.6667em;
|
||||
}
|
||||
|
||||
.ctools-dropbutton-processed.open .ctools-twisty {
|
||||
top: 0.3333em;
|
||||
}
|
||||
|
||||
.ctools-dropbutton-processed li a,
|
||||
.views-ui-display-tab-actions .ctools-dropbutton-processed input {
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
.views-ui-display-tab-actions .ctools-button-processed input.form-submit {
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group Collapsible */
|
||||
|
||||
.ctools-toggle {
|
||||
margin-top: 0.9em;
|
||||
}
|
||||
|
||||
.ctools-toggle.ctools-toggle-collapsed {
|
||||
margin-top: 0.72em;
|
||||
}
|
||||
|
||||
.views-display-column > .ctools-toggle {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.views-display-column > .ctools-toggle.ctools-toggle-collapsed {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.views-ui-display-tab-actions .ctools-button input {
|
||||
color: #0071B3;
|
||||
}
|
||||
|
||||
.views-ui-display-tab-actions .ctools-button input:hover,
|
||||
.views-ui-display-tab-actions .ctools-button input:focus {
|
||||
color: #018FE2;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @end */
|
||||
@@ -1,263 +0,0 @@
|
||||
/**
|
||||
* The .garland.css file is intended to contain styles that override declarations
|
||||
* in the Garland theme.
|
||||
*/
|
||||
|
||||
/* @group Attachment details tabs
|
||||
*
|
||||
* The tabs that switch between sections
|
||||
*/
|
||||
|
||||
.views-displays .region-content .secondary,
|
||||
.views-displays .region-content .secondary {
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.views-displays .secondary .action-list {
|
||||
left: 1px;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.views-displays .secondary .action-list li,
|
||||
.views-displays .secondary a {
|
||||
border-color: #e9e9e9;
|
||||
}
|
||||
|
||||
.views-displays .secondary a {
|
||||
font-size: 12px;
|
||||
padding: 2px 7px;
|
||||
}
|
||||
|
||||
.views-displays ul.secondary li a:hover,
|
||||
.views-displays ul.secondary li.active a {
|
||||
border: 1px solid transparent;
|
||||
padding: 2px 7px;
|
||||
}
|
||||
|
||||
.views-displays .secondary > li a {
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.views-displays .secondary > li.open a {
|
||||
background-image: none;
|
||||
-moz-border-radius: 5px 5px 0 0;
|
||||
-webkit-border-bottom-left-radius: 0;
|
||||
-webkit-border-bottom-right-radius: 0;
|
||||
-webkit-border-top-left-radius: 5px;
|
||||
-webkit-border-top-right-radius: 5px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.views-displays .secondary .open > a:hover {
|
||||
border-color: #e9e9e9 #e9e9e9 #f1f1f1 #e9e9e9;
|
||||
border-width: 1px 1px 1px 1px;
|
||||
color: #0071B3;
|
||||
}
|
||||
|
||||
.views-displays .secondary input.form-submit {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group Attachment buckets
|
||||
*
|
||||
* These are the individual "buckets," or boxes, inside the display settings area
|
||||
*/
|
||||
|
||||
.views-ui-display-tab-bucket h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group Modal dialog box
|
||||
*
|
||||
* The contents of the popup dialog on the views edit form.
|
||||
*/
|
||||
|
||||
.views-filterable-options .form-type-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.views-filterable-options .even .form-type-checkbox {
|
||||
background-color: #F9F9F9;
|
||||
}
|
||||
|
||||
.views-ui-dialog .ui-dialog-titlebar-close,
|
||||
.views-ui-dialog #views-ajax-title,
|
||||
.views-ui-dialog .views-override,
|
||||
.views-ui-dialog .form-buttons {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group CTools */
|
||||
|
||||
/* @group Buttons */
|
||||
|
||||
.ctools-button-processed {
|
||||
background-image:
|
||||
-moz-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
background-image:
|
||||
-webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
color-stop(0.0, rgba(255, 255, 255, 1.0)),
|
||||
color-stop(1.0, rgba(249, 249, 249, 1.0))
|
||||
);
|
||||
background-image:
|
||||
-webkit-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
padding-bottom: 1px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.ctools-button-processed:hover {
|
||||
background-image:
|
||||
-moz-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f1f1f1 100%);
|
||||
background-image:
|
||||
-webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
color-stop(0.0, rgba(255, 255, 255, 1.0)),
|
||||
color-stop(1.0, rgba(241, 241, 241, 1.0))
|
||||
);
|
||||
background-image:
|
||||
-webkit-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f1f1f1 100%);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f1f1f1 100%);
|
||||
}
|
||||
|
||||
.ctools-button-processed ol li,
|
||||
.ctools-button-processed ul li {
|
||||
margin: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.views-ui-display-tab-actions .ctools-button-processed input {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ctools-content ul.actions {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.ctools-dropbutton-processed.open:hover {
|
||||
background-image:
|
||||
-moz-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
background-image:
|
||||
-webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
color-stop(0.0, rgba(255, 255, 255, 1.0)),
|
||||
color-stop(1.0, rgba(249, 249, 249, 1.0))
|
||||
);
|
||||
background-image:
|
||||
-webkit-linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
-90deg,
|
||||
#ffffff 0px,
|
||||
#f9f9f9 100%);
|
||||
}
|
||||
|
||||
.ctools-dropbutton-processed.open {
|
||||
-moz-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
|
||||
-webkit-box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
|
||||
box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
.ctools-twisty {
|
||||
top: 0.6667em;
|
||||
}
|
||||
|
||||
.ctools-dropbutton-processed.open .ctools-twisty {
|
||||
top: 0.3333em;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group Dependent */
|
||||
|
||||
.dependent-options,
|
||||
.form-checkboxes.dependent-options,
|
||||
.form-radios.dependent-options,
|
||||
.form-checkboxes .form-item.dependent-options,
|
||||
.form-radios .form-item.dependent-options {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @group Collapsible */
|
||||
|
||||
.ctools-toggle {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.ctools-toggle,
|
||||
.views-display-settings .ctools-toggle {
|
||||
color: #494949;
|
||||
}
|
||||
|
||||
.ctools-toggle.ctools-toggle-collapsed {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.views-display-column > .ctools-toggle {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.views-display-column > .ctools-toggle.ctools-toggle-collapsed {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.views-ui-display-tab-actions .ctools-button input {
|
||||
color: #027AC6;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.views-ui-display-tab-actions .ctools-button input:hover,
|
||||
.views-ui-display-tab-actions .ctools-button input:focus {
|
||||
color: #0062A0;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
/* @end */
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
diff --git a/includes/entity.inc b/includes/entity.inc
|
||||
index 2fefd59..0b80d53 100644
|
||||
--- a/includes/entity.inc
|
||||
+++ b/includes/entity.inc
|
||||
@@ -159,6 +159,18 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
|
||||
*/
|
||||
public function load($ids = array(), $conditions = array()) {
|
||||
$entities = array();
|
||||
+
|
||||
+ # PATCH http://drupal.org/node/1003788#comment-5195682
|
||||
+ // Clean the $ids array to remove non-integer values that can be passed
|
||||
+ // in from various sources, including menu callbacks.
|
||||
+ if (is_array($ids)) {
|
||||
+ foreach ($ids as $key => $id) {
|
||||
+ if (empty($id) || ((string) $id !== (string) (int) $id)) {
|
||||
+ unset($ids[$key]);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ # endpatch
|
||||
|
||||
// Revisions are not statically cached, and require a different query to
|
||||
// other conditions, so separate the revision id into its own variable.
|
||||
@@ -0,0 +1,4 @@
|
||||
#! /bin/bash
|
||||
|
||||
sudo chown -R $USER:http sites/all/modules/features/
|
||||
sudo chmod -R g+w sites/all/modules/features/
|
||||
@@ -0,0 +1,55 @@
|
||||
diff --git a/flag_lists.module b/flag_lists.module
|
||||
index d829113..b3c04f0 100644
|
||||
--- a/flag_lists.module
|
||||
+++ b/flag_lists.module
|
||||
@@ -1587,6 +1587,32 @@ function flag_lists_fix_link(&$link, $action) {
|
||||
return $fcid;
|
||||
}
|
||||
|
||||
+
|
||||
+ /**
|
||||
+ * Remove all entries of entity_id and type
|
||||
+ *
|
||||
+ * @param $entity_id
|
||||
+ * Entity id which has been flagged.
|
||||
+ * @param $type
|
||||
+ * The entity type.
|
||||
+ */
|
||||
+ function _flag_lists_remove_entity($entity_id, $type) {
|
||||
+ $query = db_select('flag_lists_content')
|
||||
+ ->condition('entity_id', $entity_id)
|
||||
+ ->condition('entity_type', $type);
|
||||
+ $query->fields('flag_lists_content', array('fcid', 'fid', 'uid', 'sid'));
|
||||
+ $items = $query->execute()->fetchAll();
|
||||
+
|
||||
+ if ($items) {
|
||||
+ foreach ($items as $key => $value) {
|
||||
+ db_delete('flag_lists_content')
|
||||
+ ->condition('fcid', $value->fcid)
|
||||
+ ->execute();
|
||||
+ watchdog('flag_lists', t('Deleted entry @fcid from flat_lists_content', array('@fcid' => $value->fcid)));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* Updates the flag count for this content
|
||||
*/
|
||||
@@ -1766,3 +1792,17 @@ function flag_lists_views_form_substitutions() {
|
||||
$select_all_placeholder => drupal_render($select_all),
|
||||
);
|
||||
}
|
||||
+
|
||||
+
|
||||
+/**
|
||||
+ * Implements hook_entity_delete
|
||||
+ */
|
||||
+function flag_lists_entity_delete($entity, $type) {
|
||||
+ foreach (flag_get_flags($type) as $flag) {
|
||||
+ if (isset($entity->vid)) {
|
||||
+ $items = _flag_lists_remove_entity($entity->vid, $type);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+
|
||||
@@ -1,133 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Views area handlers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup views_area_handlers Views area handlers
|
||||
* @{
|
||||
* Handlers to tell Views what can display in header, footer
|
||||
* and empty text in a view.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for area handlers.
|
||||
*
|
||||
* @ingroup views_area_handlers
|
||||
*/
|
||||
class views_handler_area extends views_handler {
|
||||
|
||||
/**
|
||||
* Overrides views_handler::init().
|
||||
*
|
||||
* Make sure that no result area handlers are set to be shown when the result
|
||||
* is empty.
|
||||
*/
|
||||
function init(&$view, &$options) {
|
||||
parent::init($view, $options);
|
||||
if ($this->handler_type == 'empty') {
|
||||
$this->options['empty'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this field's label.
|
||||
*/
|
||||
function label() {
|
||||
if (!isset($this->options['label'])) {
|
||||
return $this->ui_name();
|
||||
}
|
||||
return $this->options['label'];
|
||||
}
|
||||
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$this->definition['field'] = !empty($this->definition['field']) ? $this->definition['field'] : '';
|
||||
$label = !empty($this->definition['label']) ? $this->definition['label'] : $this->definition['field'];
|
||||
$options['label'] = array('default' => $label, 'translatable' => TRUE);
|
||||
$options['empty'] = array('default' => FALSE, 'bool' => TRUE);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide extra data to the administration form
|
||||
*/
|
||||
function admin_summary() {
|
||||
return $this->label();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options form that provides the label widget that all fields
|
||||
* should have.
|
||||
*/
|
||||
function options_form(&$form, &$form_state) {
|
||||
parent::options_form($form, $form_state);
|
||||
$form['label'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Label'),
|
||||
'#default_value' => isset($this->options['label']) ? $this->options['label'] : '',
|
||||
'#description' => t('The label for this area that will be displayed only administratively.'),
|
||||
);
|
||||
|
||||
if ($form_state['type'] != 'empty') {
|
||||
$form['empty'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Display even if view has no result'),
|
||||
'#default_value' => isset($this->options['empty']) ? $this->options['empty'] : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't run a query
|
||||
*/
|
||||
function query() { }
|
||||
|
||||
/**
|
||||
* Render the area
|
||||
*/
|
||||
function render($empty = FALSE) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Area handlers shouldn't have groupby.
|
||||
*/
|
||||
function use_group_by() {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A special handler to take the place of missing or broken handlers.
|
||||
*
|
||||
* @ingroup views_area_handlers
|
||||
*/
|
||||
class views_handler_area_broken extends views_handler_area {
|
||||
function ui_name($short = FALSE) {
|
||||
return t('Broken/missing handler');
|
||||
}
|
||||
|
||||
function ensure_my_table() { /* No table to ensure! */ }
|
||||
function query($group_by = FALSE) { /* No query to run */ }
|
||||
function render($empty = FALSE) { return ''; }
|
||||
function options_form(&$form, &$form_state) {
|
||||
$form['markup'] = array(
|
||||
'#prefix' => '<div class="form-item description">',
|
||||
'#value' => t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the handler is considered 'broken'
|
||||
*/
|
||||
function broken() { return TRUE; }
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_area_text.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views area text handler.
|
||||
*
|
||||
* @ingroup views_area_handlers
|
||||
*/
|
||||
class views_handler_area_text extends views_handler_area {
|
||||
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
$options['content'] = array('default' => '', 'translatable' => TRUE, 'format_key' => 'format');
|
||||
$options['format'] = array('default' => NULL);
|
||||
$options['tokenize'] = array('default' => FALSE, 'bool' => TRUE);
|
||||
return $options;
|
||||
}
|
||||
|
||||
function options_form(&$form, &$form_state) {
|
||||
parent::options_form($form, $form_state);
|
||||
|
||||
$form['content'] = array(
|
||||
'#type' => 'text_format',
|
||||
'#default_value' => $this->options['content'],
|
||||
'#rows' => 6,
|
||||
'#format' => isset($this->options['format']) ? $this->options['format'] : filter_default_format(),
|
||||
'#wysiwyg' => FALSE,
|
||||
);
|
||||
|
||||
// @TODO: Refactor token handling into a base class.
|
||||
$form['tokenize'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Use replacement tokens from the first row'),
|
||||
'#default_value' => $this->options['tokenize'],
|
||||
);
|
||||
|
||||
// Get a list of the available fields and arguments for token replacement.
|
||||
$options = array();
|
||||
foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
|
||||
$options[t('Fields')]["[$field]"] = $handler->ui_name();
|
||||
}
|
||||
|
||||
$count = 0; // This lets us prepare the key as we want it printed.
|
||||
foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
|
||||
$options[t('Arguments')]['%' . ++$count] = t('@argument title', array('@argument' => $handler->ui_name()));
|
||||
$options[t('Arguments')]['!' . $count] = t('@argument input', array('@argument' => $handler->ui_name()));
|
||||
}
|
||||
|
||||
if (!empty($options)) {
|
||||
$output = '<p>' . t('The following tokens are available. If you would like to have the characters \'[\' and \']\' please use the html entity codes \'%5B\' or \'%5D\' or they will get replaced with empty space.' . '</p>');
|
||||
foreach (array_keys($options) as $type) {
|
||||
if (!empty($options[$type])) {
|
||||
$items = array();
|
||||
foreach ($options[$type] as $key => $value) {
|
||||
$items[] = $key . ' == ' . $value;
|
||||
}
|
||||
$output .= theme('item_list',
|
||||
array(
|
||||
'items' => $items,
|
||||
'type' => $type
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$form['token_help'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Replacement patterns'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#value' => $output,
|
||||
'#id' => 'edit-options-token-help',
|
||||
'#dependency' => array(
|
||||
'edit-options-tokenize' => array(1),
|
||||
),
|
||||
'#prefix' => '<div>',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function options_submit(&$form, &$form_state) {
|
||||
$form_state['values']['options']['format'] = $form_state['values']['options']['content']['format'];
|
||||
$form_state['values']['options']['content'] = $form_state['values']['options']['content']['value'];
|
||||
parent::options_submit($form, $form_state);
|
||||
}
|
||||
|
||||
function render($empty = FALSE) {
|
||||
$format = isset($this->options['format']) ? $this->options['format'] : filter_default_format();
|
||||
if (!$empty || !empty($this->options['empty'])) {
|
||||
return $this->render_textarea($this->options['content'], $format);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a text area, using the proper format.
|
||||
*/
|
||||
function render_textarea($value, $format) {
|
||||
if ($value) {
|
||||
if ($this->options['tokenize']) {
|
||||
$value = $this->view->style_plugin->tokenize_value($value, 0);
|
||||
}
|
||||
return check_markup($value, $format, '', FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_area_text_custom.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views area text custom handler.
|
||||
*
|
||||
* @ingroup views_area_handlers
|
||||
*/
|
||||
class views_handler_area_text_custom extends views_handler_area_text {
|
||||
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
unset($options['format']);
|
||||
return $options;
|
||||
}
|
||||
|
||||
function options_form(&$form, &$form_state) {
|
||||
parent::options_form($form, $form_state);
|
||||
|
||||
// Alter the form element, to be a regular text area.
|
||||
$form['content']['#type'] = 'textarea';
|
||||
unset($form['content']['#format']);
|
||||
unset($form['content']['#wysiwyg']);
|
||||
|
||||
// @TODO: Use the token refactored base class.
|
||||
}
|
||||
|
||||
// Empty, so we don't inherit options_submit from the parent.
|
||||
function options_submit(&$form, &$form_state) {
|
||||
}
|
||||
|
||||
function render($empty = FALSE) {
|
||||
if (!$empty || !empty($this->options['empty'])) {
|
||||
return $this->render_textarea_custom($this->options['content']);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a text area with filter_xss_admin.
|
||||
*/
|
||||
function render_textarea_custom($value) {
|
||||
if ($value) {
|
||||
if ($this->options['tokenize']) {
|
||||
$value = $this->view->style_plugin->tokenize_value($value, 0);
|
||||
}
|
||||
return $this->sanitize_value($value, 'xss_admin');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_area_view.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views area handlers. Insert a view inside of an area.
|
||||
*
|
||||
* @ingroup views_area_handlers
|
||||
*/
|
||||
class views_handler_area_view extends views_handler_area {
|
||||
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$options['view_to_insert'] = array('default' => '');
|
||||
$options['inherit_arguments'] = array('default' => FALSE, 'bool' => TRUE);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options form that provides the label widget that all fields
|
||||
* should have.
|
||||
*/
|
||||
function options_form(&$form, &$form_state) {
|
||||
parent::options_form($form, $form_state);
|
||||
|
||||
$view_display = $this->view->name . ':' . $this->view->current_display;
|
||||
|
||||
$options = array('' => t('-Select-'));
|
||||
$options += views_get_views_as_options(FALSE, 'all', $view_display, FALSE, TRUE);
|
||||
$form['view_to_insert'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('View to insert'),
|
||||
'#default_value' => $this->options['view_to_insert'],
|
||||
'#description' => t('The view to insert into this area.'),
|
||||
'#options' => $options,
|
||||
);
|
||||
|
||||
$form['inherit_arguments'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Inherit contextual filters'),
|
||||
'#default_value' => $this->options['inherit_arguments'],
|
||||
'#description' => t('If checked, this view will receive the same contextual filters as its parent.'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the area
|
||||
*/
|
||||
function render($empty = FALSE) {
|
||||
if (!empty($this->options['view_to_insert'])) {
|
||||
list($view_name, $display_id) = explode(':', $this->options['view_to_insert']);
|
||||
|
||||
$view = views_get_view($view_name);
|
||||
if (empty($view) || !$view->access($display_id)) {
|
||||
return;
|
||||
}
|
||||
$view->set_display($display_id);
|
||||
|
||||
// Avoid recursion
|
||||
$view->parent_views += $this->view->parent_views;
|
||||
$view->parent_views[] = "$view_name:$display_id";
|
||||
|
||||
// Check if the view is part of the parent views of this view
|
||||
$search = "$view_name:$display_id";
|
||||
if (in_array($search, $this->view->parent_views)) {
|
||||
drupal_set_message(t("Recursion detected in view @view display @display.", array('@view' => $view_name, '@display' => $display_id)), 'error');
|
||||
}
|
||||
else {
|
||||
if (!empty($this->options['inherit_arguments']) && !empty($this->view->args)) {
|
||||
return $view->preview($display_id, $this->view->args);
|
||||
}
|
||||
else {
|
||||
return $view->preview($display_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_argument_date.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract argument handler for dates.
|
||||
*
|
||||
* Adds an option to set a default argument based on the current date.
|
||||
*
|
||||
* @param $arg_format
|
||||
* The format string to use on the current time when
|
||||
* creating a default date argument.
|
||||
*
|
||||
* Definitions terms:
|
||||
* - many to one: If true, the "many to one" helper will be used.
|
||||
* - invalid input: A string to give to the user for obviously invalid input.
|
||||
* This is deprecated in favor of argument validators.
|
||||
*
|
||||
* @see views_many_to_one_helper()
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*/
|
||||
class views_handler_argument_date extends views_handler_argument_formula {
|
||||
var $option_name = 'default_argument_date';
|
||||
var $arg_format = 'Y-m-d';
|
||||
|
||||
/**
|
||||
* Add an option to set the default value to the current date.
|
||||
*/
|
||||
function default_argument_form(&$form, &$form_state) {
|
||||
parent::default_argument_form($form, $form_state);
|
||||
$form['default_argument_type']['#options'] += array('date' => t('Current date'));
|
||||
$form['default_argument_type']['#options'] += array('node_created' => t("Current node's creation time"));
|
||||
$form['default_argument_type']['#options'] += array('node_changed' => t("Current node's update time")); }
|
||||
|
||||
/**
|
||||
* Set the empty argument value to the current date,
|
||||
* formatted appropriately for this argument.
|
||||
*/
|
||||
function get_default_argument($raw = FALSE) {
|
||||
if (!$raw && $this->options['default_argument_type'] == 'date') {
|
||||
return date($this->arg_format, REQUEST_TIME);
|
||||
}
|
||||
else if (!$raw && in_array($this->options['default_argument_type'], array('node_created', 'node_changed'))) {
|
||||
foreach (range(1, 3) as $i) {
|
||||
$node = menu_get_object('node', $i);
|
||||
if (!empty($node)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (arg(0) == 'node' && is_numeric(arg(1))) {
|
||||
$node = node_load(arg(1));
|
||||
}
|
||||
|
||||
if (empty($node)) {
|
||||
return parent::get_default_argument();
|
||||
}
|
||||
elseif ($this->options['default_argument_type'] == 'node_created') {
|
||||
return date($this->arg_format, $node->created);
|
||||
}
|
||||
elseif ($this->options['default_argument_type'] == 'node_changed') {
|
||||
return date($this->arg_format, $node->changed);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::get_default_argument($raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* The date handler provides some default argument types, which aren't argument default plugins,
|
||||
* so addapt the export mechanism.
|
||||
*/
|
||||
function export_plugin($indent, $prefix, $storage, $option, $definition, $parents) {
|
||||
|
||||
// Only use a special behaviour for the special argument types, else just
|
||||
// use the default behaviour.
|
||||
if ($option == 'default_argument_type') {
|
||||
$type = 'argument default';
|
||||
$option_name = 'default_argument_options';
|
||||
|
||||
$plugin = $this->get_plugin($type);
|
||||
$name = $this->options[$option];
|
||||
if (in_array($name, array('date', 'node_created', 'node_changed'))) {
|
||||
|
||||
// Write which plugin to use.
|
||||
$output = $indent . $prefix . "['$option'] = '$name';\n";
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
return parent::export_plugin($indent, $prefix, $storage, $option, $definition, $parents);
|
||||
}
|
||||
|
||||
|
||||
function get_sort_name() {
|
||||
return t('Date', array(), array('context' => 'Sort order'));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_argument_group_by_numeric.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simple handler for arguments using group by.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*/
|
||||
class views_handler_argument_group_by_numeric extends views_handler_argument {
|
||||
function query($group_by = FALSE) {
|
||||
$this->ensure_my_table();
|
||||
$field = $this->get_field();
|
||||
$placeholder = $this->placeholder();
|
||||
|
||||
$this->query->add_having_expression(0, "$field = $placeholder", array($placeholder => $this->argument));
|
||||
}
|
||||
|
||||
function ui_name($short = FALSE) {
|
||||
return $this->get_field(parent::ui_name($short));
|
||||
}
|
||||
|
||||
function get_sort_name() {
|
||||
return t('Numerical', array(), array('context' => 'Sort order'));
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_field_boolean.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A handler to provide proper displays for booleans.
|
||||
*
|
||||
* Allows for display of true/false, yes/no, on/off, enabled/disabled.
|
||||
*
|
||||
* Definition terms:
|
||||
* - output formats: An array where the first entry is displayed on boolean true
|
||||
* and the second is displayed on boolean false. An example for sticky is:
|
||||
* @code
|
||||
* 'output formats' => array(
|
||||
* 'sticky' => array(t('Sticky'), ''),
|
||||
* ),
|
||||
* @endcode
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*/
|
||||
class views_handler_field_boolean extends views_handler_field {
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
$options['type'] = array('default' => 'yes-no');
|
||||
$options['not'] = array('definition bool' => 'reverse');
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
function init(&$view, &$options) {
|
||||
parent::init($view, $options);
|
||||
|
||||
$default_formats = array(
|
||||
'yes-no' => array(t('Yes'), t('No')),
|
||||
'true-false' => array(t('True'), t('False')),
|
||||
'on-off' => array(t('On'), t('Off')),
|
||||
'enabled-disabled' => array(t('Enabled'), t('Disabled')),
|
||||
'boolean' => array(1, 0),
|
||||
'unicode-yes-no' => array('✔', '✖'),
|
||||
);
|
||||
$output_formats = isset($this->definition['output formats']) ? $this->definition['output formats'] : array();
|
||||
$this->formats = array_merge($default_formats, $output_formats);
|
||||
}
|
||||
|
||||
function options_form(&$form, &$form_state) {
|
||||
foreach ($this->formats as $key => $item) {
|
||||
$options[$key] = implode('/', $item);
|
||||
}
|
||||
|
||||
$form['type'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Output format'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->options['type'],
|
||||
);
|
||||
$form['not'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Reverse'),
|
||||
'#description' => t('If checked, true will be displayed as false.'),
|
||||
'#default_value' => $this->options['not'],
|
||||
);
|
||||
parent::options_form($form, $form_state);
|
||||
}
|
||||
|
||||
function render($values) {
|
||||
$value = $this->get_value($values);
|
||||
if (!empty($this->options['not'])) {
|
||||
$value = !$value;
|
||||
}
|
||||
|
||||
if (isset($this->formats[$this->options['type']])) {
|
||||
return $value ? $this->formats[$this->options['type']][0] : $this->formats[$this->options['type']][1];
|
||||
}
|
||||
else {
|
||||
return $value ? $this->formats['yes-no'][0] : $this->formats['yes-no'][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_field_contextual_links.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a handler that adds contextual links.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*/
|
||||
class views_handler_field_contextual_links extends views_handler_field {
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$options['fields'] = array('default' => array());
|
||||
$options['destination'] = array('default' => TRUE, 'bool' => TRUE);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
function options_form(&$form, &$form_state) {
|
||||
$all_fields = $this->view->display_handler->get_field_labels();
|
||||
// Offer to include only those fields that follow this one.
|
||||
$field_options = array_slice($all_fields, 0, array_search($this->options['id'], array_keys($all_fields)));
|
||||
$form['fields'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Fields'),
|
||||
'#description' => t('Fields to be included as contextual links.'),
|
||||
'#options' => $field_options,
|
||||
'#default_value' => $this->options['fields'],
|
||||
);
|
||||
$form['destination'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Include destination'),
|
||||
'#description' => t('Include a "destination" parameter in the link to return the user to the original view upon completing the contextual action.'),
|
||||
'#default_value' => $this->options['destination'],
|
||||
);
|
||||
}
|
||||
|
||||
function pre_render(&$values) {
|
||||
// Add a row plugin css class for the contextual link.
|
||||
$class = 'contextual-links-region';
|
||||
if (!empty($this->view->style_plugin->options['row_class'])) {
|
||||
$this->view->style_plugin->options['row_class'] .= " $class";
|
||||
}
|
||||
else {
|
||||
$this->view->style_plugin->options['row_class'] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the contextual fields.
|
||||
*/
|
||||
function render($values) {
|
||||
$links = array();
|
||||
foreach ($this->options['fields'] as $field) {
|
||||
if (empty($this->view->style_plugin->rendered_fields[$this->view->row_index][$field])) {
|
||||
continue;
|
||||
}
|
||||
$title = $this->view->field[$field]->last_render_text;
|
||||
$path = '';
|
||||
if (!empty($this->view->field[$field]->options['alter']['path'])) {
|
||||
$path = $this->view->field[$field]->options['alter']['path'];
|
||||
}
|
||||
if (!empty($title) && !empty($path)) {
|
||||
// Make sure that tokens are replaced for this paths as well.
|
||||
$tokens = $this->get_render_tokens(array());
|
||||
$path = strip_tags(decode_entities(strtr($path, $tokens)));
|
||||
|
||||
$links[$field] = array(
|
||||
'href' => $path,
|
||||
'title' => $title,
|
||||
);
|
||||
if (!empty($this->options['destination'])) {
|
||||
$links[$field]['query'] = drupal_get_destination();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($links)) {
|
||||
$build = array(
|
||||
'#prefix' => '<div class="contextual-links-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
'#theme' => 'links__contextual',
|
||||
'#links' => $links,
|
||||
'#attributes' => array('class' => array('contextual-links')),
|
||||
'#attached' => array(
|
||||
'library' => array(array('contextual', 'contextual-links')),
|
||||
),
|
||||
'#access' => user_access('access contextual links'),
|
||||
);
|
||||
return drupal_render($build);
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function query() { }
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_field_counter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Field handler to show a counter of the current row.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*/
|
||||
class views_handler_field_counter extends views_handler_field {
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
$options['counter_start'] = array('default' => 1);
|
||||
return $options;
|
||||
}
|
||||
|
||||
function options_form(&$form, &$form_state) {
|
||||
$form['counter_start'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Starting value'),
|
||||
'#default_value' => $this->options['counter_start'],
|
||||
'#description' => t('Specify the number the counter should start at.'),
|
||||
'#size' => 2,
|
||||
);
|
||||
|
||||
parent::options_form($form, $form_state);
|
||||
}
|
||||
|
||||
function query() {
|
||||
// do nothing -- to override the parent query.
|
||||
}
|
||||
|
||||
function render($values) {
|
||||
// Note: 1 is subtracted from the counter start value below because the
|
||||
// counter value is incremented by 1 at the end of this function.
|
||||
$count = is_numeric($this->options['counter_start']) ? $this->options['counter_start'] - 1 : 0;
|
||||
$pager = $this->view->query->pager;
|
||||
// Get the base count of the pager.
|
||||
if ($pager->use_pager()) {
|
||||
$count += ($pager->get_items_per_page() * $pager->get_current_page() + $pager->get_offset());
|
||||
}
|
||||
// Add the counter for the current site.
|
||||
$count += $this->view->row_index + 1;
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_field_date.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A handler to provide proper displays for dates.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*/
|
||||
class views_handler_field_date extends views_handler_field {
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$options['date_format'] = array('default' => 'small');
|
||||
$options['custom_date_format'] = array('default' => '');
|
||||
$options['timezone'] = array('default' => '');
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
function options_form(&$form, &$form_state) {
|
||||
|
||||
$date_formats = array();
|
||||
$date_types = system_get_date_types();
|
||||
foreach ($date_types as $key => $value) {
|
||||
$date_formats[$value['type']] = t('@date_format format', array('@date_format' => $value['title'])) . ': ' . format_date(REQUEST_TIME, $value['type']);
|
||||
}
|
||||
|
||||
$form['date_format'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Date format'),
|
||||
'#options' => $date_formats + array(
|
||||
'custom' => t('Custom'),
|
||||
'raw time ago' => t('Time ago'),
|
||||
'time ago' => t('Time ago (with "ago" appended)'),
|
||||
'raw time hence' => t('Time hence'),
|
||||
'time hence' => t('Time hence (with "hence" appended)'),
|
||||
'raw time span' => t('Time span (future dates have "-" prepended)'),
|
||||
'inverse time span' => t('Time span (past dates have "-" prepended)'),
|
||||
'time span' => t('Time span (with "ago/hence" appended)'),
|
||||
),
|
||||
'#default_value' => isset($this->options['date_format']) ? $this->options['date_format'] : 'small',
|
||||
);
|
||||
$form['custom_date_format'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Custom date format'),
|
||||
'#description' => t('If "Custom", see the <a href="@url" target="_blank">PHP manual</a> for date formats. Otherwise, enter the number of different time units to display, which defaults to 2.', array('@url' => 'http://php.net/manual/function.date.php')),
|
||||
'#default_value' => isset($this->options['custom_date_format']) ? $this->options['custom_date_format'] : '',
|
||||
'#dependency' => array('edit-options-date-format' => array('custom', 'raw time ago', 'time ago', 'raw time hence', 'time hence', 'raw time span', 'time span', 'raw time span', 'inverse time span', 'time span')),
|
||||
);
|
||||
$form['timezone'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Timezone'),
|
||||
'#description' => t('Timezone to be used for date output.'),
|
||||
'#options' => array('' => t('- Default site/user timezone -')) + system_time_zones(FALSE),
|
||||
'#default_value' => $this->options['timezone'],
|
||||
'#dependency' => array('edit-options-date-format' => array_merge(array('custom'), array_keys($date_formats))),
|
||||
);
|
||||
|
||||
parent::options_form($form, $form_state);
|
||||
}
|
||||
|
||||
function render($values) {
|
||||
$value = $this->get_value($values);
|
||||
$format = $this->options['date_format'];
|
||||
if (in_array($format, array('custom', 'raw time ago', 'time ago', 'raw time hence', 'time hence', 'raw time span', 'time span', 'raw time span', 'inverse time span', 'time span'))) {
|
||||
$custom_format = $this->options['custom_date_format'];
|
||||
}
|
||||
|
||||
if ($value) {
|
||||
$timezone = !empty($this->options['timezone']) ? $this->options['timezone'] : NULL;
|
||||
$time_diff = REQUEST_TIME - $value; // will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence)
|
||||
switch ($format) {
|
||||
case 'raw time ago':
|
||||
return format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2);
|
||||
case 'time ago':
|
||||
return t('%time ago', array('%time' => format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2)));
|
||||
case 'raw time hence':
|
||||
return format_interval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2);
|
||||
case 'time hence':
|
||||
return t('%time hence', array('%time' => format_interval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2)));
|
||||
case 'raw time span':
|
||||
return ($time_diff < 0 ? '-' : '') . format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2);
|
||||
case 'inverse time span':
|
||||
return ($time_diff > 0 ? '-' : '') . format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2);
|
||||
case 'time span':
|
||||
return t(($time_diff < 0 ? '%time hence' : '%time ago'), array('%time' => format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2)));
|
||||
case 'custom':
|
||||
if ($custom_format == 'r') {
|
||||
return format_date($value, $format, $custom_format, $timezone, 'en');
|
||||
}
|
||||
return format_date($value, $format, $custom_format, $timezone);
|
||||
default:
|
||||
return format_date($value, $format, '', $timezone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_field_markup.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A handler to run a field through check_markup, using a companion
|
||||
* format field.
|
||||
*
|
||||
* - format: (REQUIRED) Either a string format id to use for this field or an
|
||||
* array('field' => {$field}) where $field is the field in this table
|
||||
* used to control the format such as the 'format' field in the node,
|
||||
* which goes with the 'body' field.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*/
|
||||
class views_handler_field_markup extends views_handler_field {
|
||||
/**
|
||||
* Constructor; calls to base object constructor.
|
||||
*/
|
||||
function construct() {
|
||||
parent::construct();
|
||||
|
||||
$this->format = $this->definition['format'];
|
||||
|
||||
$this->additional_fields = array();
|
||||
if (is_array($this->format)) {
|
||||
$this->additional_fields['format'] = $this->format;
|
||||
}
|
||||
}
|
||||
|
||||
function render($values) {
|
||||
$value = $this->get_value($values);
|
||||
if (is_array($this->format)) {
|
||||
$format = $this->get_value($values, 'format');
|
||||
}
|
||||
else {
|
||||
$format = $this->format;
|
||||
}
|
||||
if ($value) {
|
||||
$value = str_replace('<!--break-->', '', $value);
|
||||
return check_markup($value, $format, '');
|
||||
}
|
||||
}
|
||||
|
||||
function element_type($none_supported = FALSE, $default_empty = FALSE, $inline = FALSE) {
|
||||
if ($inline) {
|
||||
return 'span';
|
||||
}
|
||||
|
||||
if (isset($this->definition['element type'])) {
|
||||
return $this->definition['element type'];
|
||||
}
|
||||
|
||||
return 'div';
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_filter_equality.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simple filter to handle equal to / not equal to filters
|
||||
*
|
||||
* @ingroup views_filter_handlers
|
||||
*/
|
||||
class views_handler_filter_equality extends views_handler_filter {
|
||||
// exposed filter options
|
||||
var $always_multiple = TRUE;
|
||||
|
||||
/**
|
||||
* Provide simple equality operator
|
||||
*/
|
||||
function operator_options() {
|
||||
return array(
|
||||
'=' => t('Is equal to'),
|
||||
'!=' => t('Is not equal to'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a simple textfield for equality
|
||||
*/
|
||||
function value_form(&$form, &$form_state) {
|
||||
$form['value'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Value'),
|
||||
'#size' => 30,
|
||||
'#default_value' => $this->value,
|
||||
);
|
||||
|
||||
if (!empty($form_state['exposed'])) {
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
if (!isset($form_state['input'][$identifier])) {
|
||||
$form_state['input'][$identifier] = $this->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_filter_numeric.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simple filter to handle greater than/less than filters
|
||||
*
|
||||
* @ingroup views_filter_handlers
|
||||
*/
|
||||
class views_handler_filter_numeric extends views_handler_filter {
|
||||
var $always_multiple = TRUE;
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$options['value'] = array(
|
||||
'contains' => array(
|
||||
'min' => array('default' => ''),
|
||||
'max' => array('default' => ''),
|
||||
'value' => array('default' => ''),
|
||||
),
|
||||
);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
function operators() {
|
||||
$operators = array(
|
||||
'<' => array(
|
||||
'title' => t('Is less than'),
|
||||
'method' => 'op_simple',
|
||||
'short' => t('<'),
|
||||
'values' => 1,
|
||||
),
|
||||
'<=' => array(
|
||||
'title' => t('Is less than or equal to'),
|
||||
'method' => 'op_simple',
|
||||
'short' => t('<='),
|
||||
'values' => 1,
|
||||
),
|
||||
'=' => array(
|
||||
'title' => t('Is equal to'),
|
||||
'method' => 'op_simple',
|
||||
'short' => t('='),
|
||||
'values' => 1,
|
||||
),
|
||||
'!=' => array(
|
||||
'title' => t('Is not equal to'),
|
||||
'method' => 'op_simple',
|
||||
'short' => t('!='),
|
||||
'values' => 1,
|
||||
),
|
||||
'>=' => array(
|
||||
'title' => t('Is greater than or equal to'),
|
||||
'method' => 'op_simple',
|
||||
'short' => t('>='),
|
||||
'values' => 1,
|
||||
),
|
||||
'>' => array(
|
||||
'title' => t('Is greater than'),
|
||||
'method' => 'op_simple',
|
||||
'short' => t('>'),
|
||||
'values' => 1,
|
||||
),
|
||||
'between' => array(
|
||||
'title' => t('Is between'),
|
||||
'method' => 'op_between',
|
||||
'short' => t('between'),
|
||||
'values' => 2,
|
||||
),
|
||||
'not between' => array(
|
||||
'title' => t('Is not between'),
|
||||
'method' => 'op_between',
|
||||
'short' => t('not between'),
|
||||
'values' => 2,
|
||||
),
|
||||
);
|
||||
|
||||
// if the definition allows for the empty operator, add it.
|
||||
if (!empty($this->definition['allow empty'])) {
|
||||
$operators += array(
|
||||
'empty' => array(
|
||||
'title' => t('Is empty (NULL)'),
|
||||
'method' => 'op_empty',
|
||||
'short' => t('empty'),
|
||||
'values' => 0,
|
||||
),
|
||||
'not empty' => array(
|
||||
'title' => t('Is not empty (NOT NULL)'),
|
||||
'method' => 'op_empty',
|
||||
'short' => t('not empty'),
|
||||
'values' => 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Add regexp support for MySQL.
|
||||
if (Database::getConnection()->databaseType() == 'mysql') {
|
||||
$operators += array(
|
||||
'regular_expression' => array(
|
||||
'title' => t('Regular expression'),
|
||||
'short' => t('regex'),
|
||||
'method' => 'op_regex',
|
||||
'values' => 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $operators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of all the numeric operators
|
||||
*/
|
||||
function operator_options($which = 'title') {
|
||||
$options = array();
|
||||
foreach ($this->operators() as $id => $info) {
|
||||
$options[$id] = $info[$which];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
function operator_values($values = 1) {
|
||||
$options = array();
|
||||
foreach ($this->operators() as $id => $info) {
|
||||
if ($info['values'] == $values) {
|
||||
$options[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
/**
|
||||
* Provide a simple textfield for equality
|
||||
*/
|
||||
function value_form(&$form, &$form_state) {
|
||||
$form['value']['#tree'] = TRUE;
|
||||
|
||||
// We have to make some choices when creating this as an exposed
|
||||
// filter form. For example, if the operator is locked and thus
|
||||
// not rendered, we can't render dependencies; instead we only
|
||||
// render the form items we need.
|
||||
$which = 'all';
|
||||
if (!empty($form['operator'])) {
|
||||
$source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
|
||||
}
|
||||
|
||||
if (!empty($form_state['exposed'])) {
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
|
||||
if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
|
||||
// exposed and locked.
|
||||
$which = in_array($this->operator, $this->operator_values(2)) ? 'minmax' : 'value';
|
||||
}
|
||||
else {
|
||||
$source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($which == 'all') {
|
||||
$form['value']['value'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => empty($form_state['exposed']) ? t('Value') : '',
|
||||
'#size' => 30,
|
||||
'#default_value' => $this->value['value'],
|
||||
'#dependency' => array($source => $this->operator_values(1)),
|
||||
);
|
||||
if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['value'])) {
|
||||
$form_state['input'][$identifier]['value'] = $this->value['value'];
|
||||
}
|
||||
}
|
||||
elseif ($which == 'value') {
|
||||
// When exposed we drop the value-value and just do value if
|
||||
// the operator is locked.
|
||||
$form['value'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => empty($form_state['exposed']) ? t('Value') : '',
|
||||
'#size' => 30,
|
||||
'#default_value' => $this->value['value'],
|
||||
);
|
||||
if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) {
|
||||
$form_state['input'][$identifier] = $this->value['value'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($which == 'all' || $which == 'minmax') {
|
||||
$form['value']['min'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => empty($form_state['exposed']) ? t('Min') : '',
|
||||
'#size' => 30,
|
||||
'#default_value' => $this->value['min'],
|
||||
);
|
||||
$form['value']['max'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => empty($form_state['exposed']) ? t('And max') : t('And'),
|
||||
'#size' => 30,
|
||||
'#default_value' => $this->value['max'],
|
||||
);
|
||||
if ($which == 'all') {
|
||||
$dependency = array(
|
||||
'#dependency' => array($source => $this->operator_values(2)),
|
||||
);
|
||||
$form['value']['min'] += $dependency;
|
||||
$form['value']['max'] += $dependency;
|
||||
}
|
||||
if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['min'])) {
|
||||
$form_state['input'][$identifier]['min'] = $this->value['min'];
|
||||
}
|
||||
if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['max'])) {
|
||||
$form_state['input'][$identifier]['max'] = $this->value['max'];
|
||||
}
|
||||
|
||||
if (!isset($form['value'])) {
|
||||
// Ensure there is something in the 'value'.
|
||||
$form['value'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => NULL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function query() {
|
||||
$this->ensure_my_table();
|
||||
$field = "$this->table_alias.$this->real_field";
|
||||
|
||||
$info = $this->operators();
|
||||
if (!empty($info[$this->operator]['method'])) {
|
||||
$this->{$info[$this->operator]['method']}($field);
|
||||
}
|
||||
}
|
||||
|
||||
function op_between($field) {
|
||||
if ($this->operator == 'between') {
|
||||
$this->query->add_where($this->options['group'], $field, array($this->value['min'], $this->value['max']), 'BETWEEN');
|
||||
}
|
||||
else {
|
||||
$this->query->add_where($this->options['group'], db_or()->condition($field, $this->value['min'], '<=')->condition($field, $this->value['max'], '>='));
|
||||
}
|
||||
}
|
||||
|
||||
function op_simple($field) {
|
||||
$this->query->add_where($this->options['group'], $field, $this->value['value'], $this->operator);
|
||||
}
|
||||
|
||||
function op_empty($field) {
|
||||
if ($this->operator == 'empty') {
|
||||
$operator = "IS NULL";
|
||||
}
|
||||
else {
|
||||
$operator = "IS NOT NULL";
|
||||
}
|
||||
|
||||
$this->query->add_where($this->options['group'], $field, NULL, $operator);
|
||||
}
|
||||
|
||||
function op_regex($field) {
|
||||
$this->query->add_where($this->options['group'], $field, $this->value, 'RLIKE');
|
||||
}
|
||||
|
||||
function admin_summary() {
|
||||
if ($this->is_a_group()) {
|
||||
return t('grouped');
|
||||
}
|
||||
if (!empty($this->options['exposed'])) {
|
||||
return t('exposed');
|
||||
}
|
||||
|
||||
$options = $this->operator_options('short');
|
||||
$output = check_plain($options[$this->operator]);
|
||||
if (in_array($this->operator, $this->operator_values(2))) {
|
||||
$output .= ' ' . t('@min and @max', array('@min' => $this->value['min'], '@max' => $this->value['max']));
|
||||
}
|
||||
elseif (in_array($this->operator, $this->operator_values(1))) {
|
||||
$output .= ' ' . check_plain($this->value['value']);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some minor translation of the exposed input
|
||||
*/
|
||||
function accept_exposed_input($input) {
|
||||
if (empty($this->options['exposed'])) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// rewrite the input value so that it's in the correct format so that
|
||||
// the parent gets the right data.
|
||||
if (!empty($this->options['expose']['identifier'])) {
|
||||
$value = &$input[$this->options['expose']['identifier']];
|
||||
if (!is_array($value)) {
|
||||
$value = array(
|
||||
'value' => $value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$rc = parent::accept_exposed_input($input);
|
||||
|
||||
if (empty($this->options['expose']['required'])) {
|
||||
// We have to do some of our own checking for non-required filters.
|
||||
$info = $this->operators();
|
||||
if (!empty($info[$this->operator]['values'])) {
|
||||
switch ($info[$this->operator]['values']) {
|
||||
case 1:
|
||||
if ($value['value'] === '') {
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if ($value['min'] === '' && $value['max'] === '') {
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rc;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of views_handler_sort_random.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handle a random sort.
|
||||
*
|
||||
* @ingroup views_sort_handlers
|
||||
*/
|
||||
class views_handler_sort_random extends views_handler_sort {
|
||||
function query() {
|
||||
$this->query->add_orderby('rand');
|
||||
}
|
||||
|
||||
function options_form(&$form, &$form_state) {
|
||||
parent::options_form($form, $form_state);
|
||||
$form['order']['#access'] = FALSE;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
The views module allows administrators and site designers to create, manage, and display lists of content. Each list managed by the views module is known as a "view", and the output of a view is known as a "display". Displays are provided in either block or page form, and a single view may have multiple displays. Optional navigation aids, including a system path and menu item, can be set for each page-based display of a view. By default, views may be created that list content (a <em>Node</em> view type), content revisions (a <em>Node revisions</em> view type) or users (a <em>User</em> view type). A view may be restricted to members of specific user roles, and may be added, edited or deleted at the <a href="base_url:admin/structure/views">views administration page</a>.
|
||||
|
||||
For more technical users, views can be understood as a user interface to compose SQL-queries and to pull information (Content, Users, etc.) from the database and show it on a screen as desired.
|
||||
|
||||
The "building block" design of the views system provides power and flexibility, allowing parameters to be specified only when needed. While an advanced view may use all of available parameters to create complex and highly interactive applications, a simple content listing may specify only a few options. All views rely on a conceptual framework that includes:
|
||||
|
||||
<ul>
|
||||
<li><a href="topic:views/field">Fields</a>, or the individual pieces of data being displayed. Adding the fields <em>Node: Title</em>, <em>Node: Type</em>, and <em>Node: Post date</em> to a node view, for example, includes the title, content type and creation date in the displayed results </li>
|
||||
|
||||
<li><a href="topic:views/relationship">Relationships</a>, or information about how data elements relate to one another. If relationship data is available, like that provided by a CCK <em>nodereference</em> field, items from a related node may be included in the view </li>
|
||||
|
||||
<li><a href="topic:views/argument">Arguments</a>, or additional parameters that dynamically refine the view results, passed as part of the path. Adding an argument of <em>Node: Type</em> to a node view with a path of "content", for example, dynamically filters the displayed items by content type. In this example (shown with Clean URLs enabled), accessing the view through the path "<em>http://www.example.com/content/page</em>" displays all posts of the type "page", the path "<em>http://www.example.com/content/story</em>" displays all posts of the type "story", and "<em>http://www.example.com/content</em>" displays all posts regardless of type) </li>
|
||||
|
||||
<li><a href="topic:views/sort">Sort criteria</a>, which determine the order of items displayed in the view results. Adding the sort criteria <em>Node: Post date</em> (in descending order) to a node <em>view</em>, for example, sorts the displayed posts in descending order by creation date </li>
|
||||
|
||||
<li><a href="topic:views/filter">Filters</a>, which limit items displayed in the results. Adding the filter <em>Node: Published</em> (and setting it equal to "Published") to a node view, for example, prevents unpublished items from being displayed</li>
|
||||
|
||||
<li><a href="topic:views/display">Displays</a>, which control where the output will be seen. Every view has a default display, which doesn't actually display the view anywhere, but is used to hold the default settings for the view, and is used when the view is called programmatically if another display is not specified. Much more useful to users are the <a href="topic:views/display-page">page</a> display, which gives a view a path and allows it to be the primary content of a page, or the <a href="topic:views/display-block">block</a> display which allows it to appear as secondary content on other pages.</li>
|
||||
|
||||
<li><a href="topic:views/header">Header</a>, which allow you to add by default one or more text area above the views output. </li>
|
||||
|
||||
<li><a href="topic:views/footer">Footer</a>, which allow you to add by default one or more text area beneath the views output. </li>
|
||||
|
||||
<li>The <a href="topic:views/footer">Emtpy Text</a> content will be displayed, when you choose in the Arguments Section "Action to take if argument is not present" the option "Display empty text".</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
Parallels between the components in the Views interface and an sql query:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sql Query</th>
|
||||
<th>Views Component</th>
|
||||
<tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>SELECT n.title, u.name</td>
|
||||
<td>fields</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>FROM {node} n base table</td>
|
||||
<td>view type</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>INNER JOIN {users} u ON n.uid = u.uid</td>
|
||||
<td>relationship</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WHERE n.status = 1</td>
|
||||
<td>filter</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AND u.uid = arg(1)</td>
|
||||
<td>argument</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ORDER BY n.changed DESC</td>
|
||||
<td>sort</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This is the actions engine for executing stored actions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup actions Actions
|
||||
* @{
|
||||
* Functions that perform an action on a certain system object.
|
||||
*
|
||||
* Action functions are declared by modules by implementing hook_action_info().
|
||||
* Modules can cause action functions to run by calling actions_do(), and
|
||||
* trigger.module provides a user interface that lets administrators define
|
||||
* events that cause action functions to run.
|
||||
*
|
||||
* Each action function takes two to four arguments:
|
||||
* - $entity: The object that the action acts on, such as a node, comment, or
|
||||
* user.
|
||||
* - $context: Array of additional information about what triggered the action.
|
||||
* - $a1, $a2: Optional additional information, which can be passed into
|
||||
* actions_do() and will be passed along to the action function.
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Performs a given list of actions by executing their callback functions.
|
||||
*
|
||||
* Given the IDs of actions to perform, this function finds out what the
|
||||
* callback functions for the actions are by querying the database. Then
|
||||
* it calls each callback using the function call $function($object, $context,
|
||||
* $a1, $a2), passing the input arguments of this function (see below) to the
|
||||
* action function.
|
||||
*
|
||||
* @param $action_ids
|
||||
* The IDs of the actions to perform. Can be a single action ID or an array
|
||||
* of IDs. IDs of configurable actions must be given as numeric action IDs;
|
||||
* IDs of non-configurable actions may be given as action function names.
|
||||
* @param $object
|
||||
* The object that the action will act on: a node, user, or comment object.
|
||||
* @param $context
|
||||
* Associative array containing extra information about what triggered
|
||||
* the action call, with $context['hook'] giving the name of the hook
|
||||
* that resulted in this call to actions_do().
|
||||
* @param $a1
|
||||
* Passed along to the callback.
|
||||
* @param $a2
|
||||
* Passed along to the callback.
|
||||
*
|
||||
* @return
|
||||
* An associative array containing the results of the functions that
|
||||
* perform the actions, keyed on action ID.
|
||||
*
|
||||
* @ingroup actions
|
||||
*/
|
||||
function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) {
|
||||
// $stack tracks the number of recursive calls.
|
||||
static $stack;
|
||||
$stack++;
|
||||
if ($stack > variable_get('actions_max_stack', 35)) {
|
||||
watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
|
||||
return;
|
||||
}
|
||||
$actions = array();
|
||||
$available_actions = actions_list();
|
||||
$actions_result = array();
|
||||
if (is_array($action_ids)) {
|
||||
$conditions = array();
|
||||
foreach ($action_ids as $action_id) {
|
||||
if (is_numeric($action_id)) {
|
||||
$conditions[] = $action_id;
|
||||
}
|
||||
elseif (isset($available_actions[$action_id])) {
|
||||
$actions[$action_id] = $available_actions[$action_id];
|
||||
}
|
||||
}
|
||||
|
||||
// When we have action instances we must go to the database to retrieve
|
||||
// instance data.
|
||||
if (!empty($conditions)) {
|
||||
$query = db_select('actions');
|
||||
$query->addField('actions', 'aid');
|
||||
$query->addField('actions', 'type');
|
||||
$query->addField('actions', 'callback');
|
||||
$query->addField('actions', 'parameters');
|
||||
$query->condition('aid', $conditions, 'IN');
|
||||
$result = $query->execute();
|
||||
foreach ($result as $action) {
|
||||
$actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
|
||||
$actions[$action->aid]['callback'] = $action->callback;
|
||||
$actions[$action->aid]['type'] = $action->type;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire actions, in no particular order.
|
||||
foreach ($actions as $action_id => $params) {
|
||||
// Configurable actions need parameters.
|
||||
if (is_numeric($action_id)) {
|
||||
$function = $params['callback'];
|
||||
if (function_exists($function)) {
|
||||
$context = array_merge($context, $params);
|
||||
$actions_result[$action_id] = $function($object, $context, $a1, $a2);
|
||||
}
|
||||
else {
|
||||
$actions_result[$action_id] = FALSE;
|
||||
}
|
||||
}
|
||||
// Singleton action; $action_id is the function name.
|
||||
else {
|
||||
$actions_result[$action_id] = $action_id($object, $context, $a1, $a2);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optimized execution of a single action.
|
||||
else {
|
||||
// If it's a configurable action, retrieve stored parameters.
|
||||
if (is_numeric($action_ids)) {
|
||||
$action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject();
|
||||
$function = $action->callback;
|
||||
if (function_exists($function)) {
|
||||
$context = array_merge($context, unserialize($action->parameters));
|
||||
$actions_result[$action_ids] = $function($object, $context, $a1, $a2);
|
||||
}
|
||||
else {
|
||||
$actions_result[$action_ids] = FALSE;
|
||||
}
|
||||
}
|
||||
// Singleton action; $action_ids is the function name.
|
||||
else {
|
||||
if (function_exists($action_ids)) {
|
||||
$actions_result[$action_ids] = $action_ids($object, $context, $a1, $a2);
|
||||
}
|
||||
else {
|
||||
// Set to avoid undefined index error messages later.
|
||||
$actions_result[$action_ids] = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
$stack--;
|
||||
return $actions_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers all available actions by invoking hook_action_info().
|
||||
*
|
||||
* This function contrasts with actions_get_all_actions(); see the
|
||||
* documentation of actions_get_all_actions() for an explanation.
|
||||
*
|
||||
* @param $reset
|
||||
* Reset the action info static cache.
|
||||
*
|
||||
* @return
|
||||
* An associative array keyed on action function name, with the same format
|
||||
* as the return value of hook_action_info(), containing all
|
||||
* modules' hook_action_info() return values as modified by any
|
||||
* hook_action_info_alter() implementations.
|
||||
*
|
||||
* @see hook_action_info()
|
||||
*/
|
||||
function actions_list($reset = FALSE) {
|
||||
$actions = &drupal_static(__FUNCTION__);
|
||||
if (!isset($actions) || $reset) {
|
||||
$actions = module_invoke_all('action_info');
|
||||
drupal_alter('action_info', $actions);
|
||||
}
|
||||
|
||||
// See module_implements() for an explanation of this cast.
|
||||
return (array) $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all action instances from the database.
|
||||
*
|
||||
* This function differs from the actions_list() function, which gathers
|
||||
* actions by invoking hook_action_info(). The actions returned by this
|
||||
* function and the actions returned by actions_list() are partially
|
||||
* synchronized. Non-configurable actions from hook_action_info()
|
||||
* implementations are put into the database when actions_synchronize() is
|
||||
* called, which happens when admin/config/system/actions is visited.
|
||||
* Configurable actions are not added to the database until they are configured
|
||||
* in the user interface, in which case a database row is created for each
|
||||
* configuration of each action.
|
||||
*
|
||||
* @return
|
||||
* Associative array keyed by numeric action ID. Each value is an associative
|
||||
* array with keys 'callback', 'label', 'type' and 'configurable'.
|
||||
*/
|
||||
function actions_get_all_actions() {
|
||||
$actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC);
|
||||
foreach ($actions as &$action) {
|
||||
$action['configurable'] = (bool) $action['parameters'];
|
||||
unset($action['parameters']);
|
||||
unset($action['aid']);
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an associative array keyed by hashes of function names or IDs.
|
||||
*
|
||||
* Hashes are used to prevent actual function names from going out into HTML
|
||||
* forms and coming back.
|
||||
*
|
||||
* @param $actions
|
||||
* An associative array with function names or action IDs as keys
|
||||
* and associative arrays with keys 'label', 'type', etc. as values.
|
||||
* This is usually the output of actions_list() or actions_get_all_actions().
|
||||
*
|
||||
* @return
|
||||
* An associative array whose keys are hashes of the input array keys, and
|
||||
* whose corresponding values are associative arrays with components
|
||||
* 'callback', 'label', 'type', and 'configurable' from the input array.
|
||||
*/
|
||||
function actions_actions_map($actions) {
|
||||
$actions_map = array();
|
||||
foreach ($actions as $callback => $array) {
|
||||
$key = drupal_hash_base64($callback);
|
||||
$actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
|
||||
$actions_map[$key]['label'] = $array['label'];
|
||||
$actions_map[$key]['type'] = $array['type'];
|
||||
$actions_map[$key]['configurable'] = $array['configurable'];
|
||||
}
|
||||
return $actions_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an action array key (function or ID), given its hash.
|
||||
*
|
||||
* Faster than actions_actions_map() when you only need the function name or ID.
|
||||
*
|
||||
* @param $hash
|
||||
* Hash of a function name or action ID array key. The array key
|
||||
* is a key into the return value of actions_list() (array key is the action
|
||||
* function name) or actions_get_all_actions() (array key is the action ID).
|
||||
*
|
||||
* @return
|
||||
* The corresponding array key, or FALSE if no match is found.
|
||||
*/
|
||||
function actions_function_lookup($hash) {
|
||||
// Check for a function name match.
|
||||
$actions_list = actions_list();
|
||||
foreach ($actions_list as $function => $array) {
|
||||
if (drupal_hash_base64($function) == $hash) {
|
||||
return $function;
|
||||
}
|
||||
}
|
||||
$aid = FALSE;
|
||||
// Must be a configurable action; check database.
|
||||
$result = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($result as $row) {
|
||||
if (drupal_hash_base64($row['aid']) == $hash) {
|
||||
$aid = $row['aid'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $aid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes actions that are provided by modules in hook_action_info().
|
||||
*
|
||||
* Actions provided by modules in hook_action_info() implementations are
|
||||
* synchronized with actions that are stored in the actions database table.
|
||||
* This is necessary so that actions that do not require configuration can
|
||||
* receive action IDs.
|
||||
*
|
||||
* @param $delete_orphans
|
||||
* If TRUE, any actions that exist in the database but are no longer
|
||||
* found in the code (for example, because the module that provides them has
|
||||
* been disabled) will be deleted.
|
||||
*/
|
||||
function actions_synchronize($delete_orphans = FALSE) {
|
||||
$actions_in_code = actions_list(TRUE);
|
||||
$actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC);
|
||||
|
||||
// Go through all the actions provided by modules.
|
||||
foreach ($actions_in_code as $callback => $array) {
|
||||
// Ignore configurable actions since their instances get put in when the
|
||||
// user adds the action.
|
||||
if (!$array['configurable']) {
|
||||
// If we already have an action ID for this action, no need to assign aid.
|
||||
if (isset($actions_in_db[$callback])) {
|
||||
unset($actions_in_db[$callback]);
|
||||
}
|
||||
else {
|
||||
// This is a new singleton that we don't have an aid for; assign one.
|
||||
db_insert('actions')
|
||||
->fields(array(
|
||||
'aid' => $callback,
|
||||
'type' => $array['type'],
|
||||
'callback' => $callback,
|
||||
'parameters' => '',
|
||||
'label' => $array['label'],
|
||||
))
|
||||
->execute();
|
||||
watchdog('actions', "Action '%action' added.", array('%action' => $array['label']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Any actions that we have left in $actions_in_db are orphaned.
|
||||
if ($actions_in_db) {
|
||||
$orphaned = array_keys($actions_in_db);
|
||||
|
||||
if ($delete_orphans) {
|
||||
$actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll();
|
||||
foreach ($actions as $action) {
|
||||
actions_delete($action->aid);
|
||||
watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => $action->label));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$link = l(t('Remove orphaned actions'), 'admin/config/system/actions/orphan');
|
||||
$count = count($actions_in_db);
|
||||
$orphans = implode(', ', $orphaned);
|
||||
watchdog('actions', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), WATCHDOG_INFO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an action and its user-supplied parameter values to the database.
|
||||
*
|
||||
* @param $function
|
||||
* The name of the function to be called when this action is performed.
|
||||
* @param $type
|
||||
* The type of action, to describe grouping and/or context, e.g., 'node',
|
||||
* 'user', 'comment', or 'system'.
|
||||
* @param $params
|
||||
* An associative array with parameter names as keys and parameter values as
|
||||
* values.
|
||||
* @param $label
|
||||
* A user-supplied label of this particular action, e.g., 'Send e-mail
|
||||
* to Jim'.
|
||||
* @param $aid
|
||||
* The ID of this action. If omitted, a new action is created.
|
||||
*
|
||||
* @return
|
||||
* The ID of the action.
|
||||
*/
|
||||
function actions_save($function, $type, $params, $label, $aid = NULL) {
|
||||
// aid is the callback for singleton actions so we need to keep a separate
|
||||
// table for numeric aids.
|
||||
if (!$aid) {
|
||||
$aid = db_next_id();
|
||||
}
|
||||
|
||||
db_merge('actions')
|
||||
->key(array('aid' => $aid))
|
||||
->fields(array(
|
||||
'callback' => $function,
|
||||
'type' => $type,
|
||||
'parameters' => serialize($params),
|
||||
'label' => $label,
|
||||
))
|
||||
->execute();
|
||||
|
||||
watchdog('actions', 'Action %action saved.', array('%action' => $label));
|
||||
return $aid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single action from the database.
|
||||
*
|
||||
* @param $aid
|
||||
* The ID of the action to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The appropriate action row from the database as an object.
|
||||
*/
|
||||
function actions_load($aid) {
|
||||
return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single action from the database.
|
||||
*
|
||||
* @param $aid
|
||||
* The ID of the action to delete.
|
||||
*/
|
||||
function actions_delete($aid) {
|
||||
db_delete('actions')
|
||||
->condition('aid', $aid)
|
||||
->execute();
|
||||
module_invoke_all('actions_delete', $aid);
|
||||
}
|
||||
-5461
File diff suppressed because it is too large
Load Diff
+1271
-329
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Shared classes and interfaces for the archiver system.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the common interface for all Archiver classes.
|
||||
*/
|
||||
interface ArchiverInterface {
|
||||
|
||||
/**
|
||||
* Constructs a new archiver instance.
|
||||
*
|
||||
* @param $file_path
|
||||
* The full system path of the archive to manipulate. Only local files
|
||||
* are supported. If the file does not yet exist, it will be created if
|
||||
* appropriate.
|
||||
*/
|
||||
public function __construct($file_path);
|
||||
|
||||
/**
|
||||
* Adds the specified file or directory to the archive.
|
||||
*
|
||||
* @param $file_path
|
||||
* The full system path of the file or directory to add. Only local files
|
||||
* and directories are supported.
|
||||
*
|
||||
* @return ArchiverInterface
|
||||
* The called object.
|
||||
*/
|
||||
public function add($file_path);
|
||||
|
||||
/**
|
||||
* Removes the specified file from the archive.
|
||||
*
|
||||
* @param $path
|
||||
* The file name relative to the root of the archive to remove.
|
||||
*
|
||||
* @return ArchiverInterface
|
||||
* The called object.
|
||||
*/
|
||||
public function remove($path);
|
||||
|
||||
/**
|
||||
* Extracts multiple files in the archive to the specified path.
|
||||
*
|
||||
* @param $path
|
||||
* A full system path of the directory to which to extract files.
|
||||
* @param $files
|
||||
* Optionally specify a list of files to be extracted. Files are
|
||||
* relative to the root of the archive. If not specified, all files
|
||||
* in the archive will be extracted.
|
||||
*
|
||||
* @return ArchiverInterface
|
||||
* The called object.
|
||||
*/
|
||||
public function extract($path, array $files = array());
|
||||
|
||||
/**
|
||||
* Lists all files in the archive.
|
||||
*
|
||||
* @return
|
||||
* An array of file names relative to the root of the archive.
|
||||
*/
|
||||
public function listContents();
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Helper functions and form handlers used for the authorize.php script.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Form constructor for the file transfer authorization form.
|
||||
*
|
||||
* Allows the user to choose a FileTransfer type and supply credentials.
|
||||
*
|
||||
* @see authorize_filetransfer_form_validate()
|
||||
* @see authorize_filetransfer_form_submit()
|
||||
* @ingroup forms
|
||||
*/
|
||||
function authorize_filetransfer_form($form, &$form_state) {
|
||||
global $base_url, $is_https;
|
||||
$form = array();
|
||||
|
||||
// If possible, we want to post this form securely via HTTPS.
|
||||
$form['#https'] = TRUE;
|
||||
|
||||
// CSS we depend on lives in modules/system/maintenance.css, which is loaded
|
||||
// via the default maintenance theme.
|
||||
$form['#attached']['js'][] = $base_url . '/misc/authorize.js';
|
||||
|
||||
// Get all the available ways to transfer files.
|
||||
if (empty($_SESSION['authorize_filetransfer_info'])) {
|
||||
drupal_set_message(t('Unable to continue, no available methods of file transfer'), 'error');
|
||||
return array();
|
||||
}
|
||||
$available_backends = $_SESSION['authorize_filetransfer_info'];
|
||||
|
||||
if (!$is_https) {
|
||||
$form['information']['https_warning'] = array(
|
||||
'#prefix' => '<div class="messages error">',
|
||||
'#markup' => t('WARNING: You are not using an encrypted connection, so your password will be sent in plain text. <a href="@https-link">Learn more</a>.', array('@https-link' => 'http://drupal.org/https-information')),
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
}
|
||||
|
||||
// Decide on a default backend.
|
||||
if (isset($form_state['values']['connection_settings']['authorize_filetransfer_default'])) {
|
||||
$authorize_filetransfer_default = $form_state['values']['connection_settings']['authorize_filetransfer_default'];
|
||||
}
|
||||
elseif ($authorize_filetransfer_default = variable_get('authorize_filetransfer_default', NULL));
|
||||
else {
|
||||
$authorize_filetransfer_default = key($available_backends);
|
||||
}
|
||||
|
||||
$form['information']['main_header'] = array(
|
||||
'#prefix' => '<h3>',
|
||||
'#markup' => t('To continue, provide your server connection details'),
|
||||
'#suffix' => '</h3>',
|
||||
);
|
||||
|
||||
$form['connection_settings']['#tree'] = TRUE;
|
||||
$form['connection_settings']['authorize_filetransfer_default'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Connection method'),
|
||||
'#default_value' => $authorize_filetransfer_default,
|
||||
'#weight' => -10,
|
||||
);
|
||||
|
||||
/*
|
||||
* Here we create two submit buttons. For a JS enabled client, they will
|
||||
* only ever see submit_process. However, if a client doesn't have JS
|
||||
* enabled, they will see submit_connection on the first form (when picking
|
||||
* what filetransfer type to use, and submit_process on the second one (which
|
||||
* leads to the actual operation).
|
||||
*/
|
||||
$form['submit_connection'] = array(
|
||||
'#prefix' => "<br style='clear:both'/>",
|
||||
'#name' => 'enter_connection_settings',
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Enter connection settings'),
|
||||
'#weight' => 100,
|
||||
);
|
||||
|
||||
$form['submit_process'] = array(
|
||||
'#name' => 'process_updates',
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Continue'),
|
||||
'#weight' => 100,
|
||||
'#attributes' => array('style' => 'display:none'),
|
||||
);
|
||||
|
||||
// Build a container for each connection type.
|
||||
foreach ($available_backends as $name => $backend) {
|
||||
$form['connection_settings']['authorize_filetransfer_default']['#options'][$name] = $backend['title'];
|
||||
$form['connection_settings'][$name] = array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array('class' => array("filetransfer-$name", 'filetransfer')),
|
||||
);
|
||||
// We can't use #prefix on the container itself since then the header won't
|
||||
// be hidden and shown when the containers are being manipulated via JS.
|
||||
$form['connection_settings'][$name]['header'] = array(
|
||||
'#markup' => '<h4>' . t('@backend connection settings', array('@backend' => $backend['title'])) . '</h4>',
|
||||
);
|
||||
|
||||
$form['connection_settings'][$name] += _authorize_filetransfer_connection_settings($name);
|
||||
|
||||
// Start non-JS code.
|
||||
if (isset($form_state['values']['connection_settings']['authorize_filetransfer_default']) && $form_state['values']['connection_settings']['authorize_filetransfer_default'] == $name) {
|
||||
|
||||
// If the user switches from JS to non-JS, Drupal (and Batch API) will
|
||||
// barf. This is a known bug: http://drupal.org/node/229825.
|
||||
setcookie('has_js', '', time() - 3600, '/');
|
||||
unset($_COOKIE['has_js']);
|
||||
|
||||
// Change the submit button to the submit_process one.
|
||||
$form['submit_process']['#attributes'] = array();
|
||||
unset($form['submit_connection']);
|
||||
|
||||
// Activate the proper filetransfer settings form.
|
||||
$form['connection_settings'][$name]['#attributes']['style'] = 'display:block';
|
||||
// Disable the select box.
|
||||
$form['connection_settings']['authorize_filetransfer_default']['#disabled'] = TRUE;
|
||||
|
||||
// Create a button for changing the type of connection.
|
||||
$form['connection_settings']['change_connection_type'] = array(
|
||||
'#name' => 'change_connection_type',
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Change connection type'),
|
||||
'#weight' => -5,
|
||||
'#attributes' => array('class' => array('filetransfer-change-connection-type')),
|
||||
);
|
||||
}
|
||||
// End non-JS code.
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the Form API array for a given connection backend's settings.
|
||||
*
|
||||
* @param $backend
|
||||
* The name of the backend (e.g. 'ftp', 'ssh', etc).
|
||||
*
|
||||
* @return
|
||||
* Form API array of connection settings for the given backend.
|
||||
*
|
||||
* @see hook_filetransfer_backends()
|
||||
*/
|
||||
function _authorize_filetransfer_connection_settings($backend) {
|
||||
$defaults = variable_get('authorize_filetransfer_connection_settings_' . $backend, array());
|
||||
$form = array();
|
||||
|
||||
// Create an instance of the file transfer class to get its settings form.
|
||||
$filetransfer = authorize_get_filetransfer($backend);
|
||||
if ($filetransfer) {
|
||||
$form = $filetransfer->getSettingsForm();
|
||||
}
|
||||
// Fill in the defaults based on the saved settings, if any.
|
||||
_authorize_filetransfer_connection_settings_set_defaults($form, NULL, $defaults);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default settings on a file transfer connection form recursively.
|
||||
*
|
||||
* The default settings for the file transfer connection forms are saved in
|
||||
* the database. The settings are stored as a nested array in the case of a
|
||||
* settings form that has fieldsets or otherwise uses a nested structure.
|
||||
* Therefore, to properly add defaults, we need to walk through all the
|
||||
* children form elements and process those defaults recursively.
|
||||
*
|
||||
* @param $element
|
||||
* Reference to the Form API form element we're operating on.
|
||||
* @param $key
|
||||
* The key for our current form element, if any.
|
||||
* @param array $defaults
|
||||
* The default settings for the file transfer backend we're operating on.
|
||||
*/
|
||||
function _authorize_filetransfer_connection_settings_set_defaults(&$element, $key, array $defaults) {
|
||||
// If we're operating on a form element which isn't a fieldset, and we have
|
||||
// a default setting saved, stash it in #default_value.
|
||||
if (!empty($key) && isset($defaults[$key]) && isset($element['#type']) && $element['#type'] != 'fieldset') {
|
||||
$element['#default_value'] = $defaults[$key];
|
||||
}
|
||||
// Now, we walk through all the child elements, and recursively invoke
|
||||
// ourself on each one. Since the $defaults settings array can be nested
|
||||
// (because of #tree, any values inside fieldsets will be nested), if
|
||||
// there's a subarray of settings for the form key we're currently
|
||||
// processing, pass in that subarray to the recursive call. Otherwise, just
|
||||
// pass on the whole $defaults array.
|
||||
foreach (element_children($element) as $child_key) {
|
||||
_authorize_filetransfer_connection_settings_set_defaults($element[$child_key], $child_key, ((isset($defaults[$key]) && is_array($defaults[$key])) ? $defaults[$key] : $defaults));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation handler for authorize_filetransfer_form().
|
||||
*
|
||||
* @see authorize_filetransfer_form()
|
||||
* @see authorize_filetransfer_submit()
|
||||
*/
|
||||
function authorize_filetransfer_form_validate($form, &$form_state) {
|
||||
// Only validate the form if we have collected all of the user input and are
|
||||
// ready to proceed with updating or installing.
|
||||
if ($form_state['triggering_element']['#name'] != 'process_updates') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($form_state['values']['connection_settings'])) {
|
||||
$backend = $form_state['values']['connection_settings']['authorize_filetransfer_default'];
|
||||
$filetransfer = authorize_get_filetransfer($backend, $form_state['values']['connection_settings'][$backend]);
|
||||
try {
|
||||
if (!$filetransfer) {
|
||||
throw new Exception(t('Error, this type of connection protocol (%backend) does not exist.', array('%backend' => $backend)));
|
||||
}
|
||||
$filetransfer->connect();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// The format of this error message is similar to that used on the
|
||||
// database connection form in the installer.
|
||||
form_set_error('connection_settings', t('Failed to connect to the server. The server reports the following message: !message For more help installing or updating code on your server, see the <a href="@handbook_url">handbook</a>.', array(
|
||||
'!message' => '<p class="error">' . $e->getMessage() . '</p>',
|
||||
'@handbook_url' => 'http://drupal.org/documentation/install/modules-themes',
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for authorize_filetransfer_form().
|
||||
*
|
||||
* @see authorize_filetransfer_form()
|
||||
* @see authorize_filetransfer_validate()
|
||||
*/
|
||||
function authorize_filetransfer_form_submit($form, &$form_state) {
|
||||
global $base_url;
|
||||
switch ($form_state['triggering_element']['#name']) {
|
||||
case 'process_updates':
|
||||
|
||||
// Save the connection settings to the DB.
|
||||
$filetransfer_backend = $form_state['values']['connection_settings']['authorize_filetransfer_default'];
|
||||
|
||||
// If the database is available then try to save our settings. We have
|
||||
// to make sure it is available since this code could potentially (will
|
||||
// likely) be called during the installation process, before the
|
||||
// database is set up.
|
||||
try {
|
||||
$connection_settings = array();
|
||||
foreach ($form_state['values']['connection_settings'][$filetransfer_backend] as $key => $value) {
|
||||
// We do *not* want to store passwords in the database, unless the
|
||||
// backend explicitly says so via the magic #filetransfer_save form
|
||||
// property. Otherwise, we store everything that's not explicitly
|
||||
// marked with #filetransfer_save set to FALSE.
|
||||
if (!isset($form['connection_settings'][$filetransfer_backend][$key]['#filetransfer_save'])) {
|
||||
if ($form['connection_settings'][$filetransfer_backend][$key]['#type'] != 'password') {
|
||||
$connection_settings[$key] = $value;
|
||||
}
|
||||
}
|
||||
// The attribute is defined, so only save if set to TRUE.
|
||||
elseif ($form['connection_settings'][$filetransfer_backend][$key]['#filetransfer_save']) {
|
||||
$connection_settings[$key] = $value;
|
||||
}
|
||||
}
|
||||
// Set this one as the default authorize method.
|
||||
variable_set('authorize_filetransfer_default', $filetransfer_backend);
|
||||
// Save the connection settings minus the password.
|
||||
variable_set('authorize_filetransfer_connection_settings_' . $filetransfer_backend, $connection_settings);
|
||||
|
||||
$filetransfer = authorize_get_filetransfer($filetransfer_backend, $form_state['values']['connection_settings'][$filetransfer_backend]);
|
||||
|
||||
// Now run the operation.
|
||||
authorize_run_operation($filetransfer);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// If there is no database available, we don't care and just skip
|
||||
// this part entirely.
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'enter_connection_settings':
|
||||
$form_state['rebuild'] = TRUE;
|
||||
break;
|
||||
|
||||
case 'change_connection_type':
|
||||
$form_state['rebuild'] = TRUE;
|
||||
unset($form_state['values']['connection_settings']['authorize_filetransfer_default']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the operation specified in $_SESSION['authorize_operation'].
|
||||
*
|
||||
* @param $filetransfer
|
||||
* The FileTransfer object to use for running the operation.
|
||||
*/
|
||||
function authorize_run_operation($filetransfer) {
|
||||
$operation = $_SESSION['authorize_operation'];
|
||||
unset($_SESSION['authorize_operation']);
|
||||
|
||||
if (!empty($operation['page_title'])) {
|
||||
drupal_set_title($operation['page_title']);
|
||||
}
|
||||
|
||||
require_once DRUPAL_ROOT . '/' . $operation['file'];
|
||||
call_user_func_array($operation['callback'], array_merge(array($filetransfer), $operation['arguments']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a FileTransfer class for a specific transfer method and settings.
|
||||
*
|
||||
* @param $backend
|
||||
* The FileTransfer backend to get the class for.
|
||||
* @param $settings
|
||||
* Array of settings for the FileTransfer.
|
||||
*
|
||||
* @return
|
||||
* An instantiated FileTransfer object for the requested method and settings,
|
||||
* or FALSE if there was an error finding or instantiating it.
|
||||
*/
|
||||
function authorize_get_filetransfer($backend, $settings = array()) {
|
||||
$filetransfer = FALSE;
|
||||
if (!empty($_SESSION['authorize_filetransfer_info'][$backend])) {
|
||||
$backend_info = $_SESSION['authorize_filetransfer_info'][$backend];
|
||||
if (!empty($backend_info['file'])) {
|
||||
$file = $backend_info['file path'] . '/' . $backend_info['file'];
|
||||
require_once $file;
|
||||
}
|
||||
if (class_exists($backend_info['class'])) {
|
||||
// PHP 5.2 doesn't support $class::factory() syntax, so we have to
|
||||
// use call_user_func_array() until we can require PHP 5.3.
|
||||
$filetransfer = call_user_func_array(array($backend_info['class'], 'factory'), array(DRUPAL_ROOT, $settings));
|
||||
}
|
||||
}
|
||||
return $filetransfer;
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides the basic object definitions used by plugins and handlers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Basic definition for many views objects.
|
||||
*/
|
||||
class views_object {
|
||||
/**
|
||||
* Except for displays, options for the object will be held here.
|
||||
*/
|
||||
var $options = array();
|
||||
|
||||
/**
|
||||
* The top object of a view.
|
||||
*
|
||||
* @var view
|
||||
*/
|
||||
var $view = NULL;
|
||||
|
||||
/**
|
||||
* Handler's definition
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $definition;
|
||||
|
||||
/**
|
||||
* Information about options for all kinds of purposes will be held here.
|
||||
* @code
|
||||
* 'option_name' => array(
|
||||
* - 'default' => default value,
|
||||
* - 'translatable' => (optional) TRUE/FALSE (wrap in t() on export if true),
|
||||
* - 'contains' => (optional) array of items this contains, with its own
|
||||
* defaults, etc. If contains is set, the default will be ignored and
|
||||
* assumed to be array().
|
||||
* - 'bool' => (optional) TRUE/FALSE Is the value a boolean value. This will
|
||||
* change the export format to TRUE/FALSE instead of 1/0.
|
||||
* - 'export' => (optional) FALSE or a callback for special export handling
|
||||
* if necessary.
|
||||
* - 'unpack_translatable' => (optional) callback for special handling for
|
||||
* translating data within the option, if necessary.
|
||||
* ),
|
||||
*
|
||||
* @return array
|
||||
* Returns the options of this handler/plugin.
|
||||
*
|
||||
* @see views_object::export_option()
|
||||
* @see views_object::export_option_always()
|
||||
* @see views_object::unpack_translatable()
|
||||
*/
|
||||
function option_definition() { return array(); }
|
||||
|
||||
/**
|
||||
* Views handlers use a special construct function so that we can more
|
||||
* easily construct them with variable arguments.
|
||||
*/
|
||||
function construct() { $this->set_default_options(); }
|
||||
|
||||
/**
|
||||
* Set default options on this object. Called by the constructor in a
|
||||
* complex chain to deal with backward compatibility.
|
||||
*
|
||||
* @deprecated since views2
|
||||
*/
|
||||
function options(&$options) { }
|
||||
|
||||
/**
|
||||
* Set default options.
|
||||
* For backward compatibility, it sends the options array; this is a
|
||||
* feature that will likely disappear at some point.
|
||||
*/
|
||||
function set_default_options() {
|
||||
$this->_set_option_defaults($this->options, $this->option_definition());
|
||||
|
||||
// Retained for complex defaults plus backward compatibility.
|
||||
$this->options($this->options);
|
||||
}
|
||||
|
||||
function _set_option_defaults(&$storage, $options, $level = 0) {
|
||||
foreach ($options as $option => $definition) {
|
||||
if (isset($definition['contains']) && is_array($definition['contains'])) {
|
||||
$storage[$option] = array();
|
||||
$this->_set_option_defaults($storage[$option], $definition['contains'], $level++);
|
||||
}
|
||||
elseif (!empty($definition['translatable']) && !empty($definition['default'])) {
|
||||
$storage[$option] = t($definition['default']);
|
||||
}
|
||||
else {
|
||||
$storage[$option] = isset($definition['default']) ? $definition['default'] : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack options over our existing defaults, drilling down into arrays
|
||||
* so that defaults don't get totally blown away.
|
||||
*/
|
||||
function unpack_options(&$storage, $options, $definition = NULL, $all = TRUE, $check = TRUE, $localization_keys = array()) {
|
||||
if ($check && !is_array($options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($definition)) {
|
||||
$definition = $this->option_definition();
|
||||
}
|
||||
|
||||
if (!empty($this->view)) {
|
||||
// Ensure we have a localization plugin.
|
||||
$this->view->init_localization();
|
||||
|
||||
// Set up default localization keys. Handlers and such set this for us
|
||||
if (empty($localization_keys) && isset($this->localization_keys)) {
|
||||
$localization_keys = $this->localization_keys;
|
||||
}
|
||||
// but plugins don't because there isn't a common init() these days.
|
||||
else if (!empty($this->is_plugin)) {
|
||||
if ($this->plugin_type != 'display') {
|
||||
$localization_keys = array($this->view->current_display);
|
||||
$localization_keys[] = $this->plugin_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
// Ignore arrays with no definition.
|
||||
if (!$all && empty($definition[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($storage[$key]) || !is_array($storage[$key])) {
|
||||
$storage[$key] = array();
|
||||
}
|
||||
|
||||
// If we're just unpacking our known options, and we're dropping an
|
||||
// unknown array (as might happen for a dependent plugin fields) go
|
||||
// ahead and drop that in.
|
||||
if (!$all && isset($definition[$key]) && !isset($definition[$key]['contains'])) {
|
||||
$storage[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->unpack_options($storage[$key], $value, isset($definition[$key]['contains']) ? $definition[$key]['contains'] : array(), $all, FALSE, array_merge($localization_keys, array($key)));
|
||||
}
|
||||
// Don't localize strings during editing. When editing, we need to work with
|
||||
// the original data, not the translated version.
|
||||
else if (empty($this->view->editing) && !empty($definition[$key]['translatable']) && !empty($value) || !empty($definition['contains'][$key]['translatable']) && !empty($value)) {
|
||||
if (!empty($this->view) && $this->view->is_translatable()) {
|
||||
// Allow other modules to make changes to the string before it's
|
||||
// sent for translation.
|
||||
// The $keys array is built from the view name, any localization keys
|
||||
// sent in, and the name of the property being processed.
|
||||
$format = NULL;
|
||||
if (isset($definition[$key]['format_key']) && isset($options[$definition[$key]['format_key']])) {
|
||||
$format = $options[$definition[$key]['format_key']];
|
||||
}
|
||||
$translation_data = array(
|
||||
'value' => $value,
|
||||
'format' => $format,
|
||||
'keys' => array_merge(array($this->view->name), $localization_keys, array($key)),
|
||||
);
|
||||
$storage[$key] = $this->view->localization_plugin->translate($translation_data);
|
||||
}
|
||||
// Otherwise, this is a code-based string, so we can use t().
|
||||
else {
|
||||
$storage[$key] = t($value);
|
||||
}
|
||||
}
|
||||
else if ($all || !empty($definition[$key])) {
|
||||
$storage[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the handler know what its full definition is.
|
||||
*/
|
||||
function set_definition($definition) {
|
||||
$this->definition = $definition;
|
||||
if (isset($definition['field'])) {
|
||||
$this->real_field = $definition['field'];
|
||||
}
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
if (isset($this->view)) {
|
||||
unset($this->view);
|
||||
}
|
||||
|
||||
if (isset($this->display)) {
|
||||
unset($this->display);
|
||||
}
|
||||
|
||||
if (isset($this->query)) {
|
||||
unset($this->query);
|
||||
}
|
||||
}
|
||||
|
||||
function export_options($indent, $prefix) {
|
||||
$output = '';
|
||||
foreach ($this->option_definition() as $option => $definition) {
|
||||
$output .= $this->export_option($indent, $prefix, $this->options, $option, $definition, array());
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function export_option($indent, $prefix, $storage, $option, $definition, $parents) {
|
||||
// Do not export options for which we have no settings.
|
||||
if (!isset($storage[$option])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($definition['export'])) {
|
||||
if ($definition['export'] === FALSE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handling for some items
|
||||
if (method_exists($this, $definition['export'])) {
|
||||
return $this->{$definition['export']}($indent, $prefix, $storage, $option, $definition, $parents);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the current option to the parents tree.
|
||||
$parents[] = $option;
|
||||
$output = '';
|
||||
|
||||
// If it has child items, export those separately.
|
||||
if (isset($definition['contains'])) {
|
||||
foreach ($definition['contains'] as $sub_option => $sub_definition) {
|
||||
$output .= $this->export_option($indent, $prefix, $storage[$option], $sub_option, $sub_definition, $parents);
|
||||
}
|
||||
}
|
||||
// Otherwise export just this item.
|
||||
else {
|
||||
$default = isset($definition['default']) ? $definition['default'] : NULL;
|
||||
$value = $storage[$option];
|
||||
if (isset($definition['bool'])) {
|
||||
$value = (bool) $value;
|
||||
}
|
||||
|
||||
if ($value !== $default) {
|
||||
$output .= $indent . $prefix . "['" . implode("']['", $parents) . "'] = ";
|
||||
if (isset($definition['bool'])) {
|
||||
$output .= empty($storage[$option]) ? 'FALSE' : 'TRUE';
|
||||
}
|
||||
else {
|
||||
$output .= views_var_export($storage[$option], $indent);
|
||||
}
|
||||
|
||||
$output .= ";\n";
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always exports the option, regardless of the default value.
|
||||
*/
|
||||
function export_option_always($indent, $prefix, $storage, $option, $definition, $parents) {
|
||||
// If there is no default, the option will always be exported.
|
||||
unset($definition['default']);
|
||||
// Unset our export method to prevent recursion.
|
||||
unset($definition['export']);
|
||||
return $this->export_option($indent, $prefix, $storage, $option, $definition, $parents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks each handler to store translatable texts.
|
||||
*/
|
||||
function unpack_translatables(&$translatable, $parents = array()) {
|
||||
foreach ($this->option_definition() as $option => $definition) {
|
||||
$this->unpack_translatable($translatable, $this->options, $option, $definition, $parents, array());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack a single option definition.
|
||||
*
|
||||
* This function run's through all suboptions recursive.
|
||||
*
|
||||
* @param $translatable
|
||||
* Stores all available translatable items.
|
||||
* @param $storage
|
||||
* @param $option
|
||||
* @param $definition
|
||||
* @param $parents
|
||||
* @param $keys
|
||||
*/
|
||||
function unpack_translatable(&$translatable, $storage, $option, $definition, $parents, $keys = array()) {
|
||||
// Do not export options for which we have no settings.
|
||||
if (!isset($storage[$option])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handling for some items
|
||||
if (isset($definition['unpack_translatable']) && method_exists($this, $definition['unpack_translatable'])) {
|
||||
return $this->{$definition['unpack_translatable']}($translatable, $storage, $option, $definition, $parents, $keys);
|
||||
}
|
||||
|
||||
if (isset($definition['translatable'])) {
|
||||
if ($definition['translatable'] === FALSE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the current option to the parents tree.
|
||||
$parents[] = $option;
|
||||
|
||||
// If it has child items, unpack those separately.
|
||||
if (isset($definition['contains'])) {
|
||||
foreach ($definition['contains'] as $sub_option => $sub_definition) {
|
||||
$translation_keys = array_merge($keys, array($sub_option));
|
||||
$this->unpack_translatable($translatable, $storage[$option], $sub_option, $sub_definition, $parents, $translation_keys);
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Figure out this double definition stuff.
|
||||
$options = $storage[$option];
|
||||
if (is_array($options)) {
|
||||
foreach ($options as $key => $value) {
|
||||
$translation_keys = array_merge($keys, array($key));
|
||||
if (is_array($value)) {
|
||||
$this->unpack_translatable($translatable, $options, $key, $definition, $parents, $translation_keys);
|
||||
}
|
||||
else if (!empty($definition[$key]['translatable']) && !empty($value)) {
|
||||
// Build source data and add to the array
|
||||
$format = NULL;
|
||||
if (isset($definition['format_key']) && isset($options[$definition['format_key']])) {
|
||||
$format = $options[$definition['format_key']];
|
||||
}
|
||||
$translatable[] = array(
|
||||
'value' => $value,
|
||||
'keys' => $translation_keys,
|
||||
'format' => $format,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!empty($definition['translatable']) && !empty($options)) {
|
||||
$value = $options;
|
||||
// Build source data and add to the array
|
||||
$format = NULL;
|
||||
if (isset($definition['format_key']) && isset($options[$definition['format_key']])) {
|
||||
$format = $options[$definition['format_key']];
|
||||
}
|
||||
$translatable[] = array(
|
||||
'value' => $value,
|
||||
'keys' => isset($translation_keys) ? $translation_keys : $parents,
|
||||
'format' => $format,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Batch processing API for processes to run in multiple HTTP requests.
|
||||
*
|
||||
* Note that batches are usually invoked by form submissions, which is
|
||||
* why the core interaction functions of the batch processing API live in
|
||||
* form.inc.
|
||||
*
|
||||
* @see form.inc
|
||||
* @see batch_set()
|
||||
* @see batch_process()
|
||||
* @see batch_get()
|
||||
*/
|
||||
|
||||
/**
|
||||
* Loads a batch from the database.
|
||||
*
|
||||
* @param $id
|
||||
* The ID of the batch to load. When a progressive batch is being processed,
|
||||
* the relevant ID is found in $_REQUEST['id'].
|
||||
*
|
||||
* @return
|
||||
* An array representing the batch, or FALSE if no batch was found.
|
||||
*/
|
||||
function batch_load($id) {
|
||||
$batch = db_query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", array(
|
||||
':bid' => $id,
|
||||
':token' => drupal_get_token($id),
|
||||
))->fetchField();
|
||||
if ($batch) {
|
||||
return unserialize($batch);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the batch processing page based on the current state of the batch.
|
||||
*
|
||||
* @see _batch_shutdown()
|
||||
*/
|
||||
function _batch_page() {
|
||||
$batch = &batch_get();
|
||||
|
||||
if (!isset($_REQUEST['id'])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Retrieve the current state of the batch.
|
||||
if (!$batch) {
|
||||
$batch = batch_load($_REQUEST['id']);
|
||||
if (!$batch) {
|
||||
drupal_set_message(t('No active batch.'), 'error');
|
||||
drupal_goto();
|
||||
}
|
||||
}
|
||||
|
||||
// Register database update for the end of processing.
|
||||
drupal_register_shutdown_function('_batch_shutdown');
|
||||
|
||||
// Add batch-specific CSS.
|
||||
foreach ($batch['sets'] as $batch_set) {
|
||||
if (isset($batch_set['css'])) {
|
||||
foreach ($batch_set['css'] as $css) {
|
||||
drupal_add_css($css);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
|
||||
$output = NULL;
|
||||
switch ($op) {
|
||||
case 'start':
|
||||
$output = _batch_start();
|
||||
break;
|
||||
|
||||
case 'do':
|
||||
// JavaScript-based progress page callback.
|
||||
_batch_do();
|
||||
break;
|
||||
|
||||
case 'do_nojs':
|
||||
// Non-JavaScript-based progress page.
|
||||
$output = _batch_progress_page_nojs();
|
||||
break;
|
||||
|
||||
case 'finished':
|
||||
$output = _batch_finished();
|
||||
break;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the batch processing.
|
||||
*
|
||||
* JavaScript-enabled clients are identified by the 'has_js' cookie set in
|
||||
* drupal.js. If no JavaScript-enabled page has been visited during the current
|
||||
* user's browser session, the non-JavaScript version is returned.
|
||||
*/
|
||||
function _batch_start() {
|
||||
if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
|
||||
return _batch_progress_page_js();
|
||||
}
|
||||
else {
|
||||
return _batch_progress_page_nojs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a batch processing page with JavaScript support.
|
||||
*
|
||||
* This initializes the batch and error messages. Note that in JavaScript-based
|
||||
* processing, the batch processing page is displayed only once and updated via
|
||||
* AHAH requests, so only the first batch set gets to define the page title.
|
||||
* Titles specified by subsequent batch sets are not displayed.
|
||||
*
|
||||
* @see batch_set()
|
||||
* @see _batch_do()
|
||||
*/
|
||||
function _batch_progress_page_js() {
|
||||
$batch = batch_get();
|
||||
|
||||
$current_set = _batch_current_set();
|
||||
drupal_set_title($current_set['title'], PASS_THROUGH);
|
||||
|
||||
// Merge required query parameters for batch processing into those provided by
|
||||
// batch_set() or hook_batch_alter().
|
||||
$batch['url_options']['query']['id'] = $batch['id'];
|
||||
|
||||
$js_setting = array(
|
||||
'batch' => array(
|
||||
'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'],
|
||||
'initMessage' => $current_set['init_message'],
|
||||
'uri' => url($batch['url'], $batch['url_options']),
|
||||
),
|
||||
);
|
||||
drupal_add_js($js_setting, 'setting');
|
||||
drupal_add_library('system', 'drupal.batch');
|
||||
|
||||
return '<div id="progress"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Does one execution pass with JavaScript and returns progress to the browser.
|
||||
*
|
||||
* @see _batch_progress_page_js()
|
||||
* @see _batch_process()
|
||||
*/
|
||||
function _batch_do() {
|
||||
// HTTP POST required.
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||
drupal_set_message(t('HTTP POST is required.'), 'error');
|
||||
drupal_set_title(t('Error'));
|
||||
return '';
|
||||
}
|
||||
|
||||
// Perform actual processing.
|
||||
list($percentage, $message) = _batch_process();
|
||||
|
||||
drupal_json_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a batch processing page without JavaScript support.
|
||||
*
|
||||
* @see _batch_process()
|
||||
*/
|
||||
function _batch_progress_page_nojs() {
|
||||
$batch = &batch_get();
|
||||
|
||||
$current_set = _batch_current_set();
|
||||
drupal_set_title($current_set['title'], PASS_THROUGH);
|
||||
|
||||
$new_op = 'do_nojs';
|
||||
|
||||
if (!isset($batch['running'])) {
|
||||
// This is the first page so we return some output immediately.
|
||||
$percentage = 0;
|
||||
$message = $current_set['init_message'];
|
||||
$batch['running'] = TRUE;
|
||||
}
|
||||
else {
|
||||
// This is one of the later requests; do some processing first.
|
||||
|
||||
// Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
|
||||
// function), it will output whatever is in the output buffer, followed by
|
||||
// the error message.
|
||||
ob_start();
|
||||
$fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
|
||||
$fallback = theme('maintenance_page', array('content' => $fallback, 'show_messages' => FALSE));
|
||||
|
||||
// We strip the end of the page using a marker in the template, so any
|
||||
// additional HTML output by PHP shows up inside the page rather than below
|
||||
// it. While this causes invalid HTML, the same would be true if we didn't,
|
||||
// as content is not allowed to appear after </html> anyway.
|
||||
list($fallback) = explode('<!--partial-->', $fallback);
|
||||
print $fallback;
|
||||
|
||||
// Perform actual processing.
|
||||
list($percentage, $message) = _batch_process($batch);
|
||||
if ($percentage == 100) {
|
||||
$new_op = 'finished';
|
||||
}
|
||||
|
||||
// PHP did not die; remove the fallback output.
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// Merge required query parameters for batch processing into those provided by
|
||||
// batch_set() or hook_batch_alter().
|
||||
$batch['url_options']['query']['id'] = $batch['id'];
|
||||
$batch['url_options']['query']['op'] = $new_op;
|
||||
|
||||
$url = url($batch['url'], $batch['url_options']);
|
||||
$element = array(
|
||||
'#tag' => 'meta',
|
||||
'#attributes' => array(
|
||||
'http-equiv' => 'Refresh',
|
||||
'content' => '0; URL=' . $url,
|
||||
),
|
||||
);
|
||||
drupal_add_html_head($element, 'batch_progress_meta_refresh');
|
||||
|
||||
return theme('progress_bar', array('percent' => $percentage, 'message' => $message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes sets in a batch.
|
||||
*
|
||||
* If the batch was marked for progressive execution (default), this executes as
|
||||
* many operations in batch sets until an execution time of 1 second has been
|
||||
* exceeded. It will continue with the next operation of the same batch set in
|
||||
* the next request.
|
||||
*
|
||||
* @return
|
||||
* An array containing a completion value (in percent) and a status message.
|
||||
*/
|
||||
function _batch_process() {
|
||||
$batch = &batch_get();
|
||||
$current_set = &_batch_current_set();
|
||||
// Indicate that this batch set needs to be initialized.
|
||||
$set_changed = TRUE;
|
||||
|
||||
// If this batch was marked for progressive execution (e.g. forms submitted by
|
||||
// drupal_form_submit()), initialize a timer to determine whether we need to
|
||||
// proceed with the same batch phase when a processing time of 1 second has
|
||||
// been exceeded.
|
||||
if ($batch['progressive']) {
|
||||
timer_start('batch_processing');
|
||||
}
|
||||
|
||||
if (empty($current_set['start'])) {
|
||||
$current_set['start'] = microtime(TRUE);
|
||||
}
|
||||
|
||||
$queue = _batch_queue($current_set);
|
||||
|
||||
while (!$current_set['success']) {
|
||||
// If this is the first time we iterate this batch set in the current
|
||||
// request, we check if it requires an additional file for functions
|
||||
// definitions.
|
||||
if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
|
||||
include_once DRUPAL_ROOT . '/' . $current_set['file'];
|
||||
}
|
||||
|
||||
$task_message = '';
|
||||
// Assume a single pass operation and set the completion level to 1 by
|
||||
// default.
|
||||
$finished = 1;
|
||||
|
||||
if ($item = $queue->claimItem()) {
|
||||
list($function, $args) = $item->data;
|
||||
|
||||
// Build the 'context' array and execute the function call.
|
||||
$batch_context = array(
|
||||
'sandbox' => &$current_set['sandbox'],
|
||||
'results' => &$current_set['results'],
|
||||
'finished' => &$finished,
|
||||
'message' => &$task_message,
|
||||
);
|
||||
call_user_func_array($function, array_merge($args, array(&$batch_context)));
|
||||
|
||||
if ($finished >= 1) {
|
||||
// Make sure this step is not counted twice when computing $current.
|
||||
$finished = 0;
|
||||
// Remove the processed operation and clear the sandbox.
|
||||
$queue->deleteItem($item);
|
||||
$current_set['count']--;
|
||||
$current_set['sandbox'] = array();
|
||||
}
|
||||
}
|
||||
|
||||
// When all operations in the current batch set are completed, browse
|
||||
// through the remaining sets, marking them 'successfully processed'
|
||||
// along the way, until we find a set that contains operations.
|
||||
// _batch_next_set() executes form submit handlers stored in 'control'
|
||||
// sets (see form_execute_handlers()), which can in turn add new sets to
|
||||
// the batch.
|
||||
$set_changed = FALSE;
|
||||
$old_set = $current_set;
|
||||
while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
|
||||
$current_set = &_batch_current_set();
|
||||
$current_set['start'] = microtime(TRUE);
|
||||
$set_changed = TRUE;
|
||||
}
|
||||
|
||||
// At this point, either $current_set contains operations that need to be
|
||||
// processed or all sets have been completed.
|
||||
$queue = _batch_queue($current_set);
|
||||
|
||||
// If we are in progressive mode, break processing after 1 second.
|
||||
if ($batch['progressive'] && timer_read('batch_processing') > 1000) {
|
||||
// Record elapsed wall clock time.
|
||||
$current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($batch['progressive']) {
|
||||
// Gather progress information.
|
||||
|
||||
// Reporting 100% progress will cause the whole batch to be considered
|
||||
// processed. If processing was paused right after moving to a new set,
|
||||
// we have to use the info from the new (unprocessed) set.
|
||||
if ($set_changed && isset($current_set['queue'])) {
|
||||
// Processing will continue with a fresh batch set.
|
||||
$remaining = $current_set['count'];
|
||||
$total = $current_set['total'];
|
||||
$progress_message = $current_set['init_message'];
|
||||
$task_message = '';
|
||||
}
|
||||
else {
|
||||
// Processing will continue with the current batch set.
|
||||
$remaining = $old_set['count'];
|
||||
$total = $old_set['total'];
|
||||
$progress_message = $old_set['progress_message'];
|
||||
}
|
||||
|
||||
// Total progress is the number of operations that have fully run plus the
|
||||
// completion level of the current operation.
|
||||
$current = $total - $remaining + $finished;
|
||||
$percentage = _batch_api_percentage($total, $current);
|
||||
$elapsed = isset($current_set['elapsed']) ? $current_set['elapsed'] : 0;
|
||||
$values = array(
|
||||
'@remaining' => $remaining,
|
||||
'@total' => $total,
|
||||
'@current' => floor($current),
|
||||
'@percentage' => $percentage,
|
||||
'@elapsed' => format_interval($elapsed / 1000),
|
||||
// If possible, estimate remaining processing time.
|
||||
'@estimate' => ($current > 0) ? format_interval(($elapsed * ($total - $current) / $current) / 1000) : '-',
|
||||
);
|
||||
$message = strtr($progress_message, $values);
|
||||
if (!empty($message)) {
|
||||
$message .= '<br />';
|
||||
}
|
||||
if (!empty($task_message)) {
|
||||
$message .= $task_message;
|
||||
}
|
||||
|
||||
return array($percentage, $message);
|
||||
}
|
||||
else {
|
||||
// If we are not in progressive mode, the entire batch has been processed.
|
||||
return _batch_finished();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the percent completion for a batch set.
|
||||
*
|
||||
* @param $total
|
||||
* The total number of operations.
|
||||
* @param $current
|
||||
* The number of the current operation. This may be a floating point number
|
||||
* rather than an integer in the case of a multi-step operation that is not
|
||||
* yet complete; in that case, the fractional part of $current represents the
|
||||
* fraction of the operation that has been completed.
|
||||
*
|
||||
* @return
|
||||
* The properly formatted percentage, as a string. We output percentages
|
||||
* using the correct number of decimal places so that we never print "100%"
|
||||
* until we are finished, but we also never print more decimal places than
|
||||
* are meaningful.
|
||||
*
|
||||
* @see _batch_process()
|
||||
*/
|
||||
function _batch_api_percentage($total, $current) {
|
||||
if (!$total || $total == $current) {
|
||||
// If $total doesn't evaluate as true or is equal to the current set, then
|
||||
// we're finished, and we can return "100".
|
||||
$percentage = "100";
|
||||
}
|
||||
else {
|
||||
// We add a new digit at 200, 2000, etc. (since, for example, 199/200
|
||||
// would round up to 100% if we didn't).
|
||||
$decimal_places = max(0, floor(log10($total / 2.0)) - 1);
|
||||
do {
|
||||
// Calculate the percentage to the specified number of decimal places.
|
||||
$percentage = sprintf('%01.' . $decimal_places . 'f', round($current / $total * 100, $decimal_places));
|
||||
// When $current is an integer, the above calculation will always be
|
||||
// correct. However, if $current is a floating point number (in the case
|
||||
// of a multi-step batch operation that is not yet complete), $percentage
|
||||
// may be erroneously rounded up to 100%. To prevent that, we add one
|
||||
// more decimal place and try again.
|
||||
$decimal_places++;
|
||||
} while ($percentage == '100');
|
||||
}
|
||||
return $percentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the batch set being currently processed.
|
||||
*/
|
||||
function &_batch_current_set() {
|
||||
$batch = &batch_get();
|
||||
return $batch['sets'][$batch['current_set']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the next set in a batch.
|
||||
*
|
||||
* If there is a subsequent set in this batch, assign it as the new set to
|
||||
* process and execute its form submit handler (if defined), which may add
|
||||
* further sets to this batch.
|
||||
*
|
||||
* @return
|
||||
* TRUE if a subsequent set was found in the batch.
|
||||
*/
|
||||
function _batch_next_set() {
|
||||
$batch = &batch_get();
|
||||
if (isset($batch['sets'][$batch['current_set'] + 1])) {
|
||||
$batch['current_set']++;
|
||||
$current_set = &_batch_current_set();
|
||||
if (isset($current_set['form_submit']) && ($function = $current_set['form_submit']) && function_exists($function)) {
|
||||
// We use our stored copies of $form and $form_state to account for
|
||||
// possible alterations by previous form submit handlers.
|
||||
$function($batch['form_state']['complete form'], $batch['form_state']);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the batch processing.
|
||||
*
|
||||
* Call the 'finished' callback of each batch set to allow custom handling of
|
||||
* the results and resolve page redirection.
|
||||
*/
|
||||
function _batch_finished() {
|
||||
$batch = &batch_get();
|
||||
|
||||
// Execute the 'finished' callbacks for each batch set, if defined.
|
||||
foreach ($batch['sets'] as $batch_set) {
|
||||
if (isset($batch_set['finished'])) {
|
||||
// Check if the set requires an additional file for function definitions.
|
||||
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
|
||||
include_once DRUPAL_ROOT . '/' . $batch_set['file'];
|
||||
}
|
||||
if (is_callable($batch_set['finished'])) {
|
||||
$queue = _batch_queue($batch_set);
|
||||
$operations = $queue->getAllItems();
|
||||
call_user_func($batch_set['finished'], $batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the batch table and unset the static $batch variable.
|
||||
if ($batch['progressive']) {
|
||||
db_delete('batch')
|
||||
->condition('bid', $batch['id'])
|
||||
->execute();
|
||||
foreach ($batch['sets'] as $batch_set) {
|
||||
if ($queue = _batch_queue($batch_set)) {
|
||||
$queue->deleteQueue();
|
||||
}
|
||||
}
|
||||
// Clean-up the session. Not needed for CLI updates.
|
||||
if (isset($_SESSION)) {
|
||||
unset($_SESSION['batches'][$batch['id']]);
|
||||
if (empty($_SESSION['batches'])) {
|
||||
unset($_SESSION['batches']);
|
||||
}
|
||||
}
|
||||
}
|
||||
$_batch = $batch;
|
||||
$batch = NULL;
|
||||
|
||||
// Redirect if needed.
|
||||
if ($_batch['progressive']) {
|
||||
// Revert the 'destination' that was saved in batch_process().
|
||||
if (isset($_batch['destination'])) {
|
||||
$_GET['destination'] = $_batch['destination'];
|
||||
}
|
||||
|
||||
// Determine the target path to redirect to.
|
||||
if (!isset($_batch['form_state']['redirect'])) {
|
||||
if (isset($_batch['redirect'])) {
|
||||
$_batch['form_state']['redirect'] = $_batch['redirect'];
|
||||
}
|
||||
else {
|
||||
$_batch['form_state']['redirect'] = $_batch['source_url'];
|
||||
}
|
||||
}
|
||||
|
||||
// Use drupal_redirect_form() to handle the redirection logic.
|
||||
drupal_redirect_form($_batch['form_state']);
|
||||
|
||||
// If no redirection happened, redirect to the originating page. In case the
|
||||
// form needs to be rebuilt, save the final $form_state for
|
||||
// drupal_build_form().
|
||||
if (!empty($_batch['form_state']['rebuild'])) {
|
||||
$_SESSION['batch_form_state'] = $_batch['form_state'];
|
||||
}
|
||||
$function = $_batch['redirect_callback'];
|
||||
if (function_exists($function)) {
|
||||
$function($_batch['source_url'], array('query' => array('op' => 'finish', 'id' => $_batch['id'])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown function: Stores the current batch data for the next request.
|
||||
*
|
||||
* @see _batch_page()
|
||||
* @see drupal_register_shutdown_function()
|
||||
*/
|
||||
function _batch_shutdown() {
|
||||
if ($batch = batch_get()) {
|
||||
db_update('batch')
|
||||
->fields(array('batch' => serialize($batch)))
|
||||
->condition('bid', $batch['id'])
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Queue handlers used by the Batch API.
|
||||
*
|
||||
* These implementations:
|
||||
* - Ensure FIFO ordering.
|
||||
* - Allow an item to be repeatedly claimed until it is actually deleted (no
|
||||
* notion of lease time or 'expire' date), to allow multipass operations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a batch queue.
|
||||
*
|
||||
* Stale items from failed batches are cleaned from the {queue} table on cron
|
||||
* using the 'created' date.
|
||||
*/
|
||||
class BatchQueue extends SystemQueue {
|
||||
|
||||
/**
|
||||
* Overrides SystemQueue::claimItem().
|
||||
*
|
||||
* Unlike SystemQueue::claimItem(), this method provides a default lease
|
||||
* time of 0 (no expiration) instead of 30. This allows the item to be
|
||||
* claimed repeatedly until it is deleted.
|
||||
*/
|
||||
public function claimItem($lease_time = 0) {
|
||||
$item = db_query_range('SELECT data, item_id FROM {queue} q WHERE name = :name ORDER BY item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject();
|
||||
if ($item) {
|
||||
$item->data = unserialize($item->data);
|
||||
return $item;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all remaining items in the queue.
|
||||
*
|
||||
* This is specific to Batch API and is not part of the DrupalQueueInterface.
|
||||
*/
|
||||
public function getAllItems() {
|
||||
$result = array();
|
||||
$items = db_query('SELECT data FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchAll();
|
||||
foreach ($items as $item) {
|
||||
$result[] = unserialize($item->data);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a batch queue for non-progressive batches.
|
||||
*/
|
||||
class BatchMemoryQueue extends MemoryQueue {
|
||||
|
||||
/**
|
||||
* Overrides MemoryQueue::claimItem().
|
||||
*
|
||||
* Unlike MemoryQueue::claimItem(), this method provides a default lease
|
||||
* time of 0 (no expiration) instead of 30. This allows the item to be
|
||||
* claimed repeatedly until it is deleted.
|
||||
*/
|
||||
public function claimItem($lease_time = 0) {
|
||||
if (!empty($this->queue)) {
|
||||
reset($this->queue);
|
||||
return current($this->queue);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all remaining items in the queue.
|
||||
*
|
||||
* This is specific to Batch API and is not part of the DrupalQueueInterface.
|
||||
*/
|
||||
public function getAllItems() {
|
||||
$result = array();
|
||||
foreach ($this->queue as $item) {
|
||||
$result[] = $item->data;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides a stub cache implementation to be used during installation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a stub cache implementation to be used during installation.
|
||||
*
|
||||
* The stub implementation is needed when database access is not yet available.
|
||||
* Because Drupal's caching system never requires that cached data be present,
|
||||
* these stub functions can short-circuit the process and sidestep the need for
|
||||
* any persistent storage. Obviously, using this cache implementation during
|
||||
* normal operations would have a negative impact on performance.
|
||||
*/
|
||||
class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterface {
|
||||
|
||||
/**
|
||||
* Overrides DrupalDatabaseCache::get().
|
||||
*/
|
||||
function get($cid) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalDatabaseCache::getMultiple().
|
||||
*/
|
||||
function getMultiple(&$cids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalDatabaseCache::set().
|
||||
*/
|
||||
function set($cid, $data, $expire = CACHE_PERMANENT) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalDatabaseCache::clear().
|
||||
*/
|
||||
function clear($cid = NULL, $wildcard = FALSE) {
|
||||
// If there is a database cache, attempt to clear it whenever possible. The
|
||||
// reason for doing this is that the database cache can accumulate data
|
||||
// during installation due to any full bootstraps that may occur at the
|
||||
// same time (for example, Ajax requests triggered by the installer). If we
|
||||
// didn't try to clear it whenever this function is called, the data in the
|
||||
// cache would become stale; for example, the installer sometimes calls
|
||||
// variable_set(), which updates the {variable} table and then clears the
|
||||
// cache to make sure that the next page request picks up the new value.
|
||||
// Not actually clearing the cache here therefore leads old variables to be
|
||||
// loaded on the first page requests after installation, which can cause
|
||||
// subtle bugs, some of which would not be fixed unless the site
|
||||
// administrator cleared the cache manually.
|
||||
try {
|
||||
if (class_exists('Database')) {
|
||||
parent::clear($cid, $wildcard);
|
||||
}
|
||||
}
|
||||
// If the attempt at clearing the cache causes an error, that means that
|
||||
// either the database connection is not set up yet or the relevant cache
|
||||
// table in the database has not yet been created, so we can safely do
|
||||
// nothing here.
|
||||
catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalDatabaseCache::isEmpty().
|
||||
*/
|
||||
function isEmpty() {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
+568
-171
@@ -2,192 +2,589 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Load Views' data so that it knows what is available to build queries from.
|
||||
* Functions and interfaces for cache handling.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetch Views' data from the cache
|
||||
* Gets the cache object for a cache bin.
|
||||
*
|
||||
* @param $move
|
||||
* Under certain circumstances it makes sense to not get the moved table, but the old one.
|
||||
* One example is views_get_handler.
|
||||
*/
|
||||
function _views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
|
||||
$cache = &drupal_static(__FUNCTION__ . '_cache');
|
||||
$recursion_protection = &drupal_static(__FUNCTION__ . '_recursion_protected');
|
||||
$fully_loaded = &drupal_static(__FUNCTION__ . '_fully_loaded');
|
||||
if ($reset) {
|
||||
$cache = NULL;
|
||||
$fully_loaded = FALSE;
|
||||
}
|
||||
if ($table) {
|
||||
if (!isset($cache[$table])) {
|
||||
$cid = 'views_data:' . $table;
|
||||
$data = views_cache_get($cid, TRUE);
|
||||
if (!empty($data->data)) {
|
||||
$cache[$table] = $data->data;
|
||||
}
|
||||
else {
|
||||
// No cache entry, rebuild.
|
||||
$cache = _views_fetch_data_build();
|
||||
$fully_loaded = TRUE;
|
||||
}
|
||||
}
|
||||
if (isset($cache[$table])) {
|
||||
if (isset($cache[$table]['moved to']) && $move) {
|
||||
$moved_table = $cache[$table]['moved to'];
|
||||
if (!empty($recursion_protection[$table])) {
|
||||
// recursion detected!
|
||||
return NULL;
|
||||
}
|
||||
$recursion_protection[$table] = TRUE;
|
||||
$data = _views_fetch_data($moved_table);
|
||||
$recursion_protection = array();
|
||||
return $data;
|
||||
}
|
||||
return $cache[$table];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!$fully_loaded) {
|
||||
$data = views_cache_get('views_data', TRUE);
|
||||
if (!empty($data->data)) {
|
||||
$cache = $data->data;
|
||||
}
|
||||
|
||||
if (empty($cache)) {
|
||||
$cache = _views_fetch_data_build();
|
||||
}
|
||||
$fully_loaded = TRUE;
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
// Return an empty array if there is no match.
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and set the views data cache if empty.
|
||||
*/
|
||||
function _views_fetch_data_build() {
|
||||
views_include_handlers();
|
||||
$cache = module_invoke_all('views_data');
|
||||
foreach (module_implements('views_data_alter') as $module) {
|
||||
$function = $module . '_views_data_alter';
|
||||
$function($cache);
|
||||
}
|
||||
_views_data_process_entity_types($cache);
|
||||
|
||||
// Keep a record with all data.
|
||||
views_cache_set('views_data', $cache, TRUE);
|
||||
// Save data in seperate cache entries.
|
||||
foreach ($cache as $key => $data) {
|
||||
$cid = 'views_data:' . $key;
|
||||
views_cache_set($cid, $data, TRUE);
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links tables having an 'entity type' specified to the respective generic entity-type tables.
|
||||
*/
|
||||
function _views_data_process_entity_types(&$data) {
|
||||
foreach ($data as $table_name => $table_info) {
|
||||
// Add in a join from the entity-table if an entity-type is given.
|
||||
if (!empty($table_info['table']['entity type'])) {
|
||||
$entity_table = 'views_entity_' . $table_info['table']['entity type'];
|
||||
|
||||
$data[$entity_table]['table']['join'][$table_name] = array(
|
||||
'left_table' => $table_name,
|
||||
);
|
||||
$data[$entity_table]['table']['entity type'] = $table_info['table']['entity type'];
|
||||
// Copy over the default table group if we have none yet.
|
||||
if (!empty($table_info['table']['group']) && empty($data[$entity_table]['table']['group'])) {
|
||||
$data[$entity_table]['table']['group'] = $table_info['table']['group'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the plugin data from cache.
|
||||
*/
|
||||
function _views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
|
||||
static $cache = NULL;
|
||||
if (!isset($cache) || $reset) {
|
||||
$start = microtime(TRUE);
|
||||
views_include('plugins');
|
||||
views_include_handlers();
|
||||
|
||||
$cache = views_discover_plugins();
|
||||
|
||||
}
|
||||
|
||||
if (!$type && !$plugin) {
|
||||
return $cache;
|
||||
}
|
||||
elseif (!$plugin) {
|
||||
// Not in the if above so the else below won't run
|
||||
if (isset($cache[$type])) {
|
||||
return $cache[$type];
|
||||
}
|
||||
}
|
||||
elseif (isset($cache[$type][$plugin])) {
|
||||
return $cache[$type][$plugin];
|
||||
}
|
||||
|
||||
// Return an empty array if there is no match.
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cached item in the views cache.
|
||||
* By default, this returns an instance of the DrupalDatabaseCache class.
|
||||
* Classes implementing DrupalCacheInterface can register themselves both as a
|
||||
* default implementation and for specific bins.
|
||||
*
|
||||
* This is just a convenience wrapper around cache_set().
|
||||
* @param $bin
|
||||
* The cache bin for which the cache object should be returned.
|
||||
*
|
||||
* @return DrupalCacheInterface
|
||||
* The cache object associated with the specified bin.
|
||||
*
|
||||
* @see DrupalCacheInterface
|
||||
*/
|
||||
function _cache_get_object($bin) {
|
||||
// We do not use drupal_static() here because we do not want to change the
|
||||
// storage of a cache bin mid-request.
|
||||
static $cache_objects;
|
||||
if (!isset($cache_objects[$bin])) {
|
||||
$class = variable_get('cache_class_' . $bin);
|
||||
if (!isset($class)) {
|
||||
$class = variable_get('cache_default_class', 'DrupalDatabaseCache');
|
||||
}
|
||||
$cache_objects[$bin] = new $class($bin);
|
||||
}
|
||||
return $cache_objects[$bin];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data from the persistent cache.
|
||||
*
|
||||
* Data may be stored as either plain text or as serialized data. cache_get
|
||||
* will automatically return unserialized objects and arrays.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache ID of the data to retrieve.
|
||||
* @param $bin
|
||||
* The cache bin to store the data in. Valid core values are 'cache_block',
|
||||
* 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
|
||||
* 'cache_menu', 'cache_page', 'cache_path', 'cache_update' or 'cache' for
|
||||
* the default cache.
|
||||
*
|
||||
* @return
|
||||
* The cache or FALSE on failure.
|
||||
*
|
||||
* @see cache_set()
|
||||
*/
|
||||
function cache_get($cid, $bin = 'cache') {
|
||||
return _cache_get_object($bin)->get($cid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data from the persistent cache when given an array of cache IDs.
|
||||
*
|
||||
* @param $cids
|
||||
* An array of cache IDs for the data to retrieve. This is passed by
|
||||
* reference, and will have the IDs successfully returned from cache removed.
|
||||
* @param $bin
|
||||
* The cache bin where the data is stored.
|
||||
*
|
||||
* @return
|
||||
* An array of the items successfully returned from cache indexed by cid.
|
||||
*/
|
||||
function cache_get_multiple(array &$cids, $bin = 'cache') {
|
||||
return _cache_get_object($bin)->getMultiple($cids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores data in the persistent cache.
|
||||
*
|
||||
* The persistent cache is split up into several cache bins. In the default
|
||||
* cache implementation, each cache bin corresponds to a database table by the
|
||||
* same name. Other implementations might want to store several bins in data
|
||||
* structures that get flushed together. While it is not a problem for most
|
||||
* cache bins if the entries in them are flushed before their expire time, some
|
||||
* might break functionality or are extremely expensive to recalculate. The
|
||||
* other bins are expired automatically by core. Contributed modules can add
|
||||
* additional bins and get them expired automatically by implementing
|
||||
* hook_flush_caches().
|
||||
*
|
||||
* The reasons for having several bins are as follows:
|
||||
* - Smaller bins mean smaller database tables and allow for faster selects and
|
||||
* inserts.
|
||||
* - We try to put fast changing cache items and rather static ones into
|
||||
* different bins. The effect is that only the fast changing bins will need a
|
||||
* lot of writes to disk. The more static bins will also be better cacheable
|
||||
* with MySQL's query cache.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache ID of the data to store.
|
||||
* @param $data
|
||||
* The data to store in the cache. Complex data types will be automatically serialized before insertion.
|
||||
* Strings will be stored as plain text and not serialized.
|
||||
* @param $use_language
|
||||
* If TRUE, the data will be cached specific to the currently active language.
|
||||
* The data to store in the cache. Complex data types will be automatically
|
||||
* serialized before insertion. Strings will be stored as plain text and are
|
||||
* not serialized. Some storage engines only allow objects up to a maximum of
|
||||
* 1MB in size to be stored by default. When caching large arrays or similar,
|
||||
* take care to ensure $data does not exceed this size.
|
||||
* @param $bin
|
||||
* (optional) The cache bin to store the data in. Valid core values are:
|
||||
* - cache: (default) Generic cache storage bin (used for theme registry,
|
||||
* locale date, list of simpletest tests, etc.).
|
||||
* - cache_block: Stores the content of various blocks.
|
||||
* - cache_bootstrap: Stores the class registry, the system list of modules,
|
||||
* the list of which modules implement which hooks, and the Drupal variable
|
||||
* list.
|
||||
* - cache_field: Stores the field data belonging to a given object.
|
||||
* - cache_filter: Stores filtered pieces of content.
|
||||
* - cache_form: Stores multistep forms. Flushing this bin means that some
|
||||
* forms displayed to users lose their state and the data already submitted
|
||||
* to them. This bin should not be flushed before its expired time.
|
||||
* - cache_menu: Stores the structure of visible navigation menus per page.
|
||||
* - cache_page: Stores generated pages for anonymous users. It is flushed
|
||||
* very often, whenever a page changes, at least for every node and comment
|
||||
* submission. This is the only bin affected by the page cache setting on
|
||||
* the administrator panel.
|
||||
* - cache_path: Stores the system paths that have an alias.
|
||||
* @param $expire
|
||||
* (optional) Controls the maximum lifetime of this cache entry. Note that
|
||||
* caches might be subject to clearing at any time, so this setting does not
|
||||
* guarantee a minimum lifetime. With this in mind, the cache should not be
|
||||
* used for data that must be kept during a cache clear, like sessions.
|
||||
*
|
||||
* Use one of the following values:
|
||||
* - CACHE_PERMANENT: Indicates that the item should never be removed unless
|
||||
* explicitly told to using cache_clear_all() with a cache ID.
|
||||
* - CACHE_TEMPORARY: Indicates that the item should be removed at the next
|
||||
* general cache wipe.
|
||||
* - A Unix timestamp: Indicates that the item should be kept at least until
|
||||
* the given time, after which it behaves like CACHE_TEMPORARY.
|
||||
*
|
||||
* @see _update_cache_set()
|
||||
* @see cache_get()
|
||||
*/
|
||||
function views_cache_set($cid, $data, $use_language = FALSE) {
|
||||
global $language;
|
||||
|
||||
if (variable_get('views_skip_cache', FALSE)) {
|
||||
return;
|
||||
}
|
||||
if ($use_language) {
|
||||
$cid .= ':' . $language->language;
|
||||
}
|
||||
|
||||
cache_set($cid, $data, 'cache_views');
|
||||
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
|
||||
return _cache_get_object($bin)->set($cid, $data, $expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data from the persistent views cache.
|
||||
* Expires data from the cache.
|
||||
*
|
||||
* This is just a convenience wrapper around cache_get().
|
||||
* If called with the arguments $cid and $bin set to NULL or omitted, then
|
||||
* expirable entries will be cleared from the cache_page and cache_block bins,
|
||||
* and the $wildcard argument is ignored.
|
||||
*
|
||||
* @param int $cid
|
||||
* The cache ID of the data to retrieve.
|
||||
* @param bool $use_language
|
||||
* If TRUE, the data will be requested specific to the currently active language.
|
||||
*
|
||||
* @return stdClass|bool
|
||||
* The cache or FALSE on failure.
|
||||
* @param $cid
|
||||
* If set, the cache ID or an array of cache IDs. Otherwise, all cache entries
|
||||
* that can expire are deleted. The $wildcard argument will be ignored if set
|
||||
* to NULL.
|
||||
* @param $bin
|
||||
* If set, the cache bin to delete from. Mandatory argument if $cid is set.
|
||||
* @param $wildcard
|
||||
* If TRUE, the $cid argument must contain a string value and cache IDs
|
||||
* starting with $cid are deleted in addition to the exact cache ID specified
|
||||
* by $cid. If $wildcard is TRUE and $cid is '*', the entire cache is emptied.
|
||||
*/
|
||||
function views_cache_get($cid, $use_language = FALSE) {
|
||||
global $language;
|
||||
|
||||
if (variable_get('views_skip_cache', FALSE)) {
|
||||
return FALSE;
|
||||
function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
|
||||
if (!isset($cid) && !isset($bin)) {
|
||||
// Clear the block cache first, so stale data will
|
||||
// not end up in the page cache.
|
||||
if (module_exists('block')) {
|
||||
cache_clear_all(NULL, 'cache_block');
|
||||
}
|
||||
cache_clear_all(NULL, 'cache_page');
|
||||
return;
|
||||
}
|
||||
return _cache_get_object($bin)->clear($cid, $wildcard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a cache bin is empty.
|
||||
*
|
||||
* A cache bin is considered empty if it does not contain any valid data for any
|
||||
* cache ID.
|
||||
*
|
||||
* @param $bin
|
||||
* The cache bin to check.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the cache bin specified is empty.
|
||||
*/
|
||||
function cache_is_empty($bin) {
|
||||
return _cache_get_object($bin)->isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an interface for cache implementations.
|
||||
*
|
||||
* All cache implementations have to implement this interface.
|
||||
* DrupalDatabaseCache provides the default implementation, which can be
|
||||
* consulted as an example.
|
||||
*
|
||||
* To make Drupal use your implementation for a certain cache bin, you have to
|
||||
* set a variable with the name of the cache bin as its key and the name of
|
||||
* your class as its value. For example, if your implementation of
|
||||
* DrupalCacheInterface was called MyCustomCache, the following line would make
|
||||
* Drupal use it for the 'cache_page' bin:
|
||||
* @code
|
||||
* variable_set('cache_class_cache_page', 'MyCustomCache');
|
||||
* @endcode
|
||||
*
|
||||
* Additionally, you can register your cache implementation to be used by
|
||||
* default for all cache bins by setting the variable 'cache_default_class' to
|
||||
* the name of your implementation of the DrupalCacheInterface, e.g.
|
||||
* @code
|
||||
* variable_set('cache_default_class', 'MyCustomCache');
|
||||
* @endcode
|
||||
*
|
||||
* To implement a completely custom cache bin, use the same variable format:
|
||||
* @code
|
||||
* variable_set('cache_class_custom_bin', 'MyCustomCache');
|
||||
* @endcode
|
||||
* To access your custom cache bin, specify the name of the bin when storing
|
||||
* or retrieving cached data:
|
||||
* @code
|
||||
* cache_set($cid, $data, 'custom_bin', $expire);
|
||||
* cache_get($cid, 'custom_bin');
|
||||
* @endcode
|
||||
*
|
||||
* @see _cache_get_object()
|
||||
* @see DrupalDatabaseCache
|
||||
*/
|
||||
interface DrupalCacheInterface {
|
||||
|
||||
/**
|
||||
* Returns data from the persistent cache.
|
||||
*
|
||||
* Data may be stored as either plain text or as serialized data. cache_get()
|
||||
* will automatically return unserialized objects and arrays.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache ID of the data to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The cache or FALSE on failure.
|
||||
*/
|
||||
function get($cid);
|
||||
|
||||
/**
|
||||
* Returns data from the persistent cache when given an array of cache IDs.
|
||||
*
|
||||
* @param $cids
|
||||
* An array of cache IDs for the data to retrieve. This is passed by
|
||||
* reference, and will have the IDs successfully returned from cache
|
||||
* removed.
|
||||
*
|
||||
* @return
|
||||
* An array of the items successfully returned from cache indexed by cid.
|
||||
*/
|
||||
function getMultiple(&$cids);
|
||||
|
||||
/**
|
||||
* Stores data in the persistent cache.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache ID of the data to store.
|
||||
* @param $data
|
||||
* The data to store in the cache. Complex data types will be automatically
|
||||
* serialized before insertion. Strings will be stored as plain text and not
|
||||
* serialized. Some storage engines only allow objects up to a maximum of
|
||||
* 1MB in size to be stored by default. When caching large arrays or
|
||||
* similar, take care to ensure $data does not exceed this size.
|
||||
* @param $expire
|
||||
* (optional) Controls the maximum lifetime of this cache entry. Note that
|
||||
* caches might be subject to clearing at any time, so this setting does not
|
||||
* guarantee a minimum lifetime. With this in mind, the cache should not be
|
||||
* used for data that must be kept during a cache clear, like sessions.
|
||||
*
|
||||
* Use one of the following values:
|
||||
* - CACHE_PERMANENT: Indicates that the item should never be removed unless
|
||||
* explicitly told to using cache_clear_all() with a cache ID.
|
||||
* - CACHE_TEMPORARY: Indicates that the item should be removed at the next
|
||||
* general cache wipe.
|
||||
* - A Unix timestamp: Indicates that the item should be kept at least until
|
||||
* the given time, after which it behaves like CACHE_TEMPORARY.
|
||||
*/
|
||||
function set($cid, $data, $expire = CACHE_PERMANENT);
|
||||
|
||||
|
||||
/**
|
||||
* Expires data from the cache.
|
||||
*
|
||||
* If called without arguments, expirable entries will be cleared from the
|
||||
* cache_page and cache_block bins.
|
||||
*
|
||||
* @param $cid
|
||||
* If set, the cache ID or an array of cache IDs. Otherwise, all cache
|
||||
* entries that can expire are deleted. The $wildcard argument will be
|
||||
* ignored if set to NULL.
|
||||
* @param $wildcard
|
||||
* If TRUE, the $cid argument must contain a string value and cache IDs
|
||||
* starting with $cid are deleted in addition to the exact cache ID
|
||||
* specified by $cid. If $wildcard is TRUE and $cid is '*', the entire
|
||||
* cache is emptied.
|
||||
*/
|
||||
function clear($cid = NULL, $wildcard = FALSE);
|
||||
|
||||
/**
|
||||
* Checks if a cache bin is empty.
|
||||
*
|
||||
* A cache bin is considered empty if it does not contain any valid data for
|
||||
* any cache ID.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the cache bin specified is empty.
|
||||
*/
|
||||
function isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a default cache implementation.
|
||||
*
|
||||
* This is Drupal's default cache implementation. It uses the database to store
|
||||
* cached data. Each cache bin corresponds to a database table by the same name.
|
||||
*/
|
||||
class DrupalDatabaseCache implements DrupalCacheInterface {
|
||||
protected $bin;
|
||||
|
||||
/**
|
||||
* Constructs a DrupalDatabaseCache object.
|
||||
*
|
||||
* @param $bin
|
||||
* The cache bin for which the object is created.
|
||||
*/
|
||||
function __construct($bin) {
|
||||
$this->bin = $bin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalCacheInterface::get().
|
||||
*/
|
||||
function get($cid) {
|
||||
$cids = array($cid);
|
||||
$cache = $this->getMultiple($cids);
|
||||
return reset($cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalCacheInterface::getMultiple().
|
||||
*/
|
||||
function getMultiple(&$cids) {
|
||||
try {
|
||||
// Garbage collection necessary when enforcing a minimum cache lifetime.
|
||||
$this->garbageCollection($this->bin);
|
||||
|
||||
// When serving cached pages, the overhead of using db_select() was found
|
||||
// to add around 30% overhead to the request. Since $this->bin is a
|
||||
// variable, this means the call to db_query() here uses a concatenated
|
||||
// string. This is highly discouraged under any other circumstances, and
|
||||
// is used here only due to the performance overhead we would incur
|
||||
// otherwise. When serving an uncached page, the overhead of using
|
||||
// db_select() is a much smaller proportion of the request.
|
||||
$result = db_query('SELECT cid, data, created, expire, serialized FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
|
||||
$cache = array();
|
||||
foreach ($result as $item) {
|
||||
$item = $this->prepareItem($item);
|
||||
if ($item) {
|
||||
$cache[$item->cid] = $item;
|
||||
}
|
||||
}
|
||||
$cids = array_diff($cids, array_keys($cache));
|
||||
return $cache;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// If the database is never going to be available, cache requests should
|
||||
// return FALSE in order to allow exception handling to occur.
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collection for get() and getMultiple().
|
||||
*
|
||||
* @param $bin
|
||||
* The bin being requested.
|
||||
*/
|
||||
protected function garbageCollection() {
|
||||
$cache_lifetime = variable_get('cache_lifetime', 0);
|
||||
|
||||
// Clean-up the per-user cache expiration session data, so that the session
|
||||
// handler can properly clean-up the session data for anonymous users.
|
||||
if (isset($_SESSION['cache_expiration'])) {
|
||||
$expire = REQUEST_TIME - $cache_lifetime;
|
||||
foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) {
|
||||
if ($timestamp < $expire) {
|
||||
unset($_SESSION['cache_expiration'][$bin]);
|
||||
}
|
||||
}
|
||||
if (!$_SESSION['cache_expiration']) {
|
||||
unset($_SESSION['cache_expiration']);
|
||||
}
|
||||
}
|
||||
|
||||
// Garbage collection of temporary items is only necessary when enforcing
|
||||
// a minimum cache lifetime.
|
||||
if (!$cache_lifetime) {
|
||||
return;
|
||||
}
|
||||
// When cache lifetime is in force, avoid running garbage collection too
|
||||
// often since this will remove temporary cache items indiscriminately.
|
||||
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
|
||||
if ($cache_flush && ($cache_flush + $cache_lifetime <= REQUEST_TIME)) {
|
||||
// Reset the variable immediately to prevent a meltdown in heavy load situations.
|
||||
variable_set('cache_flush_' . $this->bin, 0);
|
||||
// Time to flush old cache data
|
||||
db_delete($this->bin)
|
||||
->condition('expire', CACHE_PERMANENT, '<>')
|
||||
->condition('expire', $cache_flush, '<=')
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a cached item.
|
||||
*
|
||||
* Checks that items are either permanent or did not expire, and unserializes
|
||||
* data as appropriate.
|
||||
*
|
||||
* @param $cache
|
||||
* An item loaded from cache_get() or cache_get_multiple().
|
||||
*
|
||||
* @return
|
||||
* The item with data unserialized as appropriate or FALSE if there is no
|
||||
* valid item to load.
|
||||
*/
|
||||
protected function prepareItem($cache) {
|
||||
global $user;
|
||||
|
||||
if (!isset($cache->data)) {
|
||||
return FALSE;
|
||||
}
|
||||
// If the cached data is temporary and subject to a per-user minimum
|
||||
// lifetime, compare the cache entry timestamp with the user session
|
||||
// cache_expiration timestamp. If the cache entry is too old, ignore it.
|
||||
if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && isset($_SESSION['cache_expiration'][$this->bin]) && $_SESSION['cache_expiration'][$this->bin] > $cache->created) {
|
||||
// Ignore cache data that is too old and thus not valid for this user.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// If the data is permanent or not subject to a minimum cache lifetime,
|
||||
// unserialize and return the cached data.
|
||||
if ($cache->serialized) {
|
||||
$cache->data = unserialize($cache->data);
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalCacheInterface::set().
|
||||
*/
|
||||
function set($cid, $data, $expire = CACHE_PERMANENT) {
|
||||
$fields = array(
|
||||
'serialized' => 0,
|
||||
'created' => REQUEST_TIME,
|
||||
'expire' => $expire,
|
||||
);
|
||||
if (!is_string($data)) {
|
||||
$fields['data'] = serialize($data);
|
||||
$fields['serialized'] = 1;
|
||||
}
|
||||
else {
|
||||
$fields['data'] = $data;
|
||||
$fields['serialized'] = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
db_merge($this->bin)
|
||||
->key(array('cid' => $cid))
|
||||
->fields($fields)
|
||||
->execute();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// The database may not be available, so we'll ignore cache_set requests.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalCacheInterface::clear().
|
||||
*/
|
||||
function clear($cid = NULL, $wildcard = FALSE) {
|
||||
global $user;
|
||||
|
||||
if (empty($cid)) {
|
||||
if (variable_get('cache_lifetime', 0)) {
|
||||
// We store the time in the current user's session. We then simulate
|
||||
// that the cache was flushed for this user by not returning cached
|
||||
// data that was cached before the timestamp.
|
||||
$_SESSION['cache_expiration'][$this->bin] = REQUEST_TIME;
|
||||
|
||||
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
|
||||
if ($cache_flush == 0) {
|
||||
// This is the first request to clear the cache, start a timer.
|
||||
variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
|
||||
}
|
||||
elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) {
|
||||
// Clear the cache for everyone, cache_lifetime seconds have
|
||||
// passed since the first request to clear the cache.
|
||||
db_delete($this->bin)
|
||||
->condition('expire', CACHE_PERMANENT, '<>')
|
||||
->condition('expire', REQUEST_TIME, '<')
|
||||
->execute();
|
||||
variable_set('cache_flush_' . $this->bin, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No minimum cache lifetime, flush all temporary cache entries now.
|
||||
db_delete($this->bin)
|
||||
->condition('expire', CACHE_PERMANENT, '<>')
|
||||
->condition('expire', REQUEST_TIME, '<')
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($wildcard) {
|
||||
if ($cid == '*') {
|
||||
// Check if $this->bin is a cache table before truncating. Other
|
||||
// cache_clear_all() operations throw a PDO error in this situation,
|
||||
// so we don't need to verify them first. This ensures that non-cache
|
||||
// tables cannot be truncated accidentally.
|
||||
if ($this->isValidBin()) {
|
||||
db_truncate($this->bin)->execute();
|
||||
}
|
||||
else {
|
||||
throw new Exception(t('Invalid or missing cache bin specified: %bin', array('%bin' => $this->bin)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
db_delete($this->bin)
|
||||
->condition('cid', db_like($cid) . '%', 'LIKE')
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
elseif (is_array($cid)) {
|
||||
// Delete in chunks when a large array is passed.
|
||||
do {
|
||||
db_delete($this->bin)
|
||||
->condition('cid', array_splice($cid, 0, 1000), 'IN')
|
||||
->execute();
|
||||
}
|
||||
while (count($cid));
|
||||
}
|
||||
else {
|
||||
db_delete($this->bin)
|
||||
->condition('cid', $cid)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalCacheInterface::isEmpty().
|
||||
*/
|
||||
function isEmpty() {
|
||||
$this->garbageCollection();
|
||||
$query = db_select($this->bin);
|
||||
$query->addExpression('1');
|
||||
$result = $query->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
return empty($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $this->bin represents a valid cache table.
|
||||
*
|
||||
* This check is required to ensure that non-cache tables are not truncated
|
||||
* accidentally when calling cache_clear_all().
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
function isValidBin() {
|
||||
if ($this->bin == 'cache' || substr($this->bin, 0, 6) == 'cache_') {
|
||||
// Skip schema check for bins with standard table names.
|
||||
return TRUE;
|
||||
}
|
||||
// These fields are required for any cache table.
|
||||
$fields = array('cid', 'data', 'expire', 'created', 'serialized');
|
||||
// Load the table schema.
|
||||
$schema = drupal_get_schema($this->bin);
|
||||
// Confirm that all fields are present.
|
||||
return isset($schema['fields']) && !array_diff($fields, array_keys($schema['fields']));
|
||||
}
|
||||
if ($use_language) {
|
||||
$cid .= ':' . $language->language;
|
||||
}
|
||||
|
||||
return cache_get($cid, 'cache_views');
|
||||
}
|
||||
|
||||
+8562
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Logging classes for the database layer.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Database query logger.
|
||||
*
|
||||
* We log queries in a separate object rather than in the connection object
|
||||
* because we want to be able to see all queries sent to a given database, not
|
||||
* database target. If we logged the queries in each connection object we
|
||||
* would not be able to track what queries went to which target.
|
||||
*
|
||||
* Every connection has one and only one logging object on it for all targets
|
||||
* and logging keys.
|
||||
*/
|
||||
class DatabaseLog {
|
||||
|
||||
/**
|
||||
* Cache of logged queries. This will only be used if the query logger is enabled.
|
||||
*
|
||||
* The structure for the logging array is as follows:
|
||||
*
|
||||
* array(
|
||||
* $logging_key = array(
|
||||
* array(query => '', args => array(), caller => '', target => '', time => 0),
|
||||
* array(query => '', args => array(), caller => '', target => '', time => 0),
|
||||
* ),
|
||||
* );
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $queryLog = array();
|
||||
|
||||
/**
|
||||
* The connection key for which this object is logging.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $connectionKey = 'default';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param $key
|
||||
* The database connection key for which to enable logging.
|
||||
*/
|
||||
public function __construct($key = 'default') {
|
||||
$this->connectionKey = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin logging queries to the specified connection and logging key.
|
||||
*
|
||||
* If the specified logging key is already running this method does nothing.
|
||||
*
|
||||
* @param $logging_key
|
||||
* The identification key for this log request. By specifying different
|
||||
* logging keys we are able to start and stop multiple logging runs
|
||||
* simultaneously without them colliding.
|
||||
*/
|
||||
public function start($logging_key) {
|
||||
if (empty($this->queryLog[$logging_key])) {
|
||||
$this->clear($logging_key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the query log for the specified logging key so far.
|
||||
*
|
||||
* @param $logging_key
|
||||
* The logging key to fetch.
|
||||
* @return
|
||||
* An indexed array of all query records for this logging key.
|
||||
*/
|
||||
public function get($logging_key) {
|
||||
return $this->queryLog[$logging_key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the query log for the specified logging key.
|
||||
*
|
||||
* This method does not stop logging, it simply clears the log. To stop
|
||||
* logging, use the end() method.
|
||||
*
|
||||
* @param $logging_key
|
||||
* The logging key to empty.
|
||||
*/
|
||||
public function clear($logging_key) {
|
||||
$this->queryLog[$logging_key] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop logging for the specified logging key.
|
||||
*
|
||||
* @param $logging_key
|
||||
* The logging key to stop.
|
||||
*/
|
||||
public function end($logging_key) {
|
||||
unset($this->queryLog[$logging_key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a query to all active logging keys.
|
||||
*
|
||||
* @param $statement
|
||||
* The prepared statement object to log.
|
||||
* @param $args
|
||||
* The arguments passed to the statement object.
|
||||
* @param $time
|
||||
* The time in milliseconds the query took to execute.
|
||||
*/
|
||||
public function log(DatabaseStatementInterface $statement, $args, $time) {
|
||||
foreach (array_keys($this->queryLog) as $key) {
|
||||
$this->queryLog[$key][] = array(
|
||||
'query' => $statement->getQueryString(),
|
||||
'args' => $args,
|
||||
'target' => $statement->dbh->getTarget(),
|
||||
'caller' => $this->findCaller(),
|
||||
'time' => $time,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the routine that called this query.
|
||||
*
|
||||
* We define "the routine that called this query" as the first entry in
|
||||
* the call stack that is not inside includes/database and does have a file
|
||||
* (which excludes call_user_func_array(), anonymous functions and similar).
|
||||
* That makes the climbing logic very simple, and handles the variable stack
|
||||
* depth caused by the query builders.
|
||||
*
|
||||
* @link http://www.php.net/debug_backtrace
|
||||
* @return
|
||||
* This method returns a stack trace entry similar to that generated by
|
||||
* debug_backtrace(). However, it flattens the trace entry and the trace
|
||||
* entry before it so that we get the function and args of the function that
|
||||
* called into the database system, not the function and args of the
|
||||
* database call itself.
|
||||
*/
|
||||
public function findCaller() {
|
||||
$stack = debug_backtrace();
|
||||
$stack_count = count($stack);
|
||||
for ($i = 0; $i < $stack_count; ++$i) {
|
||||
if (!empty($stack[$i]['file']) && strpos($stack[$i]['file'], 'includes' . DIRECTORY_SEPARATOR . 'database') === FALSE) {
|
||||
$stack[$i] += array('args' => array());
|
||||
return array(
|
||||
'file' => $stack[$i]['file'],
|
||||
'line' => $stack[$i]['line'],
|
||||
'function' => $stack[$i + 1]['function'],
|
||||
'class' => isset($stack[$i + 1]['class']) ? $stack[$i + 1]['class'] : NULL,
|
||||
'type' => isset($stack[$i + 1]['type']) ? $stack[$i + 1]['type'] : NULL,
|
||||
'args' => $stack[$i + 1]['args'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Database interface code for MySQL database servers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
class DatabaseConnection_mysql extends DatabaseConnection {
|
||||
|
||||
/**
|
||||
* Flag to indicate if the cleanup function in __destruct() should run.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $needsCleanup = FALSE;
|
||||
|
||||
public function __construct(array $connection_options = array()) {
|
||||
// This driver defaults to transaction support, except if explicitly passed FALSE.
|
||||
$this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
|
||||
|
||||
// MySQL never supports transactional DDL.
|
||||
$this->transactionalDDLSupport = FALSE;
|
||||
|
||||
$this->connectionOptions = $connection_options;
|
||||
|
||||
$charset = 'utf8';
|
||||
// Check if the charset is overridden to utf8mb4 in settings.php.
|
||||
if ($this->utf8mb4IsActive()) {
|
||||
$charset = 'utf8mb4';
|
||||
}
|
||||
|
||||
// The DSN should use either a socket or a host/port.
|
||||
if (isset($connection_options['unix_socket'])) {
|
||||
$dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
|
||||
}
|
||||
else {
|
||||
// Default to TCP connection on port 3306.
|
||||
$dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
|
||||
}
|
||||
// Character set is added to dsn to ensure PDO uses the proper character
|
||||
// set when escaping. This has security implications. See
|
||||
// https://www.drupal.org/node/1201452 for further discussion.
|
||||
$dsn .= ';charset=' . $charset;
|
||||
$dsn .= ';dbname=' . $connection_options['database'];
|
||||
// Allow PDO options to be overridden.
|
||||
$connection_options += array(
|
||||
'pdo' => array(),
|
||||
);
|
||||
$connection_options['pdo'] += array(
|
||||
// So we don't have to mess around with cursors and unbuffered queries by default.
|
||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
|
||||
// Because MySQL's prepared statements skip the query cache, because it's dumb.
|
||||
PDO::ATTR_EMULATE_PREPARES => TRUE,
|
||||
);
|
||||
if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
|
||||
// An added connection option in PHP 5.5.21+ to optionally limit SQL to a
|
||||
// single statement like mysqli.
|
||||
$connection_options['pdo'] += array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => FALSE);
|
||||
}
|
||||
|
||||
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
|
||||
|
||||
// Force MySQL to use the UTF-8 character set. Also set the collation, if a
|
||||
// certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
|
||||
// for UTF-8.
|
||||
if (!empty($connection_options['collation'])) {
|
||||
$this->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
|
||||
}
|
||||
else {
|
||||
$this->exec('SET NAMES ' . $charset);
|
||||
}
|
||||
|
||||
// Set MySQL init_commands if not already defined. Default Drupal's MySQL
|
||||
// behavior to conform more closely to SQL standards. This allows Drupal
|
||||
// to run almost seamlessly on many different kinds of database systems.
|
||||
// These settings force MySQL to behave the same as postgresql, or sqlite
|
||||
// in regards to syntax interpretation and invalid data handling. See
|
||||
// http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5
|
||||
// changed the meaning of TRADITIONAL we need to spell out the modes one by
|
||||
// one.
|
||||
$connection_options += array(
|
||||
'init_commands' => array(),
|
||||
);
|
||||
$connection_options['init_commands'] += array(
|
||||
'sql_mode' => "SET sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
|
||||
);
|
||||
// Execute initial commands.
|
||||
foreach ($connection_options['init_commands'] as $sql) {
|
||||
$this->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
if ($this->needsCleanup) {
|
||||
$this->nextIdDelete();
|
||||
}
|
||||
}
|
||||
|
||||
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
|
||||
return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
|
||||
}
|
||||
|
||||
public function queryTemporary($query, array $args = array(), array $options = array()) {
|
||||
$tablename = $this->generateTemporaryTableName();
|
||||
$this->query('CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY ' . $query, $args, $options);
|
||||
return $tablename;
|
||||
}
|
||||
|
||||
public function driver() {
|
||||
return 'mysql';
|
||||
}
|
||||
|
||||
public function databaseType() {
|
||||
return 'mysql';
|
||||
}
|
||||
|
||||
public function mapConditionOperator($operator) {
|
||||
// We don't want to override any of the defaults.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
public function nextId($existing_id = 0) {
|
||||
$new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
|
||||
// This should only happen after an import or similar event.
|
||||
if ($existing_id >= $new_id) {
|
||||
// If we INSERT a value manually into the sequences table, on the next
|
||||
// INSERT, MySQL will generate a larger value. However, there is no way
|
||||
// of knowing whether this value already exists in the table. MySQL
|
||||
// provides an INSERT IGNORE which would work, but that can mask problems
|
||||
// other than duplicate keys. Instead, we use INSERT ... ON DUPLICATE KEY
|
||||
// UPDATE in such a way that the UPDATE does not do anything. This way,
|
||||
// duplicate keys do not generate errors but everything else does.
|
||||
$this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id));
|
||||
$new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
|
||||
}
|
||||
$this->needsCleanup = TRUE;
|
||||
return $new_id;
|
||||
}
|
||||
|
||||
public function nextIdDelete() {
|
||||
// While we want to clean up the table to keep it up from occupying too
|
||||
// much storage and memory, we must keep the highest value in the table
|
||||
// because InnoDB uses an in-memory auto-increment counter as long as the
|
||||
// server runs. When the server is stopped and restarted, InnoDB
|
||||
// reinitializes the counter for each table for the first INSERT to the
|
||||
// table based solely on values from the table so deleting all values would
|
||||
// be a problem in this case. Also, TRUNCATE resets the auto increment
|
||||
// counter.
|
||||
try {
|
||||
$max_id = $this->query('SELECT MAX(value) FROM {sequences}')->fetchField();
|
||||
// We know we are using MySQL here, no need for the slower db_delete().
|
||||
$this->query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id));
|
||||
}
|
||||
// During testing, this function is called from shutdown with the
|
||||
// simpletest prefix stored in $this->connection, and those tables are gone
|
||||
// by the time shutdown is called so we need to ignore the database
|
||||
// errors. There is no problem with completely ignoring errors here: if
|
||||
// these queries fail, the sequence will work just fine, just use a bit
|
||||
// more database storage and memory.
|
||||
catch (PDOException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to work around issues to MySQL not supporting transactional DDL.
|
||||
*/
|
||||
protected function popCommittableTransactions() {
|
||||
// Commit all the committable layers.
|
||||
foreach (array_reverse($this->transactionLayers) as $name => $active) {
|
||||
// Stop once we found an active transaction.
|
||||
if ($active) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If there are no more layers left then we should commit.
|
||||
unset($this->transactionLayers[$name]);
|
||||
if (empty($this->transactionLayers)) {
|
||||
if (!PDO::commit()) {
|
||||
throw new DatabaseTransactionCommitFailedException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Attempt to release this savepoint in the standard way.
|
||||
try {
|
||||
$this->query('RELEASE SAVEPOINT ' . $name);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
// However, in MySQL (InnoDB), savepoints are automatically committed
|
||||
// when tables are altered or created (DDL transactions are not
|
||||
// supported). This can cause exceptions due to trying to release
|
||||
// savepoints which no longer exist.
|
||||
//
|
||||
// To avoid exceptions when no actual error has occurred, we silently
|
||||
// succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
|
||||
if ($e->errorInfo[1] == '1305') {
|
||||
// If one SAVEPOINT was released automatically, then all were.
|
||||
// Therefore, clean the transaction stack.
|
||||
$this->transactionLayers = array();
|
||||
// We also have to explain to PDO that the transaction stack has
|
||||
// been cleaned-up.
|
||||
PDO::commit();
|
||||
}
|
||||
else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function utf8mb4IsConfigurable() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function utf8mb4IsActive() {
|
||||
return isset($this->connectionOptions['charset']) && $this->connectionOptions['charset'] === 'utf8mb4';
|
||||
}
|
||||
|
||||
public function utf8mb4IsSupported() {
|
||||
// Ensure that the MySQL driver supports utf8mb4 encoding.
|
||||
$version = $this->getAttribute(PDO::ATTR_CLIENT_VERSION);
|
||||
if (strpos($version, 'mysqlnd') !== FALSE) {
|
||||
// The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
|
||||
$version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
|
||||
if (version_compare($version, '5.0.9', '<')) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
|
||||
if (version_compare($version, '5.5.3', '<')) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the MySQL server supports large prefixes and utf8mb4.
|
||||
try {
|
||||
$this->query("CREATE TABLE {drupal_utf8mb4_test} (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB");
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return FALSE;
|
||||
}
|
||||
$this->query("DROP TABLE {drupal_utf8mb4_test}");
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation code for MySQL embedded database engine.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Specifies installation tasks for MySQL and equivalent databases.
|
||||
*/
|
||||
class DatabaseTasks_mysql extends DatabaseTasks {
|
||||
/**
|
||||
* The PDO driver name for MySQL and equivalent databases.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pdoDriver = 'mysql';
|
||||
|
||||
/**
|
||||
* Returns a human-readable name string for MySQL and equivalent databases.
|
||||
*/
|
||||
public function name() {
|
||||
return st('MySQL, MariaDB, or equivalent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum version for MySQL.
|
||||
*/
|
||||
public function minimumVersion() {
|
||||
return '5.0.15';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Query code for MySQL embedded database engine.
|
||||
*/
|
||||
|
||||
|
||||
class InsertQuery_mysql extends InsertQuery {
|
||||
|
||||
public function execute() {
|
||||
if (!$this->preExecute()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If we're selecting from a SelectQuery, finish building the query and
|
||||
// pass it back, as any remaining options are irrelevant.
|
||||
if (empty($this->fromQuery)) {
|
||||
$max_placeholder = 0;
|
||||
$values = array();
|
||||
foreach ($this->insertValues as $insert_values) {
|
||||
foreach ($insert_values as $value) {
|
||||
$values[':db_insert_placeholder_' . $max_placeholder++] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$values = $this->fromQuery->getArguments();
|
||||
}
|
||||
|
||||
$last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions);
|
||||
|
||||
// Re-initialize the values array so that we can re-use this query.
|
||||
$this->insertValues = array();
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
// Create a sanitized comment string to prepend to the query.
|
||||
$comments = $this->connection->makeComment($this->comments);
|
||||
|
||||
// Default fields are always placed first for consistency.
|
||||
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
|
||||
|
||||
// If we're selecting from a SelectQuery, finish building the query and
|
||||
// pass it back, as any remaining options are irrelevant.
|
||||
if (!empty($this->fromQuery)) {
|
||||
$insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
|
||||
return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
|
||||
}
|
||||
|
||||
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
|
||||
|
||||
$max_placeholder = 0;
|
||||
$values = array();
|
||||
if (count($this->insertValues)) {
|
||||
foreach ($this->insertValues as $insert_values) {
|
||||
$placeholders = array();
|
||||
|
||||
// Default fields aren't really placeholders, but this is the most convenient
|
||||
// way to handle them.
|
||||
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
|
||||
|
||||
$new_placeholder = $max_placeholder + count($insert_values);
|
||||
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
|
||||
$placeholders[] = ':db_insert_placeholder_' . $i;
|
||||
}
|
||||
$max_placeholder = $new_placeholder;
|
||||
$values[] = '(' . implode(', ', $placeholders) . ')';
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If there are no values, then this is a default-only query. We still need to handle that.
|
||||
$placeholders = array_fill(0, count($this->defaultFields), 'default');
|
||||
$values[] = '(' . implode(', ', $placeholders) . ')';
|
||||
}
|
||||
|
||||
$query .= implode(', ', $values);
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
||||
class TruncateQuery_mysql extends TruncateQuery { }
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
@@ -0,0 +1,544 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Database schema code for MySQL database servers.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @addtogroup schemaapi
|
||||
* @{
|
||||
*/
|
||||
|
||||
class DatabaseSchema_mysql extends DatabaseSchema {
|
||||
|
||||
/**
|
||||
* Maximum length of a table comment in MySQL.
|
||||
*/
|
||||
const COMMENT_MAX_TABLE = 60;
|
||||
|
||||
/**
|
||||
* Maximum length of a column comment in MySQL.
|
||||
*/
|
||||
const COMMENT_MAX_COLUMN = 255;
|
||||
|
||||
/**
|
||||
* Get information about the table and database name from the prefix.
|
||||
*
|
||||
* @return
|
||||
* A keyed array with information about the database, table name and prefix.
|
||||
*/
|
||||
protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
|
||||
$info = array('prefix' => $this->connection->tablePrefix($table));
|
||||
if ($add_prefix) {
|
||||
$table = $info['prefix'] . $table;
|
||||
}
|
||||
if (($pos = strpos($table, '.')) !== FALSE) {
|
||||
$info['database'] = substr($table, 0, $pos);
|
||||
$info['table'] = substr($table, ++$pos);
|
||||
}
|
||||
else {
|
||||
$db_info = $this->connection->getConnectionOptions();
|
||||
$info['database'] = $db_info['database'];
|
||||
$info['table'] = $table;
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a condition to match a table name against a standard information_schema.
|
||||
*
|
||||
* MySQL uses databases like schemas rather than catalogs so when we build
|
||||
* a condition to query the information_schema.tables, we set the default
|
||||
* database as the schema unless specified otherwise, and exclude table_catalog
|
||||
* from the condition criteria.
|
||||
*/
|
||||
protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
|
||||
$info = $this->connection->getConnectionOptions();
|
||||
|
||||
$table_info = $this->getPrefixInfo($table_name, $add_prefix);
|
||||
|
||||
$condition = new DatabaseCondition('AND');
|
||||
$condition->condition('table_schema', $table_info['database']);
|
||||
$condition->condition('table_name', $table_info['table'], $operator);
|
||||
return $condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL to create a new table from a Drupal schema definition.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the table to create.
|
||||
* @param $table
|
||||
* A Schema API table definition array.
|
||||
* @return
|
||||
* An array of SQL statements to create the table.
|
||||
*/
|
||||
protected function createTableSql($name, $table) {
|
||||
$info = $this->connection->getConnectionOptions();
|
||||
|
||||
// Provide defaults if needed.
|
||||
$table += array(
|
||||
'mysql_engine' => 'InnoDB',
|
||||
// Allow the default charset to be overridden in settings.php.
|
||||
'mysql_character_set' => $this->connection->utf8mb4IsActive() ? 'utf8mb4' : 'utf8',
|
||||
);
|
||||
|
||||
$sql = "CREATE TABLE {" . $name . "} (\n";
|
||||
|
||||
// Add the SQL statement for each field.
|
||||
foreach ($table['fields'] as $field_name => $field) {
|
||||
$sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n";
|
||||
}
|
||||
|
||||
// Process keys & indexes.
|
||||
$keys = $this->createKeysSql($table);
|
||||
if (count($keys)) {
|
||||
$sql .= implode(", \n", $keys) . ", \n";
|
||||
}
|
||||
|
||||
// Remove the last comma and space.
|
||||
$sql = substr($sql, 0, -3) . "\n) ";
|
||||
|
||||
$sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
|
||||
// By default, MySQL uses the default collation for new tables, which is
|
||||
// 'utf8_general_ci' for utf8. If an alternate collation has been set, it
|
||||
// needs to be explicitly specified.
|
||||
// @see DatabaseConnection_mysql
|
||||
if (!empty($info['collation'])) {
|
||||
$sql .= ' COLLATE ' . $info['collation'];
|
||||
}
|
||||
|
||||
// The row format needs to be either DYNAMIC or COMPRESSED in order to allow
|
||||
// for the innodb_large_prefix setting to take effect, see
|
||||
// https://dev.mysql.com/doc/refman/5.6/en/create-table.html
|
||||
if ($this->connection->utf8mb4IsActive()) {
|
||||
$sql .= ' ROW_FORMAT=DYNAMIC';
|
||||
}
|
||||
|
||||
// Add table comment.
|
||||
if (!empty($table['description'])) {
|
||||
$sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
|
||||
}
|
||||
|
||||
return array($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an SQL string for a field to be used in table creation or alteration.
|
||||
*
|
||||
* Before passing a field out of a schema definition into this function it has
|
||||
* to be processed by _db_process_field().
|
||||
*
|
||||
* @param $name
|
||||
* Name of the field.
|
||||
* @param $spec
|
||||
* The field specification, as per the schema data structure format.
|
||||
*/
|
||||
protected function createFieldSql($name, $spec) {
|
||||
$sql = "`" . $name . "` " . $spec['mysql_type'];
|
||||
|
||||
if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT'))) {
|
||||
if (isset($spec['length'])) {
|
||||
$sql .= '(' . $spec['length'] . ')';
|
||||
}
|
||||
if (!empty($spec['binary'])) {
|
||||
$sql .= ' BINARY';
|
||||
}
|
||||
}
|
||||
elseif (isset($spec['precision']) && isset($spec['scale'])) {
|
||||
$sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
|
||||
}
|
||||
|
||||
if (!empty($spec['unsigned'])) {
|
||||
$sql .= ' unsigned';
|
||||
}
|
||||
|
||||
if (isset($spec['not null'])) {
|
||||
if ($spec['not null']) {
|
||||
$sql .= ' NOT NULL';
|
||||
}
|
||||
else {
|
||||
$sql .= ' NULL';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($spec['auto_increment'])) {
|
||||
$sql .= ' auto_increment';
|
||||
}
|
||||
|
||||
// $spec['default'] can be NULL, so we explicitly check for the key here.
|
||||
if (array_key_exists('default', $spec)) {
|
||||
if (is_string($spec['default'])) {
|
||||
$spec['default'] = "'" . $spec['default'] . "'";
|
||||
}
|
||||
elseif (!isset($spec['default'])) {
|
||||
$spec['default'] = 'NULL';
|
||||
}
|
||||
$sql .= ' DEFAULT ' . $spec['default'];
|
||||
}
|
||||
|
||||
if (empty($spec['not null']) && !isset($spec['default'])) {
|
||||
$sql .= ' DEFAULT NULL';
|
||||
}
|
||||
|
||||
// Add column comment.
|
||||
if (!empty($spec['description'])) {
|
||||
$sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database-engine specific properties for a field.
|
||||
*
|
||||
* @param $field
|
||||
* A field description array, as specified in the schema documentation.
|
||||
*/
|
||||
protected function processField($field) {
|
||||
|
||||
if (!isset($field['size'])) {
|
||||
$field['size'] = 'normal';
|
||||
}
|
||||
|
||||
// Set the correct database-engine specific datatype.
|
||||
// In case one is already provided, force it to uppercase.
|
||||
if (isset($field['mysql_type'])) {
|
||||
$field['mysql_type'] = drupal_strtoupper($field['mysql_type']);
|
||||
}
|
||||
else {
|
||||
$map = $this->getFieldTypeMap();
|
||||
$field['mysql_type'] = $map[$field['type'] . ':' . $field['size']];
|
||||
}
|
||||
|
||||
if (isset($field['type']) && $field['type'] == 'serial') {
|
||||
$field['auto_increment'] = TRUE;
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
public function getFieldTypeMap() {
|
||||
// Put :normal last so it gets preserved by array_flip. This makes
|
||||
// it much easier for modules (such as schema.module) to map
|
||||
// database types back into schema types.
|
||||
// $map does not use drupal_static as its value never changes.
|
||||
static $map = array(
|
||||
'varchar:normal' => 'VARCHAR',
|
||||
'char:normal' => 'CHAR',
|
||||
|
||||
'text:tiny' => 'TINYTEXT',
|
||||
'text:small' => 'TINYTEXT',
|
||||
'text:medium' => 'MEDIUMTEXT',
|
||||
'text:big' => 'LONGTEXT',
|
||||
'text:normal' => 'TEXT',
|
||||
|
||||
'serial:tiny' => 'TINYINT',
|
||||
'serial:small' => 'SMALLINT',
|
||||
'serial:medium' => 'MEDIUMINT',
|
||||
'serial:big' => 'BIGINT',
|
||||
'serial:normal' => 'INT',
|
||||
|
||||
'int:tiny' => 'TINYINT',
|
||||
'int:small' => 'SMALLINT',
|
||||
'int:medium' => 'MEDIUMINT',
|
||||
'int:big' => 'BIGINT',
|
||||
'int:normal' => 'INT',
|
||||
|
||||
'float:tiny' => 'FLOAT',
|
||||
'float:small' => 'FLOAT',
|
||||
'float:medium' => 'FLOAT',
|
||||
'float:big' => 'DOUBLE',
|
||||
'float:normal' => 'FLOAT',
|
||||
|
||||
'numeric:normal' => 'DECIMAL',
|
||||
|
||||
'blob:big' => 'LONGBLOB',
|
||||
'blob:normal' => 'BLOB',
|
||||
);
|
||||
return $map;
|
||||
}
|
||||
|
||||
protected function createKeysSql($spec) {
|
||||
$keys = array();
|
||||
|
||||
if (!empty($spec['primary key'])) {
|
||||
$keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')';
|
||||
}
|
||||
if (!empty($spec['unique keys'])) {
|
||||
foreach ($spec['unique keys'] as $key => $fields) {
|
||||
$keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeysSqlHelper($fields) . ')';
|
||||
}
|
||||
}
|
||||
if (!empty($spec['indexes'])) {
|
||||
foreach ($spec['indexes'] as $index => $fields) {
|
||||
$keys[] = 'INDEX `' . $index . '` (' . $this->createKeysSqlHelper($fields) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
protected function createKeySql($fields) {
|
||||
$return = array();
|
||||
foreach ($fields as $field) {
|
||||
if (is_array($field)) {
|
||||
$return[] = '`' . $field[0] . '`(' . $field[1] . ')';
|
||||
}
|
||||
else {
|
||||
$return[] = '`' . $field . '`';
|
||||
}
|
||||
}
|
||||
return implode(', ', $return);
|
||||
}
|
||||
|
||||
protected function createKeysSqlHelper($fields) {
|
||||
$return = array();
|
||||
foreach ($fields as $field) {
|
||||
if (is_array($field)) {
|
||||
$return[] = '`' . $field[0] . '`(' . $field[1] . ')';
|
||||
}
|
||||
else {
|
||||
$return[] = '`' . $field . '`';
|
||||
}
|
||||
}
|
||||
return implode(', ', $return);
|
||||
}
|
||||
|
||||
public function renameTable($table, $new_name) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
|
||||
}
|
||||
if ($this->tableExists($new_name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
|
||||
}
|
||||
|
||||
$info = $this->getPrefixInfo($new_name);
|
||||
return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`');
|
||||
}
|
||||
|
||||
public function dropTable($table) {
|
||||
if (!$this->tableExists($table)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('DROP TABLE {' . $table . '}');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function addField($table, $field, $spec, $keys_new = array()) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
|
||||
}
|
||||
if ($this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
|
||||
}
|
||||
|
||||
$fixnull = FALSE;
|
||||
if (!empty($spec['not null']) && !isset($spec['default'])) {
|
||||
$fixnull = TRUE;
|
||||
$spec['not null'] = FALSE;
|
||||
}
|
||||
$query = 'ALTER TABLE {' . $table . '} ADD ';
|
||||
$query .= $this->createFieldSql($field, $this->processField($spec));
|
||||
if ($keys_sql = $this->createKeysSql($keys_new)) {
|
||||
$query .= ', ADD ' . implode(', ADD ', $keys_sql);
|
||||
}
|
||||
$this->connection->query($query);
|
||||
if (isset($spec['initial'])) {
|
||||
$this->connection->update($table)
|
||||
->fields(array($field => $spec['initial']))
|
||||
->execute();
|
||||
}
|
||||
if ($fixnull) {
|
||||
$spec['not null'] = TRUE;
|
||||
$this->changeField($table, $field, $field, $spec);
|
||||
}
|
||||
}
|
||||
|
||||
public function dropField($table, $field) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function fieldSetDefault($table, $field, $default) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
|
||||
}
|
||||
|
||||
if (!isset($default)) {
|
||||
$default = 'NULL';
|
||||
}
|
||||
else {
|
||||
$default = is_string($default) ? "'$default'" : $default;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $default);
|
||||
}
|
||||
|
||||
public function fieldSetNoDefault($table, $field) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT');
|
||||
}
|
||||
|
||||
public function indexExists($table, $name) {
|
||||
// Returns one row for each column in the index. Result is string or FALSE.
|
||||
// Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
|
||||
$row = $this->connection->query('SHOW INDEX FROM {' . $table . "} WHERE key_name = '$name'")->fetchAssoc();
|
||||
return isset($row['Key_name']);
|
||||
}
|
||||
|
||||
public function addPrimaryKey($table, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
|
||||
}
|
||||
if ($this->indexExists($table, 'PRIMARY')) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
|
||||
}
|
||||
|
||||
public function dropPrimaryKey($table) {
|
||||
if (!$this->indexExists($table, 'PRIMARY')) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function addUniqueKey($table, $name, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
if ($this->indexExists($table, $name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')');
|
||||
}
|
||||
|
||||
public function dropUniqueKey($table, $name) {
|
||||
if (!$this->indexExists($table, $name)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function addIndex($table, $name, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
if ($this->indexExists($table, $name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')');
|
||||
}
|
||||
|
||||
public function dropIndex($table, $name) {
|
||||
if (!$this->indexExists($table, $name)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
|
||||
}
|
||||
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
|
||||
if ($keys_sql = $this->createKeysSql($keys_new)) {
|
||||
$sql .= ', ADD ' . implode(', ADD ', $keys_sql);
|
||||
}
|
||||
$this->connection->query($sql);
|
||||
}
|
||||
|
||||
public function prepareComment($comment, $length = NULL) {
|
||||
// Work around a bug in some versions of PDO, see http://bugs.php.net/bug.php?id=41125
|
||||
$comment = str_replace("'", '’', $comment);
|
||||
|
||||
// Truncate comment to maximum comment length.
|
||||
if (isset($length)) {
|
||||
// Add table prefixes before truncating.
|
||||
$comment = truncate_utf8($this->connection->prefixTables($comment), $length, TRUE, TRUE);
|
||||
}
|
||||
|
||||
return $this->connection->quote($comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a table or column comment.
|
||||
*/
|
||||
public function getComment($table, $column = NULL) {
|
||||
$condition = $this->buildTableNameCondition($table);
|
||||
if (isset($column)) {
|
||||
$condition->condition('column_name', $column);
|
||||
$condition->compile($this->connection, $this);
|
||||
// Don't use {} around information_schema.columns table.
|
||||
return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
|
||||
}
|
||||
$condition->compile($this->connection, $this);
|
||||
// Don't use {} around information_schema.tables table.
|
||||
$comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
|
||||
// Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
|
||||
return preg_replace('/; InnoDB free:.*$/', '', $comment);
|
||||
}
|
||||
|
||||
public function tableExists($table) {
|
||||
// The information_schema table is very slow to query under MySQL 5.0.
|
||||
// Instead, we try to select from the table in question. If it fails,
|
||||
// the most likely reason is that it does not exist. That is dramatically
|
||||
// faster than using information_schema.
|
||||
// @link http://bugs.mysql.com/bug.php?id=19588
|
||||
// @todo: This override should be removed once we require a version of MySQL
|
||||
// that has that bug fixed.
|
||||
try {
|
||||
$this->connection->queryRange("SELECT 1 FROM {" . $table . "}", 0, 1);
|
||||
return TRUE;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public function fieldExists($table, $column) {
|
||||
// The information_schema table is very slow to query under MySQL 5.0.
|
||||
// Instead, we try to select from the table and field in question. If it
|
||||
// fails, the most likely reason is that it does not exist. That is
|
||||
// dramatically faster than using information_schema.
|
||||
// @link http://bugs.mysql.com/bug.php?id=19588
|
||||
// @todo: This override should be removed once we require a version of MySQL
|
||||
// that has that bug fixed.
|
||||
try {
|
||||
$this->connection->queryRange("SELECT $column FROM {" . $table . "}", 0, 1);
|
||||
return TRUE;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup schemaapi".
|
||||
*/
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Database interface code for PostgreSQL database servers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* The name by which to obtain a lock for retrieving the next insert id.
|
||||
*/
|
||||
define('POSTGRESQL_NEXTID_LOCK', 1000);
|
||||
|
||||
class DatabaseConnection_pgsql extends DatabaseConnection {
|
||||
|
||||
public function __construct(array $connection_options = array()) {
|
||||
// This driver defaults to transaction support, except if explicitly passed FALSE.
|
||||
$this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
|
||||
|
||||
// Transactional DDL is always available in PostgreSQL,
|
||||
// but we'll only enable it if standard transactions are.
|
||||
$this->transactionalDDLSupport = $this->transactionSupport;
|
||||
|
||||
// Default to TCP connection on port 5432.
|
||||
if (empty($connection_options['port'])) {
|
||||
$connection_options['port'] = 5432;
|
||||
}
|
||||
|
||||
// PostgreSQL in trust mode doesn't require a password to be supplied.
|
||||
if (empty($connection_options['password'])) {
|
||||
$connection_options['password'] = NULL;
|
||||
}
|
||||
// If the password contains a backslash it is treated as an escape character
|
||||
// http://bugs.php.net/bug.php?id=53217
|
||||
// so backslashes in the password need to be doubled up.
|
||||
// The bug was reported against pdo_pgsql 1.0.2, backslashes in passwords
|
||||
// will break on this doubling up when the bug is fixed, so check the version
|
||||
//elseif (phpversion('pdo_pgsql') < 'version_this_was_fixed_in') {
|
||||
else {
|
||||
$connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']);
|
||||
}
|
||||
|
||||
$this->connectionOptions = $connection_options;
|
||||
|
||||
$dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
|
||||
|
||||
// Allow PDO options to be overridden.
|
||||
$connection_options += array(
|
||||
'pdo' => array(),
|
||||
);
|
||||
$connection_options['pdo'] += array(
|
||||
// Prepared statements are most effective for performance when queries
|
||||
// are recycled (used several times). However, if they are not re-used,
|
||||
// prepared statements become inefficient. Since most of Drupal's
|
||||
// prepared queries are not re-used, it should be faster to emulate
|
||||
// the preparation than to actually ready statements for re-use. If in
|
||||
// doubt, reset to FALSE and measure performance.
|
||||
PDO::ATTR_EMULATE_PREPARES => TRUE,
|
||||
// Convert numeric values to strings when fetching.
|
||||
PDO::ATTR_STRINGIFY_FETCHES => TRUE,
|
||||
);
|
||||
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
|
||||
|
||||
// Force PostgreSQL to use the UTF-8 character set by default.
|
||||
$this->exec("SET NAMES 'UTF8'");
|
||||
|
||||
// Execute PostgreSQL init_commands.
|
||||
if (isset($connection_options['init_commands'])) {
|
||||
$this->exec(implode('; ', $connection_options['init_commands']));
|
||||
}
|
||||
}
|
||||
|
||||
public function prepareQuery($query) {
|
||||
// mapConditionOperator converts LIKE operations to ILIKE for consistency
|
||||
// with MySQL. However, Postgres does not support ILIKE on bytea (blobs)
|
||||
// fields.
|
||||
// To make the ILIKE operator work, we type-cast bytea fields into text.
|
||||
// @todo This workaround only affects bytea fields, but the involved field
|
||||
// types involved in the query are unknown, so there is no way to
|
||||
// conditionally execute this for affected queries only.
|
||||
return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE) /i', ' ${1}::text ${2} ', $query));
|
||||
}
|
||||
|
||||
public function query($query, array $args = array(), $options = array()) {
|
||||
|
||||
$options += $this->defaultOptions();
|
||||
|
||||
// The PDO PostgreSQL driver has a bug which
|
||||
// doesn't type cast booleans correctly when
|
||||
// parameters are bound using associative
|
||||
// arrays.
|
||||
// See http://bugs.php.net/bug.php?id=48383
|
||||
foreach ($args as &$value) {
|
||||
if (is_bool($value)) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if ($query instanceof DatabaseStatementInterface) {
|
||||
$stmt = $query;
|
||||
$stmt->execute(NULL, $options);
|
||||
}
|
||||
else {
|
||||
$this->expandArguments($query, $args);
|
||||
$stmt = $this->prepareQuery($query);
|
||||
$stmt->execute($args, $options);
|
||||
}
|
||||
|
||||
switch ($options['return']) {
|
||||
case Database::RETURN_STATEMENT:
|
||||
return $stmt;
|
||||
case Database::RETURN_AFFECTED:
|
||||
return $stmt->rowCount();
|
||||
case Database::RETURN_INSERT_ID:
|
||||
return $this->lastInsertId($options['sequence_name']);
|
||||
case Database::RETURN_NULL:
|
||||
return;
|
||||
default:
|
||||
throw new PDOException('Invalid return directive: ' . $options['return']);
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
if ($options['throw_exception']) {
|
||||
// Add additional debug information.
|
||||
if ($query instanceof DatabaseStatementInterface) {
|
||||
$e->query_string = $stmt->getQueryString();
|
||||
}
|
||||
else {
|
||||
$e->query_string = $query;
|
||||
}
|
||||
$e->args = $args;
|
||||
throw $e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
|
||||
return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options);
|
||||
}
|
||||
|
||||
public function queryTemporary($query, array $args = array(), array $options = array()) {
|
||||
$tablename = $this->generateTemporaryTableName();
|
||||
$this->query('CREATE TEMPORARY TABLE {' . $tablename . '} AS ' . $query, $args, $options);
|
||||
return $tablename;
|
||||
}
|
||||
|
||||
public function driver() {
|
||||
return 'pgsql';
|
||||
}
|
||||
|
||||
public function databaseType() {
|
||||
return 'pgsql';
|
||||
}
|
||||
|
||||
public function mapConditionOperator($operator) {
|
||||
static $specials;
|
||||
|
||||
// Function calls not allowed in static declarations, thus this method.
|
||||
if (!isset($specials)) {
|
||||
$specials = array(
|
||||
// In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
|
||||
// statements, we need to use ILIKE instead.
|
||||
'LIKE' => array('operator' => 'ILIKE'),
|
||||
'NOT LIKE' => array('operator' => 'NOT ILIKE'),
|
||||
);
|
||||
}
|
||||
|
||||
return isset($specials[$operator]) ? $specials[$operator] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next id in a sequence.
|
||||
*
|
||||
* PostgreSQL has built in sequences. We'll use these instead of inserting
|
||||
* and updating a sequences table.
|
||||
*/
|
||||
public function nextId($existing = 0) {
|
||||
|
||||
// Retrieve the name of the sequence. This information cannot be cached
|
||||
// because the prefix may change, for example, like it does in simpletests.
|
||||
$sequence_name = $this->makeSequenceName('sequences', 'value');
|
||||
|
||||
// When PostgreSQL gets a value too small then it will lock the table,
|
||||
// retry the INSERT and if it's still too small then alter the sequence.
|
||||
$id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
|
||||
if ($id > $existing) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
// PostgreSQL advisory locks are simply locks to be used by an
|
||||
// application such as Drupal. This will prevent other Drupal processes
|
||||
// from altering the sequence while we are.
|
||||
$this->query("SELECT pg_advisory_lock(" . POSTGRESQL_NEXTID_LOCK . ")");
|
||||
|
||||
// While waiting to obtain the lock, the sequence may have been altered
|
||||
// so lets try again to obtain an adequate value.
|
||||
$id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
|
||||
if ($id > $existing) {
|
||||
$this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")");
|
||||
return $id;
|
||||
}
|
||||
|
||||
// Reset the sequence to a higher value than the existing id.
|
||||
$this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1));
|
||||
|
||||
// Retrieve the next id. We know this will be as high as we want it.
|
||||
$id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
|
||||
|
||||
$this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")");
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function utf8mb4IsActive() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function utf8mb4IsSupported() {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install functions for PostgreSQL embedded database engine.
|
||||
*/
|
||||
|
||||
|
||||
// PostgreSQL specific install functions
|
||||
|
||||
class DatabaseTasks_pgsql extends DatabaseTasks {
|
||||
protected $pdoDriver = 'pgsql';
|
||||
|
||||
public function __construct() {
|
||||
$this->tasks[] = array(
|
||||
'function' => 'checkEncoding',
|
||||
'arguments' => array(),
|
||||
);
|
||||
$this->tasks[] = array(
|
||||
'function' => 'checkPHPVersion',
|
||||
'arguments' => array(),
|
||||
);
|
||||
$this->tasks[] = array(
|
||||
'function' => 'checkBinaryOutput',
|
||||
'arguments' => array(),
|
||||
);
|
||||
$this->tasks[] = array(
|
||||
'function' => 'initializeDatabase',
|
||||
'arguments' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
public function name() {
|
||||
return st('PostgreSQL');
|
||||
}
|
||||
|
||||
public function minimumVersion() {
|
||||
return '8.3';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check encoding is UTF8.
|
||||
*/
|
||||
protected function checkEncoding() {
|
||||
try {
|
||||
if (db_query('SHOW server_encoding')->fetchField() == 'UTF8') {
|
||||
$this->pass(st('Database is encoded in UTF-8'));
|
||||
}
|
||||
else {
|
||||
$replacements = array(
|
||||
'%encoding' => 'UTF8',
|
||||
'%driver' => $this->name(),
|
||||
'!link' => '<a href="INSTALL.pgsql.txt">INSTALL.pgsql.txt</a>'
|
||||
);
|
||||
$text = 'The %driver database must use %encoding encoding to work with Drupal.';
|
||||
$text .= 'Recreate the database with %encoding encoding. See !link for more details.';
|
||||
$this->fail(st($text, $replacements));
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->fail(st('Drupal could not determine the encoding of the database was set to UTF-8'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PHP version.
|
||||
*
|
||||
* There are two bugs in PDO_pgsql affecting Drupal:
|
||||
*
|
||||
* - in versions < 5.2.7, PDO_pgsql refuses to insert an empty string into
|
||||
* a NOT NULL BLOB column. See: http://bugs.php.net/bug.php?id=46249
|
||||
* - in versions < 5.2.11 and < 5.3.1 that prevents inserting integer values
|
||||
* into numeric columns that exceed the PHP_INT_MAX value.
|
||||
* See: http://bugs.php.net/bug.php?id=48924
|
||||
*/
|
||||
function checkPHPVersion() {
|
||||
if (!version_compare(PHP_VERSION, '5.2.11', '>=') || (version_compare(PHP_VERSION, '5.3.0', '>=') && !version_compare(PHP_VERSION, '5.3.1', '>='))) {
|
||||
$this->fail(st('The version of PHP you are using has known issues with PostgreSQL. You need to upgrade PHP to 5.2.11, 5.3.1 or greater.'));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Binary Output.
|
||||
*
|
||||
* Unserializing does not work on Postgresql 9 when bytea_output is 'hex'.
|
||||
*/
|
||||
function checkBinaryOutput() {
|
||||
// PostgreSQL < 9 doesn't support bytea_output, so verify we are running
|
||||
// at least PostgreSQL 9.
|
||||
$database_connection = Database::getConnection();
|
||||
if (version_compare($database_connection->version(), '9') >= 0) {
|
||||
if (!$this->checkBinaryOutputSuccess()) {
|
||||
// First try to alter the database. If it fails, raise an error telling
|
||||
// the user to do it themselves.
|
||||
$connection_options = $database_connection->getConnectionOptions();
|
||||
// It is safe to include the database name directly here, because this
|
||||
// code is only called when a connection to the database is already
|
||||
// established, thus the database name is guaranteed to be a correct
|
||||
// value.
|
||||
$query = "ALTER DATABASE \"" . $connection_options['database'] . "\" SET bytea_output = 'escape';";
|
||||
try {
|
||||
db_query($query);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Ignore possible errors when the user doesn't have the necessary
|
||||
// privileges to ALTER the database.
|
||||
}
|
||||
|
||||
// Close the database connection so that the configuration parameter
|
||||
// is applied to the current connection.
|
||||
db_close();
|
||||
|
||||
// Recheck, if it fails, finally just rely on the end user to do the
|
||||
// right thing.
|
||||
if (!$this->checkBinaryOutputSuccess()) {
|
||||
$replacements = array(
|
||||
'%setting' => 'bytea_output',
|
||||
'%current_value' => 'hex',
|
||||
'%needed_value' => 'escape',
|
||||
'!query' => "<code>" . $query . "</code>",
|
||||
);
|
||||
$this->fail(st("The %setting setting is currently set to '%current_value', but needs to be '%needed_value'. Change this by running the following query: !query", $replacements));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a binary data roundtrip returns the original string.
|
||||
*/
|
||||
protected function checkBinaryOutputSuccess() {
|
||||
$bytea_output = db_query("SELECT 'encoding'::bytea AS output")->fetchField();
|
||||
return ($bytea_output == 'encoding');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make PostgreSQL Drupal friendly.
|
||||
*/
|
||||
function initializeDatabase() {
|
||||
// We create some functions using global names instead of prefixing them
|
||||
// like we do with table names. This is so that we don't double up if more
|
||||
// than one instance of Drupal is running on a single database. We therefore
|
||||
// avoid trying to create them again in that case.
|
||||
|
||||
try {
|
||||
// Create functions.
|
||||
db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
|
||||
\'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
|
||||
LANGUAGE \'sql\''
|
||||
);
|
||||
db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
|
||||
\'SELECT greatest($1, greatest($2, $3));\'
|
||||
LANGUAGE \'sql\''
|
||||
);
|
||||
// Don't use {} around pg_proc table.
|
||||
if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
|
||||
db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
|
||||
\'SELECT random();\'
|
||||
LANGUAGE \'sql\''
|
||||
);
|
||||
}
|
||||
|
||||
db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
|
||||
\'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
|
||||
LANGUAGE \'sql\''
|
||||
);
|
||||
|
||||
// Using || to concatenate in Drupal is not recommended because there are
|
||||
// database drivers for Drupal that do not support the syntax, however
|
||||
// they do support CONCAT(item1, item2) which we can replicate in
|
||||
// PostgreSQL. PostgreSQL requires the function to be defined for each
|
||||
// different argument variation the function can handle.
|
||||
db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, anynonarray) RETURNS text AS
|
||||
\'SELECT CAST($1 AS text) || CAST($2 AS text);\'
|
||||
LANGUAGE \'sql\'
|
||||
');
|
||||
db_query('CREATE OR REPLACE FUNCTION "concat"(text, anynonarray) RETURNS text AS
|
||||
\'SELECT $1 || CAST($2 AS text);\'
|
||||
LANGUAGE \'sql\'
|
||||
');
|
||||
db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, text) RETURNS text AS
|
||||
\'SELECT CAST($1 AS text) || $2;\'
|
||||
LANGUAGE \'sql\'
|
||||
');
|
||||
db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS
|
||||
\'SELECT $1 || $2;\'
|
||||
LANGUAGE \'sql\'
|
||||
');
|
||||
|
||||
$this->pass(st('PostgreSQL has initialized itself.'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->fail(st('Drupal could not be correctly setup with the existing database. Revise any errors.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @ingroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Query code for PostgreSQL embedded database engine.
|
||||
*/
|
||||
|
||||
|
||||
class InsertQuery_pgsql extends InsertQuery {
|
||||
|
||||
public function execute() {
|
||||
if (!$this->preExecute()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$stmt = $this->connection->prepareQuery((string) $this);
|
||||
|
||||
// Fetch the list of blobs and sequences used on that table.
|
||||
$table_information = $this->connection->schema()->queryTableInformation($this->table);
|
||||
|
||||
$max_placeholder = 0;
|
||||
$blobs = array();
|
||||
$blob_count = 0;
|
||||
foreach ($this->insertValues as $insert_values) {
|
||||
foreach ($this->insertFields as $idx => $field) {
|
||||
if (isset($table_information->blob_fields[$field])) {
|
||||
$blobs[$blob_count] = fopen('php://memory', 'a');
|
||||
fwrite($blobs[$blob_count], $insert_values[$idx]);
|
||||
rewind($blobs[$blob_count]);
|
||||
|
||||
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], PDO::PARAM_LOB);
|
||||
|
||||
// Pre-increment is faster in PHP than increment.
|
||||
++$blob_count;
|
||||
}
|
||||
else {
|
||||
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]);
|
||||
}
|
||||
}
|
||||
// Check if values for a serial field has been passed.
|
||||
if (!empty($table_information->serial_fields)) {
|
||||
foreach ($table_information->serial_fields as $index => $serial_field) {
|
||||
$serial_key = array_search($serial_field, $this->insertFields);
|
||||
if ($serial_key !== FALSE) {
|
||||
$serial_value = $insert_values[$serial_key];
|
||||
|
||||
// Force $last_insert_id to the specified value. This is only done
|
||||
// if $index is 0.
|
||||
if ($index == 0) {
|
||||
$last_insert_id = $serial_value;
|
||||
}
|
||||
// Set the sequence to the bigger value of either the passed
|
||||
// value or the max value of the column. It can happen that another
|
||||
// thread calls nextval() which could lead to a serial number being
|
||||
// used twice. However, trying to insert a value into a serial
|
||||
// column should only be done in very rare cases and is not thread
|
||||
// safe by definition.
|
||||
$this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', GREATEST(MAX(" . $serial_field . "), :serial_value)) FROM {" . $this->table . "}", array(':serial_value' => (int)$serial_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($this->fromQuery)) {
|
||||
// bindParam stores only a reference to the variable that is followed when
|
||||
// the statement is executed. We pass $arguments[$key] instead of $value
|
||||
// because the second argument to bindParam is passed by reference and
|
||||
// the foreach statement assigns the element to the existing reference.
|
||||
$arguments = $this->fromQuery->getArguments();
|
||||
foreach ($arguments as $key => $value) {
|
||||
$stmt->bindParam($key, $arguments[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// PostgreSQL requires the table name to be specified explicitly
|
||||
// when requesting the last insert ID, so we pass that in via
|
||||
// the options array.
|
||||
$options = $this->queryOptions;
|
||||
|
||||
if (!empty($table_information->sequences)) {
|
||||
$options['sequence_name'] = $table_information->sequences[0];
|
||||
}
|
||||
// If there are no sequences then we can't get a last insert id.
|
||||
elseif ($options['return'] == Database::RETURN_INSERT_ID) {
|
||||
$options['return'] = Database::RETURN_NULL;
|
||||
}
|
||||
// Only use the returned last_insert_id if it is not already set.
|
||||
if (!empty($last_insert_id)) {
|
||||
$this->connection->query($stmt, array(), $options);
|
||||
}
|
||||
else {
|
||||
$last_insert_id = $this->connection->query($stmt, array(), $options);
|
||||
}
|
||||
|
||||
// Re-initialize the values array so that we can re-use this query.
|
||||
$this->insertValues = array();
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
// Create a sanitized comment string to prepend to the query.
|
||||
$comments = $this->connection->makeComment($this->comments);
|
||||
|
||||
// Default fields are always placed first for consistency.
|
||||
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
|
||||
|
||||
// If we're selecting from a SelectQuery, finish building the query and
|
||||
// pass it back, as any remaining options are irrelevant.
|
||||
if (!empty($this->fromQuery)) {
|
||||
$insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
|
||||
return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
|
||||
}
|
||||
|
||||
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
|
||||
|
||||
$max_placeholder = 0;
|
||||
$values = array();
|
||||
if (count($this->insertValues)) {
|
||||
foreach ($this->insertValues as $insert_values) {
|
||||
$placeholders = array();
|
||||
|
||||
// Default fields aren't really placeholders, but this is the most convenient
|
||||
// way to handle them.
|
||||
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
|
||||
|
||||
$new_placeholder = $max_placeholder + count($insert_values);
|
||||
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
|
||||
$placeholders[] = ':db_insert_placeholder_' . $i;
|
||||
}
|
||||
$max_placeholder = $new_placeholder;
|
||||
$values[] = '(' . implode(', ', $placeholders) . ')';
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If there are no values, then this is a default-only query. We still need to handle that.
|
||||
$placeholders = array_fill(0, count($this->defaultFields), 'default');
|
||||
$values[] = '(' . implode(', ', $placeholders) . ')';
|
||||
}
|
||||
|
||||
$query .= implode(', ', $values);
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateQuery_pgsql extends UpdateQuery {
|
||||
public function execute() {
|
||||
$max_placeholder = 0;
|
||||
$blobs = array();
|
||||
$blob_count = 0;
|
||||
|
||||
// Because we filter $fields the same way here and in __toString(), the
|
||||
// placeholders will all match up properly.
|
||||
$stmt = $this->connection->prepareQuery((string) $this);
|
||||
|
||||
// Fetch the list of blobs and sequences used on that table.
|
||||
$table_information = $this->connection->schema()->queryTableInformation($this->table);
|
||||
|
||||
// Expressions take priority over literal fields, so we process those first
|
||||
// and remove any literal fields that conflict.
|
||||
$fields = $this->fields;
|
||||
$expression_fields = array();
|
||||
foreach ($this->expressionFields as $field => $data) {
|
||||
if (!empty($data['arguments'])) {
|
||||
foreach ($data['arguments'] as $placeholder => $argument) {
|
||||
// We assume that an expression will never happen on a BLOB field,
|
||||
// which is a fairly safe assumption to make since in most cases
|
||||
// it would be an invalid query anyway.
|
||||
$stmt->bindParam($placeholder, $data['arguments'][$placeholder]);
|
||||
}
|
||||
}
|
||||
unset($fields[$field]);
|
||||
}
|
||||
|
||||
foreach ($fields as $field => $value) {
|
||||
$placeholder = ':db_update_placeholder_' . ($max_placeholder++);
|
||||
|
||||
if (isset($table_information->blob_fields[$field])) {
|
||||
$blobs[$blob_count] = fopen('php://memory', 'a');
|
||||
fwrite($blobs[$blob_count], $value);
|
||||
rewind($blobs[$blob_count]);
|
||||
$stmt->bindParam($placeholder, $blobs[$blob_count], PDO::PARAM_LOB);
|
||||
++$blob_count;
|
||||
}
|
||||
else {
|
||||
$stmt->bindParam($placeholder, $fields[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($this->condition)) {
|
||||
$this->condition->compile($this->connection, $this);
|
||||
|
||||
$arguments = $this->condition->arguments();
|
||||
foreach ($arguments as $placeholder => $value) {
|
||||
$stmt->bindParam($placeholder, $arguments[$placeholder]);
|
||||
}
|
||||
}
|
||||
|
||||
$options = $this->queryOptions;
|
||||
$options['already_prepared'] = TRUE;
|
||||
$this->connection->query($stmt, $options);
|
||||
|
||||
return $stmt->rowCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Database schema code for PostgreSQL database servers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup schemaapi
|
||||
* @{
|
||||
*/
|
||||
|
||||
class DatabaseSchema_pgsql extends DatabaseSchema {
|
||||
|
||||
/**
|
||||
* A cache of information about blob columns and sequences of tables.
|
||||
*
|
||||
* This is collected by DatabaseConnection_pgsql->queryTableInformation(),
|
||||
* by introspecting the database.
|
||||
*
|
||||
* @see DatabaseConnection_pgsql->queryTableInformation()
|
||||
* @var array
|
||||
*/
|
||||
protected $tableInformation = array();
|
||||
|
||||
/**
|
||||
* Fetch the list of blobs and sequences used on a table.
|
||||
*
|
||||
* We introspect the database to collect the information required by insert
|
||||
* and update queries.
|
||||
*
|
||||
* @param $table_name
|
||||
* The non-prefixed name of the table.
|
||||
* @return
|
||||
* An object with two member variables:
|
||||
* - 'blob_fields' that lists all the blob fields in the table.
|
||||
* - 'sequences' that lists the sequences used in that table.
|
||||
*/
|
||||
public function queryTableInformation($table) {
|
||||
// Generate a key to reference this table's information on.
|
||||
$key = $this->connection->prefixTables('{' . $table . '}');
|
||||
if (!strpos($key, '.')) {
|
||||
$key = 'public.' . $key;
|
||||
}
|
||||
|
||||
if (!isset($this->tableInformation[$key])) {
|
||||
// Split the key into schema and table for querying.
|
||||
list($schema, $table_name) = explode('.', $key);
|
||||
$table_information = (object) array(
|
||||
'blob_fields' => array(),
|
||||
'sequences' => array(),
|
||||
);
|
||||
// Don't use {} around information_schema.columns table.
|
||||
$result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(
|
||||
':schema' => $schema,
|
||||
':table' => $table_name,
|
||||
':default' => '%nextval%',
|
||||
));
|
||||
foreach ($result as $column) {
|
||||
if ($column->data_type == 'bytea') {
|
||||
$table_information->blob_fields[$column->column_name] = TRUE;
|
||||
}
|
||||
elseif (preg_match("/nextval\('([^']+)'/", $column->column_default, $matches)) {
|
||||
// We must know of any sequences in the table structure to help us
|
||||
// return the last insert id. If there is more than 1 sequences the
|
||||
// first one (index 0 of the sequences array) will be used.
|
||||
$table_information->sequences[] = $matches[1];
|
||||
$table_information->serial_fields[] = $column->column_name;
|
||||
}
|
||||
}
|
||||
$this->tableInformation[$key] = $table_information;
|
||||
}
|
||||
return $this->tableInformation[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the list of CHECK constraints used on a field.
|
||||
*
|
||||
* We introspect the database to collect the information required by field
|
||||
* alteration.
|
||||
*
|
||||
* @param $table
|
||||
* The non-prefixed name of the table.
|
||||
* @param $field
|
||||
* The name of the field.
|
||||
* @return
|
||||
* An array of all the checks for the field.
|
||||
*/
|
||||
public function queryFieldInformation($table, $field) {
|
||||
$prefixInfo = $this->getPrefixInfo($table, TRUE);
|
||||
|
||||
// Split the key into schema and table for querying.
|
||||
$schema = $prefixInfo['schema'];
|
||||
$table_name = $prefixInfo['table'];
|
||||
|
||||
$field_information = (object) array(
|
||||
'checks' => array(),
|
||||
);
|
||||
$checks = $this->connection->query("SELECT conname FROM pg_class cl INNER JOIN pg_constraint co ON co.conrelid = cl.oid INNER JOIN pg_attribute attr ON attr.attrelid = cl.oid AND attr.attnum = ANY (co.conkey) INNER JOIN pg_namespace ns ON cl.relnamespace = ns.oid WHERE co.contype = 'c' AND ns.nspname = :schema AND cl.relname = :table AND attr.attname = :column", array(
|
||||
':schema' => $schema,
|
||||
':table' => $table_name,
|
||||
':column' => $field,
|
||||
));
|
||||
$field_information = $checks->fetchCol();
|
||||
|
||||
return $field_information;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL to create a new table from a Drupal schema definition.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the table to create.
|
||||
* @param $table
|
||||
* A Schema API table definition array.
|
||||
* @return
|
||||
* An array of SQL statements to create the table.
|
||||
*/
|
||||
protected function createTableSql($name, $table) {
|
||||
$sql_fields = array();
|
||||
foreach ($table['fields'] as $field_name => $field) {
|
||||
$sql_fields[] = $this->createFieldSql($field_name, $this->processField($field));
|
||||
}
|
||||
|
||||
$sql_keys = array();
|
||||
if (isset($table['primary key']) && is_array($table['primary key'])) {
|
||||
$sql_keys[] = 'PRIMARY KEY (' . implode(', ', $table['primary key']) . ')';
|
||||
}
|
||||
if (isset($table['unique keys']) && is_array($table['unique keys'])) {
|
||||
foreach ($table['unique keys'] as $key_name => $key) {
|
||||
$sql_keys[] = 'CONSTRAINT ' . $this->prefixNonTable($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "CREATE TABLE {" . $name . "} (\n\t";
|
||||
$sql .= implode(",\n\t", $sql_fields);
|
||||
if (count($sql_keys) > 0) {
|
||||
$sql .= ",\n\t";
|
||||
}
|
||||
$sql .= implode(",\n\t", $sql_keys);
|
||||
$sql .= "\n)";
|
||||
$statements[] = $sql;
|
||||
|
||||
if (isset($table['indexes']) && is_array($table['indexes'])) {
|
||||
foreach ($table['indexes'] as $key_name => $key) {
|
||||
$statements[] = $this->_createIndexSql($name, $key_name, $key);
|
||||
}
|
||||
}
|
||||
|
||||
// Add table comment.
|
||||
if (!empty($table['description'])) {
|
||||
$statements[] = 'COMMENT ON TABLE {' . $name . '} IS ' . $this->prepareComment($table['description']);
|
||||
}
|
||||
|
||||
// Add column comments.
|
||||
foreach ($table['fields'] as $field_name => $field) {
|
||||
if (!empty($field['description'])) {
|
||||
$statements[] = 'COMMENT ON COLUMN {' . $name . '}.' . $field_name . ' IS ' . $this->prepareComment($field['description']);
|
||||
}
|
||||
}
|
||||
|
||||
return $statements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an SQL string for a field to be used in table creation or
|
||||
* alteration.
|
||||
*
|
||||
* Before passing a field out of a schema definition into this
|
||||
* function it has to be processed by _db_process_field().
|
||||
*
|
||||
* @param $name
|
||||
* Name of the field.
|
||||
* @param $spec
|
||||
* The field specification, as per the schema data structure format.
|
||||
*/
|
||||
protected function createFieldSql($name, $spec) {
|
||||
$sql = $name . ' ' . $spec['pgsql_type'];
|
||||
|
||||
if (isset($spec['type']) && $spec['type'] == 'serial') {
|
||||
unset($spec['not null']);
|
||||
}
|
||||
|
||||
if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
|
||||
$sql .= '(' . $spec['length'] . ')';
|
||||
}
|
||||
elseif (isset($spec['precision']) && isset($spec['scale'])) {
|
||||
$sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
|
||||
}
|
||||
|
||||
if (!empty($spec['unsigned'])) {
|
||||
$sql .= " CHECK ($name >= 0)";
|
||||
}
|
||||
|
||||
if (isset($spec['not null'])) {
|
||||
if ($spec['not null']) {
|
||||
$sql .= ' NOT NULL';
|
||||
}
|
||||
else {
|
||||
$sql .= ' NULL';
|
||||
}
|
||||
}
|
||||
if (isset($spec['default'])) {
|
||||
$default = is_string($spec['default']) ? "'" . $spec['default'] . "'" : $spec['default'];
|
||||
$sql .= " default $default";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database-engine specific properties for a field.
|
||||
*
|
||||
* @param $field
|
||||
* A field description array, as specified in the schema documentation.
|
||||
*/
|
||||
protected function processField($field) {
|
||||
if (!isset($field['size'])) {
|
||||
$field['size'] = 'normal';
|
||||
}
|
||||
|
||||
// Set the correct database-engine specific datatype.
|
||||
// In case one is already provided, force it to lowercase.
|
||||
if (isset($field['pgsql_type'])) {
|
||||
$field['pgsql_type'] = drupal_strtolower($field['pgsql_type']);
|
||||
}
|
||||
else {
|
||||
$map = $this->getFieldTypeMap();
|
||||
$field['pgsql_type'] = $map[$field['type'] . ':' . $field['size']];
|
||||
}
|
||||
|
||||
if (!empty($field['unsigned'])) {
|
||||
// Unsigned datatypes are not supported in PostgreSQL 8.3. In MySQL,
|
||||
// they are used to ensure a positive number is inserted and it also
|
||||
// doubles the maximum integer size that can be stored in a field.
|
||||
// The PostgreSQL schema in Drupal creates a check constraint
|
||||
// to ensure that a value inserted is >= 0. To provide the extra
|
||||
// integer capacity, here, we bump up the column field size.
|
||||
if (!isset($map)) {
|
||||
$map = $this->getFieldTypeMap();
|
||||
}
|
||||
switch ($field['pgsql_type']) {
|
||||
case 'smallint':
|
||||
$field['pgsql_type'] = $map['int:medium'];
|
||||
break;
|
||||
case 'int' :
|
||||
$field['pgsql_type'] = $map['int:big'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($field['type']) && $field['type'] == 'serial') {
|
||||
unset($field['not null']);
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* This maps a generic data type in combination with its data size
|
||||
* to the engine-specific data type.
|
||||
*/
|
||||
function getFieldTypeMap() {
|
||||
// Put :normal last so it gets preserved by array_flip. This makes
|
||||
// it much easier for modules (such as schema.module) to map
|
||||
// database types back into schema types.
|
||||
// $map does not use drupal_static as its value never changes.
|
||||
static $map = array(
|
||||
'varchar:normal' => 'varchar',
|
||||
'char:normal' => 'character',
|
||||
|
||||
'text:tiny' => 'text',
|
||||
'text:small' => 'text',
|
||||
'text:medium' => 'text',
|
||||
'text:big' => 'text',
|
||||
'text:normal' => 'text',
|
||||
|
||||
'int:tiny' => 'smallint',
|
||||
'int:small' => 'smallint',
|
||||
'int:medium' => 'int',
|
||||
'int:big' => 'bigint',
|
||||
'int:normal' => 'int',
|
||||
|
||||
'float:tiny' => 'real',
|
||||
'float:small' => 'real',
|
||||
'float:medium' => 'real',
|
||||
'float:big' => 'double precision',
|
||||
'float:normal' => 'real',
|
||||
|
||||
'numeric:normal' => 'numeric',
|
||||
|
||||
'blob:big' => 'bytea',
|
||||
'blob:normal' => 'bytea',
|
||||
|
||||
'serial:tiny' => 'serial',
|
||||
'serial:small' => 'serial',
|
||||
'serial:medium' => 'serial',
|
||||
'serial:big' => 'bigserial',
|
||||
'serial:normal' => 'serial',
|
||||
);
|
||||
return $map;
|
||||
}
|
||||
|
||||
protected function _createKeySql($fields) {
|
||||
$return = array();
|
||||
foreach ($fields as $field) {
|
||||
if (is_array($field)) {
|
||||
$return[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')';
|
||||
}
|
||||
else {
|
||||
$return[] = '"' . $field . '"';
|
||||
}
|
||||
}
|
||||
return implode(', ', $return);
|
||||
}
|
||||
|
||||
function renameTable($table, $new_name) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
|
||||
}
|
||||
if ($this->tableExists($new_name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
|
||||
}
|
||||
|
||||
// Get the schema and tablename for the old table.
|
||||
$old_full_name = $this->connection->prefixTables('{' . $table . '}');
|
||||
list($old_schema, $old_table_name) = strpos($old_full_name, '.') ? explode('.', $old_full_name) : array('public', $old_full_name);
|
||||
|
||||
// Index names and constraint names are global in PostgreSQL, so we need to
|
||||
// rename them when renaming the table.
|
||||
$indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name));
|
||||
foreach ($indexes as $index) {
|
||||
if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)$/', $index->indexname, $matches)) {
|
||||
$index_name = $matches[1];
|
||||
$this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO {' . $new_name . '}_' . $index_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Now rename the table.
|
||||
// Ensure the new table name does not include schema syntax.
|
||||
$prefixInfo = $this->getPrefixInfo($new_name);
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $prefixInfo['table']);
|
||||
}
|
||||
|
||||
public function dropTable($table) {
|
||||
if (!$this->tableExists($table)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('DROP TABLE {' . $table . '}');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function addField($table, $field, $spec, $new_keys = array()) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
|
||||
}
|
||||
if ($this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
|
||||
}
|
||||
|
||||
$fixnull = FALSE;
|
||||
if (!empty($spec['not null']) && !isset($spec['default'])) {
|
||||
$fixnull = TRUE;
|
||||
$spec['not null'] = FALSE;
|
||||
}
|
||||
$query = 'ALTER TABLE {' . $table . '} ADD COLUMN ';
|
||||
$query .= $this->createFieldSql($field, $this->processField($spec));
|
||||
$this->connection->query($query);
|
||||
if (isset($spec['initial'])) {
|
||||
$this->connection->update($table)
|
||||
->fields(array($field => $spec['initial']))
|
||||
->execute();
|
||||
}
|
||||
if ($fixnull) {
|
||||
$this->connection->query("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL");
|
||||
}
|
||||
if (isset($new_keys)) {
|
||||
$this->_createKeys($table, $new_keys);
|
||||
}
|
||||
// Add column comment.
|
||||
if (!empty($spec['description'])) {
|
||||
$this->connection->query('COMMENT ON COLUMN {' . $table . '}.' . $field . ' IS ' . $this->prepareComment($spec['description']));
|
||||
}
|
||||
}
|
||||
|
||||
public function dropField($table, $field) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} DROP COLUMN "' . $field . '"');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function fieldSetDefault($table, $field, $default) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
|
||||
}
|
||||
|
||||
if (!isset($default)) {
|
||||
$default = 'NULL';
|
||||
}
|
||||
else {
|
||||
$default = is_string($default) ? "'$default'" : $default;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default);
|
||||
}
|
||||
|
||||
public function fieldSetNoDefault($table, $field) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT');
|
||||
}
|
||||
|
||||
public function indexExists($table, $name) {
|
||||
// Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html
|
||||
$index_name = '{' . $table . '}_' . $name . '_idx';
|
||||
return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE indexname = '$index_name'")->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: check if a constraint (PK, FK, UK) exists.
|
||||
*
|
||||
* @param $table
|
||||
* The name of the table.
|
||||
* @param $name
|
||||
* The name of the constraint (typically 'pkey' or '[constraint]_key').
|
||||
*/
|
||||
protected function constraintExists($table, $name) {
|
||||
$constraint_name = '{' . $table . '}_' . $name;
|
||||
return (bool) $this->connection->query("SELECT 1 FROM pg_constraint WHERE conname = '$constraint_name'")->fetchField();
|
||||
}
|
||||
|
||||
public function addPrimaryKey($table, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
|
||||
}
|
||||
if ($this->constraintExists($table, 'pkey')) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')');
|
||||
}
|
||||
|
||||
public function dropPrimaryKey($table) {
|
||||
if (!$this->constraintExists($table, 'pkey')) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->prefixNonTable($table, 'pkey'));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
function addUniqueKey($table, $name, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
if ($this->constraintExists($table, $name . '_key')) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
|
||||
}
|
||||
|
||||
public function dropUniqueKey($table, $name) {
|
||||
if (!$this->constraintExists($table, $name . '_key')) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '"');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function addIndex($table, $name, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
if ($this->indexExists($table, $name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
|
||||
$this->connection->query($this->_createIndexSql($table, $name, $fields));
|
||||
}
|
||||
|
||||
public function dropIndex($table, $name) {
|
||||
if (!$this->indexExists($table, $name)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name, 'idx'));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function changeField($table, $field, $field_new, $spec, $new_keys = array()) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
|
||||
}
|
||||
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
|
||||
}
|
||||
|
||||
$spec = $this->processField($spec);
|
||||
|
||||
// We need to typecast the new column to best be able to transfer the data
|
||||
// Schema_pgsql::getFieldTypeMap() will return possibilities that are not
|
||||
// 'cast-able' such as 'serial' - so they need to be casted int instead.
|
||||
if (in_array($spec['pgsql_type'], array('serial', 'bigserial', 'numeric'))) {
|
||||
$typecast = 'int';
|
||||
}
|
||||
else {
|
||||
$typecast = $spec['pgsql_type'];
|
||||
}
|
||||
|
||||
if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
|
||||
$typecast .= '(' . $spec['length'] . ')';
|
||||
}
|
||||
elseif (isset($spec['precision']) && isset($spec['scale'])) {
|
||||
$typecast .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
|
||||
}
|
||||
|
||||
// Remove old check constraints.
|
||||
$field_info = $this->queryFieldInformation($table, $field);
|
||||
|
||||
foreach ($field_info as $check) {
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $check . '"');
|
||||
}
|
||||
|
||||
// Remove old default.
|
||||
$this->fieldSetNoDefault($table, $field);
|
||||
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING "' . $field . '"::' . $typecast);
|
||||
|
||||
if (isset($spec['not null'])) {
|
||||
if ($spec['not null']) {
|
||||
$nullaction = 'SET NOT NULL';
|
||||
}
|
||||
else {
|
||||
$nullaction = 'DROP NOT NULL';
|
||||
}
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" ' . $nullaction);
|
||||
}
|
||||
|
||||
if (in_array($spec['pgsql_type'], array('serial', 'bigserial'))) {
|
||||
// Type "serial" is known to PostgreSQL, but *only* during table creation,
|
||||
// not when altering. Because of that, the sequence needs to be created
|
||||
// and initialized by hand.
|
||||
$seq = "{" . $table . "}_" . $field_new . "_seq";
|
||||
$this->connection->query("CREATE SEQUENCE " . $seq);
|
||||
// Set sequence to maximal field value to not conflict with existing
|
||||
// entries.
|
||||
$this->connection->query("SELECT setval('" . $seq . "', MAX(\"" . $field . '")) FROM {' . $table . "}");
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" SET DEFAULT nextval(\'' . $seq . '\')');
|
||||
}
|
||||
|
||||
// Rename the column if necessary.
|
||||
if ($field != $field_new) {
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field_new . '"');
|
||||
}
|
||||
|
||||
// Add unsigned check if necessary.
|
||||
if (!empty($spec['unsigned'])) {
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ADD CHECK ("' . $field_new . '" >= 0)');
|
||||
}
|
||||
|
||||
// Add default if necessary.
|
||||
if (isset($spec['default'])) {
|
||||
$this->fieldSetDefault($table, $field_new, $spec['default']);
|
||||
}
|
||||
|
||||
// Change description if necessary.
|
||||
if (!empty($spec['description'])) {
|
||||
$this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description']));
|
||||
}
|
||||
|
||||
if (isset($new_keys)) {
|
||||
$this->_createKeys($table, $new_keys);
|
||||
}
|
||||
}
|
||||
|
||||
protected function _createIndexSql($table, $name, $fields) {
|
||||
$query = 'CREATE INDEX "' . $this->prefixNonTable($table, $name, 'idx') . '" ON {' . $table . '} (';
|
||||
$query .= $this->_createKeySql($fields) . ')';
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function _createKeys($table, $new_keys) {
|
||||
if (isset($new_keys['primary key'])) {
|
||||
$this->addPrimaryKey($table, $new_keys['primary key']);
|
||||
}
|
||||
if (isset($new_keys['unique keys'])) {
|
||||
foreach ($new_keys['unique keys'] as $name => $fields) {
|
||||
$this->addUniqueKey($table, $name, $fields);
|
||||
}
|
||||
}
|
||||
if (isset($new_keys['indexes'])) {
|
||||
foreach ($new_keys['indexes'] as $name => $fields) {
|
||||
$this->addIndex($table, $name, $fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a table or column comment.
|
||||
*/
|
||||
public function getComment($table, $column = NULL) {
|
||||
$info = $this->getPrefixInfo($table);
|
||||
// Don't use {} around pg_class, pg_attribute tables.
|
||||
if (isset($column)) {
|
||||
return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($info['table'], $column))->fetchField();
|
||||
}
|
||||
else {
|
||||
return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Select builder for PostgreSQL database engine.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
class SelectQuery_pgsql extends SelectQuery {
|
||||
|
||||
public function orderRandom() {
|
||||
$alias = $this->addExpression('RANDOM()', 'random_field');
|
||||
$this->orderBy($alias);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides SelectQuery::orderBy().
|
||||
*
|
||||
* PostgreSQL adheres strictly to the SQL-92 standard and requires that when
|
||||
* using DISTINCT or GROUP BY conditions, fields and expressions that are
|
||||
* ordered on also need to be selected. This is a best effort implementation
|
||||
* to handle the cases that can be automated by adding the field if it is not
|
||||
* yet selected.
|
||||
*
|
||||
* @code
|
||||
* $query = db_select('node', 'n');
|
||||
* $query->join('node_revision', 'nr', 'n.vid = nr.vid');
|
||||
* $query
|
||||
* ->distinct()
|
||||
* ->fields('n')
|
||||
* ->orderBy('timestamp');
|
||||
* @endcode
|
||||
*
|
||||
* In this query, it is not possible (without relying on the schema) to know
|
||||
* whether timestamp belongs to node_revisions and needs to be added or
|
||||
* belongs to node and is already selected. Queries like this will need to be
|
||||
* corrected in the original query by adding an explicit call to
|
||||
* SelectQuery::addField() or SelectQuery::fields().
|
||||
*
|
||||
* Since this has a small performance impact, both by the additional
|
||||
* processing in this function and in the database that needs to return the
|
||||
* additional fields, this is done as an override instead of implementing it
|
||||
* directly in SelectQuery::orderBy().
|
||||
*/
|
||||
public function orderBy($field, $direction = 'ASC') {
|
||||
// Call parent function to order on this.
|
||||
$return = parent::orderBy($field, $direction);
|
||||
|
||||
// If there is a table alias specified, split it up.
|
||||
if (strpos($field, '.') !== FALSE) {
|
||||
list($table, $table_field) = explode('.', $field);
|
||||
}
|
||||
// Figure out if the field has already been added.
|
||||
foreach ($this->fields as $existing_field) {
|
||||
if (!empty($table)) {
|
||||
// If table alias is given, check if field and table exists.
|
||||
if ($existing_field['table'] == $table && $existing_field['field'] == $table_field) {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If there is no table, simply check if the field exists as a field or
|
||||
// an aliased field.
|
||||
if ($existing_field['alias'] == $field) {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check expression aliases.
|
||||
foreach ($this->expressions as $expression) {
|
||||
if ($expression['alias'] == $field) {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
// If a table loads all fields, it can not be added again. It would
|
||||
// result in an ambiguous alias error because that field would be loaded
|
||||
// twice: Once through table_alias.* and once directly. If the field
|
||||
// actually belongs to a different table, it must be added manually.
|
||||
foreach ($this->tables as $table) {
|
||||
if (!empty($table['all_fields'])) {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
// If $field contains an characters which are not allowed in a field name
|
||||
// it is considered an expression, these can't be handled automatically
|
||||
// either.
|
||||
if ($this->connection->escapeField($field) != $field) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
// This is a case that can be handled automatically, add the field.
|
||||
$this->addField(NULL, $field);
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Database interface code for engines that need complete control over their
|
||||
* result sets. For example, SQLite will prefix some column names by the name
|
||||
* of the table. We post-process the data, by renaming the column names
|
||||
* using the same convention as MySQL and PostgreSQL.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* An implementation of DatabaseStatementInterface that prefetches all data.
|
||||
*
|
||||
* This class behaves very similar to a PDOStatement but as it always fetches
|
||||
* every row it is possible to manipulate those results.
|
||||
*/
|
||||
class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
|
||||
|
||||
/**
|
||||
* The query string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $queryString;
|
||||
|
||||
/**
|
||||
* Driver-specific options. Can be used by child classes.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $driverOptions;
|
||||
|
||||
/**
|
||||
* Reference to the database connection object for this statement.
|
||||
*
|
||||
* The name $dbh is inherited from PDOStatement.
|
||||
*
|
||||
* @var DatabaseConnection
|
||||
*/
|
||||
public $dbh;
|
||||
|
||||
/**
|
||||
* Main data store.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* The current row, retrieved in PDO::FETCH_ASSOC format.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $currentRow = NULL;
|
||||
|
||||
/**
|
||||
* The key of the current row.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $currentKey = NULL;
|
||||
|
||||
/**
|
||||
* The list of column names in this result set.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $columnNames = NULL;
|
||||
|
||||
/**
|
||||
* The number of rows affected by the last query.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rowCount = NULL;
|
||||
|
||||
/**
|
||||
* The number of rows in this result set.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $resultRowCount = 0;
|
||||
|
||||
/**
|
||||
* Holds the current fetch style (which will be used by the next fetch).
|
||||
* @see PDOStatement::fetch()
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $fetchStyle = PDO::FETCH_OBJ;
|
||||
|
||||
/**
|
||||
* Holds supplementary current fetch options (which will be used by the next fetch).
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $fetchOptions = array(
|
||||
'class' => 'stdClass',
|
||||
'constructor_args' => array(),
|
||||
'object' => NULL,
|
||||
'column' => 0,
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds the default fetch style.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $defaultFetchStyle = PDO::FETCH_OBJ;
|
||||
|
||||
/**
|
||||
* Holds supplementary default fetch options.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $defaultFetchOptions = array(
|
||||
'class' => 'stdClass',
|
||||
'constructor_args' => array(),
|
||||
'object' => NULL,
|
||||
'column' => 0,
|
||||
);
|
||||
|
||||
public function __construct(DatabaseConnection $connection, $query, array $driver_options = array()) {
|
||||
$this->dbh = $connection;
|
||||
$this->queryString = $query;
|
||||
$this->driverOptions = $driver_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a prepared statement.
|
||||
*
|
||||
* @param $args
|
||||
* An array of values with as many elements as there are bound parameters in the SQL statement being executed.
|
||||
* @param $options
|
||||
* An array of options for this query.
|
||||
* @return
|
||||
* TRUE on success, or FALSE on failure.
|
||||
*/
|
||||
public function execute($args = array(), $options = array()) {
|
||||
if (isset($options['fetch'])) {
|
||||
if (is_string($options['fetch'])) {
|
||||
// Default to an object. Note: db fields will be added to the object
|
||||
// before the constructor is run. If you need to assign fields after
|
||||
// the constructor is run, see http://drupal.org/node/315092.
|
||||
$this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']);
|
||||
}
|
||||
else {
|
||||
$this->setFetchMode($options['fetch']);
|
||||
}
|
||||
}
|
||||
|
||||
$logger = $this->dbh->getLogger();
|
||||
if (!empty($logger)) {
|
||||
$query_start = microtime(TRUE);
|
||||
}
|
||||
|
||||
// Prepare the query.
|
||||
$statement = $this->getStatement($this->queryString, $args);
|
||||
if (!$statement) {
|
||||
$this->throwPDOException();
|
||||
}
|
||||
|
||||
$return = $statement->execute($args);
|
||||
if (!$return) {
|
||||
$this->throwPDOException();
|
||||
}
|
||||
|
||||
// Fetch all the data from the reply, in order to release any lock
|
||||
// as soon as possible.
|
||||
$this->rowCount = $statement->rowCount();
|
||||
$this->data = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
// Destroy the statement as soon as possible. See
|
||||
// DatabaseConnection_sqlite::PDOPrepare() for explanation.
|
||||
unset($statement);
|
||||
|
||||
$this->resultRowCount = count($this->data);
|
||||
|
||||
if ($this->resultRowCount) {
|
||||
$this->columnNames = array_keys($this->data[0]);
|
||||
}
|
||||
else {
|
||||
$this->columnNames = array();
|
||||
}
|
||||
|
||||
if (!empty($logger)) {
|
||||
$query_end = microtime(TRUE);
|
||||
$logger->log($this, $args, $query_end - $query_start);
|
||||
}
|
||||
|
||||
// Initialize the first row in $this->currentRow.
|
||||
$this->next();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a PDO Exception based on the last PDO error.
|
||||
*/
|
||||
protected function throwPDOException() {
|
||||
$error_info = $this->dbh->errorInfo();
|
||||
// We rebuild a message formatted in the same way as PDO.
|
||||
$exception = new PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
|
||||
$exception->errorInfo = $error_info;
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab a PDOStatement object from a given query and its arguments.
|
||||
*
|
||||
* Some drivers (including SQLite) will need to perform some preparation
|
||||
* themselves to get the statement right.
|
||||
*
|
||||
* @param $query
|
||||
* The query.
|
||||
* @param array $args
|
||||
* An array of arguments.
|
||||
* @return PDOStatement
|
||||
* A PDOStatement object.
|
||||
*/
|
||||
protected function getStatement($query, &$args = array()) {
|
||||
return $this->dbh->prepare($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object's SQL query string.
|
||||
*/
|
||||
public function getQueryString() {
|
||||
return $this->queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PDOStatement::setFetchMode()
|
||||
*/
|
||||
public function setFetchMode($fetchStyle, $a2 = NULL, $a3 = NULL) {
|
||||
$this->defaultFetchStyle = $fetchStyle;
|
||||
switch ($fetchStyle) {
|
||||
case PDO::FETCH_CLASS:
|
||||
$this->defaultFetchOptions['class'] = $a2;
|
||||
if ($a3) {
|
||||
$this->defaultFetchOptions['constructor_args'] = $a3;
|
||||
}
|
||||
break;
|
||||
case PDO::FETCH_COLUMN:
|
||||
$this->defaultFetchOptions['column'] = $a2;
|
||||
break;
|
||||
case PDO::FETCH_INTO:
|
||||
$this->defaultFetchOptions['object'] = $a2;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the values for the next fetch.
|
||||
$this->fetchStyle = $this->defaultFetchStyle;
|
||||
$this->fetchOptions = $this->defaultFetchOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current row formatted according to the current fetch style.
|
||||
*
|
||||
* This is the core method of this class. It grabs the value at the current
|
||||
* array position in $this->data and format it according to $this->fetchStyle
|
||||
* and $this->fetchMode.
|
||||
*
|
||||
* @return
|
||||
* The current row formatted as requested.
|
||||
*/
|
||||
public function current() {
|
||||
if (isset($this->currentRow)) {
|
||||
switch ($this->fetchStyle) {
|
||||
case PDO::FETCH_ASSOC:
|
||||
return $this->currentRow;
|
||||
case PDO::FETCH_BOTH:
|
||||
// PDO::FETCH_BOTH returns an array indexed by both the column name
|
||||
// and the column number.
|
||||
return $this->currentRow + array_values($this->currentRow);
|
||||
case PDO::FETCH_NUM:
|
||||
return array_values($this->currentRow);
|
||||
case PDO::FETCH_LAZY:
|
||||
// We do not do lazy as everything is fetched already. Fallback to
|
||||
// PDO::FETCH_OBJ.
|
||||
case PDO::FETCH_OBJ:
|
||||
return (object) $this->currentRow;
|
||||
case PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE:
|
||||
$class_name = array_unshift($this->currentRow);
|
||||
// Deliberate no break.
|
||||
case PDO::FETCH_CLASS:
|
||||
if (!isset($class_name)) {
|
||||
$class_name = $this->fetchOptions['class'];
|
||||
}
|
||||
if (count($this->fetchOptions['constructor_args'])) {
|
||||
$reflector = new ReflectionClass($class_name);
|
||||
$result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
|
||||
}
|
||||
else {
|
||||
$result = new $class_name();
|
||||
}
|
||||
foreach ($this->currentRow as $k => $v) {
|
||||
$result->$k = $v;
|
||||
}
|
||||
return $result;
|
||||
case PDO::FETCH_INTO:
|
||||
foreach ($this->currentRow as $k => $v) {
|
||||
$this->fetchOptions['object']->$k = $v;
|
||||
}
|
||||
return $this->fetchOptions['object'];
|
||||
case PDO::FETCH_COLUMN:
|
||||
if (isset($this->columnNames[$this->fetchOptions['column']])) {
|
||||
return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]];
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Implementations of Iterator. */
|
||||
|
||||
public function key() {
|
||||
return $this->currentKey;
|
||||
}
|
||||
|
||||
public function rewind() {
|
||||
// Nothing to do: our DatabaseStatement can't be rewound.
|
||||
}
|
||||
|
||||
public function next() {
|
||||
if (!empty($this->data)) {
|
||||
$this->currentRow = reset($this->data);
|
||||
$this->currentKey = key($this->data);
|
||||
unset($this->data[$this->currentKey]);
|
||||
}
|
||||
else {
|
||||
$this->currentRow = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public function valid() {
|
||||
return isset($this->currentRow);
|
||||
}
|
||||
|
||||
/* Implementations of DatabaseStatementInterface. */
|
||||
|
||||
public function rowCount() {
|
||||
return $this->rowCount;
|
||||
}
|
||||
|
||||
public function fetch($fetch_style = NULL, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
|
||||
if (isset($this->currentRow)) {
|
||||
// Set the fetch parameter.
|
||||
$this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
|
||||
$this->fetchOptions = $this->defaultFetchOptions;
|
||||
|
||||
// Grab the row in the format specified above.
|
||||
$return = $this->current();
|
||||
// Advance the cursor.
|
||||
$this->next();
|
||||
|
||||
// Reset the fetch parameters to the value stored using setFetchMode().
|
||||
$this->fetchStyle = $this->defaultFetchStyle;
|
||||
$this->fetchOptions = $this->defaultFetchOptions;
|
||||
return $return;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchColumn($index = 0) {
|
||||
if (isset($this->currentRow) && isset($this->columnNames[$index])) {
|
||||
// We grab the value directly from $this->data, and format it.
|
||||
$return = $this->currentRow[$this->columnNames[$index]];
|
||||
$this->next();
|
||||
return $return;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchField($index = 0) {
|
||||
return $this->fetchColumn($index);
|
||||
}
|
||||
|
||||
public function fetchObject($class_name = NULL, $constructor_args = array()) {
|
||||
if (isset($this->currentRow)) {
|
||||
if (!isset($class_name)) {
|
||||
// Directly cast to an object to avoid a function call.
|
||||
$result = (object) $this->currentRow;
|
||||
}
|
||||
else {
|
||||
$this->fetchStyle = PDO::FETCH_CLASS;
|
||||
$this->fetchOptions = array('constructor_args' => $constructor_args);
|
||||
// Grab the row in the format specified above.
|
||||
$result = $this->current();
|
||||
// Reset the fetch parameters to the value stored using setFetchMode().
|
||||
$this->fetchStyle = $this->defaultFetchStyle;
|
||||
$this->fetchOptions = $this->defaultFetchOptions;
|
||||
}
|
||||
|
||||
$this->next();
|
||||
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchAssoc() {
|
||||
if (isset($this->currentRow)) {
|
||||
$result = $this->currentRow;
|
||||
$this->next();
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchAll($fetch_style = NULL, $fetch_column = NULL, $constructor_args = NULL) {
|
||||
$this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
|
||||
$this->fetchOptions = $this->defaultFetchOptions;
|
||||
if (isset($fetch_column)) {
|
||||
$this->fetchOptions['column'] = $fetch_column;
|
||||
}
|
||||
if (isset($constructor_args)) {
|
||||
$this->fetchOptions['constructor_args'] = $constructor_args;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
// Traverse the array as PHP would have done.
|
||||
while (isset($this->currentRow)) {
|
||||
// Grab the row in the format specified above.
|
||||
$result[] = $this->current();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
// Reset the fetch parameters to the value stored using setFetchMode().
|
||||
$this->fetchStyle = $this->defaultFetchStyle;
|
||||
$this->fetchOptions = $this->defaultFetchOptions;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function fetchCol($index = 0) {
|
||||
if (isset($this->columnNames[$index])) {
|
||||
$column = $this->columnNames[$index];
|
||||
$result = array();
|
||||
// Traverse the array as PHP would have done.
|
||||
while (isset($this->currentRow)) {
|
||||
$result[] = $this->currentRow[$this->columnNames[$index]];
|
||||
$this->next();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchAllKeyed($key_index = 0, $value_index = 1) {
|
||||
if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index]))
|
||||
return array();
|
||||
|
||||
$key = $this->columnNames[$key_index];
|
||||
$value = $this->columnNames[$value_index];
|
||||
|
||||
$result = array();
|
||||
// Traverse the array as PHP would have done.
|
||||
while (isset($this->currentRow)) {
|
||||
$result[$this->currentRow[$key]] = $this->currentRow[$value];
|
||||
$this->next();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function fetchAllAssoc($key, $fetch_style = NULL) {
|
||||
$this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
|
||||
$this->fetchOptions = $this->defaultFetchOptions;
|
||||
|
||||
$result = array();
|
||||
// Traverse the array as PHP would have done.
|
||||
while (isset($this->currentRow)) {
|
||||
// Grab the row in its raw PDO::FETCH_ASSOC format.
|
||||
$row = $this->currentRow;
|
||||
// Grab the row in the format specified above.
|
||||
$result_row = $this->current();
|
||||
$result[$this->currentRow[$key]] = $result_row;
|
||||
$this->next();
|
||||
}
|
||||
|
||||
// Reset the fetch parameters to the value stored using setFetchMode().
|
||||
$this->fetchStyle = $this->defaultFetchStyle;
|
||||
$this->fetchOptions = $this->defaultFetchOptions;
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,733 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Generic Database schema code.
|
||||
*/
|
||||
|
||||
require_once dirname(__FILE__) . '/query.inc';
|
||||
|
||||
/**
|
||||
* @defgroup schemaapi Schema API
|
||||
* @{
|
||||
* API to handle database schemas.
|
||||
*
|
||||
* A Drupal schema definition is an array structure representing one or
|
||||
* more tables and their related keys and indexes. A schema is defined by
|
||||
* hook_schema(), which usually lives in a modulename.install file.
|
||||
*
|
||||
* By implementing hook_schema() and specifying the tables your module
|
||||
* declares, you can easily create and drop these tables on all
|
||||
* supported database engines. You don't have to deal with the
|
||||
* different SQL dialects for table creation and alteration of the
|
||||
* supported database engines.
|
||||
*
|
||||
* hook_schema() should return an array with a key for each table that
|
||||
* the module defines.
|
||||
*
|
||||
* The following keys are defined:
|
||||
* - 'description': A string in non-markup plain text describing this table
|
||||
* and its purpose. References to other tables should be enclosed in
|
||||
* curly-brackets. For example, the node_revisions table
|
||||
* description field might contain "Stores per-revision title and
|
||||
* body data for each {node}."
|
||||
* - 'fields': An associative array ('fieldname' => specification)
|
||||
* that describes the table's database columns. The specification
|
||||
* is also an array. The following specification parameters are defined:
|
||||
* - 'description': A string in non-markup plain text describing this field
|
||||
* and its purpose. References to other tables should be enclosed in
|
||||
* curly-brackets. For example, the node table vid field
|
||||
* description might contain "Always holds the largest (most
|
||||
* recent) {node_revision}.vid value for this nid."
|
||||
* - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int',
|
||||
* 'float', 'numeric', or 'serial'. Most types just map to the according
|
||||
* database engine specific datatypes. Use 'serial' for auto incrementing
|
||||
* fields. This will expand to 'INT auto_increment' on MySQL.
|
||||
* - 'mysql_type', 'pgsql_type', 'sqlite_type', etc.: If you need to
|
||||
* use a record type not included in the officially supported list
|
||||
* of types above, you can specify a type for each database
|
||||
* backend. In this case, you can leave out the type parameter,
|
||||
* but be advised that your schema will fail to load on backends that
|
||||
* do not have a type specified. A possible solution can be to
|
||||
* use the "text" type as a fallback.
|
||||
* - 'serialize': A boolean indicating whether the field will be stored as
|
||||
* a serialized string.
|
||||
* - 'size': The data size: 'tiny', 'small', 'medium', 'normal',
|
||||
* 'big'. This is a hint about the largest value the field will
|
||||
* store and determines which of the database engine specific
|
||||
* datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT).
|
||||
* 'normal', the default, selects the base type (e.g. on MySQL,
|
||||
* INT, VARCHAR, BLOB, etc.).
|
||||
* Not all sizes are available for all data types. See
|
||||
* DatabaseSchema::getFieldTypeMap() for possible combinations.
|
||||
* - 'not null': If true, no NULL values will be allowed in this
|
||||
* database column. Defaults to false.
|
||||
* - 'default': The field's default value. The PHP type of the
|
||||
* value matters: '', '0', and 0 are all different. If you
|
||||
* specify '0' as the default value for a type 'int' field it
|
||||
* will not work because '0' is a string containing the
|
||||
* character "zero", not an integer.
|
||||
* - 'length': The maximal length of a type 'char', 'varchar' or 'text'
|
||||
* field. Ignored for other field types.
|
||||
* - 'unsigned': A boolean indicating whether a type 'int', 'float'
|
||||
* and 'numeric' only is signed or unsigned. Defaults to
|
||||
* FALSE. Ignored for other field types.
|
||||
* - 'precision', 'scale': For type 'numeric' fields, indicates
|
||||
* the precision (total number of significant digits) and scale
|
||||
* (decimal digits right of the decimal point). Both values are
|
||||
* mandatory. Ignored for other field types.
|
||||
* - 'binary': A boolean indicating that MySQL should force 'char',
|
||||
* 'varchar' or 'text' fields to use case-sensitive binary collation.
|
||||
* This has no effect on other database types for which case sensitivity
|
||||
* is already the default behavior.
|
||||
* All parameters apart from 'type' are optional except that type
|
||||
* 'numeric' columns must specify 'precision' and 'scale', and type
|
||||
* 'varchar' must specify the 'length' parameter.
|
||||
* - 'primary key': An array of one or more key column specifiers (see below)
|
||||
* that form the primary key.
|
||||
* - 'unique keys': An associative array of unique keys ('keyname' =>
|
||||
* specification). Each specification is an array of one or more
|
||||
* key column specifiers (see below) that form a unique key on the table.
|
||||
* - 'foreign keys': An associative array of relations ('my_relation' =>
|
||||
* specification). Each specification is an array containing the name of
|
||||
* the referenced table ('table'), and an array of column mappings
|
||||
* ('columns'). Column mappings are defined by key pairs ('source_column' =>
|
||||
* 'referenced_column'). This key is for documentation purposes only; foreign
|
||||
* keys are not created in the database, nor are they enforced by Drupal.
|
||||
* - 'indexes': An associative array of indexes ('indexname' =>
|
||||
* specification). Each specification is an array of one or more
|
||||
* key column specifiers (see below) that form an index on the
|
||||
* table.
|
||||
*
|
||||
* A key column specifier is either a string naming a column or an
|
||||
* array of two elements, column name and length, specifying a prefix
|
||||
* of the named column.
|
||||
*
|
||||
* As an example, here is a SUBSET of the schema definition for
|
||||
* Drupal's 'node' table. It show four fields (nid, vid, type, and
|
||||
* title), the primary key on field 'nid', a unique key named 'vid' on
|
||||
* field 'vid', and two indexes, one named 'nid' on field 'nid' and
|
||||
* one named 'node_title_type' on the field 'title' and the first four
|
||||
* bytes of the field 'type':
|
||||
*
|
||||
* @code
|
||||
* $schema['node'] = array(
|
||||
* 'description' => 'The base table for nodes.',
|
||||
* 'fields' => array(
|
||||
* 'nid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
|
||||
* 'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE,'default' => 0),
|
||||
* 'type' => array('type' => 'varchar','length' => 32,'not null' => TRUE, 'default' => ''),
|
||||
* 'language' => array('type' => 'varchar','length' => 12,'not null' => TRUE,'default' => ''),
|
||||
* 'title' => array('type' => 'varchar','length' => 255,'not null' => TRUE, 'default' => ''),
|
||||
* 'uid' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
* 'status' => array('type' => 'int', 'not null' => TRUE, 'default' => 1),
|
||||
* 'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
* 'changed' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
* 'comment' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
* 'promote' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
* 'moderate' => array('type' => 'int', 'not null' => TRUE,'default' => 0),
|
||||
* 'sticky' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
* 'tnid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
|
||||
* 'translate' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
* ),
|
||||
* 'indexes' => array(
|
||||
* 'node_changed' => array('changed'),
|
||||
* 'node_created' => array('created'),
|
||||
* 'node_moderate' => array('moderate'),
|
||||
* 'node_frontpage' => array('promote', 'status', 'sticky', 'created'),
|
||||
* 'node_status_type' => array('status', 'type', 'nid'),
|
||||
* 'node_title_type' => array('title', array('type', 4)),
|
||||
* 'node_type' => array(array('type', 4)),
|
||||
* 'uid' => array('uid'),
|
||||
* 'tnid' => array('tnid'),
|
||||
* 'translate' => array('translate'),
|
||||
* ),
|
||||
* 'unique keys' => array(
|
||||
* 'vid' => array('vid'),
|
||||
* ),
|
||||
* // For documentation purposes only; foreign keys are not created in the
|
||||
* // database.
|
||||
* 'foreign keys' => array(
|
||||
* 'node_revision' => array(
|
||||
* 'table' => 'node_revision',
|
||||
* 'columns' => array('vid' => 'vid'),
|
||||
* ),
|
||||
* 'node_author' => array(
|
||||
* 'table' => 'users',
|
||||
* 'columns' => array('uid' => 'uid'),
|
||||
* ),
|
||||
* ),
|
||||
* 'primary key' => array('nid'),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @see drupal_install_schema()
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for database schema definitions.
|
||||
*/
|
||||
abstract class DatabaseSchema implements QueryPlaceholderInterface {
|
||||
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The placeholder counter.
|
||||
*/
|
||||
protected $placeholder = 0;
|
||||
|
||||
/**
|
||||
* Definition of prefixInfo array structure.
|
||||
*
|
||||
* Rather than redefining DatabaseSchema::getPrefixInfo() for each driver,
|
||||
* by defining the defaultSchema variable only MySQL has to re-write the
|
||||
* method.
|
||||
*
|
||||
* @see DatabaseSchema::getPrefixInfo()
|
||||
*/
|
||||
protected $defaultSchema = 'public';
|
||||
|
||||
/**
|
||||
* A unique identifier for this query object.
|
||||
*/
|
||||
protected $uniqueIdentifier;
|
||||
|
||||
public function __construct($connection) {
|
||||
$this->uniqueIdentifier = uniqid('', TRUE);
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the magic __clone function.
|
||||
*/
|
||||
public function __clone() {
|
||||
$this->uniqueIdentifier = uniqid('', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements QueryPlaceHolderInterface::uniqueIdentifier().
|
||||
*/
|
||||
public function uniqueIdentifier() {
|
||||
return $this->uniqueIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements QueryPlaceHolderInterface::nextPlaceholder().
|
||||
*/
|
||||
public function nextPlaceholder() {
|
||||
return $this->placeholder++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the table name and schema from the prefix.
|
||||
*
|
||||
* @param
|
||||
* Name of table to look prefix up for. Defaults to 'default' because thats
|
||||
* default key for prefix.
|
||||
* @param $add_prefix
|
||||
* Boolean that indicates whether the given table name should be prefixed.
|
||||
*
|
||||
* @return
|
||||
* A keyed array with information about the schema, table name and prefix.
|
||||
*/
|
||||
protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
|
||||
$info = array(
|
||||
'schema' => $this->defaultSchema,
|
||||
'prefix' => $this->connection->tablePrefix($table),
|
||||
);
|
||||
if ($add_prefix) {
|
||||
$table = $info['prefix'] . $table;
|
||||
}
|
||||
// If the prefix contains a period in it, then that means the prefix also
|
||||
// contains a schema reference in which case we will change the schema key
|
||||
// to the value before the period in the prefix. Everything after the dot
|
||||
// will be prefixed onto the front of the table.
|
||||
if (($pos = strpos($table, '.')) !== FALSE) {
|
||||
// Grab everything before the period.
|
||||
$info['schema'] = substr($table, 0, $pos);
|
||||
// Grab everything after the dot.
|
||||
$info['table'] = substr($table, ++$pos);
|
||||
}
|
||||
else {
|
||||
$info['table'] = $table;
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create names for indexes, primary keys and constraints.
|
||||
*
|
||||
* This prevents using {} around non-table names like indexes and keys.
|
||||
*/
|
||||
function prefixNonTable($table) {
|
||||
$args = func_get_args();
|
||||
$info = $this->getPrefixInfo($table);
|
||||
$args[0] = $info['table'];
|
||||
return implode('_', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a condition to match a table name against a standard information_schema.
|
||||
*
|
||||
* The information_schema is a SQL standard that provides information about the
|
||||
* database server and the databases, schemas, tables, columns and users within
|
||||
* it. This makes information_schema a useful tool to use across the drupal
|
||||
* database drivers and is used by a few different functions. The function below
|
||||
* describes the conditions to be meet when querying information_schema.tables
|
||||
* for drupal tables or information associated with drupal tables. Even though
|
||||
* this is the standard method, not all databases follow standards and so this
|
||||
* method should be overwritten by a database driver if the database provider
|
||||
* uses alternate methods. Because information_schema.tables is used in a few
|
||||
* different functions, a database driver will only need to override this function
|
||||
* to make all the others work. For example see includes/databases/mysql/schema.inc.
|
||||
*
|
||||
* @param $table_name
|
||||
* The name of the table in question.
|
||||
* @param $operator
|
||||
* The operator to apply on the 'table' part of the condition.
|
||||
* @param $add_prefix
|
||||
* Boolean to indicate whether the table name needs to be prefixed.
|
||||
*
|
||||
* @return QueryConditionInterface
|
||||
* A DatabaseCondition object.
|
||||
*/
|
||||
protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
|
||||
$info = $this->connection->getConnectionOptions();
|
||||
|
||||
// Retrieve the table name and schema
|
||||
$table_info = $this->getPrefixInfo($table_name, $add_prefix);
|
||||
|
||||
$condition = new DatabaseCondition('AND');
|
||||
$condition->condition('table_catalog', $info['database']);
|
||||
$condition->condition('table_schema', $table_info['schema']);
|
||||
$condition->condition('table_name', $table_info['table'], $operator);
|
||||
return $condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table exists.
|
||||
*
|
||||
* @param $table
|
||||
* The name of the table in drupal (no prefixing).
|
||||
*
|
||||
* @return
|
||||
* TRUE if the given table exists, otherwise FALSE.
|
||||
*/
|
||||
public function tableExists($table) {
|
||||
$condition = $this->buildTableNameCondition($table);
|
||||
$condition->compile($this->connection, $this);
|
||||
// Normally, we would heartily discourage the use of string
|
||||
// concatenation for conditionals like this however, we
|
||||
// couldn't use db_select() here because it would prefix
|
||||
// information_schema.tables and the query would fail.
|
||||
// Don't use {} around information_schema.tables table.
|
||||
return (bool) $this->connection->query("SELECT 1 FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all tables that are like the specified base table name.
|
||||
*
|
||||
* @param $table_expression
|
||||
* An SQL expression, for example "simpletest%" (without the quotes).
|
||||
* BEWARE: this is not prefixed, the caller should take care of that.
|
||||
*
|
||||
* @return
|
||||
* Array, both the keys and the values are the matching tables.
|
||||
*/
|
||||
public function findTables($table_expression) {
|
||||
$condition = $this->buildTableNameCondition($table_expression, 'LIKE', FALSE);
|
||||
|
||||
$condition->compile($this->connection, $this);
|
||||
// Normally, we would heartily discourage the use of string
|
||||
// concatenation for conditionals like this however, we
|
||||
// couldn't use db_select() here because it would prefix
|
||||
// information_schema.tables and the query would fail.
|
||||
// Don't use {} around information_schema.tables table.
|
||||
return $this->connection->query("SELECT table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchAllKeyed(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a column exists in the given table.
|
||||
*
|
||||
* @param $table
|
||||
* The name of the table in drupal (no prefixing).
|
||||
* @param $name
|
||||
* The name of the column.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the given column exists, otherwise FALSE.
|
||||
*/
|
||||
public function fieldExists($table, $column) {
|
||||
$condition = $this->buildTableNameCondition($table);
|
||||
$condition->condition('column_name', $column);
|
||||
$condition->compile($this->connection, $this);
|
||||
// Normally, we would heartily discourage the use of string
|
||||
// concatenation for conditionals like this however, we
|
||||
// couldn't use db_select() here because it would prefix
|
||||
// information_schema.tables and the query would fail.
|
||||
// Don't use {} around information_schema.columns table.
|
||||
return (bool) $this->connection->query("SELECT 1 FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mapping of Drupal schema field names to DB-native field types.
|
||||
*
|
||||
* Because different field types do not map 1:1 between databases, Drupal has
|
||||
* its own normalized field type names. This function returns a driver-specific
|
||||
* mapping table from Drupal names to the native names for each database.
|
||||
*
|
||||
* @return array
|
||||
* An array of Schema API field types to driver-specific field types.
|
||||
*/
|
||||
abstract public function getFieldTypeMap();
|
||||
|
||||
/**
|
||||
* Rename a table.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be renamed.
|
||||
* @param $new_name
|
||||
* The new name for the table.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectDoesNotExistException
|
||||
* If the specified table doesn't exist.
|
||||
* @throws DatabaseSchemaObjectExistsException
|
||||
* If a table with the specified new name already exists.
|
||||
*/
|
||||
abstract public function renameTable($table, $new_name);
|
||||
|
||||
/**
|
||||
* Drop a table.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be dropped.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the table was successfully dropped, FALSE if there was no table
|
||||
* by that name to begin with.
|
||||
*/
|
||||
abstract public function dropTable($table);
|
||||
|
||||
/**
|
||||
* Add a new field to a table.
|
||||
*
|
||||
* @param $table
|
||||
* Name of the table to be altered.
|
||||
* @param $field
|
||||
* Name of the field to be added.
|
||||
* @param $spec
|
||||
* The field specification array, as taken from a schema definition.
|
||||
* The specification may also contain the key 'initial', the newly
|
||||
* created field will be set to the value of the key in all rows.
|
||||
* This is most useful for creating NOT NULL columns with no default
|
||||
* value in existing tables.
|
||||
* @param $keys_new
|
||||
* (optional) Keys and indexes specification to be created on the
|
||||
* table along with adding the field. The format is the same as a
|
||||
* table specification but without the 'fields' element. If you are
|
||||
* adding a type 'serial' field, you MUST specify at least one key
|
||||
* or index including it in this array. See db_change_field() for more
|
||||
* explanation why.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectDoesNotExistException
|
||||
* If the specified table doesn't exist.
|
||||
* @throws DatabaseSchemaObjectExistsException
|
||||
* If the specified table already has a field by that name.
|
||||
*/
|
||||
abstract public function addField($table, $field, $spec, $keys_new = array());
|
||||
|
||||
/**
|
||||
* Drop a field.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $field
|
||||
* The field to be dropped.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the field was successfully dropped, FALSE if there was no field
|
||||
* by that name to begin with.
|
||||
*/
|
||||
abstract public function dropField($table, $field);
|
||||
|
||||
/**
|
||||
* Set the default value for a field.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $field
|
||||
* The field to be altered.
|
||||
* @param $default
|
||||
* Default value to be set. NULL for 'default NULL'.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectDoesNotExistException
|
||||
* If the specified table or field doesn't exist.
|
||||
*/
|
||||
abstract public function fieldSetDefault($table, $field, $default);
|
||||
|
||||
/**
|
||||
* Set a field to have no default value.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $field
|
||||
* The field to be altered.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectDoesNotExistException
|
||||
* If the specified table or field doesn't exist.
|
||||
*/
|
||||
abstract public function fieldSetNoDefault($table, $field);
|
||||
|
||||
/**
|
||||
* Checks if an index exists in the given table.
|
||||
*
|
||||
* @param $table
|
||||
* The name of the table in drupal (no prefixing).
|
||||
* @param $name
|
||||
* The name of the index in drupal (no prefixing).
|
||||
*
|
||||
* @return
|
||||
* TRUE if the given index exists, otherwise FALSE.
|
||||
*/
|
||||
abstract public function indexExists($table, $name);
|
||||
|
||||
/**
|
||||
* Add a primary key.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $fields
|
||||
* Fields for the primary key.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectDoesNotExistException
|
||||
* If the specified table doesn't exist.
|
||||
* @throws DatabaseSchemaObjectExistsException
|
||||
* If the specified table already has a primary key.
|
||||
*/
|
||||
abstract public function addPrimaryKey($table, $fields);
|
||||
|
||||
/**
|
||||
* Drop the primary key.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the primary key was successfully dropped, FALSE if there was no
|
||||
* primary key on this table to begin with.
|
||||
*/
|
||||
abstract public function dropPrimaryKey($table);
|
||||
|
||||
/**
|
||||
* Add a unique key.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $name
|
||||
* The name of the key.
|
||||
* @param $fields
|
||||
* An array of field names.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectDoesNotExistException
|
||||
* If the specified table doesn't exist.
|
||||
* @throws DatabaseSchemaObjectExistsException
|
||||
* If the specified table already has a key by that name.
|
||||
*/
|
||||
abstract public function addUniqueKey($table, $name, $fields);
|
||||
|
||||
/**
|
||||
* Drop a unique key.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $name
|
||||
* The name of the key.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the key was successfully dropped, FALSE if there was no key by
|
||||
* that name to begin with.
|
||||
*/
|
||||
abstract public function dropUniqueKey($table, $name);
|
||||
|
||||
/**
|
||||
* Add an index.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $name
|
||||
* The name of the index.
|
||||
* @param $fields
|
||||
* An array of field names.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectDoesNotExistException
|
||||
* If the specified table doesn't exist.
|
||||
* @throws DatabaseSchemaObjectExistsException
|
||||
* If the specified table already has an index by that name.
|
||||
*/
|
||||
abstract public function addIndex($table, $name, $fields);
|
||||
|
||||
/**
|
||||
* Drop an index.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $name
|
||||
* The name of the index.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the index was successfully dropped, FALSE if there was no index
|
||||
* by that name to begin with.
|
||||
*/
|
||||
abstract public function dropIndex($table, $name);
|
||||
|
||||
/**
|
||||
* Change a field definition.
|
||||
*
|
||||
* IMPORTANT NOTE: To maintain database portability, you have to explicitly
|
||||
* recreate all indices and primary keys that are using the changed field.
|
||||
*
|
||||
* That means that you have to drop all affected keys and indexes with
|
||||
* db_drop_{primary_key,unique_key,index}() before calling db_change_field().
|
||||
* To recreate the keys and indices, pass the key definitions as the
|
||||
* optional $keys_new argument directly to db_change_field().
|
||||
*
|
||||
* For example, suppose you have:
|
||||
* @code
|
||||
* $schema['foo'] = array(
|
||||
* 'fields' => array(
|
||||
* 'bar' => array('type' => 'int', 'not null' => TRUE)
|
||||
* ),
|
||||
* 'primary key' => array('bar')
|
||||
* );
|
||||
* @endcode
|
||||
* and you want to change foo.bar to be type serial, leaving it as the
|
||||
* primary key. The correct sequence is:
|
||||
* @code
|
||||
* db_drop_primary_key('foo');
|
||||
* db_change_field('foo', 'bar', 'bar',
|
||||
* array('type' => 'serial', 'not null' => TRUE),
|
||||
* array('primary key' => array('bar')));
|
||||
* @endcode
|
||||
*
|
||||
* The reasons for this are due to the different database engines:
|
||||
*
|
||||
* On PostgreSQL, changing a field definition involves adding a new field
|
||||
* and dropping an old one which* causes any indices, primary keys and
|
||||
* sequences (from serial-type fields) that use the changed field to be dropped.
|
||||
*
|
||||
* On MySQL, all type 'serial' fields must be part of at least one key
|
||||
* or index as soon as they are created. You cannot use
|
||||
* db_add_{primary_key,unique_key,index}() for this purpose because
|
||||
* the ALTER TABLE command will fail to add the column without a key
|
||||
* or index specification. The solution is to use the optional
|
||||
* $keys_new argument to create the key or index at the same time as
|
||||
* field.
|
||||
*
|
||||
* You could use db_add_{primary_key,unique_key,index}() in all cases
|
||||
* unless you are converting a field to be type serial. You can use
|
||||
* the $keys_new argument in all cases.
|
||||
*
|
||||
* @param $table
|
||||
* Name of the table.
|
||||
* @param $field
|
||||
* Name of the field to change.
|
||||
* @param $field_new
|
||||
* New name for the field (set to the same as $field if you don't want to change the name).
|
||||
* @param $spec
|
||||
* The field specification for the new field.
|
||||
* @param $keys_new
|
||||
* (optional) Keys and indexes specification to be created on the
|
||||
* table along with changing the field. The format is the same as a
|
||||
* table specification but without the 'fields' element.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectDoesNotExistException
|
||||
* If the specified table or source field doesn't exist.
|
||||
* @throws DatabaseSchemaObjectExistsException
|
||||
* If the specified destination field already exists.
|
||||
*/
|
||||
abstract public function changeField($table, $field, $field_new, $spec, $keys_new = array());
|
||||
|
||||
/**
|
||||
* Create a new table from a Drupal table definition.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the table to create.
|
||||
* @param $table
|
||||
* A Schema API table definition array.
|
||||
*
|
||||
* @throws DatabaseSchemaObjectExistsException
|
||||
* If the specified table already exists.
|
||||
*/
|
||||
public function createTable($name, $table) {
|
||||
if ($this->tableExists($name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t('Table @name already exists.', array('@name' => $name)));
|
||||
}
|
||||
$statements = $this->createTableSql($name, $table);
|
||||
foreach ($statements as $statement) {
|
||||
$this->connection->query($statement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of field names from an array of key/index column specifiers.
|
||||
*
|
||||
* This is usually an identity function but if a key/index uses a column prefix
|
||||
* specification, this function extracts just the name.
|
||||
*
|
||||
* @param $fields
|
||||
* An array of key/index column specifiers.
|
||||
*
|
||||
* @return
|
||||
* An array of field names.
|
||||
*/
|
||||
public function fieldNames($fields) {
|
||||
$return = array();
|
||||
foreach ($fields as $field) {
|
||||
if (is_array($field)) {
|
||||
$return[] = $field[0];
|
||||
}
|
||||
else {
|
||||
$return[] = $field;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a table or column comment for database query.
|
||||
*
|
||||
* @param $comment
|
||||
* The comment string to prepare.
|
||||
* @param $length
|
||||
* Optional upper limit on the returned string length.
|
||||
*
|
||||
* @return
|
||||
* The prepared comment.
|
||||
*/
|
||||
public function prepareComment($comment, $length = NULL) {
|
||||
return $this->connection->quote($comment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown if an object being created already exists.
|
||||
*
|
||||
* For example, this exception should be thrown whenever there is an attempt to
|
||||
* create a new database table, field, or index that already exists in the
|
||||
* database schema.
|
||||
*/
|
||||
class DatabaseSchemaObjectExistsException extends Exception {}
|
||||
|
||||
/**
|
||||
* Exception thrown if an object being modified doesn't exist yet.
|
||||
*
|
||||
* For example, this exception should be thrown whenever there is an attempt to
|
||||
* modify a database table, field, or index that does not currently exist in
|
||||
* the database schema.
|
||||
*/
|
||||
class DatabaseSchemaObjectDoesNotExistException extends Exception {}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup schemaapi".
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,527 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Database interface code for SQLite embedded database engine.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
include_once DRUPAL_ROOT . '/includes/database/prefetch.inc';
|
||||
|
||||
/**
|
||||
* Specific SQLite implementation of DatabaseConnection.
|
||||
*/
|
||||
class DatabaseConnection_sqlite extends DatabaseConnection {
|
||||
|
||||
/**
|
||||
* Whether this database connection supports savepoints.
|
||||
*
|
||||
* Version of sqlite lower then 3.6.8 can't use savepoints.
|
||||
* See http://www.sqlite.org/releaselog/3_6_8.html
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $savepointSupport = FALSE;
|
||||
|
||||
/**
|
||||
* Whether or not the active transaction (if any) will be rolled back.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $willRollback;
|
||||
|
||||
/**
|
||||
* All databases attached to the current database. This is used to allow
|
||||
* prefixes to be safely handled without locking the table
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attachedDatabases = array();
|
||||
|
||||
/**
|
||||
* Whether or not a table has been dropped this request: the destructor will
|
||||
* only try to get rid of unnecessary databases if there is potential of them
|
||||
* being empty.
|
||||
*
|
||||
* This variable is set to public because DatabaseSchema_sqlite needs to
|
||||
* access it. However, it should not be manually set.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
var $tableDropped = FALSE;
|
||||
|
||||
public function __construct(array $connection_options = array()) {
|
||||
// We don't need a specific PDOStatement class here, we simulate it below.
|
||||
$this->statementClass = NULL;
|
||||
|
||||
// This driver defaults to transaction support, except if explicitly passed FALSE.
|
||||
$this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
|
||||
|
||||
$this->connectionOptions = $connection_options;
|
||||
|
||||
// Allow PDO options to be overridden.
|
||||
$connection_options += array(
|
||||
'pdo' => array(),
|
||||
);
|
||||
$connection_options['pdo'] += array(
|
||||
// Convert numeric values to strings when fetching.
|
||||
PDO::ATTR_STRINGIFY_FETCHES => TRUE,
|
||||
);
|
||||
parent::__construct('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']);
|
||||
|
||||
// Attach one database for each registered prefix.
|
||||
$prefixes = $this->prefixes;
|
||||
foreach ($prefixes as $table => &$prefix) {
|
||||
// Empty prefix means query the main database -- no need to attach anything.
|
||||
if (!empty($prefix)) {
|
||||
// Only attach the database once.
|
||||
if (!isset($this->attachedDatabases[$prefix])) {
|
||||
$this->attachedDatabases[$prefix] = $prefix;
|
||||
$this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix));
|
||||
}
|
||||
|
||||
// Add a ., so queries become prefix.table, which is proper syntax for
|
||||
// querying an attached database.
|
||||
$prefix .= '.';
|
||||
}
|
||||
}
|
||||
// Regenerate the prefixes replacement table.
|
||||
$this->setPrefix($prefixes);
|
||||
|
||||
// Detect support for SAVEPOINT.
|
||||
$version = $this->query('SELECT sqlite_version()')->fetchField();
|
||||
$this->savepointSupport = (version_compare($version, '3.6.8') >= 0);
|
||||
|
||||
// Create functions needed by SQLite.
|
||||
$this->sqliteCreateFunction('if', array($this, 'sqlFunctionIf'));
|
||||
$this->sqliteCreateFunction('greatest', array($this, 'sqlFunctionGreatest'));
|
||||
$this->sqliteCreateFunction('pow', 'pow', 2);
|
||||
$this->sqliteCreateFunction('length', 'strlen', 1);
|
||||
$this->sqliteCreateFunction('md5', 'md5', 1);
|
||||
$this->sqliteCreateFunction('concat', array($this, 'sqlFunctionConcat'));
|
||||
$this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3);
|
||||
$this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3);
|
||||
$this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand'));
|
||||
|
||||
// Execute sqlite init_commands.
|
||||
if (isset($connection_options['init_commands'])) {
|
||||
$this->exec(implode('; ', $connection_options['init_commands']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor for the SQLite connection.
|
||||
*
|
||||
* We prune empty databases on destruct, but only if tables have been
|
||||
* dropped. This is especially needed when running the test suite, which
|
||||
* creates and destroy databases several times in a row.
|
||||
*/
|
||||
public function __destruct() {
|
||||
if ($this->tableDropped && !empty($this->attachedDatabases)) {
|
||||
foreach ($this->attachedDatabases as $prefix) {
|
||||
// Check if the database is now empty, ignore the internal SQLite tables.
|
||||
try {
|
||||
$count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField();
|
||||
|
||||
// We can prune the database file if it doesn't have any tables.
|
||||
if ($count == 0) {
|
||||
// Detach the database.
|
||||
$this->query('DETACH DATABASE :schema', array(':schema' => $prefix));
|
||||
// Destroy the database file.
|
||||
unlink($this->connectionOptions['database'] . '-' . $prefix);
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Ignore the exception and continue. There is nothing we can do here
|
||||
// to report the error or fail safe.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite compatibility implementation for the IF() SQL function.
|
||||
*/
|
||||
public function sqlFunctionIf($condition, $expr1, $expr2 = NULL) {
|
||||
return $condition ? $expr1 : $expr2;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite compatibility implementation for the GREATEST() SQL function.
|
||||
*/
|
||||
public function sqlFunctionGreatest() {
|
||||
$args = func_get_args();
|
||||
foreach ($args as $k => $v) {
|
||||
if (!isset($v)) {
|
||||
unset($args);
|
||||
}
|
||||
}
|
||||
if (count($args)) {
|
||||
return max($args);
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite compatibility implementation for the CONCAT() SQL function.
|
||||
*/
|
||||
public function sqlFunctionConcat() {
|
||||
$args = func_get_args();
|
||||
return implode('', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite compatibility implementation for the SUBSTRING() SQL function.
|
||||
*/
|
||||
public function sqlFunctionSubstring($string, $from, $length) {
|
||||
return substr($string, $from - 1, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function.
|
||||
*/
|
||||
public function sqlFunctionSubstringIndex($string, $delimiter, $count) {
|
||||
// If string is empty, simply return an empty string.
|
||||
if (empty($string)) {
|
||||
return '';
|
||||
}
|
||||
$end = 0;
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$end = strpos($string, $delimiter, $end + 1);
|
||||
if ($end === FALSE) {
|
||||
$end = strlen($string);
|
||||
}
|
||||
}
|
||||
return substr($string, 0, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite compatibility implementation for the RAND() SQL function.
|
||||
*/
|
||||
public function sqlFunctionRand($seed = NULL) {
|
||||
if (isset($seed)) {
|
||||
mt_srand($seed);
|
||||
}
|
||||
return mt_rand() / mt_getrandmax();
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite-specific implementation of DatabaseConnection::prepare().
|
||||
*
|
||||
* We don't use prepared statements at all at this stage. We just create
|
||||
* a DatabaseStatement_sqlite object, that will create a PDOStatement
|
||||
* using the semi-private PDOPrepare() method below.
|
||||
*/
|
||||
public function prepare($query, $options = array()) {
|
||||
return new DatabaseStatement_sqlite($this, $query, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* NEVER CALL THIS FUNCTION: YOU MIGHT DEADLOCK YOUR PHP PROCESS.
|
||||
*
|
||||
* This is a wrapper around the parent PDO::prepare method. However, as
|
||||
* the PDO SQLite driver only closes SELECT statements when the PDOStatement
|
||||
* destructor is called and SQLite does not allow data change (INSERT,
|
||||
* UPDATE etc) on a table which has open SELECT statements, you should never
|
||||
* call this function and keep a PDOStatement object alive as that can lead
|
||||
* to a deadlock. This really, really should be private, but as
|
||||
* DatabaseStatement_sqlite needs to call it, we have no other choice but to
|
||||
* expose this function to the world.
|
||||
*/
|
||||
public function PDOPrepare($query, array $options = array()) {
|
||||
return parent::prepare($query, $options);
|
||||
}
|
||||
|
||||
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
|
||||
return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
|
||||
}
|
||||
|
||||
public function queryTemporary($query, array $args = array(), array $options = array()) {
|
||||
// Generate a new temporary table name and protect it from prefixing.
|
||||
// SQLite requires that temporary tables to be non-qualified.
|
||||
$tablename = $this->generateTemporaryTableName();
|
||||
$prefixes = $this->prefixes;
|
||||
$prefixes[$tablename] = '';
|
||||
$this->setPrefix($prefixes);
|
||||
|
||||
$this->query('CREATE TEMPORARY TABLE ' . $tablename . ' AS ' . $query, $args, $options);
|
||||
return $tablename;
|
||||
}
|
||||
|
||||
public function driver() {
|
||||
return 'sqlite';
|
||||
}
|
||||
|
||||
public function databaseType() {
|
||||
return 'sqlite';
|
||||
}
|
||||
|
||||
public function mapConditionOperator($operator) {
|
||||
// We don't want to override any of the defaults.
|
||||
static $specials = array(
|
||||
'LIKE' => array('postfix' => " ESCAPE '\\'"),
|
||||
'NOT LIKE' => array('postfix' => " ESCAPE '\\'"),
|
||||
);
|
||||
return isset($specials[$operator]) ? $specials[$operator] : NULL;
|
||||
}
|
||||
|
||||
public function prepareQuery($query) {
|
||||
return $this->prepare($this->prefixTables($query));
|
||||
}
|
||||
|
||||
public function nextId($existing_id = 0) {
|
||||
$transaction = $this->startTransaction();
|
||||
// We can safely use literal queries here instead of the slower query
|
||||
// builder because if a given database breaks here then it can simply
|
||||
// override nextId. However, this is unlikely as we deal with short strings
|
||||
// and integers and no known databases require special handling for those
|
||||
// simple cases. If another transaction wants to write the same row, it will
|
||||
// wait until this transaction commits.
|
||||
$stmt = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array(
|
||||
':existing_id' => $existing_id,
|
||||
));
|
||||
if (!$stmt->rowCount()) {
|
||||
$this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array(
|
||||
':existing_id' => $existing_id,
|
||||
));
|
||||
}
|
||||
// The transaction gets committed when the transaction object gets destroyed
|
||||
// because it gets out of scope.
|
||||
return $this->query('SELECT value FROM {sequences}')->fetchField();
|
||||
}
|
||||
|
||||
public function rollback($savepoint_name = 'drupal_transaction') {
|
||||
if ($this->savepointSupport) {
|
||||
return parent::rollBack($savepoint_name);
|
||||
}
|
||||
|
||||
if (!$this->inTransaction()) {
|
||||
throw new DatabaseTransactionNoActiveException();
|
||||
}
|
||||
// A previous rollback to an earlier savepoint may mean that the savepoint
|
||||
// in question has already been rolled back.
|
||||
if (!in_array($savepoint_name, $this->transactionLayers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to find the point we're rolling back to, all other savepoints
|
||||
// before are no longer needed.
|
||||
while ($savepoint = array_pop($this->transactionLayers)) {
|
||||
if ($savepoint == $savepoint_name) {
|
||||
// Mark whole stack of transactions as needed roll back.
|
||||
$this->willRollback = TRUE;
|
||||
// If it is the last the transaction in the stack, then it is not a
|
||||
// savepoint, it is the transaction itself so we will need to roll back
|
||||
// the transaction rather than a savepoint.
|
||||
if (empty($this->transactionLayers)) {
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ($this->supportsTransactions()) {
|
||||
PDO::rollBack();
|
||||
}
|
||||
}
|
||||
|
||||
public function pushTransaction($name) {
|
||||
if ($this->savepointSupport) {
|
||||
return parent::pushTransaction($name);
|
||||
}
|
||||
if (!$this->supportsTransactions()) {
|
||||
return;
|
||||
}
|
||||
if (isset($this->transactionLayers[$name])) {
|
||||
throw new DatabaseTransactionNameNonUniqueException($name . " is already in use.");
|
||||
}
|
||||
if (!$this->inTransaction()) {
|
||||
PDO::beginTransaction();
|
||||
}
|
||||
$this->transactionLayers[$name] = $name;
|
||||
}
|
||||
|
||||
public function popTransaction($name) {
|
||||
if ($this->savepointSupport) {
|
||||
return parent::popTransaction($name);
|
||||
}
|
||||
if (!$this->supportsTransactions()) {
|
||||
return;
|
||||
}
|
||||
if (!$this->inTransaction()) {
|
||||
throw new DatabaseTransactionNoActiveException();
|
||||
}
|
||||
|
||||
// Commit everything since SAVEPOINT $name.
|
||||
while($savepoint = array_pop($this->transactionLayers)) {
|
||||
if ($savepoint != $name) continue;
|
||||
|
||||
// If there are no more layers left then we should commit or rollback.
|
||||
if (empty($this->transactionLayers)) {
|
||||
// If there was any rollback() we should roll back whole transaction.
|
||||
if ($this->willRollback) {
|
||||
$this->willRollback = FALSE;
|
||||
PDO::rollBack();
|
||||
}
|
||||
elseif (!PDO::commit()) {
|
||||
throw new DatabaseTransactionCommitFailedException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function utf8mb4IsActive() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function utf8mb4IsSupported() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific SQLite implementation of DatabaseConnection.
|
||||
*
|
||||
* See DatabaseConnection_sqlite::PDOPrepare() for reasons why we must prefetch
|
||||
* the data instead of using PDOStatement.
|
||||
*
|
||||
* @see DatabaseConnection_sqlite::PDOPrepare()
|
||||
*/
|
||||
class DatabaseStatement_sqlite extends DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
|
||||
|
||||
/**
|
||||
* SQLite specific implementation of getStatement().
|
||||
*
|
||||
* The PDO SQLite layer doesn't replace numeric placeholders in queries
|
||||
* correctly, and this makes numeric expressions (such as COUNT(*) >= :count)
|
||||
* fail. We replace numeric placeholders in the query ourselves to work
|
||||
* around this bug.
|
||||
*
|
||||
* See http://bugs.php.net/bug.php?id=45259 for more details.
|
||||
*/
|
||||
protected function getStatement($query, &$args = array()) {
|
||||
if (count($args)) {
|
||||
// Check if $args is a simple numeric array.
|
||||
if (range(0, count($args) - 1) === array_keys($args)) {
|
||||
// In that case, we have unnamed placeholders.
|
||||
$count = 0;
|
||||
$new_args = array();
|
||||
foreach ($args as $value) {
|
||||
if (is_float($value) || is_int($value)) {
|
||||
if (is_float($value)) {
|
||||
// Force the conversion to float so as not to loose precision
|
||||
// in the automatic cast.
|
||||
$value = sprintf('%F', $value);
|
||||
}
|
||||
$query = substr_replace($query, $value, strpos($query, '?'), 1);
|
||||
}
|
||||
else {
|
||||
$placeholder = ':db_statement_placeholder_' . $count++;
|
||||
$query = substr_replace($query, $placeholder, strpos($query, '?'), 1);
|
||||
$new_args[$placeholder] = $value;
|
||||
}
|
||||
}
|
||||
$args = $new_args;
|
||||
}
|
||||
else {
|
||||
// Else, this is using named placeholders.
|
||||
foreach ($args as $placeholder => $value) {
|
||||
if (is_float($value) || is_int($value)) {
|
||||
if (is_float($value)) {
|
||||
// Force the conversion to float so as not to loose precision
|
||||
// in the automatic cast.
|
||||
$value = sprintf('%F', $value);
|
||||
}
|
||||
|
||||
// We will remove this placeholder from the query as PDO throws an
|
||||
// exception if the number of placeholders in the query and the
|
||||
// arguments does not match.
|
||||
unset($args[$placeholder]);
|
||||
// PDO allows placeholders to not be prefixed by a colon. See
|
||||
// http://marc.info/?l=php-internals&m=111234321827149&w=2 for
|
||||
// more.
|
||||
if ($placeholder[0] != ':') {
|
||||
$placeholder = ":$placeholder";
|
||||
}
|
||||
// When replacing the placeholders, make sure we search for the
|
||||
// exact placeholder. For example, if searching for
|
||||
// ':db_placeholder_1', do not replace ':db_placeholder_11'.
|
||||
$query = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $query);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->dbh->PDOPrepare($query);
|
||||
}
|
||||
|
||||
public function execute($args = array(), $options = array()) {
|
||||
try {
|
||||
$return = parent::execute($args, $options);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
|
||||
// The schema has changed. SQLite specifies that we must resend the query.
|
||||
$return = parent::execute($args, $options);
|
||||
}
|
||||
else {
|
||||
// Rethrow the exception.
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// In some weird cases, SQLite will prefix some column names by the name
|
||||
// of the table. We post-process the data, by renaming the column names
|
||||
// using the same convention as MySQL and PostgreSQL.
|
||||
$rename_columns = array();
|
||||
foreach ($this->columnNames as $k => $column) {
|
||||
// In some SQLite versions, SELECT DISTINCT(field) will return "(field)"
|
||||
// instead of "field".
|
||||
if (preg_match("/^\((.*)\)$/", $column, $matches)) {
|
||||
$rename_columns[$column] = $matches[1];
|
||||
$this->columnNames[$k] = $matches[1];
|
||||
$column = $matches[1];
|
||||
}
|
||||
|
||||
// Remove "table." prefixes.
|
||||
if (preg_match("/^.*\.(.*)$/", $column, $matches)) {
|
||||
$rename_columns[$column] = $matches[1];
|
||||
$this->columnNames[$k] = $matches[1];
|
||||
}
|
||||
}
|
||||
if ($rename_columns) {
|
||||
// DatabaseStatementPrefetch already extracted the first row,
|
||||
// put it back into the result set.
|
||||
if (isset($this->currentRow)) {
|
||||
$this->data[0] = &$this->currentRow;
|
||||
}
|
||||
|
||||
// Then rename all the columns across the result set.
|
||||
foreach ($this->data as $k => $row) {
|
||||
foreach ($rename_columns as $old_column => $new_column) {
|
||||
$this->data[$k][$new_column] = $this->data[$k][$old_column];
|
||||
unset($this->data[$k][$old_column]);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, extract the first row again.
|
||||
$this->currentRow = $this->data[0];
|
||||
unset($this->data[0]);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* SQLite specific install functions
|
||||
*/
|
||||
|
||||
class DatabaseTasks_sqlite extends DatabaseTasks {
|
||||
protected $pdoDriver = 'sqlite';
|
||||
|
||||
public function name() {
|
||||
return st('SQLite');
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum engine version.
|
||||
*/
|
||||
public function minimumVersion() {
|
||||
return '3.3.7';
|
||||
}
|
||||
|
||||
public function getFormOptions($database) {
|
||||
$form = parent::getFormOptions($database);
|
||||
|
||||
// Remove the options that only apply to client/server style databases.
|
||||
unset($form['username'], $form['password'], $form['advanced_options']['host'], $form['advanced_options']['port']);
|
||||
|
||||
// Make the text more accurate for SQLite.
|
||||
$form['database']['#title'] = st('Database file');
|
||||
$form['database']['#description'] = st('The absolute path to the file where @drupal data will be stored. This must be writable by the web server and should exist outside of the web root.', array('@drupal' => drupal_install_profile_distribution_name()));
|
||||
$default_database = conf_path(FALSE, TRUE) . '/files/.ht.sqlite';
|
||||
$form['database']['#default_value'] = empty($database['database']) ? $default_database : $database['database'];
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function validateDatabaseSettings($database) {
|
||||
// Perform standard validation.
|
||||
$errors = parent::validateDatabaseSettings($database);
|
||||
|
||||
// Verify the database is writable.
|
||||
$db_directory = new SplFileInfo(dirname($database['database']));
|
||||
if (!$db_directory->isWritable()) {
|
||||
$errors[$database['driver'] . '][database'] = st('The directory you specified is not writable by the web server.');
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Query code for SQLite embedded database engine.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* SQLite specific implementation of InsertQuery.
|
||||
*
|
||||
* We ignore all the default fields and use the clever SQLite syntax:
|
||||
* INSERT INTO table DEFAULT VALUES
|
||||
* for degenerated "default only" queries.
|
||||
*/
|
||||
class InsertQuery_sqlite extends InsertQuery {
|
||||
|
||||
public function execute() {
|
||||
if (!$this->preExecute()) {
|
||||
return NULL;
|
||||
}
|
||||
if (count($this->insertFields)) {
|
||||
return parent::execute();
|
||||
}
|
||||
else {
|
||||
return $this->connection->query('INSERT INTO {' . $this->table . '} DEFAULT VALUES', array(), $this->queryOptions);
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
// Create a sanitized comment string to prepend to the query.
|
||||
$comments = $this->connection->makeComment($this->comments);
|
||||
|
||||
// Produce as many generic placeholders as necessary.
|
||||
$placeholders = array_fill(0, count($this->insertFields), '?');
|
||||
|
||||
// If we're selecting from a SelectQuery, finish building the query and
|
||||
// pass it back, as any remaining options are irrelevant.
|
||||
if (!empty($this->fromQuery)) {
|
||||
$insert_fields_string = $this->insertFields ? ' (' . implode(', ', $this->insertFields) . ') ' : ' ';
|
||||
return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
|
||||
}
|
||||
|
||||
return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite specific implementation of UpdateQuery.
|
||||
*
|
||||
* SQLite counts all the rows that match the conditions as modified, even if they
|
||||
* will not be affected by the query. We workaround this by ensuring that
|
||||
* we don't select those rows.
|
||||
*
|
||||
* A query like this one:
|
||||
* UPDATE test SET col1 = 'newcol1', col2 = 'newcol2' WHERE tid = 1
|
||||
* will become:
|
||||
* UPDATE test SET col1 = 'newcol1', col2 = 'newcol2' WHERE tid = 1 AND (col1 <> 'newcol1' OR col2 <> 'newcol2')
|
||||
*/
|
||||
class UpdateQuery_sqlite extends UpdateQuery {
|
||||
public function execute() {
|
||||
if (!empty($this->queryOptions['sqlite_return_matched_rows'])) {
|
||||
return parent::execute();
|
||||
}
|
||||
|
||||
// Get the fields used in the update query.
|
||||
$fields = $this->expressionFields + $this->fields;
|
||||
|
||||
// Add the inverse of the fields to the condition.
|
||||
$condition = new DatabaseCondition('OR');
|
||||
foreach ($fields as $field => $data) {
|
||||
if (is_array($data)) {
|
||||
// The field is an expression.
|
||||
$condition->where($field . ' <> ' . $data['expression']);
|
||||
$condition->isNull($field);
|
||||
}
|
||||
elseif (!isset($data)) {
|
||||
// The field will be set to NULL.
|
||||
$condition->isNotNull($field);
|
||||
}
|
||||
else {
|
||||
$condition->condition($field, $data, '<>');
|
||||
$condition->isNull($field);
|
||||
}
|
||||
}
|
||||
if (count($condition)) {
|
||||
$condition->compile($this->connection, $this);
|
||||
$this->condition->where((string) $condition, $condition->arguments());
|
||||
}
|
||||
return parent::execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite specific implementation of DeleteQuery.
|
||||
*/
|
||||
class DeleteQuery_sqlite extends DeleteQuery {
|
||||
public function execute() {
|
||||
// When the WHERE is omitted from a DELETE statement and the table being
|
||||
// deleted has no triggers, SQLite uses an optimization to erase the entire
|
||||
// table content without having to visit each row of the table individually.
|
||||
// Prior to SQLite 3.6.5, SQLite does not return the actual number of rows
|
||||
// deleted by that optimized "truncate" optimization. But we want to return
|
||||
// the number of rows affected, so we calculate it directly.
|
||||
if (!count($this->condition)) {
|
||||
$total_rows = $this->connection->query('SELECT COUNT(*) FROM {' . $this->connection->escapeTable($this->table) . '}')->fetchField();
|
||||
parent::execute();
|
||||
return $total_rows;
|
||||
}
|
||||
else {
|
||||
return parent::execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite specific implementation of TruncateQuery.
|
||||
*
|
||||
* SQLite doesn't support TRUNCATE, but a DELETE query with no condition has
|
||||
* exactly the effect (it is implemented by DROPing the table).
|
||||
*/
|
||||
class TruncateQuery_sqlite extends TruncateQuery {
|
||||
public function __toString() {
|
||||
// Create a sanitized comment string to prepend to the query.
|
||||
$comments = $this->connection->makeComment($this->comments);
|
||||
|
||||
return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} ';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
@@ -0,0 +1,683 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Database schema code for SQLite databases.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup schemaapi
|
||||
* @{
|
||||
*/
|
||||
|
||||
class DatabaseSchema_sqlite extends DatabaseSchema {
|
||||
|
||||
/**
|
||||
* Override DatabaseSchema::$defaultSchema
|
||||
*/
|
||||
protected $defaultSchema = 'main';
|
||||
|
||||
public function tableExists($table) {
|
||||
$info = $this->getPrefixInfo($table);
|
||||
|
||||
// Don't use {} around sqlite_master table.
|
||||
return (bool) $this->connection->query('SELECT 1 FROM ' . $info['schema'] . '.sqlite_master WHERE type = :type AND name = :name', array(':type' => 'table', ':name' => $info['table']))->fetchField();
|
||||
}
|
||||
|
||||
public function fieldExists($table, $column) {
|
||||
$schema = $this->introspectSchema($table);
|
||||
return !empty($schema['fields'][$column]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL to create a new table from a Drupal schema definition.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the table to create.
|
||||
* @param $table
|
||||
* A Schema API table definition array.
|
||||
* @return
|
||||
* An array of SQL statements to create the table.
|
||||
*/
|
||||
public function createTableSql($name, $table) {
|
||||
$sql = array();
|
||||
$sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumsSql($name, $table) . "\n);\n";
|
||||
return array_merge($sql, $this->createIndexSql($name, $table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL expression for indexes.
|
||||
*/
|
||||
protected function createIndexSql($tablename, $schema) {
|
||||
$sql = array();
|
||||
$info = $this->getPrefixInfo($tablename);
|
||||
if (!empty($schema['unique keys'])) {
|
||||
foreach ($schema['unique keys'] as $key => $fields) {
|
||||
$sql[] = 'CREATE UNIQUE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
|
||||
}
|
||||
}
|
||||
if (!empty($schema['indexes'])) {
|
||||
foreach ($schema['indexes'] as $key => $fields) {
|
||||
$sql[] = 'CREATE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL expression for creating columns.
|
||||
*/
|
||||
protected function createColumsSql($tablename, $schema) {
|
||||
$sql_array = array();
|
||||
|
||||
// Add the SQL statement for each field.
|
||||
foreach ($schema['fields'] as $name => $field) {
|
||||
if (isset($field['type']) && $field['type'] == 'serial') {
|
||||
if (isset($schema['primary key']) && ($key = array_search($name, $schema['primary key'])) !== FALSE) {
|
||||
unset($schema['primary key'][$key]);
|
||||
}
|
||||
}
|
||||
$sql_array[] = $this->createFieldSql($name, $this->processField($field));
|
||||
}
|
||||
|
||||
// Process keys.
|
||||
if (!empty($schema['primary key'])) {
|
||||
$sql_array[] = " PRIMARY KEY (" . $this->createKeySql($schema['primary key']) . ")";
|
||||
}
|
||||
|
||||
return implode(", \n", $sql_array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL expression for keys.
|
||||
*/
|
||||
protected function createKeySql($fields) {
|
||||
$return = array();
|
||||
foreach ($fields as $field) {
|
||||
if (is_array($field)) {
|
||||
$return[] = $field[0];
|
||||
}
|
||||
else {
|
||||
$return[] = $field;
|
||||
}
|
||||
}
|
||||
return implode(', ', $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database-engine specific properties for a field.
|
||||
*
|
||||
* @param $field
|
||||
* A field description array, as specified in the schema documentation.
|
||||
*/
|
||||
protected function processField($field) {
|
||||
if (!isset($field['size'])) {
|
||||
$field['size'] = 'normal';
|
||||
}
|
||||
|
||||
// Set the correct database-engine specific datatype.
|
||||
// In case one is already provided, force it to uppercase.
|
||||
if (isset($field['sqlite_type'])) {
|
||||
$field['sqlite_type'] = drupal_strtoupper($field['sqlite_type']);
|
||||
}
|
||||
else {
|
||||
$map = $this->getFieldTypeMap();
|
||||
$field['sqlite_type'] = $map[$field['type'] . ':' . $field['size']];
|
||||
}
|
||||
|
||||
if (isset($field['type']) && $field['type'] == 'serial') {
|
||||
$field['auto_increment'] = TRUE;
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an SQL string for a field to be used in table creation or alteration.
|
||||
*
|
||||
* Before passing a field out of a schema definition into this function it has
|
||||
* to be processed by db_processField().
|
||||
*
|
||||
* @param $name
|
||||
* Name of the field.
|
||||
* @param $spec
|
||||
* The field specification, as per the schema data structure format.
|
||||
*/
|
||||
protected function createFieldSql($name, $spec) {
|
||||
if (!empty($spec['auto_increment'])) {
|
||||
$sql = $name . " INTEGER PRIMARY KEY AUTOINCREMENT";
|
||||
if (!empty($spec['unsigned'])) {
|
||||
$sql .= ' CHECK (' . $name . '>= 0)';
|
||||
}
|
||||
}
|
||||
else {
|
||||
$sql = $name . ' ' . $spec['sqlite_type'];
|
||||
|
||||
if (in_array($spec['sqlite_type'], array('VARCHAR', 'TEXT')) && isset($spec['length'])) {
|
||||
$sql .= '(' . $spec['length'] . ')';
|
||||
}
|
||||
|
||||
if (isset($spec['not null'])) {
|
||||
if ($spec['not null']) {
|
||||
$sql .= ' NOT NULL';
|
||||
}
|
||||
else {
|
||||
$sql .= ' NULL';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($spec['unsigned'])) {
|
||||
$sql .= ' CHECK (' . $name . '>= 0)';
|
||||
}
|
||||
|
||||
if (isset($spec['default'])) {
|
||||
if (is_string($spec['default'])) {
|
||||
$spec['default'] = "'" . $spec['default'] . "'";
|
||||
}
|
||||
$sql .= ' DEFAULT ' . $spec['default'];
|
||||
}
|
||||
|
||||
if (empty($spec['not null']) && !isset($spec['default'])) {
|
||||
$sql .= ' DEFAULT NULL';
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* This maps a generic data type in combination with its data size
|
||||
* to the engine-specific data type.
|
||||
*/
|
||||
public function getFieldTypeMap() {
|
||||
// Put :normal last so it gets preserved by array_flip. This makes
|
||||
// it much easier for modules (such as schema.module) to map
|
||||
// database types back into schema types.
|
||||
// $map does not use drupal_static as its value never changes.
|
||||
static $map = array(
|
||||
'varchar:normal' => 'VARCHAR',
|
||||
'char:normal' => 'CHAR',
|
||||
|
||||
'text:tiny' => 'TEXT',
|
||||
'text:small' => 'TEXT',
|
||||
'text:medium' => 'TEXT',
|
||||
'text:big' => 'TEXT',
|
||||
'text:normal' => 'TEXT',
|
||||
|
||||
'serial:tiny' => 'INTEGER',
|
||||
'serial:small' => 'INTEGER',
|
||||
'serial:medium' => 'INTEGER',
|
||||
'serial:big' => 'INTEGER',
|
||||
'serial:normal' => 'INTEGER',
|
||||
|
||||
'int:tiny' => 'INTEGER',
|
||||
'int:small' => 'INTEGER',
|
||||
'int:medium' => 'INTEGER',
|
||||
'int:big' => 'INTEGER',
|
||||
'int:normal' => 'INTEGER',
|
||||
|
||||
'float:tiny' => 'FLOAT',
|
||||
'float:small' => 'FLOAT',
|
||||
'float:medium' => 'FLOAT',
|
||||
'float:big' => 'FLOAT',
|
||||
'float:normal' => 'FLOAT',
|
||||
|
||||
'numeric:normal' => 'NUMERIC',
|
||||
|
||||
'blob:big' => 'BLOB',
|
||||
'blob:normal' => 'BLOB',
|
||||
);
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function renameTable($table, $new_name) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
|
||||
}
|
||||
if ($this->tableExists($new_name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
|
||||
}
|
||||
|
||||
$schema = $this->introspectSchema($table);
|
||||
|
||||
// SQLite doesn't allow you to rename tables outside of the current
|
||||
// database. So the syntax '...RENAME TO database.table' would fail.
|
||||
// So we must determine the full table name here rather than surrounding
|
||||
// the table with curly braces incase the db_prefix contains a reference
|
||||
// to a database outside of our existing database.
|
||||
$info = $this->getPrefixInfo($new_name);
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']);
|
||||
|
||||
// Drop the indexes, there is no RENAME INDEX command in SQLite.
|
||||
if (!empty($schema['unique keys'])) {
|
||||
foreach ($schema['unique keys'] as $key => $fields) {
|
||||
$this->dropIndex($table, $key);
|
||||
}
|
||||
}
|
||||
if (!empty($schema['indexes'])) {
|
||||
foreach ($schema['indexes'] as $index => $fields) {
|
||||
$this->dropIndex($table, $index);
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate the indexes.
|
||||
$statements = $this->createIndexSql($new_name, $schema);
|
||||
foreach ($statements as $statement) {
|
||||
$this->connection->query($statement);
|
||||
}
|
||||
}
|
||||
|
||||
public function dropTable($table) {
|
||||
if (!$this->tableExists($table)) {
|
||||
return FALSE;
|
||||
}
|
||||
$this->connection->tableDropped = TRUE;
|
||||
$this->connection->query('DROP TABLE {' . $table . '}');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function addField($table, $field, $specification, $keys_new = array()) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
|
||||
}
|
||||
if ($this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
|
||||
}
|
||||
|
||||
// SQLite doesn't have a full-featured ALTER TABLE statement. It only
|
||||
// supports adding new fields to a table, in some simple cases. In most
|
||||
// cases, we have to create a new table and copy the data over.
|
||||
if (empty($keys_new) && (empty($specification['not null']) || isset($specification['default']))) {
|
||||
// When we don't have to create new keys and we are not creating a
|
||||
// NOT NULL column without a default value, we can use the quicker version.
|
||||
$query = 'ALTER TABLE {' . $table . '} ADD ' . $this->createFieldSql($field, $this->processField($specification));
|
||||
$this->connection->query($query);
|
||||
|
||||
// Apply the initial value if set.
|
||||
if (isset($specification['initial'])) {
|
||||
$this->connection->update($table)
|
||||
->fields(array($field => $specification['initial']))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We cannot add the field directly. Use the slower table alteration
|
||||
// method, starting from the old schema.
|
||||
$old_schema = $this->introspectSchema($table);
|
||||
$new_schema = $old_schema;
|
||||
|
||||
// Add the new field.
|
||||
$new_schema['fields'][$field] = $specification;
|
||||
|
||||
// Build the mapping between the old fields and the new fields.
|
||||
$mapping = array();
|
||||
if (isset($specification['initial'])) {
|
||||
// If we have a initial value, copy it over.
|
||||
$mapping[$field] = array(
|
||||
'expression' => ':newfieldinitial',
|
||||
'arguments' => array(':newfieldinitial' => $specification['initial']),
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Else use the default of the field.
|
||||
$mapping[$field] = NULL;
|
||||
}
|
||||
|
||||
// Add the new indexes.
|
||||
$new_schema += $keys_new;
|
||||
|
||||
$this->alterTable($table, $old_schema, $new_schema, $mapping);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a table with a new schema containing the old content.
|
||||
*
|
||||
* As SQLite does not support ALTER TABLE (with a few exceptions) it is
|
||||
* necessary to create a new table and copy over the old content.
|
||||
*
|
||||
* @param $table
|
||||
* Name of the table to be altered.
|
||||
* @param $old_schema
|
||||
* The old schema array for the table.
|
||||
* @param $new_schema
|
||||
* The new schema array for the table.
|
||||
* @param $mapping
|
||||
* An optional mapping between the fields of the old specification and the
|
||||
* fields of the new specification. An associative array, whose keys are
|
||||
* the fields of the new table, and values can take two possible forms:
|
||||
* - a simple string, which is interpreted as the name of a field of the
|
||||
* old table,
|
||||
* - an associative array with two keys 'expression' and 'arguments',
|
||||
* that will be used as an expression field.
|
||||
*/
|
||||
protected function alterTable($table, $old_schema, $new_schema, array $mapping = array()) {
|
||||
$i = 0;
|
||||
do {
|
||||
$new_table = $table . '_' . $i++;
|
||||
} while ($this->tableExists($new_table));
|
||||
|
||||
$this->createTable($new_table, $new_schema);
|
||||
|
||||
// Build a SQL query to migrate the data from the old table to the new.
|
||||
$select = $this->connection->select($table);
|
||||
|
||||
// Complete the mapping.
|
||||
$possible_keys = array_keys($new_schema['fields']);
|
||||
$mapping += array_combine($possible_keys, $possible_keys);
|
||||
|
||||
// Now add the fields.
|
||||
foreach ($mapping as $field_alias => $field_source) {
|
||||
// Just ignore this field (ie. use it's default value).
|
||||
if (!isset($field_source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($field_source)) {
|
||||
$select->addExpression($field_source['expression'], $field_alias, $field_source['arguments']);
|
||||
}
|
||||
else {
|
||||
$select->addField($table, $field_source, $field_alias);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the data migration query.
|
||||
$this->connection->insert($new_table)
|
||||
->from($select)
|
||||
->execute();
|
||||
|
||||
$old_count = $this->connection->query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField();
|
||||
$new_count = $this->connection->query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField();
|
||||
if ($old_count == $new_count) {
|
||||
$this->dropTable($table);
|
||||
$this->renameTable($new_table, $table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out the schema of a table.
|
||||
*
|
||||
* This function uses introspection methods provided by the database to
|
||||
* create a schema array. This is useful, for example, during update when
|
||||
* the old schema is not available.
|
||||
*
|
||||
* @param $table
|
||||
* Name of the table.
|
||||
* @return
|
||||
* An array representing the schema, from drupal_get_schema().
|
||||
* @see drupal_get_schema()
|
||||
*/
|
||||
protected function introspectSchema($table) {
|
||||
$mapped_fields = array_flip($this->getFieldTypeMap());
|
||||
$schema = array(
|
||||
'fields' => array(),
|
||||
'primary key' => array(),
|
||||
'unique keys' => array(),
|
||||
'indexes' => array(),
|
||||
);
|
||||
|
||||
$info = $this->getPrefixInfo($table);
|
||||
$result = $this->connection->query('PRAGMA ' . $info['schema'] . '.table_info(' . $info['table'] . ')');
|
||||
foreach ($result as $row) {
|
||||
if (preg_match('/^([^(]+)\((.*)\)$/', $row->type, $matches)) {
|
||||
$type = $matches[1];
|
||||
$length = $matches[2];
|
||||
}
|
||||
else {
|
||||
$type = $row->type;
|
||||
$length = NULL;
|
||||
}
|
||||
if (isset($mapped_fields[$type])) {
|
||||
list($type, $size) = explode(':', $mapped_fields[$type]);
|
||||
$schema['fields'][$row->name] = array(
|
||||
'type' => $type,
|
||||
'size' => $size,
|
||||
'not null' => !empty($row->notnull),
|
||||
'default' => trim($row->dflt_value, "'"),
|
||||
);
|
||||
if ($length) {
|
||||
$schema['fields'][$row->name]['length'] = $length;
|
||||
}
|
||||
if ($row->pk) {
|
||||
$schema['primary key'][] = $row->name;
|
||||
}
|
||||
}
|
||||
else {
|
||||
new Exception("Unable to parse the column type " . $row->type);
|
||||
}
|
||||
}
|
||||
$indexes = array();
|
||||
$result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_list(' . $info['table'] . ')');
|
||||
foreach ($result as $row) {
|
||||
if (strpos($row->name, 'sqlite_autoindex_') !== 0) {
|
||||
$indexes[] = array(
|
||||
'schema_key' => $row->unique ? 'unique keys' : 'indexes',
|
||||
'name' => $row->name,
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach ($indexes as $index) {
|
||||
$name = $index['name'];
|
||||
// Get index name without prefix.
|
||||
$index_name = substr($name, strlen($info['table']) + 1);
|
||||
$result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $name . ')');
|
||||
foreach ($result as $row) {
|
||||
$schema[$index['schema_key']][$index_name][] = $row->name;
|
||||
}
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
|
||||
public function dropField($table, $field) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$old_schema = $this->introspectSchema($table);
|
||||
$new_schema = $old_schema;
|
||||
|
||||
unset($new_schema['fields'][$field]);
|
||||
foreach ($new_schema['indexes'] as $index => $fields) {
|
||||
foreach ($fields as $key => $field_name) {
|
||||
if ($field_name == $field) {
|
||||
unset($new_schema['indexes'][$index][$key]);
|
||||
}
|
||||
}
|
||||
// If this index has no more fields then remove it.
|
||||
if (empty($new_schema['indexes'][$index])) {
|
||||
unset($new_schema['indexes'][$index]);
|
||||
}
|
||||
}
|
||||
$this->alterTable($table, $old_schema, $new_schema);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
|
||||
}
|
||||
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
|
||||
}
|
||||
|
||||
$old_schema = $this->introspectSchema($table);
|
||||
$new_schema = $old_schema;
|
||||
|
||||
// Map the old field to the new field.
|
||||
if ($field != $field_new) {
|
||||
$mapping[$field_new] = $field;
|
||||
}
|
||||
else {
|
||||
$mapping = array();
|
||||
}
|
||||
|
||||
// Remove the previous definition and swap in the new one.
|
||||
unset($new_schema['fields'][$field]);
|
||||
$new_schema['fields'][$field_new] = $spec;
|
||||
|
||||
// Map the former indexes to the new column name.
|
||||
$new_schema['primary key'] = $this->mapKeyDefinition($new_schema['primary key'], $mapping);
|
||||
foreach (array('unique keys', 'indexes') as $k) {
|
||||
foreach ($new_schema[$k] as &$key_definition) {
|
||||
$key_definition = $this->mapKeyDefinition($key_definition, $mapping);
|
||||
}
|
||||
}
|
||||
|
||||
// Add in the keys from $keys_new.
|
||||
if (isset($keys_new['primary key'])) {
|
||||
$new_schema['primary key'] = $keys_new['primary key'];
|
||||
}
|
||||
foreach (array('unique keys', 'indexes') as $k) {
|
||||
if (!empty($keys_new[$k])) {
|
||||
$new_schema[$k] = $keys_new[$k] + $new_schema[$k];
|
||||
}
|
||||
}
|
||||
|
||||
$this->alterTable($table, $old_schema, $new_schema, $mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method: rename columns in an index definition according to a new mapping.
|
||||
*
|
||||
* @param $key_definition
|
||||
* The key definition.
|
||||
* @param $mapping
|
||||
* The new mapping.
|
||||
*/
|
||||
protected function mapKeyDefinition(array $key_definition, array $mapping) {
|
||||
foreach ($key_definition as &$field) {
|
||||
// The key definition can be an array($field, $length).
|
||||
if (is_array($field)) {
|
||||
$field = &$field[0];
|
||||
}
|
||||
if (isset($mapping[$field])) {
|
||||
$field = $mapping[$field];
|
||||
}
|
||||
}
|
||||
return $key_definition;
|
||||
}
|
||||
|
||||
public function addIndex($table, $name, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
if ($this->indexExists($table, $name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
|
||||
$schema['indexes'][$name] = $fields;
|
||||
$statements = $this->createIndexSql($table, $schema);
|
||||
foreach ($statements as $statement) {
|
||||
$this->connection->query($statement);
|
||||
}
|
||||
}
|
||||
|
||||
public function indexExists($table, $name) {
|
||||
$info = $this->getPrefixInfo($table);
|
||||
|
||||
return $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $info['table'] . '_' . $name . ')')->fetchField() != '';
|
||||
}
|
||||
|
||||
public function dropIndex($table, $name) {
|
||||
if (!$this->indexExists($table, $name)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$info = $this->getPrefixInfo($table);
|
||||
|
||||
$this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function addUniqueKey($table, $name, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
if ($this->indexExists($table, $name)) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
|
||||
}
|
||||
|
||||
$schema['unique keys'][$name] = $fields;
|
||||
$statements = $this->createIndexSql($table, $schema);
|
||||
foreach ($statements as $statement) {
|
||||
$this->connection->query($statement);
|
||||
}
|
||||
}
|
||||
|
||||
public function dropUniqueKey($table, $name) {
|
||||
if (!$this->indexExists($table, $name)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$info = $this->getPrefixInfo($table);
|
||||
|
||||
$this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function addPrimaryKey($table, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
|
||||
}
|
||||
|
||||
$old_schema = $this->introspectSchema($table);
|
||||
$new_schema = $old_schema;
|
||||
|
||||
if (!empty($new_schema['primary key'])) {
|
||||
throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
|
||||
}
|
||||
|
||||
$new_schema['primary key'] = $fields;
|
||||
$this->alterTable($table, $old_schema, $new_schema);
|
||||
}
|
||||
|
||||
public function dropPrimaryKey($table) {
|
||||
$old_schema = $this->introspectSchema($table);
|
||||
$new_schema = $old_schema;
|
||||
|
||||
if (empty($new_schema['primary key'])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
unset($new_schema['primary key']);
|
||||
$this->alterTable($table, $old_schema, $new_schema);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function fieldSetDefault($table, $field, $default) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
|
||||
}
|
||||
|
||||
$old_schema = $this->introspectSchema($table);
|
||||
$new_schema = $old_schema;
|
||||
|
||||
$new_schema['fields'][$field]['default'] = $default;
|
||||
$this->alterTable($table, $old_schema, $new_schema);
|
||||
}
|
||||
|
||||
public function fieldSetNoDefault($table, $field) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
|
||||
}
|
||||
|
||||
$old_schema = $this->introspectSchema($table);
|
||||
$new_schema = $old_schema;
|
||||
|
||||
unset($new_schema['fields'][$field]['default']);
|
||||
$this->alterTable($table, $old_schema, $new_schema);
|
||||
}
|
||||
|
||||
public function findTables($table_expression) {
|
||||
// Don't add the prefix, $table_expression already includes the prefix.
|
||||
$info = $this->getPrefixInfo($table_expression, FALSE);
|
||||
|
||||
// Can't use query placeholders for the schema because the query would have
|
||||
// to be :prefixsqlite_master, which does not work.
|
||||
$result = db_query("SELECT name FROM " . $info['schema'] . ".sqlite_master WHERE type = :type AND name LIKE :table_name", array(
|
||||
':type' => 'table',
|
||||
':table_name' => $info['table'],
|
||||
));
|
||||
return $result->fetchAllKeyed(0, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Select builder for SQLite embedded database engine.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* SQLite specific query builder for SELECT statements.
|
||||
*/
|
||||
class SelectQuery_sqlite extends SelectQuery {
|
||||
public function forUpdate($set = TRUE) {
|
||||
// SQLite does not support FOR UPDATE so nothing to do.
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Initializes the list of date formats and their locales.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a default system list of date formats for system_date_formats().
|
||||
*/
|
||||
function system_default_date_formats() {
|
||||
$formats = array();
|
||||
|
||||
// Short date formats.
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'm/d/Y - H:i',
|
||||
'locales' => array('en-us'),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'd/m/Y - H:i',
|
||||
'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'Y/m/d - H:i',
|
||||
'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'd.m.Y - H:i',
|
||||
'locales' => array('de-ch', 'de-de', 'de-lu', 'fi-fi', 'fr-ch', 'is-is', 'pl-pl', 'ro-ro', 'ru-ru'),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'Y-m-d H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'm/d/Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'd/m/Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'Y/m/d - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'M j Y - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'j M Y - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'Y M j - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'M j Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'j M Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'short',
|
||||
'format' => 'Y M j - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
|
||||
// Medium date formats.
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'D, m/d/Y - H:i',
|
||||
'locales' => array('en-us'),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'D, d/m/Y - H:i',
|
||||
'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'D, Y/m/d - H:i',
|
||||
'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'D, Y-m-d H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'F j, Y - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'j F, Y - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'Y, F j - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'D, m/d/Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'D, d/m/Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'D, Y/m/d - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'F j, Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'j F Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'Y, F j - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'medium',
|
||||
'format' => 'j. F Y - G:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
|
||||
// Long date formats.
|
||||
$formats[] = array(
|
||||
'type' => 'long',
|
||||
'format' => 'l, F j, Y - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'long',
|
||||
'format' => 'l, j F, Y - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'long',
|
||||
'format' => 'l, Y, F j - H:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'long',
|
||||
'format' => 'l, F j, Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'long',
|
||||
'format' => 'l, j F Y - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'long',
|
||||
'format' => 'l, Y, F j - g:ia',
|
||||
'locales' => array(),
|
||||
);
|
||||
$formats[] = array(
|
||||
'type' => 'long',
|
||||
'format' => 'l, j. F Y - G:i',
|
||||
'locales' => array(),
|
||||
);
|
||||
|
||||
return $formats;
|
||||
}
|
||||
+1436
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions for error handling.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maps PHP error constants to watchdog severity levels.
|
||||
*
|
||||
* The error constants are documented at
|
||||
* http://php.net/manual/errorfunc.constants.php
|
||||
*
|
||||
* @ingroup logging_severity_levels
|
||||
*/
|
||||
function drupal_error_levels() {
|
||||
$types = array(
|
||||
E_ERROR => array('Error', WATCHDOG_ERROR),
|
||||
E_WARNING => array('Warning', WATCHDOG_WARNING),
|
||||
E_PARSE => array('Parse error', WATCHDOG_ERROR),
|
||||
E_NOTICE => array('Notice', WATCHDOG_NOTICE),
|
||||
E_CORE_ERROR => array('Core error', WATCHDOG_ERROR),
|
||||
E_CORE_WARNING => array('Core warning', WATCHDOG_WARNING),
|
||||
E_COMPILE_ERROR => array('Compile error', WATCHDOG_ERROR),
|
||||
E_COMPILE_WARNING => array('Compile warning', WATCHDOG_WARNING),
|
||||
E_USER_ERROR => array('User error', WATCHDOG_ERROR),
|
||||
E_USER_WARNING => array('User warning', WATCHDOG_WARNING),
|
||||
E_USER_NOTICE => array('User notice', WATCHDOG_NOTICE),
|
||||
E_STRICT => array('Strict warning', WATCHDOG_DEBUG),
|
||||
E_RECOVERABLE_ERROR => array('Recoverable fatal error', WATCHDOG_ERROR),
|
||||
);
|
||||
// E_DEPRECATED and E_USER_DEPRECATED were added in PHP 5.3.0.
|
||||
if (defined('E_DEPRECATED')) {
|
||||
$types[E_DEPRECATED] = array('Deprecated function', WATCHDOG_DEBUG);
|
||||
$types[E_USER_DEPRECATED] = array('User deprecated function', WATCHDOG_DEBUG);
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides custom PHP error handling.
|
||||
*
|
||||
* @param $error_level
|
||||
* The level of the error raised.
|
||||
* @param $message
|
||||
* The error message.
|
||||
* @param $filename
|
||||
* The filename that the error was raised in.
|
||||
* @param $line
|
||||
* The line number the error was raised at.
|
||||
* @param $context
|
||||
* An array that points to the active symbol table at the point the error
|
||||
* occurred.
|
||||
*/
|
||||
function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) {
|
||||
if ($error_level & error_reporting()) {
|
||||
$types = drupal_error_levels();
|
||||
list($severity_msg, $severity_level) = $types[$error_level];
|
||||
$caller = _drupal_get_last_caller(debug_backtrace());
|
||||
|
||||
if (!function_exists('filter_xss_admin')) {
|
||||
require_once DRUPAL_ROOT . '/includes/common.inc';
|
||||
}
|
||||
|
||||
// We treat recoverable errors as fatal.
|
||||
_drupal_log_error(array(
|
||||
'%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
|
||||
// The standard PHP error handler considers that the error messages
|
||||
// are HTML. We mimic this behavior here.
|
||||
'!message' => filter_xss_admin($message),
|
||||
'%function' => $caller['function'],
|
||||
'%file' => $caller['file'],
|
||||
'%line' => $caller['line'],
|
||||
'severity_level' => $severity_level,
|
||||
), $error_level == E_RECOVERABLE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an exception and retrieves the correct caller.
|
||||
*
|
||||
* @param $exception
|
||||
* The exception object that was thrown.
|
||||
*
|
||||
* @return
|
||||
* An error in the format expected by _drupal_log_error().
|
||||
*/
|
||||
function _drupal_decode_exception($exception) {
|
||||
$message = $exception->getMessage();
|
||||
|
||||
$backtrace = $exception->getTrace();
|
||||
// Add the line throwing the exception to the backtrace.
|
||||
array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
|
||||
|
||||
// For PDOException errors, we try to return the initial caller,
|
||||
// skipping internal functions of the database layer.
|
||||
if ($exception instanceof PDOException) {
|
||||
// The first element in the stack is the call, the second element gives us the caller.
|
||||
// We skip calls that occurred in one of the classes of the database layer
|
||||
// or in one of its global functions.
|
||||
$db_functions = array('db_query', 'db_query_range');
|
||||
while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
|
||||
((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
|
||||
in_array($caller['function'], $db_functions))) {
|
||||
// We remove that call.
|
||||
array_shift($backtrace);
|
||||
}
|
||||
if (isset($exception->query_string, $exception->args)) {
|
||||
$message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
|
||||
}
|
||||
}
|
||||
$caller = _drupal_get_last_caller($backtrace);
|
||||
|
||||
return array(
|
||||
'%type' => get_class($exception),
|
||||
// The standard PHP exception handler considers that the exception message
|
||||
// is plain-text. We mimic this behavior here.
|
||||
'!message' => check_plain($message),
|
||||
'%function' => $caller['function'],
|
||||
'%file' => $caller['file'],
|
||||
'%line' => $caller['line'],
|
||||
'severity_level' => WATCHDOG_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an exception error message without further exceptions.
|
||||
*
|
||||
* @param $exception
|
||||
* The exception object that was thrown.
|
||||
* @return
|
||||
* An error message.
|
||||
*/
|
||||
function _drupal_render_exception_safe($exception) {
|
||||
return check_plain(strtr('%type: !message in %function (line %line of %file).', _drupal_decode_exception($exception)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an error should be displayed.
|
||||
*
|
||||
* When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL,
|
||||
* all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error
|
||||
* will be examined to determine if it should be displayed.
|
||||
*
|
||||
* @param $error
|
||||
* Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
|
||||
*
|
||||
* @return
|
||||
* TRUE if an error should be displayed.
|
||||
*/
|
||||
function error_displayable($error = NULL) {
|
||||
$error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL);
|
||||
$updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update');
|
||||
$all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL);
|
||||
$error_needs_display = ($error_level == ERROR_REPORTING_DISPLAY_SOME &&
|
||||
isset($error) && $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning');
|
||||
|
||||
return ($updating || $all_errors_displayed || $error_needs_display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a PHP error or exception and displays an error page in fatal cases.
|
||||
*
|
||||
* @param $error
|
||||
* An array with the following keys: %type, !message, %function, %file, %line
|
||||
* and severity_level. All the parameters are plain-text, with the exception
|
||||
* of !message, which needs to be a safe HTML string.
|
||||
* @param $fatal
|
||||
* TRUE if the error is fatal.
|
||||
*/
|
||||
function _drupal_log_error($error, $fatal = FALSE) {
|
||||
// Initialize a maintenance theme if the bootstrap was not complete.
|
||||
// Do it early because drupal_set_message() triggers a drupal_theme_initialize().
|
||||
if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
|
||||
unset($GLOBALS['theme']);
|
||||
if (!defined('MAINTENANCE_MODE')) {
|
||||
define('MAINTENANCE_MODE', 'error');
|
||||
}
|
||||
drupal_maintenance_theme();
|
||||
}
|
||||
|
||||
// When running inside the testing framework, we relay the errors
|
||||
// to the tested site by the way of HTTP headers.
|
||||
$test_info = &$GLOBALS['drupal_test_info'];
|
||||
if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
|
||||
// $number does not use drupal_static as it should not be reset
|
||||
// as it uniquely identifies each PHP error.
|
||||
static $number = 0;
|
||||
$assertion = array(
|
||||
$error['!message'],
|
||||
$error['%type'],
|
||||
array(
|
||||
'function' => $error['%function'],
|
||||
'file' => $error['%file'],
|
||||
'line' => $error['%line'],
|
||||
),
|
||||
);
|
||||
header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
|
||||
$number++;
|
||||
}
|
||||
|
||||
// Log the error immediately, unless this is a non-fatal error which has been
|
||||
// triggered via drupal_trigger_error_with_delayed_logging(); in that case
|
||||
// trigger it in a shutdown function. Fatal errors are always triggered
|
||||
// immediately since for a fatal error the page request will end here anyway.
|
||||
if (!$fatal && drupal_static('_drupal_trigger_error_with_delayed_logging')) {
|
||||
drupal_register_shutdown_function('watchdog', 'php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
|
||||
}
|
||||
else {
|
||||
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
|
||||
}
|
||||
|
||||
if ($fatal) {
|
||||
drupal_add_http_header('Status', '500 Service unavailable (with message)');
|
||||
}
|
||||
|
||||
if (drupal_is_cli()) {
|
||||
if ($fatal) {
|
||||
// When called from CLI, simply output a plain text message.
|
||||
print html_entity_decode(strip_tags(t('%type: !message in %function (line %line of %file).', $error))). "\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
|
||||
if ($fatal) {
|
||||
if (error_displayable($error)) {
|
||||
// When called from JavaScript, simply output the error message.
|
||||
print t('%type: !message in %function (line %line of %file).', $error);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Display the message if the current error reporting level allows this type
|
||||
// of message to be displayed, and unconditionally in update.php.
|
||||
if (error_displayable($error)) {
|
||||
$class = 'error';
|
||||
|
||||
// If error type is 'User notice' then treat it as debug information
|
||||
// instead of an error message, see dd().
|
||||
if ($error['%type'] == 'User notice') {
|
||||
$error['%type'] = 'Debug';
|
||||
$class = 'status';
|
||||
}
|
||||
|
||||
drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class);
|
||||
}
|
||||
|
||||
if ($fatal) {
|
||||
drupal_set_title(t('Error'));
|
||||
// We fallback to a maintenance page at this point, because the page generation
|
||||
// itself can generate errors.
|
||||
print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.')));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last caller from a backtrace.
|
||||
*
|
||||
* @param $backtrace
|
||||
* A standard PHP backtrace.
|
||||
*
|
||||
* @return
|
||||
* An associative array with keys 'file', 'line' and 'function'.
|
||||
*/
|
||||
function _drupal_get_last_caller($backtrace) {
|
||||
// Errors that occur inside PHP internal functions do not generate
|
||||
// information about file and line. Ignore black listed functions.
|
||||
$blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler');
|
||||
while (($backtrace && !isset($backtrace[0]['line'])) ||
|
||||
(isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) {
|
||||
array_shift($backtrace);
|
||||
}
|
||||
|
||||
// The first trace is the call itself.
|
||||
// It gives us the line and the file of the last call.
|
||||
$call = $backtrace[0];
|
||||
|
||||
// The second call give us the function where the call originated.
|
||||
if (isset($backtrace[1])) {
|
||||
if (isset($backtrace[1]['class'])) {
|
||||
$call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
|
||||
}
|
||||
else {
|
||||
$call['function'] = $backtrace[1]['function'] . '()';
|
||||
}
|
||||
}
|
||||
else {
|
||||
$call['function'] = 'main()';
|
||||
}
|
||||
return $call;
|
||||
}
|
||||
+2639
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,879 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides mimetype mappings.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return an array of MIME extension mappings.
|
||||
*
|
||||
* Returns the mapping after modules have altered the default mapping.
|
||||
*
|
||||
* @return
|
||||
* Array of mimetypes correlated to the extensions that relate to them.
|
||||
*
|
||||
* @see file_get_mimetype()
|
||||
*/
|
||||
function file_mimetype_mapping() {
|
||||
$mapping = &drupal_static(__FUNCTION__);
|
||||
if (!isset($mapping)) {
|
||||
$mapping = file_default_mimetype_mapping();
|
||||
// Allow modules to alter the default mapping.
|
||||
drupal_alter('file_mimetype_mapping', $mapping);
|
||||
}
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default MIME extension mapping.
|
||||
*
|
||||
* @return
|
||||
* Array of mimetypes correlated to the extensions that relate to them.
|
||||
*
|
||||
* @see file_get_mimetype()
|
||||
*/
|
||||
function file_default_mimetype_mapping() {
|
||||
return array(
|
||||
'mimetypes' => array(
|
||||
0 => 'application/andrew-inset',
|
||||
1 => 'application/atom',
|
||||
2 => 'application/atomcat+xml',
|
||||
3 => 'application/atomserv+xml',
|
||||
4 => 'application/cap',
|
||||
5 => 'application/cu-seeme',
|
||||
6 => 'application/dsptype',
|
||||
350 => 'application/epub+zip',
|
||||
7 => 'application/hta',
|
||||
8 => 'application/java-archive',
|
||||
9 => 'application/java-serialized-object',
|
||||
10 => 'application/java-vm',
|
||||
11 => 'application/mac-binhex40',
|
||||
12 => 'application/mathematica',
|
||||
13 => 'application/msaccess',
|
||||
14 => 'application/msword',
|
||||
15 => 'application/octet-stream',
|
||||
16 => 'application/oda',
|
||||
17 => 'application/ogg',
|
||||
18 => 'application/pdf',
|
||||
19 => 'application/pgp-keys',
|
||||
20 => 'application/pgp-signature',
|
||||
21 => 'application/pics-rules',
|
||||
22 => 'application/postscript',
|
||||
23 => 'application/rar',
|
||||
24 => 'application/rdf+xml',
|
||||
25 => 'application/rss+xml',
|
||||
26 => 'application/rtf',
|
||||
27 => 'application/smil',
|
||||
349 => 'application/vnd.amazon.ebook',
|
||||
28 => 'application/vnd.cinderella',
|
||||
29 => 'application/vnd.google-earth.kml+xml',
|
||||
30 => 'application/vnd.google-earth.kmz',
|
||||
31 => 'application/vnd.mozilla.xul+xml',
|
||||
32 => 'application/vnd.ms-excel',
|
||||
33 => 'application/vnd.ms-excel.addin.macroEnabled.12',
|
||||
34 => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||||
35 => 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||
36 => 'application/vnd.ms-excel.template.macroEnabled.12',
|
||||
37 => 'application/vnd.ms-pki.seccat',
|
||||
38 => 'application/vnd.ms-pki.stl',
|
||||
39 => 'application/vnd.ms-powerpoint',
|
||||
40 => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
|
||||
41 => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
|
||||
42 => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
|
||||
43 => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
|
||||
44 => 'application/vnd.ms-word.document.macroEnabled.12',
|
||||
45 => 'application/vnd.ms-word.template.macroEnabled.12',
|
||||
46 => 'application/vnd.ms-xpsdocument',
|
||||
47 => 'application/vnd.oasis.opendocument.chart',
|
||||
48 => 'application/vnd.oasis.opendocument.database',
|
||||
49 => 'application/vnd.oasis.opendocument.formula',
|
||||
50 => 'application/vnd.oasis.opendocument.graphics',
|
||||
51 => 'application/vnd.oasis.opendocument.graphics-template',
|
||||
52 => 'application/vnd.oasis.opendocument.image',
|
||||
53 => 'application/vnd.oasis.opendocument.presentation',
|
||||
54 => 'application/vnd.oasis.opendocument.presentation-template',
|
||||
55 => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
56 => 'application/vnd.oasis.opendocument.spreadsheet-template',
|
||||
57 => 'application/vnd.oasis.opendocument.text',
|
||||
58 => 'application/vnd.oasis.opendocument.text-master',
|
||||
59 => 'application/vnd.oasis.opendocument.text-template',
|
||||
60 => 'application/vnd.oasis.opendocument.text-web',
|
||||
61 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
62 => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
63 => 'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
64 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
65 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
66 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
67 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
68 => 'application/vnd.rim.cod',
|
||||
69 => 'application/vnd.smaf',
|
||||
70 => 'application/vnd.stardivision.calc',
|
||||
71 => 'application/vnd.stardivision.chart',
|
||||
72 => 'application/vnd.stardivision.draw',
|
||||
73 => 'application/vnd.stardivision.impress',
|
||||
74 => 'application/vnd.stardivision.math',
|
||||
75 => 'application/vnd.stardivision.writer',
|
||||
76 => 'application/vnd.stardivision.writer-global',
|
||||
77 => 'application/vnd.sun.xml.calc',
|
||||
78 => 'application/vnd.sun.xml.calc.template',
|
||||
79 => 'application/vnd.sun.xml.draw',
|
||||
80 => 'application/vnd.sun.xml.draw.template',
|
||||
81 => 'application/vnd.sun.xml.impress',
|
||||
82 => 'application/vnd.sun.xml.impress.template',
|
||||
83 => 'application/vnd.sun.xml.math',
|
||||
84 => 'application/vnd.sun.xml.writer',
|
||||
85 => 'application/vnd.sun.xml.writer.global',
|
||||
86 => 'application/vnd.sun.xml.writer.template',
|
||||
87 => 'application/vnd.symbian.install',
|
||||
88 => 'application/vnd.visio',
|
||||
89 => 'application/vnd.wap.wbxml',
|
||||
90 => 'application/vnd.wap.wmlc',
|
||||
91 => 'application/vnd.wap.wmlscriptc',
|
||||
92 => 'application/wordperfect',
|
||||
93 => 'application/wordperfect5.1',
|
||||
94 => 'application/x-123',
|
||||
95 => 'application/x-7z-compressed',
|
||||
96 => 'application/x-abiword',
|
||||
97 => 'application/x-apple-diskimage',
|
||||
98 => 'application/x-bcpio',
|
||||
99 => 'application/x-bittorrent',
|
||||
100 => 'application/x-cab',
|
||||
101 => 'application/x-cbr',
|
||||
102 => 'application/x-cbz',
|
||||
103 => 'application/x-cdf',
|
||||
104 => 'application/x-cdlink',
|
||||
105 => 'application/x-chess-pgn',
|
||||
106 => 'application/x-cpio',
|
||||
107 => 'application/x-debian-package',
|
||||
108 => 'application/x-director',
|
||||
109 => 'application/x-dms',
|
||||
110 => 'application/x-doom',
|
||||
111 => 'application/x-dvi',
|
||||
112 => 'application/x-flac',
|
||||
113 => 'application/x-font',
|
||||
114 => 'application/x-freemind',
|
||||
115 => 'application/x-futuresplash',
|
||||
116 => 'application/x-gnumeric',
|
||||
117 => 'application/x-go-sgf',
|
||||
118 => 'application/x-graphing-calculator',
|
||||
119 => 'application/x-gtar',
|
||||
120 => 'application/x-hdf',
|
||||
121 => 'application/x-httpd-eruby',
|
||||
122 => 'application/x-httpd-php',
|
||||
123 => 'application/x-httpd-php-source',
|
||||
124 => 'application/x-httpd-php3',
|
||||
125 => 'application/x-httpd-php3-preprocessed',
|
||||
126 => 'application/x-httpd-php4',
|
||||
127 => 'application/x-ica',
|
||||
128 => 'application/x-internet-signup',
|
||||
129 => 'application/x-iphone',
|
||||
130 => 'application/x-iso9660-image',
|
||||
131 => 'application/x-java-jnlp-file',
|
||||
132 => 'application/x-javascript',
|
||||
133 => 'application/x-jmol',
|
||||
134 => 'application/x-kchart',
|
||||
135 => 'application/x-killustrator',
|
||||
136 => 'application/x-koan',
|
||||
137 => 'application/x-kpresenter',
|
||||
138 => 'application/x-kspread',
|
||||
139 => 'application/x-kword',
|
||||
140 => 'application/x-latex',
|
||||
141 => 'application/x-lha',
|
||||
142 => 'application/x-lyx',
|
||||
143 => 'application/x-lzh',
|
||||
144 => 'application/x-lzx',
|
||||
145 => 'application/x-maker',
|
||||
146 => 'application/x-mif',
|
||||
351 => 'application/x-mobipocket-ebook',
|
||||
352 => 'application/x-mobipocket-ebook',
|
||||
147 => 'application/x-ms-wmd',
|
||||
148 => 'application/x-ms-wmz',
|
||||
149 => 'application/x-msdos-program',
|
||||
150 => 'application/x-msi',
|
||||
151 => 'application/x-netcdf',
|
||||
152 => 'application/x-ns-proxy-autoconfig',
|
||||
153 => 'application/x-nwc',
|
||||
154 => 'application/x-object',
|
||||
155 => 'application/x-oz-application',
|
||||
156 => 'application/x-pkcs7-certreqresp',
|
||||
157 => 'application/x-pkcs7-crl',
|
||||
158 => 'application/x-python-code',
|
||||
159 => 'application/x-quicktimeplayer',
|
||||
160 => 'application/x-redhat-package-manager',
|
||||
161 => 'application/x-shar',
|
||||
162 => 'application/x-shockwave-flash',
|
||||
163 => 'application/x-stuffit',
|
||||
164 => 'application/x-sv4cpio',
|
||||
165 => 'application/x-sv4crc',
|
||||
166 => 'application/x-tar',
|
||||
167 => 'application/x-tcl',
|
||||
168 => 'application/x-tex-gf',
|
||||
169 => 'application/x-tex-pk',
|
||||
170 => 'application/x-texinfo',
|
||||
171 => 'application/x-trash',
|
||||
172 => 'application/x-troff',
|
||||
173 => 'application/x-troff-man',
|
||||
174 => 'application/x-troff-me',
|
||||
175 => 'application/x-troff-ms',
|
||||
176 => 'application/x-ustar',
|
||||
177 => 'application/x-wais-source',
|
||||
178 => 'application/x-wingz',
|
||||
179 => 'application/x-x509-ca-cert',
|
||||
180 => 'application/x-xcf',
|
||||
181 => 'application/x-xfig',
|
||||
182 => 'application/x-xpinstall',
|
||||
183 => 'application/xhtml+xml',
|
||||
184 => 'application/xml',
|
||||
185 => 'application/zip',
|
||||
186 => 'audio/basic',
|
||||
187 => 'audio/midi',
|
||||
346 => 'audio/mp4',
|
||||
188 => 'audio/mpeg',
|
||||
189 => 'audio/ogg',
|
||||
190 => 'audio/prs.sid',
|
||||
356 => 'audio/webm',
|
||||
191 => 'audio/x-aiff',
|
||||
192 => 'audio/x-gsm',
|
||||
354 => 'audio/x-matroska',
|
||||
193 => 'audio/x-mpegurl',
|
||||
194 => 'audio/x-ms-wax',
|
||||
195 => 'audio/x-ms-wma',
|
||||
196 => 'audio/x-pn-realaudio',
|
||||
197 => 'audio/x-realaudio',
|
||||
198 => 'audio/x-scpls',
|
||||
199 => 'audio/x-sd2',
|
||||
200 => 'audio/x-wav',
|
||||
201 => 'chemical/x-alchemy',
|
||||
202 => 'chemical/x-cache',
|
||||
203 => 'chemical/x-cache-csf',
|
||||
204 => 'chemical/x-cactvs-binary',
|
||||
205 => 'chemical/x-cdx',
|
||||
206 => 'chemical/x-cerius',
|
||||
207 => 'chemical/x-chem3d',
|
||||
208 => 'chemical/x-chemdraw',
|
||||
209 => 'chemical/x-cif',
|
||||
210 => 'chemical/x-cmdf',
|
||||
211 => 'chemical/x-cml',
|
||||
212 => 'chemical/x-compass',
|
||||
213 => 'chemical/x-crossfire',
|
||||
214 => 'chemical/x-csml',
|
||||
215 => 'chemical/x-ctx',
|
||||
216 => 'chemical/x-cxf',
|
||||
217 => 'chemical/x-embl-dl-nucleotide',
|
||||
218 => 'chemical/x-galactic-spc',
|
||||
219 => 'chemical/x-gamess-input',
|
||||
220 => 'chemical/x-gaussian-checkpoint',
|
||||
221 => 'chemical/x-gaussian-cube',
|
||||
222 => 'chemical/x-gaussian-input',
|
||||
223 => 'chemical/x-gaussian-log',
|
||||
224 => 'chemical/x-gcg8-sequence',
|
||||
225 => 'chemical/x-genbank',
|
||||
226 => 'chemical/x-hin',
|
||||
227 => 'chemical/x-isostar',
|
||||
228 => 'chemical/x-jcamp-dx',
|
||||
229 => 'chemical/x-kinemage',
|
||||
230 => 'chemical/x-macmolecule',
|
||||
231 => 'chemical/x-macromodel-input',
|
||||
232 => 'chemical/x-mdl-molfile',
|
||||
233 => 'chemical/x-mdl-rdfile',
|
||||
234 => 'chemical/x-mdl-rxnfile',
|
||||
235 => 'chemical/x-mdl-sdfile',
|
||||
236 => 'chemical/x-mdl-tgf',
|
||||
237 => 'chemical/x-mmcif',
|
||||
238 => 'chemical/x-mol2',
|
||||
239 => 'chemical/x-molconn-Z',
|
||||
240 => 'chemical/x-mopac-graph',
|
||||
241 => 'chemical/x-mopac-input',
|
||||
242 => 'chemical/x-mopac-out',
|
||||
243 => 'chemical/x-mopac-vib',
|
||||
244 => 'chemical/x-ncbi-asn1-ascii',
|
||||
245 => 'chemical/x-ncbi-asn1-binary',
|
||||
246 => 'chemical/x-ncbi-asn1-spec',
|
||||
247 => 'chemical/x-pdb',
|
||||
248 => 'chemical/x-rosdal',
|
||||
249 => 'chemical/x-swissprot',
|
||||
250 => 'chemical/x-vamas-iso14976',
|
||||
251 => 'chemical/x-vmd',
|
||||
252 => 'chemical/x-xtel',
|
||||
253 => 'chemical/x-xyz',
|
||||
254 => 'image/gif',
|
||||
255 => 'image/ief',
|
||||
256 => 'image/jpeg',
|
||||
257 => 'image/pcx',
|
||||
258 => 'image/png',
|
||||
259 => 'image/svg+xml',
|
||||
260 => 'image/tiff',
|
||||
261 => 'image/vnd.djvu',
|
||||
262 => 'image/vnd.microsoft.icon',
|
||||
263 => 'image/vnd.wap.wbmp',
|
||||
355 => 'image/webp',
|
||||
264 => 'image/x-cmu-raster',
|
||||
265 => 'image/x-coreldraw',
|
||||
266 => 'image/x-coreldrawpattern',
|
||||
267 => 'image/x-coreldrawtemplate',
|
||||
268 => 'image/x-corelphotopaint',
|
||||
269 => 'image/x-jg',
|
||||
270 => 'image/x-jng',
|
||||
271 => 'image/x-ms-bmp',
|
||||
272 => 'image/x-photoshop',
|
||||
273 => 'image/x-portable-anymap',
|
||||
274 => 'image/x-portable-bitmap',
|
||||
275 => 'image/x-portable-graymap',
|
||||
276 => 'image/x-portable-pixmap',
|
||||
277 => 'image/x-rgb',
|
||||
278 => 'image/x-xbitmap',
|
||||
279 => 'image/x-xpixmap',
|
||||
280 => 'image/x-xwindowdump',
|
||||
281 => 'message/rfc822',
|
||||
282 => 'model/iges',
|
||||
283 => 'model/mesh',
|
||||
284 => 'model/vrml',
|
||||
285 => 'text/calendar',
|
||||
286 => 'text/css',
|
||||
287 => 'text/csv',
|
||||
288 => 'text/h323',
|
||||
289 => 'text/html',
|
||||
290 => 'text/iuls',
|
||||
291 => 'text/mathml',
|
||||
292 => 'text/plain',
|
||||
293 => 'text/richtext',
|
||||
294 => 'text/scriptlet',
|
||||
295 => 'text/tab-separated-values',
|
||||
296 => 'text/texmacs',
|
||||
297 => 'text/vnd.sun.j2me.app-descriptor',
|
||||
298 => 'text/vnd.wap.wml',
|
||||
299 => 'text/vnd.wap.wmlscript',
|
||||
358 => 'text/vtt',
|
||||
300 => 'text/x-bibtex',
|
||||
301 => 'text/x-boo',
|
||||
302 => 'text/x-c++hdr',
|
||||
303 => 'text/x-c++src',
|
||||
304 => 'text/x-chdr',
|
||||
305 => 'text/x-component',
|
||||
306 => 'text/x-csh',
|
||||
307 => 'text/x-csrc',
|
||||
308 => 'text/x-diff',
|
||||
309 => 'text/x-dsrc',
|
||||
310 => 'text/x-haskell',
|
||||
311 => 'text/x-java',
|
||||
312 => 'text/x-literate-haskell',
|
||||
313 => 'text/x-moc',
|
||||
314 => 'text/x-pascal',
|
||||
315 => 'text/x-pcs-gcd',
|
||||
316 => 'text/x-perl',
|
||||
317 => 'text/x-python',
|
||||
318 => 'text/x-setext',
|
||||
319 => 'text/x-sh',
|
||||
320 => 'text/x-tcl',
|
||||
321 => 'text/x-tex',
|
||||
322 => 'text/x-vcalendar',
|
||||
323 => 'text/x-vcard',
|
||||
324 => 'video/3gpp',
|
||||
325 => 'video/dl',
|
||||
326 => 'video/dv',
|
||||
327 => 'video/fli',
|
||||
328 => 'video/gl',
|
||||
329 => 'video/mp4',
|
||||
330 => 'video/mpeg',
|
||||
331 => 'video/ogg',
|
||||
332 => 'video/quicktime',
|
||||
333 => 'video/vnd.mpegurl',
|
||||
357 => 'video/webm',
|
||||
347 => 'video/x-flv',
|
||||
334 => 'video/x-la-asf',
|
||||
348 => 'video/x-m4v',
|
||||
353 => 'video/x-matroska',
|
||||
335 => 'video/x-mng',
|
||||
336 => 'video/x-ms-asf',
|
||||
337 => 'video/x-ms-wm',
|
||||
338 => 'video/x-ms-wmv',
|
||||
339 => 'video/x-ms-wmx',
|
||||
340 => 'video/x-ms-wvx',
|
||||
341 => 'video/x-msvideo',
|
||||
342 => 'video/x-sgi-movie',
|
||||
343 => 'x-conference/x-cooltalk',
|
||||
344 => 'x-epoc/x-sisx-app',
|
||||
345 => 'x-world/x-vrml',
|
||||
),
|
||||
|
||||
// Extensions added to this list MUST be lower-case.
|
||||
'extensions' => array(
|
||||
'ez' => 0,
|
||||
'atom' => 1,
|
||||
'atomcat' => 2,
|
||||
'atomsrv' => 3,
|
||||
'cap' => 4,
|
||||
'pcap' => 4,
|
||||
'cu' => 5,
|
||||
'tsp' => 6,
|
||||
'hta' => 7,
|
||||
'jar' => 8,
|
||||
'ser' => 9,
|
||||
'class' => 10,
|
||||
'hqx' => 11,
|
||||
'nb' => 12,
|
||||
'mdb' => 13,
|
||||
'dot' => 14,
|
||||
'doc' => 14,
|
||||
'bin' => 15,
|
||||
'oda' => 16,
|
||||
'ogx' => 17,
|
||||
'pdf' => 18,
|
||||
'key' => 19,
|
||||
'pgp' => 20,
|
||||
'prf' => 21,
|
||||
'eps' => 22,
|
||||
'ai' => 22,
|
||||
'ps' => 22,
|
||||
'rar' => 23,
|
||||
'rdf' => 24,
|
||||
'rss' => 25,
|
||||
'rtf' => 26,
|
||||
'smi' => 27,
|
||||
'smil' => 27,
|
||||
'cdy' => 28,
|
||||
'kml' => 29,
|
||||
'kmz' => 30,
|
||||
'xul' => 31,
|
||||
'xlb' => 32,
|
||||
'xlt' => 32,
|
||||
'xls' => 32,
|
||||
'xlam' => 33,
|
||||
'xlsb' => 34,
|
||||
'xlsm' => 35,
|
||||
'xltm' => 36,
|
||||
'cat' => 37,
|
||||
'stl' => 38,
|
||||
'pps' => 39,
|
||||
'ppt' => 39,
|
||||
'ppam' => 40,
|
||||
'pptm' => 41,
|
||||
'ppsm' => 42,
|
||||
'potm' => 43,
|
||||
'docm' => 44,
|
||||
'dotm' => 45,
|
||||
'xps' => 46,
|
||||
'odc' => 47,
|
||||
'odb' => 48,
|
||||
'odf' => 49,
|
||||
'odg' => 50,
|
||||
'otg' => 51,
|
||||
'odi' => 52,
|
||||
'odp' => 53,
|
||||
'otp' => 54,
|
||||
'ods' => 55,
|
||||
'ots' => 56,
|
||||
'odt' => 57,
|
||||
'odm' => 58,
|
||||
'ott' => 59,
|
||||
'oth' => 60,
|
||||
'pptx' => 61,
|
||||
'ppsx' => 62,
|
||||
'potx' => 63,
|
||||
'xlsx' => 64,
|
||||
'xltx' => 65,
|
||||
'docx' => 66,
|
||||
'dotx' => 67,
|
||||
'cod' => 68,
|
||||
'mmf' => 69,
|
||||
'sdc' => 70,
|
||||
'sds' => 71,
|
||||
'sda' => 72,
|
||||
'sdd' => 73,
|
||||
'sdw' => 75,
|
||||
'sgl' => 76,
|
||||
'sxc' => 77,
|
||||
'stc' => 78,
|
||||
'sxd' => 79,
|
||||
'std' => 80,
|
||||
'sxi' => 81,
|
||||
'sti' => 82,
|
||||
'sxm' => 83,
|
||||
'sxw' => 84,
|
||||
'sxg' => 85,
|
||||
'stw' => 86,
|
||||
'sis' => 87,
|
||||
'vsd' => 88,
|
||||
'wbxml' => 89,
|
||||
'wmlc' => 90,
|
||||
'wmlsc' => 91,
|
||||
'wpd' => 92,
|
||||
'wp5' => 93,
|
||||
'wk' => 94,
|
||||
'7z' => 95,
|
||||
'abw' => 96,
|
||||
'dmg' => 97,
|
||||
'bcpio' => 98,
|
||||
'torrent' => 99,
|
||||
'cab' => 100,
|
||||
'cbr' => 101,
|
||||
'cbz' => 102,
|
||||
'cdf' => 103,
|
||||
'vcd' => 104,
|
||||
'pgn' => 105,
|
||||
'cpio' => 106,
|
||||
'udeb' => 107,
|
||||
'deb' => 107,
|
||||
'dir' => 108,
|
||||
'dxr' => 108,
|
||||
'dcr' => 108,
|
||||
'dms' => 109,
|
||||
'wad' => 110,
|
||||
'dvi' => 111,
|
||||
'flac' => 112,
|
||||
'pfa' => 113,
|
||||
'pfb' => 113,
|
||||
'pcf' => 113,
|
||||
'gsf' => 113,
|
||||
'pcf.z' => 113,
|
||||
'mm' => 114,
|
||||
'spl' => 115,
|
||||
'gnumeric' => 116,
|
||||
'sgf' => 117,
|
||||
'gcf' => 118,
|
||||
'taz' => 119,
|
||||
'gtar' => 119,
|
||||
'tgz' => 119,
|
||||
'hdf' => 120,
|
||||
'rhtml' => 121,
|
||||
'phtml' => 122,
|
||||
'pht' => 122,
|
||||
'php' => 122,
|
||||
'phps' => 123,
|
||||
'php3' => 124,
|
||||
'php3p' => 125,
|
||||
'php4' => 126,
|
||||
'ica' => 127,
|
||||
'ins' => 128,
|
||||
'isp' => 128,
|
||||
'iii' => 129,
|
||||
'iso' => 130,
|
||||
'jnlp' => 131,
|
||||
'js' => 132,
|
||||
'jmz' => 133,
|
||||
'chrt' => 134,
|
||||
'kil' => 135,
|
||||
'skp' => 136,
|
||||
'skd' => 136,
|
||||
'skm' => 136,
|
||||
'skt' => 136,
|
||||
'kpr' => 137,
|
||||
'kpt' => 137,
|
||||
'ksp' => 138,
|
||||
'kwd' => 139,
|
||||
'kwt' => 139,
|
||||
'latex' => 140,
|
||||
'lha' => 141,
|
||||
'lyx' => 142,
|
||||
'lzh' => 143,
|
||||
'lzx' => 144,
|
||||
'maker' => 145,
|
||||
'frm' => 145,
|
||||
'frame' => 145,
|
||||
'fm' => 145,
|
||||
'book' => 145,
|
||||
'fb' => 145,
|
||||
'fbdoc' => 145,
|
||||
'mif' => 146,
|
||||
'wmd' => 147,
|
||||
'wmz' => 148,
|
||||
'dll' => 149,
|
||||
'bat' => 149,
|
||||
'exe' => 149,
|
||||
'com' => 149,
|
||||
'msi' => 150,
|
||||
'nc' => 151,
|
||||
'pac' => 152,
|
||||
'nwc' => 153,
|
||||
'o' => 154,
|
||||
'oza' => 155,
|
||||
'p7r' => 156,
|
||||
'crl' => 157,
|
||||
'pyo' => 158,
|
||||
'pyc' => 158,
|
||||
'qtl' => 159,
|
||||
'rpm' => 160,
|
||||
'shar' => 161,
|
||||
'swf' => 162,
|
||||
'swfl' => 162,
|
||||
'sitx' => 163,
|
||||
'sit' => 163,
|
||||
'sv4cpio' => 164,
|
||||
'sv4crc' => 165,
|
||||
'tar' => 166,
|
||||
'gf' => 168,
|
||||
'pk' => 169,
|
||||
'texi' => 170,
|
||||
'texinfo' => 170,
|
||||
'sik' => 171,
|
||||
'~' => 171,
|
||||
'bak' => 171,
|
||||
'%' => 171,
|
||||
'old' => 171,
|
||||
't' => 172,
|
||||
'roff' => 172,
|
||||
'tr' => 172,
|
||||
'man' => 173,
|
||||
'me' => 174,
|
||||
'ms' => 175,
|
||||
'ustar' => 176,
|
||||
'src' => 177,
|
||||
'wz' => 178,
|
||||
'crt' => 179,
|
||||
'xcf' => 180,
|
||||
'fig' => 181,
|
||||
'xpi' => 182,
|
||||
'xht' => 183,
|
||||
'xhtml' => 183,
|
||||
'xml' => 184,
|
||||
'xsl' => 184,
|
||||
'zip' => 185,
|
||||
'au' => 186,
|
||||
'snd' => 186,
|
||||
'mid' => 187,
|
||||
'midi' => 187,
|
||||
'kar' => 187,
|
||||
'mpega' => 188,
|
||||
'mpga' => 188,
|
||||
'm4a' => 188,
|
||||
'mp3' => 188,
|
||||
'mp2' => 188,
|
||||
'ogg' => 189,
|
||||
'oga' => 189,
|
||||
'spx' => 189,
|
||||
'sid' => 190,
|
||||
'aif' => 191,
|
||||
'aiff' => 191,
|
||||
'aifc' => 191,
|
||||
'gsm' => 192,
|
||||
'm3u' => 193,
|
||||
'wax' => 194,
|
||||
'wma' => 195,
|
||||
'rm' => 196,
|
||||
'ram' => 196,
|
||||
'ra' => 197,
|
||||
'pls' => 198,
|
||||
'sd2' => 199,
|
||||
'wav' => 200,
|
||||
'alc' => 201,
|
||||
'cac' => 202,
|
||||
'cache' => 202,
|
||||
'csf' => 203,
|
||||
'cascii' => 204,
|
||||
'cbin' => 204,
|
||||
'ctab' => 204,
|
||||
'cdx' => 205,
|
||||
'cer' => 206,
|
||||
'c3d' => 207,
|
||||
'chm' => 208,
|
||||
'cif' => 209,
|
||||
'cmdf' => 210,
|
||||
'cml' => 211,
|
||||
'cpa' => 212,
|
||||
'bsd' => 213,
|
||||
'csml' => 214,
|
||||
'csm' => 214,
|
||||
'ctx' => 215,
|
||||
'cxf' => 216,
|
||||
'cef' => 216,
|
||||
'emb' => 217,
|
||||
'embl' => 217,
|
||||
'spc' => 218,
|
||||
'gam' => 219,
|
||||
'inp' => 219,
|
||||
'gamin' => 219,
|
||||
'fchk' => 220,
|
||||
'fch' => 220,
|
||||
'cub' => 221,
|
||||
'gau' => 222,
|
||||
'gjf' => 222,
|
||||
'gjc' => 222,
|
||||
'gal' => 223,
|
||||
'gcg' => 224,
|
||||
'gen' => 225,
|
||||
'hin' => 226,
|
||||
'istr' => 227,
|
||||
'ist' => 227,
|
||||
'dx' => 228,
|
||||
'jdx' => 228,
|
||||
'kin' => 229,
|
||||
'mcm' => 230,
|
||||
'mmd' => 231,
|
||||
'mmod' => 231,
|
||||
'mol' => 232,
|
||||
'rd' => 233,
|
||||
'rxn' => 234,
|
||||
'sdf' => 235,
|
||||
'sd' => 235,
|
||||
'tgf' => 236,
|
||||
'mcif' => 237,
|
||||
'mol2' => 238,
|
||||
'b' => 239,
|
||||
'gpt' => 240,
|
||||
'mopcrt' => 241,
|
||||
'zmt' => 241,
|
||||
'mpc' => 241,
|
||||
'dat' => 241,
|
||||
'mop' => 241,
|
||||
'moo' => 242,
|
||||
'mvb' => 243,
|
||||
'prt' => 244,
|
||||
'aso' => 245,
|
||||
'val' => 245,
|
||||
'asn' => 246,
|
||||
'ent' => 247,
|
||||
'pdb' => 247,
|
||||
'ros' => 248,
|
||||
'sw' => 249,
|
||||
'vms' => 250,
|
||||
'vmd' => 251,
|
||||
'xtel' => 252,
|
||||
'xyz' => 253,
|
||||
'gif' => 254,
|
||||
'ief' => 255,
|
||||
'jpeg' => 256,
|
||||
'jpe' => 256,
|
||||
'jpg' => 256,
|
||||
'pcx' => 257,
|
||||
'png' => 258,
|
||||
'svgz' => 259,
|
||||
'svg' => 259,
|
||||
'tif' => 260,
|
||||
'tiff' => 260,
|
||||
'djvu' => 261,
|
||||
'djv' => 261,
|
||||
'ico' => 262,
|
||||
'wbmp' => 263,
|
||||
'ras' => 264,
|
||||
'cdr' => 265,
|
||||
'pat' => 266,
|
||||
'cdt' => 267,
|
||||
'cpt' => 268,
|
||||
'art' => 269,
|
||||
'jng' => 270,
|
||||
'bmp' => 271,
|
||||
'psd' => 272,
|
||||
'pnm' => 273,
|
||||
'pbm' => 274,
|
||||
'pgm' => 275,
|
||||
'ppm' => 276,
|
||||
'rgb' => 277,
|
||||
'xbm' => 278,
|
||||
'xpm' => 279,
|
||||
'xwd' => 280,
|
||||
'eml' => 281,
|
||||
'igs' => 282,
|
||||
'iges' => 282,
|
||||
'silo' => 283,
|
||||
'msh' => 283,
|
||||
'mesh' => 283,
|
||||
'icz' => 285,
|
||||
'ics' => 285,
|
||||
'css' => 286,
|
||||
'csv' => 287,
|
||||
'323' => 288,
|
||||
'html' => 289,
|
||||
'htm' => 289,
|
||||
'shtml' => 289,
|
||||
'uls' => 290,
|
||||
'mml' => 291,
|
||||
'txt' => 292,
|
||||
'pot' => 292,
|
||||
'text' => 292,
|
||||
'asc' => 292,
|
||||
'rtx' => 293,
|
||||
'wsc' => 294,
|
||||
'sct' => 294,
|
||||
'tsv' => 295,
|
||||
'ts' => 296,
|
||||
'tm' => 296,
|
||||
'jad' => 297,
|
||||
'wml' => 298,
|
||||
'wmls' => 299,
|
||||
'bib' => 300,
|
||||
'boo' => 301,
|
||||
'hpp' => 302,
|
||||
'hh' => 302,
|
||||
'h++' => 302,
|
||||
'hxx' => 302,
|
||||
'cxx' => 303,
|
||||
'cc' => 303,
|
||||
'cpp' => 303,
|
||||
'c++' => 303,
|
||||
'h' => 304,
|
||||
'htc' => 305,
|
||||
'csh' => 306,
|
||||
'c' => 307,
|
||||
'patch' => 308,
|
||||
'diff' => 308,
|
||||
'd' => 309,
|
||||
'hs' => 310,
|
||||
'java' => 311,
|
||||
'lhs' => 312,
|
||||
'moc' => 313,
|
||||
'pas' => 314,
|
||||
'p' => 314,
|
||||
'gcd' => 315,
|
||||
'pm' => 316,
|
||||
'pl' => 316,
|
||||
'py' => 317,
|
||||
'etx' => 318,
|
||||
'sh' => 319,
|
||||
'tk' => 320,
|
||||
'tcl' => 320,
|
||||
'cls' => 321,
|
||||
'ltx' => 321,
|
||||
'sty' => 321,
|
||||
'tex' => 321,
|
||||
'vcs' => 322,
|
||||
'vcf' => 323,
|
||||
'3gp' => 324,
|
||||
'dl' => 325,
|
||||
'dif' => 326,
|
||||
'dv' => 326,
|
||||
'fli' => 327,
|
||||
'gl' => 328,
|
||||
'mp4' => 329,
|
||||
'f4v' => 329,
|
||||
'f4p' => 329,
|
||||
'mpe' => 330,
|
||||
'mpeg' => 330,
|
||||
'mpg' => 330,
|
||||
'ogv' => 331,
|
||||
'qt' => 332,
|
||||
'mov' => 332,
|
||||
'mxu' => 333,
|
||||
'lsf' => 334,
|
||||
'lsx' => 334,
|
||||
'mng' => 335,
|
||||
'asx' => 336,
|
||||
'asf' => 336,
|
||||
'wm' => 337,
|
||||
'wmv' => 338,
|
||||
'wmx' => 339,
|
||||
'wvx' => 340,
|
||||
'avi' => 341,
|
||||
'movie' => 342,
|
||||
'ice' => 343,
|
||||
'sisx' => 344,
|
||||
'wrl' => 345,
|
||||
'vrm' => 345,
|
||||
'vrml' => 345,
|
||||
'f4a' => 346,
|
||||
'f4b' => 346,
|
||||
'flv' => 347,
|
||||
'm4v' => 348,
|
||||
'azw' => 349,
|
||||
'epub' => 350,
|
||||
'mobi' => 351,
|
||||
'prc' => 352,
|
||||
'mkv' => 353,
|
||||
'mka' => 354,
|
||||
'webp' => 355,
|
||||
'weba' => 356,
|
||||
'webm' => 357,
|
||||
'vtt' => 358,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use Drupal\Core\Security\PharExtensionInterceptor;
|
||||
use TYPO3\PharStreamWrapper\Manager as PharStreamWrapperManager;
|
||||
use TYPO3\PharStreamWrapper\Behavior as PharStreamWrapperBehavior;
|
||||
use TYPO3\PharStreamWrapper\PharStreamWrapper;
|
||||
|
||||
/**
|
||||
* Registers a phar stream wrapper that is more secure than PHP's built-in one.
|
||||
*
|
||||
* @see file_get_stream_wrappers()
|
||||
*/
|
||||
function file_register_phar_wrapper() {
|
||||
$directory = DRUPAL_ROOT . '/misc/typo3/phar-stream-wrapper/src';
|
||||
include_once $directory . '/Assertable.php';
|
||||
include_once $directory . '/Behavior.php';
|
||||
include_once $directory . '/Exception.php';
|
||||
include_once $directory . '/Helper.php';
|
||||
include_once $directory . '/Manager.php';
|
||||
include_once $directory . '/PharStreamWrapper.php';
|
||||
include_once $directory . '/Collectable.php';
|
||||
include_once $directory . '/Interceptor/ConjunctionInterceptor.php';
|
||||
include_once $directory . '/Interceptor/PharMetaDataInterceptor.php';
|
||||
include_once $directory . '/Phar/Container.php';
|
||||
include_once $directory . '/Phar/DeserializationException.php';
|
||||
include_once $directory . '/Phar/Manifest.php';
|
||||
include_once $directory . '/Phar/Reader.php';
|
||||
include_once $directory . '/Phar/ReaderException.php';
|
||||
include_once $directory . '/Phar/Stub.php';
|
||||
include_once $directory . '/Resolvable.php';
|
||||
include_once $directory . '/Resolver/PharInvocation.php';
|
||||
include_once $directory . '/Resolver/PharInvocationCollection.php';
|
||||
include_once $directory . '/Resolver/PharInvocationResolver.php';
|
||||
include_once DRUPAL_ROOT . '/misc/typo3/drupal-security/PharExtensionInterceptor.php';
|
||||
include_once DRUPAL_ROOT . '/misc/brumann/polyfill-unserialize/src/Unserialize.php';
|
||||
|
||||
// Set up a stream wrapper to handle insecurities due to PHP's built-in
|
||||
// phar stream wrapper.
|
||||
try {
|
||||
$behavior = new PharStreamWrapperBehavior();
|
||||
PharStreamWrapperManager::initialize(
|
||||
$behavior->withAssertion(new PharExtensionInterceptor())
|
||||
);
|
||||
}
|
||||
catch (\LogicException $e) {
|
||||
// Continue if the PharStreamWrapperManager is already initialized.
|
||||
// For example, this occurs following a drupal_static_reset(), such
|
||||
// as during tests.
|
||||
};
|
||||
|
||||
// To prevent file_stream_wrapper_valid_scheme() treating "phar" as a valid
|
||||
// scheme, this is registered with PHP only, not with hook_stream_wrappers()
|
||||
// or the internal storage of file_get_stream_wrappers().
|
||||
stream_wrapper_register('phar', '\\TYPO3\\PharStreamWrapper\\PharStreamWrapper');
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Base FileTransfer class.
|
||||
*
|
||||
* Classes extending this class perform file operations on directories not
|
||||
* writable by the webserver. To achieve this, the class should connect back
|
||||
* to the server using some backend (for example FTP or SSH). To keep security,
|
||||
* the password should always be asked from the user and never stored. For
|
||||
* safety, all methods operate only inside a "jail", by default the Drupal root.
|
||||
*/
|
||||
abstract class FileTransfer {
|
||||
protected $username;
|
||||
protected $password;
|
||||
protected $hostname = 'localhost';
|
||||
protected $port;
|
||||
|
||||
/**
|
||||
* The constructor for the UpdateConnection class. This method is also called
|
||||
* from the classes that extend this class and override this method.
|
||||
*/
|
||||
function __construct($jail) {
|
||||
$this->jail = $jail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes that extend this class must override the factory() static method.
|
||||
*
|
||||
* @param string $jail
|
||||
* The full path where all file operations performed by this object will
|
||||
* be restricted to. This prevents the FileTransfer classes from being
|
||||
* able to touch other parts of the filesystem.
|
||||
* @param array $settings
|
||||
* An array of connection settings for the FileTransfer subclass. If the
|
||||
* getSettingsForm() method uses any nested settings, the same structure
|
||||
* will be assumed here.
|
||||
* @return object
|
||||
* New instance of the appropriate FileTransfer subclass.
|
||||
*/
|
||||
static function factory($jail, $settings) {
|
||||
throw new FileTransferException('FileTransfer::factory() static method not overridden by FileTransfer subclass.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the magic __get() method.
|
||||
*
|
||||
* If the connection isn't set to anything, this will call the connect() method
|
||||
* and set it to and return the result; afterwards, the connection will be
|
||||
* returned directly without using this method.
|
||||
*/
|
||||
function __get($name) {
|
||||
if ($name == 'connection') {
|
||||
$this->connect();
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
if ($name == 'chroot') {
|
||||
$this->setChroot();
|
||||
return $this->chroot;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the server.
|
||||
*/
|
||||
abstract protected function connect();
|
||||
|
||||
/**
|
||||
* Copies a directory.
|
||||
*
|
||||
* @param $source
|
||||
* The source path.
|
||||
* @param $destination
|
||||
* The destination path.
|
||||
*/
|
||||
public final function copyDirectory($source, $destination) {
|
||||
$source = $this->sanitizePath($source);
|
||||
$destination = $this->fixRemotePath($destination);
|
||||
$this->checkPath($destination);
|
||||
$this->copyDirectoryJailed($source, $destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://php.net/chmod
|
||||
*
|
||||
* @param string $path
|
||||
* @param long $mode
|
||||
* @param bool $recursive
|
||||
*/
|
||||
public final function chmod($path, $mode, $recursive = FALSE) {
|
||||
if (!in_array('FileTransferChmodInterface', class_implements(get_class($this)))) {
|
||||
throw new FileTransferException('Unable to change file permissions');
|
||||
}
|
||||
$path = $this->sanitizePath($path);
|
||||
$path = $this->fixRemotePath($path);
|
||||
$this->checkPath($path);
|
||||
$this->chmodJailed($path, $mode, $recursive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory.
|
||||
*
|
||||
* @param $directory
|
||||
* The directory to be created.
|
||||
*/
|
||||
public final function createDirectory($directory) {
|
||||
$directory = $this->fixRemotePath($directory);
|
||||
$this->checkPath($directory);
|
||||
$this->createDirectoryJailed($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a directory.
|
||||
*
|
||||
* @param $directory
|
||||
* The directory to be removed.
|
||||
*/
|
||||
public final function removeDirectory($directory) {
|
||||
$directory = $this->fixRemotePath($directory);
|
||||
$this->checkPath($directory);
|
||||
$this->removeDirectoryJailed($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a file.
|
||||
*
|
||||
* @param $source
|
||||
* The source file.
|
||||
* @param $destination
|
||||
* The destination file.
|
||||
*/
|
||||
public final function copyFile($source, $destination) {
|
||||
$source = $this->sanitizePath($source);
|
||||
$destination = $this->fixRemotePath($destination);
|
||||
$this->checkPath($destination);
|
||||
$this->copyFileJailed($source, $destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a file.
|
||||
*
|
||||
* @param $destination
|
||||
* The destination file to be removed.
|
||||
*/
|
||||
public final function removeFile($destination) {
|
||||
$destination = $this->fixRemotePath($destination);
|
||||
$this->checkPath($destination);
|
||||
$this->removeFileJailed($destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the path is inside the jail and throws an exception if not.
|
||||
*
|
||||
* @param $path
|
||||
* A path to check against the jail.
|
||||
*/
|
||||
protected final function checkPath($path) {
|
||||
$full_jail = $this->chroot . $this->jail;
|
||||
$full_path = drupal_realpath(substr($this->chroot . $path, 0, strlen($full_jail)));
|
||||
$full_path = $this->fixRemotePath($full_path, FALSE);
|
||||
if ($full_jail !== $full_path) {
|
||||
throw new FileTransferException('@directory is outside of the @jail', NULL, array('@directory' => $path, '@jail' => $this->jail));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a modified path suitable for passing to the server.
|
||||
* If a path is a windows path, makes it POSIX compliant by removing the drive letter.
|
||||
* If $this->chroot has a value, it is stripped from the path to allow for
|
||||
* chroot'd filetransfer systems.
|
||||
*
|
||||
* @param $path
|
||||
* @param $strip_chroot
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected final function fixRemotePath($path, $strip_chroot = TRUE) {
|
||||
$path = $this->sanitizePath($path);
|
||||
$path = preg_replace('|^([a-z]{1}):|i', '', $path); // Strip out windows driveletter if its there.
|
||||
if ($strip_chroot) {
|
||||
if ($this->chroot && strpos($path, $this->chroot) === 0) {
|
||||
$path = ($path == $this->chroot) ? '' : substr($path, strlen($this->chroot));
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes backslashes to slashes, also removes a trailing slash.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
function sanitizePath($path) {
|
||||
$path = str_replace('\\', '/', $path); // Windows path sanitization.
|
||||
if (substr($path, -1) == '/') {
|
||||
$path = substr($path, 0, -1);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a directory.
|
||||
*
|
||||
* We need a separate method to make the $destination is in the jail.
|
||||
*
|
||||
* @param $source
|
||||
* The source path.
|
||||
* @param $destination
|
||||
* The destination path.
|
||||
*/
|
||||
protected function copyDirectoryJailed($source, $destination) {
|
||||
if ($this->isDirectory($destination)) {
|
||||
$destination = $destination . '/' . drupal_basename($source);
|
||||
}
|
||||
$this->createDirectory($destination);
|
||||
foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) {
|
||||
$relative_path = substr($filename, strlen($source));
|
||||
if ($file->isDir()) {
|
||||
$this->createDirectory($destination . $relative_path);
|
||||
}
|
||||
else {
|
||||
$this->copyFile($file->getPathName(), $destination . $relative_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory.
|
||||
*
|
||||
* @param $directory
|
||||
* The directory to be created.
|
||||
*/
|
||||
abstract protected function createDirectoryJailed($directory);
|
||||
|
||||
/**
|
||||
* Removes a directory.
|
||||
*
|
||||
* @param $directory
|
||||
* The directory to be removed.
|
||||
*/
|
||||
abstract protected function removeDirectoryJailed($directory);
|
||||
|
||||
/**
|
||||
* Copies a file.
|
||||
*
|
||||
* @param $source
|
||||
* The source file.
|
||||
* @param $destination
|
||||
* The destination file.
|
||||
*/
|
||||
abstract protected function copyFileJailed($source, $destination);
|
||||
|
||||
/**
|
||||
* Removes a file.
|
||||
*
|
||||
* @param $destination
|
||||
* The destination file to be removed.
|
||||
*/
|
||||
abstract protected function removeFileJailed($destination);
|
||||
|
||||
/**
|
||||
* Checks if a particular path is a directory
|
||||
*
|
||||
* @param $path
|
||||
* The path to check
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
abstract public function isDirectory($path);
|
||||
|
||||
/**
|
||||
* Checks if a particular path is a file (not a directory).
|
||||
*
|
||||
* @param $path
|
||||
* The path to check
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
abstract public function isFile($path);
|
||||
|
||||
/**
|
||||
* Return the chroot property for this connection.
|
||||
*
|
||||
* It does this by moving up the tree until it finds itself. If successful,
|
||||
* it will return the chroot, otherwise FALSE.
|
||||
*
|
||||
* @return
|
||||
* The chroot path for this connection or FALSE.
|
||||
*/
|
||||
function findChroot() {
|
||||
// If the file exists as is, there is no chroot.
|
||||
$path = __FILE__;
|
||||
$path = $this->fixRemotePath($path, FALSE);
|
||||
if ($this->isFile($path)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$path = dirname(__FILE__);
|
||||
$path = $this->fixRemotePath($path, FALSE);
|
||||
$parts = explode('/', $path);
|
||||
$chroot = '';
|
||||
while (count($parts)) {
|
||||
$check = implode('/', $parts);
|
||||
if ($this->isFile($check . '/' . drupal_basename(__FILE__))) {
|
||||
// Remove the trailing slash.
|
||||
return substr($chroot, 0, -1);
|
||||
}
|
||||
$chroot .= array_shift($parts) . '/';
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the chroot and changes the jail to match the correct path scheme
|
||||
*
|
||||
*/
|
||||
function setChroot() {
|
||||
$this->chroot = $this->findChroot();
|
||||
$this->jail = $this->fixRemotePath($this->jail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form to collect connection settings credentials.
|
||||
*
|
||||
* Implementing classes can either extend this form with fields collecting the
|
||||
* specific information they need, or override it entirely.
|
||||
*/
|
||||
public function getSettingsForm() {
|
||||
$form['username'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Username'),
|
||||
);
|
||||
$form['password'] = array(
|
||||
'#type' => 'password',
|
||||
'#title' => t('Password'),
|
||||
'#description' => t('Your password is not saved in the database and is only used to establish a connection.'),
|
||||
);
|
||||
$form['advanced'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Advanced settings'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
$form['advanced']['hostname'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Host'),
|
||||
'#default_value' => 'localhost',
|
||||
'#description' => t('The connection will be created between your web server and the machine hosting the web server files. In the vast majority of cases, this will be the same machine, and "localhost" is correct.'),
|
||||
);
|
||||
$form['advanced']['port'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Port'),
|
||||
'#default_value' => NULL,
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FileTransferException class.
|
||||
*/
|
||||
class FileTransferException extends Exception {
|
||||
public $arguments;
|
||||
|
||||
function __construct($message, $code = 0, $arguments = array()) {
|
||||
parent::__construct($message, $code);
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A FileTransfer Class implementing this interface can be used to chmod files.
|
||||
*/
|
||||
interface FileTransferChmodInterface {
|
||||
|
||||
/**
|
||||
* Changes the permissions of the file / directory specified in $path
|
||||
*
|
||||
* @param string $path
|
||||
* Path to change permissions of.
|
||||
* @param long $mode
|
||||
* The new file permission mode to be passed to chmod().
|
||||
* @param boolean $recursive
|
||||
* Pass TRUE to recursively chmod the entire directory specified in $path.
|
||||
*/
|
||||
function chmodJailed($path, $mode, $recursive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an interface for iterating recursively over filesystem directories.
|
||||
*
|
||||
* Manually skips '.' and '..' directories, since no existing method is
|
||||
* available in PHP 5.2.
|
||||
*
|
||||
* @todo Depreciate in favor of RecursiveDirectoryIterator::SKIP_DOTS once PHP
|
||||
* 5.3 or later is required.
|
||||
*/
|
||||
class SkipDotsRecursiveDirectoryIterator extends RecursiveDirectoryIterator {
|
||||
/**
|
||||
* Constructs a SkipDotsRecursiveDirectoryIterator
|
||||
*
|
||||
* @param $path
|
||||
* The path of the directory to be iterated over.
|
||||
*/
|
||||
function __construct($path) {
|
||||
parent::__construct($path);
|
||||
$this->skipdots();
|
||||
}
|
||||
|
||||
function rewind() {
|
||||
parent::rewind();
|
||||
$this->skipdots();
|
||||
}
|
||||
|
||||
function next() {
|
||||
parent::next();
|
||||
$this->skipdots();
|
||||
}
|
||||
|
||||
protected function skipdots() {
|
||||
while ($this->isDot()) {
|
||||
parent::next();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Base class for FTP implementations.
|
||||
*/
|
||||
abstract class FileTransferFTP extends FileTransfer {
|
||||
|
||||
public function __construct($jail, $username, $password, $hostname, $port) {
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
$this->hostname = $hostname;
|
||||
$this->port = $port;
|
||||
parent::__construct($jail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object which can implement the FTP protocol.
|
||||
*
|
||||
* @param string $jail
|
||||
* @param array $settings
|
||||
* @return FileTransferFTP
|
||||
* The appropriate FileTransferFTP subclass based on the available
|
||||
* options. If the FTP PHP extension is available, use it.
|
||||
*/
|
||||
static function factory($jail, $settings) {
|
||||
$username = empty($settings['username']) ? '' : $settings['username'];
|
||||
$password = empty($settings['password']) ? '' : $settings['password'];
|
||||
$hostname = empty($settings['advanced']['hostname']) ? 'localhost' : $settings['advanced']['hostname'];
|
||||
$port = empty($settings['advanced']['port']) ? 21 : $settings['advanced']['port'];
|
||||
|
||||
if (function_exists('ftp_connect')) {
|
||||
$class = 'FileTransferFTPExtension';
|
||||
}
|
||||
else {
|
||||
throw new FileTransferException('No FTP backend available.');
|
||||
}
|
||||
|
||||
return new $class($jail, $username, $password, $hostname, $port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the form to configure the FileTransfer class for FTP.
|
||||
*/
|
||||
public function getSettingsForm() {
|
||||
$form = parent::getSettingsForm();
|
||||
$form['advanced']['port']['#default_value'] = 21;
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
||||
class FileTransferFTPExtension extends FileTransferFTP implements FileTransferChmodInterface {
|
||||
|
||||
public function connect() {
|
||||
$this->connection = ftp_connect($this->hostname, $this->port);
|
||||
|
||||
if (!$this->connection) {
|
||||
throw new FileTransferException("Cannot connect to FTP Server, check settings");
|
||||
}
|
||||
if (!ftp_login($this->connection, $this->username, $this->password)) {
|
||||
throw new FileTransferException("Cannot log in to FTP server. Check username and password");
|
||||
}
|
||||
}
|
||||
|
||||
protected function copyFileJailed($source, $destination) {
|
||||
if (!@ftp_put($this->connection, $destination, $source, FTP_BINARY)) {
|
||||
throw new FileTransferException("Cannot move @source to @destination", NULL, array("@source" => $source, "@destination" => $destination));
|
||||
}
|
||||
}
|
||||
|
||||
protected function createDirectoryJailed($directory) {
|
||||
if (!ftp_mkdir($this->connection, $directory)) {
|
||||
throw new FileTransferException("Cannot create directory @directory", NULL, array("@directory" => $directory));
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeDirectoryJailed($directory) {
|
||||
$pwd = ftp_pwd($this->connection);
|
||||
if (!ftp_chdir($this->connection, $directory)) {
|
||||
throw new FileTransferException("Unable to change to directory @directory", NULL, array('@directory' => $directory));
|
||||
}
|
||||
$list = @ftp_nlist($this->connection, '.');
|
||||
if (!$list) {
|
||||
$list = array();
|
||||
}
|
||||
foreach ($list as $item) {
|
||||
if ($item == '.' || $item == '..') {
|
||||
continue;
|
||||
}
|
||||
if (@ftp_chdir($this->connection, $item)) {
|
||||
ftp_cdup($this->connection);
|
||||
$this->removeDirectory(ftp_pwd($this->connection) . '/' . $item);
|
||||
}
|
||||
else {
|
||||
$this->removeFile(ftp_pwd($this->connection) . '/' . $item);
|
||||
}
|
||||
}
|
||||
ftp_chdir($this->connection, $pwd);
|
||||
if (!ftp_rmdir($this->connection, $directory)) {
|
||||
throw new FileTransferException("Unable to remove to directory @directory", NULL, array('@directory' => $directory));
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeFileJailed($destination) {
|
||||
if (!ftp_delete($this->connection, $destination)) {
|
||||
throw new FileTransferException("Unable to remove to file @file", NULL, array('@file' => $destination));
|
||||
}
|
||||
}
|
||||
|
||||
public function isDirectory($path) {
|
||||
$result = FALSE;
|
||||
$curr = ftp_pwd($this->connection);
|
||||
if (@ftp_chdir($this->connection, $path)) {
|
||||
$result = TRUE;
|
||||
}
|
||||
ftp_chdir($this->connection, $curr);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function isFile($path) {
|
||||
return ftp_size($this->connection, $path) != -1;
|
||||
}
|
||||
|
||||
function chmodJailed($path, $mode, $recursive) {
|
||||
if (!ftp_chmod($this->connection, $mode, $path)) {
|
||||
throw new FileTransferException("Unable to set permissions on %file", NULL, array('%file' => $path));
|
||||
}
|
||||
if ($this->isDirectory($path) && $recursive) {
|
||||
$filelist = @ftp_nlist($this->connection, $path);
|
||||
if (!$filelist) {
|
||||
//empty directory - returns false
|
||||
return;
|
||||
}
|
||||
foreach ($filelist as $file) {
|
||||
$this->chmodJailed($file, $mode, $recursive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('ftp_chmod')) {
|
||||
function ftp_chmod($ftp_stream, $mode, $filename) {
|
||||
return ftp_site($ftp_stream, sprintf('CHMOD %o %s', $mode, $filename));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The local connection class for copying files as the httpd user.
|
||||
*/
|
||||
class FileTransferLocal extends FileTransfer implements FileTransferChmodInterface {
|
||||
|
||||
function connect() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
static function factory($jail, $settings) {
|
||||
return new FileTransferLocal($jail);
|
||||
}
|
||||
|
||||
protected function copyFileJailed($source, $destination) {
|
||||
if (@!copy($source, $destination)) {
|
||||
throw new FileTransferException('Cannot copy %source to %destination.', NULL, array('%source' => $source, '%destination' => $destination));
|
||||
}
|
||||
}
|
||||
|
||||
protected function createDirectoryJailed($directory) {
|
||||
if (!is_dir($directory) && @!mkdir($directory, 0777, TRUE)) {
|
||||
throw new FileTransferException('Cannot create directory %directory.', NULL, array('%directory' => $directory));
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeDirectoryJailed($directory) {
|
||||
if (!is_dir($directory)) {
|
||||
// Programmer error assertion, not something we expect users to see.
|
||||
throw new FileTransferException('removeDirectoryJailed() called with a path (%directory) that is not a directory.', NULL, array('%directory' => $directory));
|
||||
}
|
||||
foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $filename => $file) {
|
||||
if ($file->isDir()) {
|
||||
if (@!drupal_rmdir($filename)) {
|
||||
throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $filename));
|
||||
}
|
||||
}
|
||||
elseif ($file->isFile()) {
|
||||
if (@!drupal_unlink($filename)) {
|
||||
throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (@!drupal_rmdir($directory)) {
|
||||
throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $directory));
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeFileJailed($file) {
|
||||
if (@!drupal_unlink($file)) {
|
||||
throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $file));
|
||||
}
|
||||
}
|
||||
|
||||
public function isDirectory($path) {
|
||||
return is_dir($path);
|
||||
}
|
||||
|
||||
public function isFile($path) {
|
||||
return is_file($path);
|
||||
}
|
||||
|
||||
public function chmodJailed($path, $mode, $recursive) {
|
||||
if ($recursive && is_dir($path)) {
|
||||
foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) {
|
||||
if (@!chmod($filename, $mode)) {
|
||||
throw new FileTransferException('Cannot chmod %path.', NULL, array('%path' => $filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (@!chmod($path, $mode)) {
|
||||
throw new FileTransferException('Cannot chmod %path.', NULL, array('%path' => $path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The SSH connection class for the update module.
|
||||
*/
|
||||
class FileTransferSSH extends FileTransfer implements FileTransferChmodInterface {
|
||||
|
||||
function __construct($jail, $username, $password, $hostname = "localhost", $port = 22) {
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
$this->hostname = $hostname;
|
||||
$this->port = $port;
|
||||
parent::__construct($jail);
|
||||
}
|
||||
|
||||
function connect() {
|
||||
$this->connection = @ssh2_connect($this->hostname, $this->port);
|
||||
if (!$this->connection) {
|
||||
throw new FileTransferException('SSH Connection failed to @host:@port', NULL, array('@host' => $this->hostname, '@port' => $this->port));
|
||||
}
|
||||
if (!@ssh2_auth_password($this->connection, $this->username, $this->password)) {
|
||||
throw new FileTransferException('The supplied username/password combination was not accepted.');
|
||||
}
|
||||
}
|
||||
|
||||
static function factory($jail, $settings) {
|
||||
$username = empty($settings['username']) ? '' : $settings['username'];
|
||||
$password = empty($settings['password']) ? '' : $settings['password'];
|
||||
$hostname = empty($settings['advanced']['hostname']) ? 'localhost' : $settings['advanced']['hostname'];
|
||||
$port = empty($settings['advanced']['port']) ? 22 : $settings['advanced']['port'];
|
||||
return new FileTransferSSH($jail, $username, $password, $hostname, $port);
|
||||
}
|
||||
|
||||
protected function copyFileJailed($source, $destination) {
|
||||
if (!@ssh2_scp_send($this->connection, $source, $destination)) {
|
||||
throw new FileTransferException('Cannot copy @source_file to @destination_file.', NULL, array('@source' => $source, '@destination' => $destination));
|
||||
}
|
||||
}
|
||||
|
||||
protected function copyDirectoryJailed($source, $destination) {
|
||||
if (@!ssh2_exec($this->connection, 'cp -Rp ' . escapeshellarg($source) . ' ' . escapeshellarg($destination))) {
|
||||
throw new FileTransferException('Cannot copy directory @directory.', NULL, array('@directory' => $source));
|
||||
}
|
||||
}
|
||||
|
||||
protected function createDirectoryJailed($directory) {
|
||||
if (@!ssh2_exec($this->connection, 'mkdir ' . escapeshellarg($directory))) {
|
||||
throw new FileTransferException('Cannot create directory @directory.', NULL, array('@directory' => $directory));
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeDirectoryJailed($directory) {
|
||||
if (@!ssh2_exec($this->connection, 'rm -Rf ' . escapeshellarg($directory))) {
|
||||
throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory));
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeFileJailed($destination) {
|
||||
if (!@ssh2_exec($this->connection, 'rm ' . escapeshellarg($destination))) {
|
||||
throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $destination));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: This is untested. It is not currently used, but should do the trick.
|
||||
*/
|
||||
public function isDirectory($path) {
|
||||
$directory = escapeshellarg($path);
|
||||
$cmd = "[ -d {$directory} ] && echo 'yes'";
|
||||
if ($output = @ssh2_exec($this->connection, $cmd)) {
|
||||
if ($output == 'yes') {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else {
|
||||
throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path));
|
||||
}
|
||||
}
|
||||
|
||||
public function isFile($path) {
|
||||
$file = escapeshellarg($path);
|
||||
$cmd = "[ -f {$file} ] && echo 'yes'";
|
||||
if ($output = @ssh2_exec($this->connection, $cmd)) {
|
||||
if ($output == 'yes') {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else {
|
||||
throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path));
|
||||
}
|
||||
}
|
||||
|
||||
function chmodJailed($path, $mode, $recursive) {
|
||||
$cmd = sprintf("chmod %s%o %s", $recursive ? '-R ' : '', $mode, escapeshellarg($path));
|
||||
if (@!ssh2_exec($this->connection, $cmd)) {
|
||||
throw new FileTransferException('Cannot change permissions of @path.', NULL, array('@path' => $path));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the form to configure the FileTransfer class for SSH.
|
||||
*/
|
||||
public function getSettingsForm() {
|
||||
$form = parent::getSettingsForm();
|
||||
$form['advanced']['port']['#default_value'] = 22;
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
+4809
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Directed acyclic graph manipulation.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Performs a depth-first search and sort on a directed acyclic graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A three dimensional associated array, with the first keys being the names
|
||||
* of the vertices, these can be strings or numbers. The second key is
|
||||
* 'edges' and the third one are again vertices, each such key representing
|
||||
* an edge. Values of array elements are copied over.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* $graph[1]['edges'][2] = 1;
|
||||
* $graph[2]['edges'][3] = 1;
|
||||
* $graph[2]['edges'][4] = 1;
|
||||
* $graph[3]['edges'][4] = 1;
|
||||
* @endcode
|
||||
*
|
||||
* On return you will also have:
|
||||
* @code
|
||||
* $graph[1]['paths'][2] = 1;
|
||||
* $graph[1]['paths'][3] = 1;
|
||||
* $graph[2]['reverse_paths'][1] = 1;
|
||||
* $graph[3]['reverse_paths'][1] = 1;
|
||||
* @endcode
|
||||
*
|
||||
* @return
|
||||
* The passed-in $graph with more secondary keys filled in:
|
||||
* - 'paths': Contains a list of vertices than can be reached on a path from
|
||||
* this vertex.
|
||||
* - 'reverse_paths': Contains a list of vertices that has a path from them
|
||||
* to this vertex.
|
||||
* - 'weight': If there is a path from a vertex to another then the weight of
|
||||
* the latter is higher.
|
||||
* - 'component': Vertices in the same component have the same component
|
||||
* identifier.
|
||||
*
|
||||
* @see _drupal_depth_first_search()
|
||||
*/
|
||||
function drupal_depth_first_search(&$graph) {
|
||||
$state = array(
|
||||
// The order of last visit of the depth first search. This is the reverse
|
||||
// of the topological order if the graph is acyclic.
|
||||
'last_visit_order' => array(),
|
||||
// The components of the graph.
|
||||
'components' => array(),
|
||||
);
|
||||
// Perform the actual search.
|
||||
foreach ($graph as $start => $data) {
|
||||
_drupal_depth_first_search($graph, $state, $start);
|
||||
}
|
||||
|
||||
// We do such a numbering that every component starts with 0. This is useful
|
||||
// for module installs as we can install every 0 weighted module in one
|
||||
// request, and then every 1 weighted etc.
|
||||
$component_weights = array();
|
||||
|
||||
foreach ($state['last_visit_order'] as $vertex) {
|
||||
$component = $graph[$vertex]['component'];
|
||||
if (!isset($component_weights[$component])) {
|
||||
$component_weights[$component] = 0;
|
||||
}
|
||||
$graph[$vertex]['weight'] = $component_weights[$component]--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a depth-first search on a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A three dimensional associated graph array.
|
||||
* @param $state
|
||||
* An associative array. The key 'last_visit_order' stores a list of the
|
||||
* vertices visited. The key components stores list of vertices belonging
|
||||
* to the same the component.
|
||||
* @param $start
|
||||
* An arbitrary vertex where we started traversing the graph.
|
||||
* @param $component
|
||||
* The component of the last vertex.
|
||||
*
|
||||
* @see drupal_depth_first_search()
|
||||
*/
|
||||
function _drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL) {
|
||||
// Assign new component for each new vertex, i.e. when not called recursively.
|
||||
if (!isset($component)) {
|
||||
$component = $start;
|
||||
}
|
||||
// Nothing to do, if we already visited this vertex.
|
||||
if (isset($graph[$start]['paths'])) {
|
||||
return;
|
||||
}
|
||||
// Mark $start as visited.
|
||||
$graph[$start]['paths'] = array();
|
||||
|
||||
// Assign $start to the current component.
|
||||
$graph[$start]['component'] = $component;
|
||||
$state['components'][$component][] = $start;
|
||||
|
||||
// Visit edges of $start.
|
||||
if (isset($graph[$start]['edges'])) {
|
||||
foreach ($graph[$start]['edges'] as $end => $v) {
|
||||
// Mark that $start can reach $end.
|
||||
$graph[$start]['paths'][$end] = $v;
|
||||
|
||||
if (isset($graph[$end]['component']) && $component != $graph[$end]['component']) {
|
||||
// This vertex already has a component, use that from now on and
|
||||
// reassign all the previously explored vertices.
|
||||
$new_component = $graph[$end]['component'];
|
||||
foreach ($state['components'][$component] as $vertex) {
|
||||
$graph[$vertex]['component'] = $new_component;
|
||||
$state['components'][$new_component][] = $vertex;
|
||||
}
|
||||
unset($state['components'][$component]);
|
||||
$component = $new_component;
|
||||
}
|
||||
// Only visit existing vertices.
|
||||
if (isset($graph[$end])) {
|
||||
// Visit the connected vertex.
|
||||
_drupal_depth_first_search($graph, $state, $end, $component);
|
||||
|
||||
// All vertices reachable by $end are also reachable by $start.
|
||||
$graph[$start]['paths'] += $graph[$end]['paths'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that any other subgraph has been explored, add $start to all reverse
|
||||
// paths.
|
||||
foreach ($graph[$start]['paths'] as $end => $v) {
|
||||
if (isset($graph[$end])) {
|
||||
$graph[$end]['reverse_paths'][$start] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
// Record the order of the last visit. This is the reverse of the
|
||||
// topological order if the graph is acyclic.
|
||||
$state['last_visit_order'][] = $start;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API for manipulating images.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup image Image toolkits
|
||||
* @{
|
||||
* Functions for image file manipulations.
|
||||
*
|
||||
* Drupal's image toolkits provide an abstraction layer for common image file
|
||||
* manipulations like scaling, cropping, and rotating. The abstraction frees
|
||||
* module authors from the need to support multiple image libraries, and it
|
||||
* allows site administrators to choose the library that's best for them.
|
||||
*
|
||||
* PHP includes the GD library by default so a GD toolkit is installed with
|
||||
* Drupal. Other toolkits like ImageMagick are available from contrib modules.
|
||||
* GD works well for small images, but using it with larger files may cause PHP
|
||||
* to run out of memory. In contrast the ImageMagick library does not suffer
|
||||
* from this problem, but it requires the ISP to have installed additional
|
||||
* software.
|
||||
*
|
||||
* Image toolkits are discovered based on the associated module's
|
||||
* hook_image_toolkits. Additionally the image toolkit include file
|
||||
* must be identified in the files array in the module.info file. The
|
||||
* toolkit must then be enabled using the admin/config/media/image-toolkit
|
||||
* form.
|
||||
*
|
||||
* Only one toolkit may be selected at a time. If a module author wishes to call
|
||||
* a specific toolkit they can check that it is installed by calling
|
||||
* image_get_available_toolkits(), and then calling its functions directly.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets a list of available toolkits.
|
||||
*
|
||||
* @return
|
||||
* An array with the toolkit names as keys and the descriptions as values.
|
||||
*/
|
||||
function image_get_available_toolkits() {
|
||||
// hook_image_toolkits returns an array of toolkit names.
|
||||
$toolkits = module_invoke_all('image_toolkits');
|
||||
|
||||
$output = array();
|
||||
foreach ($toolkits as $name => $info) {
|
||||
// Only allow modules that aren't marked as unavailable.
|
||||
if ($info['available']) {
|
||||
$output[$name] = $info['title'];
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the currently used toolkit.
|
||||
*
|
||||
* @return
|
||||
* String containing the name of the selected toolkit, or FALSE on error.
|
||||
*/
|
||||
function image_get_toolkit() {
|
||||
static $toolkit;
|
||||
|
||||
if (!isset($toolkit)) {
|
||||
$toolkits = image_get_available_toolkits();
|
||||
$toolkit = variable_get('image_toolkit', 'gd');
|
||||
if (!isset($toolkits[$toolkit]) || !function_exists('image_' . $toolkit . '_load')) {
|
||||
// The selected toolkit isn't available so return the first one found. If
|
||||
// none are available this will return FALSE.
|
||||
reset($toolkits);
|
||||
$toolkit = key($toolkits);
|
||||
}
|
||||
}
|
||||
|
||||
return $toolkit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given method using the currently selected toolkit.
|
||||
*
|
||||
* @param $method
|
||||
* A string containing the method to invoke.
|
||||
* @param $image
|
||||
* An image object returned by image_load().
|
||||
* @param $params
|
||||
* An optional array of parameters to pass to the toolkit method.
|
||||
*
|
||||
* @return
|
||||
* Mixed values (typically Boolean indicating successful operation).
|
||||
*/
|
||||
function image_toolkit_invoke($method, stdClass $image, array $params = array()) {
|
||||
$function = 'image_' . $image->toolkit . '_' . $method;
|
||||
if (function_exists($function)) {
|
||||
array_unshift($params, $image);
|
||||
return call_user_func_array($function, $params);
|
||||
}
|
||||
watchdog('image', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $image->toolkit, '%function' => $function), WATCHDOG_ERROR);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets details about an image.
|
||||
*
|
||||
* Drupal supports GIF, JPG and PNG file formats when used with the GD
|
||||
* toolkit, and may support others, depending on which toolkits are
|
||||
* installed.
|
||||
*
|
||||
* @param $filepath
|
||||
* String specifying the path of the image file.
|
||||
* @param $toolkit
|
||||
* An optional image toolkit name to override the default.
|
||||
*
|
||||
* @return
|
||||
* FALSE, if the file could not be found or is not an image. Otherwise, a
|
||||
* keyed array containing information about the image:
|
||||
* - "width": Width, in pixels.
|
||||
* - "height": Height, in pixels.
|
||||
* - "extension": Commonly used file extension for the image.
|
||||
* - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png').
|
||||
* - "file_size": File size in bytes.
|
||||
*/
|
||||
function image_get_info($filepath, $toolkit = FALSE) {
|
||||
$details = FALSE;
|
||||
if (!is_file($filepath) && !is_uploaded_file($filepath)) {
|
||||
return $details;
|
||||
}
|
||||
|
||||
if (!$toolkit) {
|
||||
$toolkit = image_get_toolkit();
|
||||
}
|
||||
if ($toolkit) {
|
||||
$image = new stdClass();
|
||||
$image->source = $filepath;
|
||||
$image->toolkit = $toolkit;
|
||||
$details = image_toolkit_invoke('get_info', $image);
|
||||
if (isset($details) && is_array($details)) {
|
||||
$details['file_size'] = filesize($filepath);
|
||||
}
|
||||
}
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales an image to the exact width and height given.
|
||||
*
|
||||
* This function achieves the target aspect ratio by cropping the original image
|
||||
* equally on both sides, or equally on the top and bottom. This function is
|
||||
* useful to create uniform sized avatars from larger images.
|
||||
*
|
||||
* The resulting image always has the exact target dimensions.
|
||||
*
|
||||
* @param $image
|
||||
* An image object returned by image_load().
|
||||
* @param $width
|
||||
* The target width, in pixels.
|
||||
* @param $height
|
||||
* The target height, in pixels.
|
||||
*
|
||||
* @return
|
||||
* TRUE on success, FALSE on failure.
|
||||
*
|
||||
* @see image_load()
|
||||
* @see image_resize()
|
||||
* @see image_crop()
|
||||
*/
|
||||
function image_scale_and_crop(stdClass $image, $width, $height) {
|
||||
$scale = max($width / $image->info['width'], $height / $image->info['height']);
|
||||
$x = ($image->info['width'] * $scale - $width) / 2;
|
||||
$y = ($image->info['height'] * $scale - $height) / 2;
|
||||
|
||||
if (image_resize($image, $image->info['width'] * $scale, $image->info['height'] * $scale)) {
|
||||
return image_crop($image, $x, $y, $width, $height);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales image dimensions while maintaining aspect ratio.
|
||||
*
|
||||
* The resulting dimensions can be smaller for one or both target dimensions.
|
||||
*
|
||||
* @param $dimensions
|
||||
* Dimensions to be modified - an array with components width and height, in
|
||||
* pixels.
|
||||
* @param $width
|
||||
* The target width, in pixels. If this value is NULL then the scaling will be
|
||||
* based only on the height value.
|
||||
* @param $height
|
||||
* The target height, in pixels. If this value is NULL then the scaling will
|
||||
* be based only on the width value.
|
||||
* @param $upscale
|
||||
* Boolean indicating that images smaller than the target dimensions will be
|
||||
* scaled up. This generally results in a low quality image.
|
||||
*
|
||||
* @return
|
||||
* TRUE if $dimensions was modified, FALSE otherwise.
|
||||
*
|
||||
* @see image_scale()
|
||||
*/
|
||||
function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) {
|
||||
$aspect = $dimensions['height'] / $dimensions['width'];
|
||||
|
||||
// Calculate one of the dimensions from the other target dimension,
|
||||
// ensuring the same aspect ratio as the source dimensions. If one of the
|
||||
// target dimensions is missing, that is the one that is calculated. If both
|
||||
// are specified then the dimension calculated is the one that would not be
|
||||
// calculated to be bigger than its target.
|
||||
if (($width && !$height) || ($width && $height && $aspect < $height / $width)) {
|
||||
$height = (int) round($width * $aspect);
|
||||
}
|
||||
else {
|
||||
$width = (int) round($height / $aspect);
|
||||
}
|
||||
|
||||
// Don't upscale if the option isn't enabled.
|
||||
if (!$upscale && ($width >= $dimensions['width'] || $height >= $dimensions['height'])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$dimensions['width'] = $width;
|
||||
$dimensions['height'] = $height;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales an image while maintaining aspect ratio.
|
||||
*
|
||||
* The resulting image can be smaller for one or both target dimensions.
|
||||
*
|
||||
* @param $image
|
||||
* An image object returned by image_load().
|
||||
* @param $width
|
||||
* The target width, in pixels. If this value is NULL then the scaling will
|
||||
* be based only on the height value.
|
||||
* @param $height
|
||||
* The target height, in pixels. If this value is NULL then the scaling will
|
||||
* be based only on the width value.
|
||||
* @param $upscale
|
||||
* Boolean indicating that files smaller than the dimensions will be scaled
|
||||
* up. This generally results in a low quality image.
|
||||
*
|
||||
* @return
|
||||
* TRUE on success, FALSE on failure.
|
||||
*
|
||||
* @see image_dimensions_scale()
|
||||
* @see image_load()
|
||||
* @see image_scale_and_crop()
|
||||
*/
|
||||
function image_scale(stdClass $image, $width = NULL, $height = NULL, $upscale = FALSE) {
|
||||
$dimensions = $image->info;
|
||||
|
||||
// Scale the dimensions - if they don't change then just return success.
|
||||
if (!image_dimensions_scale($dimensions, $width, $height, $upscale)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return image_resize($image, $dimensions['width'], $dimensions['height']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes an image to the given dimensions (ignoring aspect ratio).
|
||||
*
|
||||
* @param $image
|
||||
* An image object returned by image_load().
|
||||
* @param $width
|
||||
* The target width, in pixels.
|
||||
* @param $height
|
||||
* The target height, in pixels.
|
||||
*
|
||||
* @return
|
||||
* TRUE on success, FALSE on failure.
|
||||
*
|
||||
* @see image_load()
|
||||
* @see image_gd_resize()
|
||||
*/
|
||||
function image_resize(stdClass $image, $width, $height) {
|
||||
$width = (int) round($width);
|
||||
$height = (int) round($height);
|
||||
|
||||
return image_toolkit_invoke('resize', $image, array($width, $height));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates an image by the given number of degrees.
|
||||
*
|
||||
* @param $image
|
||||
* An image object returned by image_load().
|
||||
* @param $degrees
|
||||
* The number of (clockwise) degrees to rotate the image.
|
||||
* @param $background
|
||||
* An hexadecimal integer specifying the background color to use for the
|
||||
* uncovered area of the image after the rotation. E.g. 0x000000 for black,
|
||||
* 0xff00ff for magenta, and 0xffffff for white. For images that support
|
||||
* transparency, this will default to transparent. Otherwise it will
|
||||
* be white.
|
||||
*
|
||||
* @return
|
||||
* TRUE on success, FALSE on failure.
|
||||
*
|
||||
* @see image_load()
|
||||
* @see image_gd_rotate()
|
||||
*/
|
||||
function image_rotate(stdClass $image, $degrees, $background = NULL) {
|
||||
return image_toolkit_invoke('rotate', $image, array($degrees, $background));
|
||||
}
|
||||
|
||||
/**
|
||||
* Crops an image to a rectangle specified by the given dimensions.
|
||||
*
|
||||
* @param $image
|
||||
* An image object returned by image_load().
|
||||
* @param $x
|
||||
* The top left coordinate, in pixels, of the crop area (x axis value).
|
||||
* @param $y
|
||||
* The top left coordinate, in pixels, of the crop area (y axis value).
|
||||
* @param $width
|
||||
* The target width, in pixels.
|
||||
* @param $height
|
||||
* The target height, in pixels.
|
||||
*
|
||||
* @return
|
||||
* TRUE on success, FALSE on failure.
|
||||
*
|
||||
* @see image_load()
|
||||
* @see image_scale_and_crop()
|
||||
* @see image_gd_crop()
|
||||
*/
|
||||
function image_crop(stdClass $image, $x, $y, $width, $height) {
|
||||
$aspect = $image->info['height'] / $image->info['width'];
|
||||
if (empty($height)) $height = $width / $aspect;
|
||||
if (empty($width)) $width = $height * $aspect;
|
||||
|
||||
$width = (int) round($width);
|
||||
$height = (int) round($height);
|
||||
|
||||
return image_toolkit_invoke('crop', $image, array($x, $y, $width, $height));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an image to grayscale.
|
||||
*
|
||||
* @param $image
|
||||
* An image object returned by image_load().
|
||||
*
|
||||
* @return
|
||||
* TRUE on success, FALSE on failure.
|
||||
*
|
||||
* @see image_load()
|
||||
* @see image_gd_desaturate()
|
||||
*/
|
||||
function image_desaturate(stdClass $image) {
|
||||
return image_toolkit_invoke('desaturate', $image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image file and returns an image object.
|
||||
*
|
||||
* Any changes to the file are not saved until image_save() is called.
|
||||
*
|
||||
* @param $file
|
||||
* Path to an image file.
|
||||
* @param $toolkit
|
||||
* An optional, image toolkit name to override the default.
|
||||
*
|
||||
* @return
|
||||
* An image object or FALSE if there was a problem loading the file. The
|
||||
* image object has the following properties:
|
||||
* - 'source' - The original file path.
|
||||
* - 'info' - The array of information returned by image_get_info()
|
||||
* - 'toolkit' - The name of the image toolkit requested when the image was
|
||||
* loaded.
|
||||
* Image toolkits may add additional properties. The caller is advised not to
|
||||
* monkey about with them.
|
||||
*
|
||||
* @see image_save()
|
||||
* @see image_get_info()
|
||||
* @see image_get_available_toolkits()
|
||||
* @see image_gd_load()
|
||||
*/
|
||||
function image_load($file, $toolkit = FALSE) {
|
||||
if (!$toolkit) {
|
||||
$toolkit = image_get_toolkit();
|
||||
}
|
||||
if ($toolkit) {
|
||||
$image = new stdClass();
|
||||
$image->source = $file;
|
||||
$image->info = image_get_info($file, $toolkit);
|
||||
if (isset($image->info) && is_array($image->info)) {
|
||||
$image->toolkit = $toolkit;
|
||||
if (image_toolkit_invoke('load', $image)) {
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the image and saves the changes to a file.
|
||||
*
|
||||
* @param $image
|
||||
* An image object returned by image_load(). The object's 'info' property
|
||||
* will be updated if the file is saved successfully.
|
||||
* @param $destination
|
||||
* Destination path where the image should be saved. If it is empty the
|
||||
* original image file will be overwritten.
|
||||
*
|
||||
* @return
|
||||
* TRUE on success, FALSE on failure.
|
||||
*
|
||||
* @see image_load()
|
||||
* @see image_gd_save()
|
||||
*/
|
||||
function image_save(stdClass $image, $destination = NULL) {
|
||||
if (empty($destination)) {
|
||||
$destination = $image->source;
|
||||
}
|
||||
if ($return = image_toolkit_invoke('save', $image, array($destination))) {
|
||||
// Clear the cached file size and refresh the image information.
|
||||
clearstatcache();
|
||||
$image->info = image_get_info($destination, $image->toolkit);
|
||||
|
||||
if (drupal_chmod($destination)) {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup image".
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,486 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides a list of countries and languages based on ISO standards.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get an array of all country code => country name pairs.
|
||||
*
|
||||
* Get an array of all country code => country name pairs as laid out
|
||||
* in ISO 3166-1 alpha-2.
|
||||
* Grabbed from location project (http://drupal.org/project/location).
|
||||
* @return
|
||||
* An array of all country code => country name pairs.
|
||||
*/
|
||||
function _country_get_predefined_list() {
|
||||
static $countries;
|
||||
|
||||
if (isset($countries)) {
|
||||
return $countries;
|
||||
}
|
||||
$t = get_t();
|
||||
|
||||
$countries = array(
|
||||
'AD' => $t('Andorra'),
|
||||
'AE' => $t('United Arab Emirates'),
|
||||
'AF' => $t('Afghanistan'),
|
||||
'AG' => $t('Antigua and Barbuda'),
|
||||
'AI' => $t('Anguilla'),
|
||||
'AL' => $t('Albania'),
|
||||
'AM' => $t('Armenia'),
|
||||
'AN' => $t('Netherlands Antilles'),
|
||||
'AO' => $t('Angola'),
|
||||
'AQ' => $t('Antarctica'),
|
||||
'AR' => $t('Argentina'),
|
||||
'AS' => $t('American Samoa'),
|
||||
'AT' => $t('Austria'),
|
||||
'AU' => $t('Australia'),
|
||||
'AW' => $t('Aruba'),
|
||||
'AX' => $t('Aland Islands'),
|
||||
'AZ' => $t('Azerbaijan'),
|
||||
'BA' => $t('Bosnia and Herzegovina'),
|
||||
'BB' => $t('Barbados'),
|
||||
'BD' => $t('Bangladesh'),
|
||||
'BE' => $t('Belgium'),
|
||||
'BF' => $t('Burkina Faso'),
|
||||
'BG' => $t('Bulgaria'),
|
||||
'BH' => $t('Bahrain'),
|
||||
'BI' => $t('Burundi'),
|
||||
'BJ' => $t('Benin'),
|
||||
'BL' => $t('Saint Barthélemy'),
|
||||
'BM' => $t('Bermuda'),
|
||||
'BN' => $t('Brunei'),
|
||||
'BO' => $t('Bolivia'),
|
||||
'BQ' => $t('Caribbean Netherlands'),
|
||||
'BR' => $t('Brazil'),
|
||||
'BS' => $t('Bahamas'),
|
||||
'BT' => $t('Bhutan'),
|
||||
'BV' => $t('Bouvet Island'),
|
||||
'BW' => $t('Botswana'),
|
||||
'BY' => $t('Belarus'),
|
||||
'BZ' => $t('Belize'),
|
||||
'CA' => $t('Canada'),
|
||||
'CC' => $t('Cocos (Keeling) Islands'),
|
||||
'CD' => $t('Congo (Kinshasa)'),
|
||||
'CF' => $t('Central African Republic'),
|
||||
'CG' => $t('Congo (Brazzaville)'),
|
||||
'CH' => $t('Switzerland'),
|
||||
'CI' => $t('Ivory Coast'),
|
||||
'CK' => $t('Cook Islands'),
|
||||
'CL' => $t('Chile'),
|
||||
'CM' => $t('Cameroon'),
|
||||
'CN' => $t('China'),
|
||||
'CO' => $t('Colombia'),
|
||||
'CR' => $t('Costa Rica'),
|
||||
'CU' => $t('Cuba'),
|
||||
'CV' => $t('Cape Verde'),
|
||||
'CW' => $t('Curaçao'),
|
||||
'CX' => $t('Christmas Island'),
|
||||
'CY' => $t('Cyprus'),
|
||||
'CZ' => $t('Czech Republic'),
|
||||
'DE' => $t('Germany'),
|
||||
'DJ' => $t('Djibouti'),
|
||||
'DK' => $t('Denmark'),
|
||||
'DM' => $t('Dominica'),
|
||||
'DO' => $t('Dominican Republic'),
|
||||
'DZ' => $t('Algeria'),
|
||||
'EC' => $t('Ecuador'),
|
||||
'EE' => $t('Estonia'),
|
||||
'EG' => $t('Egypt'),
|
||||
'EH' => $t('Western Sahara'),
|
||||
'ER' => $t('Eritrea'),
|
||||
'ES' => $t('Spain'),
|
||||
'ET' => $t('Ethiopia'),
|
||||
'FI' => $t('Finland'),
|
||||
'FJ' => $t('Fiji'),
|
||||
'FK' => $t('Falkland Islands'),
|
||||
'FM' => $t('Micronesia'),
|
||||
'FO' => $t('Faroe Islands'),
|
||||
'FR' => $t('France'),
|
||||
'GA' => $t('Gabon'),
|
||||
'GB' => $t('United Kingdom'),
|
||||
'GD' => $t('Grenada'),
|
||||
'GE' => $t('Georgia'),
|
||||
'GF' => $t('French Guiana'),
|
||||
'GG' => $t('Guernsey'),
|
||||
'GH' => $t('Ghana'),
|
||||
'GI' => $t('Gibraltar'),
|
||||
'GL' => $t('Greenland'),
|
||||
'GM' => $t('Gambia'),
|
||||
'GN' => $t('Guinea'),
|
||||
'GP' => $t('Guadeloupe'),
|
||||
'GQ' => $t('Equatorial Guinea'),
|
||||
'GR' => $t('Greece'),
|
||||
'GS' => $t('South Georgia and the South Sandwich Islands'),
|
||||
'GT' => $t('Guatemala'),
|
||||
'GU' => $t('Guam'),
|
||||
'GW' => $t('Guinea-Bissau'),
|
||||
'GY' => $t('Guyana'),
|
||||
'HK' => $t('Hong Kong S.A.R., China'),
|
||||
'HM' => $t('Heard Island and McDonald Islands'),
|
||||
'HN' => $t('Honduras'),
|
||||
'HR' => $t('Croatia'),
|
||||
'HT' => $t('Haiti'),
|
||||
'HU' => $t('Hungary'),
|
||||
'ID' => $t('Indonesia'),
|
||||
'IE' => $t('Ireland'),
|
||||
'IL' => $t('Israel'),
|
||||
'IM' => $t('Isle of Man'),
|
||||
'IN' => $t('India'),
|
||||
'IO' => $t('British Indian Ocean Territory'),
|
||||
'IQ' => $t('Iraq'),
|
||||
'IR' => $t('Iran'),
|
||||
'IS' => $t('Iceland'),
|
||||
'IT' => $t('Italy'),
|
||||
'JE' => $t('Jersey'),
|
||||
'JM' => $t('Jamaica'),
|
||||
'JO' => $t('Jordan'),
|
||||
'JP' => $t('Japan'),
|
||||
'KE' => $t('Kenya'),
|
||||
'KG' => $t('Kyrgyzstan'),
|
||||
'KH' => $t('Cambodia'),
|
||||
'KI' => $t('Kiribati'),
|
||||
'KM' => $t('Comoros'),
|
||||
'KN' => $t('Saint Kitts and Nevis'),
|
||||
'KP' => $t('North Korea'),
|
||||
'KR' => $t('South Korea'),
|
||||
'KW' => $t('Kuwait'),
|
||||
'KY' => $t('Cayman Islands'),
|
||||
'KZ' => $t('Kazakhstan'),
|
||||
'LA' => $t('Laos'),
|
||||
'LB' => $t('Lebanon'),
|
||||
'LC' => $t('Saint Lucia'),
|
||||
'LI' => $t('Liechtenstein'),
|
||||
'LK' => $t('Sri Lanka'),
|
||||
'LR' => $t('Liberia'),
|
||||
'LS' => $t('Lesotho'),
|
||||
'LT' => $t('Lithuania'),
|
||||
'LU' => $t('Luxembourg'),
|
||||
'LV' => $t('Latvia'),
|
||||
'LY' => $t('Libya'),
|
||||
'MA' => $t('Morocco'),
|
||||
'MC' => $t('Monaco'),
|
||||
'MD' => $t('Moldova'),
|
||||
'ME' => $t('Montenegro'),
|
||||
'MF' => $t('Saint Martin (French part)'),
|
||||
'MG' => $t('Madagascar'),
|
||||
'MH' => $t('Marshall Islands'),
|
||||
'MK' => $t('Macedonia'),
|
||||
'ML' => $t('Mali'),
|
||||
'MM' => $t('Myanmar'),
|
||||
'MN' => $t('Mongolia'),
|
||||
'MO' => $t('Macao S.A.R., China'),
|
||||
'MP' => $t('Northern Mariana Islands'),
|
||||
'MQ' => $t('Martinique'),
|
||||
'MR' => $t('Mauritania'),
|
||||
'MS' => $t('Montserrat'),
|
||||
'MT' => $t('Malta'),
|
||||
'MU' => $t('Mauritius'),
|
||||
'MV' => $t('Maldives'),
|
||||
'MW' => $t('Malawi'),
|
||||
'MX' => $t('Mexico'),
|
||||
'MY' => $t('Malaysia'),
|
||||
'MZ' => $t('Mozambique'),
|
||||
'NA' => $t('Namibia'),
|
||||
'NC' => $t('New Caledonia'),
|
||||
'NE' => $t('Niger'),
|
||||
'NF' => $t('Norfolk Island'),
|
||||
'NG' => $t('Nigeria'),
|
||||
'NI' => $t('Nicaragua'),
|
||||
'NL' => $t('Netherlands'),
|
||||
'NO' => $t('Norway'),
|
||||
'NP' => $t('Nepal'),
|
||||
'NR' => $t('Nauru'),
|
||||
'NU' => $t('Niue'),
|
||||
'NZ' => $t('New Zealand'),
|
||||
'OM' => $t('Oman'),
|
||||
'PA' => $t('Panama'),
|
||||
'PE' => $t('Peru'),
|
||||
'PF' => $t('French Polynesia'),
|
||||
'PG' => $t('Papua New Guinea'),
|
||||
'PH' => $t('Philippines'),
|
||||
'PK' => $t('Pakistan'),
|
||||
'PL' => $t('Poland'),
|
||||
'PM' => $t('Saint Pierre and Miquelon'),
|
||||
'PN' => $t('Pitcairn'),
|
||||
'PR' => $t('Puerto Rico'),
|
||||
'PS' => $t('Palestinian Territory'),
|
||||
'PT' => $t('Portugal'),
|
||||
'PW' => $t('Palau'),
|
||||
'PY' => $t('Paraguay'),
|
||||
'QA' => $t('Qatar'),
|
||||
'RE' => $t('Reunion'),
|
||||
'RO' => $t('Romania'),
|
||||
'RS' => $t('Serbia'),
|
||||
'RU' => $t('Russia'),
|
||||
'RW' => $t('Rwanda'),
|
||||
'SA' => $t('Saudi Arabia'),
|
||||
'SB' => $t('Solomon Islands'),
|
||||
'SC' => $t('Seychelles'),
|
||||
'SD' => $t('Sudan'),
|
||||
'SE' => $t('Sweden'),
|
||||
'SG' => $t('Singapore'),
|
||||
'SH' => $t('Saint Helena'),
|
||||
'SI' => $t('Slovenia'),
|
||||
'SJ' => $t('Svalbard and Jan Mayen'),
|
||||
'SK' => $t('Slovakia'),
|
||||
'SL' => $t('Sierra Leone'),
|
||||
'SM' => $t('San Marino'),
|
||||
'SN' => $t('Senegal'),
|
||||
'SO' => $t('Somalia'),
|
||||
'SR' => $t('Suriname'),
|
||||
'SS' => $t('South Sudan'),
|
||||
'ST' => $t('Sao Tome and Principe'),
|
||||
'SV' => $t('El Salvador'),
|
||||
'SX' => $t('Sint Maarten'),
|
||||
'SY' => $t('Syria'),
|
||||
'SZ' => $t('Swaziland'),
|
||||
'TC' => $t('Turks and Caicos Islands'),
|
||||
'TD' => $t('Chad'),
|
||||
'TF' => $t('French Southern Territories'),
|
||||
'TG' => $t('Togo'),
|
||||
'TH' => $t('Thailand'),
|
||||
'TJ' => $t('Tajikistan'),
|
||||
'TK' => $t('Tokelau'),
|
||||
'TL' => $t('Timor-Leste'),
|
||||
'TM' => $t('Turkmenistan'),
|
||||
'TN' => $t('Tunisia'),
|
||||
'TO' => $t('Tonga'),
|
||||
'TR' => $t('Turkey'),
|
||||
'TT' => $t('Trinidad and Tobago'),
|
||||
'TV' => $t('Tuvalu'),
|
||||
'TW' => $t('Taiwan'),
|
||||
'TZ' => $t('Tanzania'),
|
||||
'UA' => $t('Ukraine'),
|
||||
'UG' => $t('Uganda'),
|
||||
'UM' => $t('United States Minor Outlying Islands'),
|
||||
'US' => $t('United States'),
|
||||
'UY' => $t('Uruguay'),
|
||||
'UZ' => $t('Uzbekistan'),
|
||||
'VA' => $t('Vatican'),
|
||||
'VC' => $t('Saint Vincent and the Grenadines'),
|
||||
'VE' => $t('Venezuela'),
|
||||
'VG' => $t('British Virgin Islands'),
|
||||
'VI' => $t('U.S. Virgin Islands'),
|
||||
'VN' => $t('Vietnam'),
|
||||
'VU' => $t('Vanuatu'),
|
||||
'WF' => $t('Wallis and Futuna'),
|
||||
'WS' => $t('Samoa'),
|
||||
'YE' => $t('Yemen'),
|
||||
'YT' => $t('Mayotte'),
|
||||
'ZA' => $t('South Africa'),
|
||||
'ZM' => $t('Zambia'),
|
||||
'ZW' => $t('Zimbabwe'),
|
||||
);
|
||||
|
||||
// Sort the list.
|
||||
natcasesort($countries);
|
||||
|
||||
return $countries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup locale-api-predefined List of predefined languages
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Some of the common languages with their English and native names
|
||||
*
|
||||
* Based on ISO 639 and http://people.w3.org/rishida/names/languages.html
|
||||
*/
|
||||
function _locale_get_predefined_list() {
|
||||
return array(
|
||||
'aa' => array('Afar'),
|
||||
'ab' => array('Abkhazian', 'аҧсуа бызшәа'),
|
||||
'ae' => array('Avestan'),
|
||||
'af' => array('Afrikaans'),
|
||||
'ak' => array('Akan'),
|
||||
'am' => array('Amharic', 'አማርኛ'),
|
||||
'ar' => array('Arabic', /* Left-to-right marker "" */ 'العربية', LANGUAGE_RTL),
|
||||
'as' => array('Assamese'),
|
||||
'ast' => array('Asturian'),
|
||||
'av' => array('Avar'),
|
||||
'ay' => array('Aymara'),
|
||||
'az' => array('Azerbaijani', 'azərbaycan'),
|
||||
'ba' => array('Bashkir'),
|
||||
'be' => array('Belarusian', 'Беларуская'),
|
||||
'bg' => array('Bulgarian', 'Български'),
|
||||
'bh' => array('Bihari'),
|
||||
'bi' => array('Bislama'),
|
||||
'bm' => array('Bambara', 'Bamanankan'),
|
||||
'bn' => array('Bengali'),
|
||||
'bo' => array('Tibetan'),
|
||||
'br' => array('Breton'),
|
||||
'bs' => array('Bosnian', 'Bosanski'),
|
||||
'ca' => array('Catalan', 'Català'),
|
||||
'ce' => array('Chechen'),
|
||||
'ch' => array('Chamorro'),
|
||||
'co' => array('Corsican'),
|
||||
'cr' => array('Cree'),
|
||||
'cs' => array('Czech', 'Čeština'),
|
||||
'cu' => array('Old Slavonic'),
|
||||
'cv' => array('Chuvash'),
|
||||
'cy' => array('Welsh', 'Cymraeg'),
|
||||
'da' => array('Danish', 'Dansk'),
|
||||
'de' => array('German', 'Deutsch'),
|
||||
'dv' => array('Maldivian'),
|
||||
'dz' => array('Bhutani'),
|
||||
'ee' => array('Ewe', 'Ɛʋɛ'),
|
||||
'el' => array('Greek', 'Ελληνικά'),
|
||||
'en' => array('English'),
|
||||
'en-gb' => array('English, British'),
|
||||
'eo' => array('Esperanto'),
|
||||
'es' => array('Spanish', 'Español'),
|
||||
'et' => array('Estonian', 'Eesti'),
|
||||
'eu' => array('Basque', 'Euskera'),
|
||||
'fa' => array('Persian', /* Left-to-right marker "" */ 'فارسی', LANGUAGE_RTL),
|
||||
'ff' => array('Fulah', 'Fulfulde'),
|
||||
'fi' => array('Finnish', 'Suomi'),
|
||||
'fil' => array('Filipino'),
|
||||
'fj' => array('Fiji'),
|
||||
'fo' => array('Faeroese'),
|
||||
'fr' => array('French', 'Français'),
|
||||
'fy' => array('Frisian', 'Frysk'),
|
||||
'ga' => array('Irish', 'Gaeilge'),
|
||||
'gd' => array('Scots Gaelic'),
|
||||
'gl' => array('Galician', 'Galego'),
|
||||
'gn' => array('Guarani'),
|
||||
'gsw-berne' => array('Swiss German'),
|
||||
'gu' => array('Gujarati'),
|
||||
'gv' => array('Manx'),
|
||||
'ha' => array('Hausa'),
|
||||
'he' => array('Hebrew', /* Left-to-right marker "" */ 'עברית', LANGUAGE_RTL),
|
||||
'hi' => array('Hindi', 'हिन्दी'),
|
||||
'ho' => array('Hiri Motu'),
|
||||
'hr' => array('Croatian', 'Hrvatski'),
|
||||
'ht' => array('Haitian Creole'),
|
||||
'hu' => array('Hungarian', 'Magyar'),
|
||||
'hy' => array('Armenian', 'Հայերեն'),
|
||||
'hz' => array('Herero'),
|
||||
'ia' => array('Interlingua'),
|
||||
'id' => array('Indonesian', 'Bahasa Indonesia'),
|
||||
'ie' => array('Interlingue'),
|
||||
'ig' => array('Igbo'),
|
||||
'ik' => array('Inupiak'),
|
||||
'is' => array('Icelandic', 'Íslenska'),
|
||||
'it' => array('Italian', 'Italiano'),
|
||||
'iu' => array('Inuktitut'),
|
||||
'ja' => array('Japanese', '日本語'),
|
||||
'jv' => array('Javanese'),
|
||||
'ka' => array('Georgian'),
|
||||
'kg' => array('Kongo'),
|
||||
'ki' => array('Kikuyu'),
|
||||
'kj' => array('Kwanyama'),
|
||||
'kk' => array('Kazakh', 'Қазақ'),
|
||||
'kl' => array('Greenlandic'),
|
||||
'km' => array('Cambodian'),
|
||||
'kn' => array('Kannada', 'ಕನ್ನಡ'),
|
||||
'ko' => array('Korean', '한국어'),
|
||||
'kr' => array('Kanuri'),
|
||||
'ks' => array('Kashmiri'),
|
||||
'ku' => array('Kurdish', 'Kurdî'),
|
||||
'kv' => array('Komi'),
|
||||
'kw' => array('Cornish'),
|
||||
'ky' => array('Kyrgyz', 'Кыргызча'),
|
||||
'la' => array('Latin', 'Latina'),
|
||||
'lb' => array('Luxembourgish'),
|
||||
'lg' => array('Luganda'),
|
||||
'ln' => array('Lingala'),
|
||||
'lo' => array('Laothian'),
|
||||
'lt' => array('Lithuanian', 'Lietuvių'),
|
||||
'lv' => array('Latvian', 'Latviešu'),
|
||||
'mg' => array('Malagasy'),
|
||||
'mh' => array('Marshallese'),
|
||||
'mi' => array('Māori'),
|
||||
'mk' => array('Macedonian', 'Македонски'),
|
||||
'ml' => array('Malayalam', 'മലയാളം'),
|
||||
'mn' => array('Mongolian'),
|
||||
'mo' => array('Moldavian'),
|
||||
'mr' => array('Marathi'),
|
||||
'ms' => array('Malay', 'Bahasa Melayu'),
|
||||
'mt' => array('Maltese', 'Malti'),
|
||||
'my' => array('Burmese'),
|
||||
'na' => array('Nauru'),
|
||||
'nd' => array('North Ndebele'),
|
||||
'ne' => array('Nepali'),
|
||||
'ng' => array('Ndonga'),
|
||||
'nl' => array('Dutch', 'Nederlands'),
|
||||
'nb' => array('Norwegian Bokmål', 'Bokmål'),
|
||||
'nn' => array('Norwegian Nynorsk', 'Nynorsk'),
|
||||
'nr' => array('South Ndebele'),
|
||||
'nv' => array('Navajo'),
|
||||
'ny' => array('Chichewa'),
|
||||
'oc' => array('Occitan'),
|
||||
'om' => array('Oromo'),
|
||||
'or' => array('Oriya'),
|
||||
'os' => array('Ossetian'),
|
||||
'pa' => array('Punjabi'),
|
||||
'pi' => array('Pali'),
|
||||
'pl' => array('Polish', 'Polski'),
|
||||
'ps' => array('Pashto', /* Left-to-right marker "" */ 'پښتو', LANGUAGE_RTL),
|
||||
'pt' => array('Portuguese, International'),
|
||||
'pt-pt' => array('Portuguese, Portugal', 'Português'),
|
||||
'pt-br' => array('Portuguese, Brazil', 'Português'),
|
||||
'qu' => array('Quechua'),
|
||||
'rm' => array('Rhaeto-Romance'),
|
||||
'rn' => array('Kirundi'),
|
||||
'ro' => array('Romanian', 'Română'),
|
||||
'ru' => array('Russian', 'Русский'),
|
||||
'rw' => array('Kinyarwanda'),
|
||||
'sa' => array('Sanskrit'),
|
||||
'sc' => array('Sardinian'),
|
||||
'sco' => array('Scots'),
|
||||
'sd' => array('Sindhi'),
|
||||
'se' => array('Northern Sami'),
|
||||
'sg' => array('Sango'),
|
||||
'sh' => array('Serbo-Croatian'),
|
||||
'si' => array('Sinhala', 'සිංහල'),
|
||||
'sk' => array('Slovak', 'Slovenčina'),
|
||||
'sl' => array('Slovenian', 'Slovenščina'),
|
||||
'sm' => array('Samoan'),
|
||||
'sn' => array('Shona'),
|
||||
'so' => array('Somali'),
|
||||
'sq' => array('Albanian', 'Shqip'),
|
||||
'sr' => array('Serbian', 'Српски'),
|
||||
'ss' => array('Siswati'),
|
||||
'st' => array('Sesotho'),
|
||||
'su' => array('Sudanese'),
|
||||
'sv' => array('Swedish', 'Svenska'),
|
||||
'sw' => array('Swahili', 'Kiswahili'),
|
||||
'ta' => array('Tamil', 'தமிழ்'),
|
||||
'te' => array('Telugu', 'తెలుగు'),
|
||||
'tg' => array('Tajik'),
|
||||
'th' => array('Thai', 'ภาษาไทย'),
|
||||
'ti' => array('Tigrinya'),
|
||||
'tk' => array('Turkmen'),
|
||||
'tl' => array('Tagalog'),
|
||||
'tn' => array('Setswana'),
|
||||
'to' => array('Tonga'),
|
||||
'tr' => array('Turkish', 'Türkçe'),
|
||||
'ts' => array('Tsonga'),
|
||||
'tt' => array('Tatar', 'Tatarça'),
|
||||
'tw' => array('Twi'),
|
||||
'ty' => array('Tahitian'),
|
||||
'ug' => array('Uyghur'),
|
||||
'uk' => array('Ukrainian', 'Українська'),
|
||||
'ur' => array('Urdu', /* Left-to-right marker "" */ 'اردو', LANGUAGE_RTL),
|
||||
'uz' => array('Uzbek', "o'zbek"),
|
||||
've' => array('Venda'),
|
||||
'vi' => array('Vietnamese', 'Tiếng Việt'),
|
||||
'wo' => array('Wolof'),
|
||||
'xh' => array('Xhosa', 'isiXhosa'),
|
||||
'xx-lolspeak' => array('Lolspeak'),
|
||||
'yi' => array('Yiddish'),
|
||||
'yo' => array('Yoruba', 'Yorùbá'),
|
||||
'za' => array('Zhuang'),
|
||||
'zh-hans' => array('Chinese, Simplified', '简体中文'),
|
||||
'zh-hant' => array('Chinese, Traditional', '繁體中文'),
|
||||
'zu' => array('Zulu', 'isiZulu'),
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @} End of "locale-api-languages-predefined"
|
||||
*/
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides a helper to properly encode HTML-safe JSON prior to PHP 5.3.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Encodes a PHP variable to HTML-safe JSON for PHP versions below 5.3.0.
|
||||
*
|
||||
* @see drupal_json_encode()
|
||||
*/
|
||||
function drupal_json_encode_helper($var) {
|
||||
switch (gettype($var)) {
|
||||
case 'boolean':
|
||||
return $var ? 'true' : 'false'; // Lowercase necessary!
|
||||
|
||||
case 'integer':
|
||||
case 'double':
|
||||
return $var;
|
||||
|
||||
case 'resource':
|
||||
case 'string':
|
||||
// Always use Unicode escape sequences (\u0022) over JSON escape
|
||||
// sequences (\") to prevent browsers interpreting these as
|
||||
// special characters.
|
||||
$replace_pairs = array(
|
||||
// ", \ and U+0000 - U+001F must be escaped according to RFC 4627.
|
||||
'\\' => '\u005C',
|
||||
'"' => '\u0022',
|
||||
"\x00" => '\u0000',
|
||||
"\x01" => '\u0001',
|
||||
"\x02" => '\u0002',
|
||||
"\x03" => '\u0003',
|
||||
"\x04" => '\u0004',
|
||||
"\x05" => '\u0005',
|
||||
"\x06" => '\u0006',
|
||||
"\x07" => '\u0007',
|
||||
"\x08" => '\u0008',
|
||||
"\x09" => '\u0009',
|
||||
"\x0a" => '\u000A',
|
||||
"\x0b" => '\u000B',
|
||||
"\x0c" => '\u000C',
|
||||
"\x0d" => '\u000D',
|
||||
"\x0e" => '\u000E',
|
||||
"\x0f" => '\u000F',
|
||||
"\x10" => '\u0010',
|
||||
"\x11" => '\u0011',
|
||||
"\x12" => '\u0012',
|
||||
"\x13" => '\u0013',
|
||||
"\x14" => '\u0014',
|
||||
"\x15" => '\u0015',
|
||||
"\x16" => '\u0016',
|
||||
"\x17" => '\u0017',
|
||||
"\x18" => '\u0018',
|
||||
"\x19" => '\u0019',
|
||||
"\x1a" => '\u001A',
|
||||
"\x1b" => '\u001B',
|
||||
"\x1c" => '\u001C',
|
||||
"\x1d" => '\u001D',
|
||||
"\x1e" => '\u001E',
|
||||
"\x1f" => '\u001F',
|
||||
// Prevent browsers from interpreting these as as special.
|
||||
"'" => '\u0027',
|
||||
'<' => '\u003C',
|
||||
'>' => '\u003E',
|
||||
'&' => '\u0026',
|
||||
// Prevent browsers from interpreting the solidus as special and
|
||||
// non-compliant JSON parsers from interpreting // as a comment.
|
||||
'/' => '\u002F',
|
||||
// While these are allowed unescaped according to ECMA-262, section
|
||||
// 15.12.2, they cause problems in some JSON parsers.
|
||||
"\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator.
|
||||
"\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator.
|
||||
);
|
||||
|
||||
return '"' . strtr($var, $replace_pairs) . '"';
|
||||
|
||||
case 'array':
|
||||
// Arrays in JSON can't be associative. If the array is empty or if it
|
||||
// has sequential whole number keys starting with 0, it's not associative
|
||||
// so we can go ahead and convert it as an array.
|
||||
if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
|
||||
$output = array();
|
||||
foreach ($var as $v) {
|
||||
$output[] = drupal_json_encode_helper($v);
|
||||
}
|
||||
return '[ ' . implode(', ', $output) . ' ]';
|
||||
}
|
||||
// Otherwise, fall through to convert the array as an object.
|
||||
|
||||
case 'object':
|
||||
$output = array();
|
||||
foreach ($var as $k => $v) {
|
||||
$output[] = drupal_json_encode_helper(strval($k)) . ':' . drupal_json_encode_helper($v);
|
||||
}
|
||||
return '{' . implode(', ', $output) . '}';
|
||||
|
||||
default:
|
||||
return 'null';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Language Negotiation API.
|
||||
*
|
||||
* @see http://drupal.org/node/1497272
|
||||
*/
|
||||
|
||||
/**
|
||||
* No language negotiation. The default language is used.
|
||||
*/
|
||||
define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default');
|
||||
|
||||
/**
|
||||
* @defgroup language_negotiation Language Negotiation API functionality
|
||||
* @{
|
||||
* Functions to customize the language types and the negotiation process.
|
||||
*
|
||||
* The language negotiation API is based on two major concepts:
|
||||
* - Language types: types of translatable data (the types of data that a user
|
||||
* can view or request).
|
||||
* - Language negotiation providers: functions for determining which language to
|
||||
* use to present a particular piece of data to the user.
|
||||
* Both language types and language negotiation providers are customizable.
|
||||
*
|
||||
* Drupal defines three built-in language types:
|
||||
* - Interface language: The page's main language, used to present translated
|
||||
* user interface elements such as titles, labels, help text, and messages.
|
||||
* - Content language: The language used to present content that is available
|
||||
* in more than one language (see
|
||||
* @link field_language Field Language API @endlink for details).
|
||||
* - URL language: The language associated with URLs. When generating a URL,
|
||||
* this value will be used by url() as a default if no explicit preference is
|
||||
* provided.
|
||||
* Modules can define additional language types through
|
||||
* hook_language_types_info(), and alter existing language type definitions
|
||||
* through hook_language_types_info_alter().
|
||||
*
|
||||
* Language types may be configurable or fixed. The language negotiation
|
||||
* providers associated with a configurable language type can be explicitly
|
||||
* set through the user interface. A fixed language type has predetermined
|
||||
* (module-defined) language negotiation settings and, thus, does not appear in
|
||||
* the configuration page. Here is a code snippet that makes the content
|
||||
* language (which by default inherits the interface language's values)
|
||||
* configurable:
|
||||
* @code
|
||||
* function mymodule_language_types_info_alter(&$language_types) {
|
||||
* unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Every language type can have a different set of language negotiation
|
||||
* providers assigned to it. Different language types often share the same
|
||||
* language negotiation settings, but they can have independent settings if
|
||||
* needed. If two language types are configured the same way, their language
|
||||
* switcher configuration will be functionally identical and the same settings
|
||||
* will act on both language types.
|
||||
*
|
||||
* Drupal defines the following built-in language negotiation providers:
|
||||
* - URL: Determine the language from the URL (path prefix or domain).
|
||||
* - Session: Determine the language from a request/session parameter.
|
||||
* - User: Follow the user's language preference.
|
||||
* - Browser: Determine the language from the browser's language settings.
|
||||
* - Default language: Use the default site language.
|
||||
* Language negotiation providers are simple callback functions that implement a
|
||||
* particular logic to return a language code. For instance, the URL provider
|
||||
* searches for a valid path prefix or domain name in the current request URL.
|
||||
* If a language negotiation provider does not return a valid language code, the
|
||||
* next provider associated to the language type (based on provider weight) is
|
||||
* invoked.
|
||||
*
|
||||
* Modules can define additional language negotiation providers through
|
||||
* hook_language_negotiation_info(), and alter existing providers through
|
||||
* hook_language_negotiation_info_alter(). Here is an example snippet that lets
|
||||
* path prefixes be ignored for administrative paths:
|
||||
* @code
|
||||
* function mymodule_language_negotiation_info_alter(&$negotiation_info) {
|
||||
* // Replace the core function with our own function.
|
||||
* module_load_include('language', 'inc', 'language.negotiation');
|
||||
* $negotiation_info[LANGUAGE_NEGOTIATION_URL]['callbacks']['language'] = 'mymodule_from_url';
|
||||
* $negotiation_info[LANGUAGE_NEGOTIATION_URL]['file'] = drupal_get_path('module', 'mymodule') . '/mymodule.module';
|
||||
* }
|
||||
*
|
||||
* function mymodule_from_url($languages) {
|
||||
* // Use the core URL language negotiation provider to get a valid language
|
||||
* // code.
|
||||
* module_load_include('language', 'inc', 'language.negotiation');
|
||||
* $langcode = language_from_url($languages);
|
||||
*
|
||||
* // If we are on an administrative path, override with the default language.
|
||||
* if (isset($_GET['q']) && strtok($_GET['q'], '/') == 'admin') {
|
||||
* return language_default()->langcode;
|
||||
* }
|
||||
* return $langcode;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* For more information, see
|
||||
* @link http://drupal.org/node/1497272 Language Negotiation API @endlink
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns all the defined language types.
|
||||
*
|
||||
* @return
|
||||
* An array of language type names. The name will be used as the global
|
||||
* variable name the language value will be stored in.
|
||||
*/
|
||||
function language_types_info() {
|
||||
$language_types = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($language_types)) {
|
||||
$language_types = module_invoke_all('language_types_info');
|
||||
// Let other modules alter the list of language types.
|
||||
drupal_alter('language_types_info', $language_types);
|
||||
}
|
||||
|
||||
return $language_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the configurable language types.
|
||||
*
|
||||
* A language type maybe configurable or fixed. A fixed language type is a type
|
||||
* whose language negotiation providers are module-defined and not altered
|
||||
* through the user interface.
|
||||
*
|
||||
* @param $stored
|
||||
* Optional. By default retrieves values from the 'language_types' variable to
|
||||
* avoid unnecessary hook invocations.
|
||||
* If set to FALSE retrieves values from the actual language type definitions.
|
||||
* This allows to react to alterations performed on the definitions by modules
|
||||
* installed after the 'language_types' variable is set.
|
||||
*
|
||||
* @return
|
||||
* An array of language type names.
|
||||
*/
|
||||
function language_types_configurable($stored = TRUE) {
|
||||
$configurable = &drupal_static(__FUNCTION__);
|
||||
|
||||
if ($stored && !isset($configurable)) {
|
||||
$types = variable_get('language_types', drupal_language_types());
|
||||
$configurable = array_keys(array_filter($types));
|
||||
}
|
||||
|
||||
if (!$stored) {
|
||||
$result = array();
|
||||
foreach (language_types_info() as $type => $info) {
|
||||
if (!isset($info['fixed'])) {
|
||||
$result[] = $type;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $configurable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the given language types.
|
||||
*
|
||||
* @param $types
|
||||
* An array of language types.
|
||||
*/
|
||||
function language_types_disable($types) {
|
||||
$enabled_types = variable_get('language_types', drupal_language_types());
|
||||
|
||||
foreach ($types as $type) {
|
||||
unset($enabled_types[$type]);
|
||||
}
|
||||
|
||||
variable_set('language_types', $enabled_types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the language type configuration.
|
||||
*/
|
||||
function language_types_set() {
|
||||
// Ensure that we are getting the defined language negotiation information. An
|
||||
// invocation of module_enable() or module_disable() could outdate the cached
|
||||
// information.
|
||||
drupal_static_reset('language_types_info');
|
||||
drupal_static_reset('language_negotiation_info');
|
||||
|
||||
// Determine which language types are configurable and which not by checking
|
||||
// whether the 'fixed' key is defined. Non-configurable (fixed) language types
|
||||
// have their language negotiation settings stored there.
|
||||
$defined_providers = language_negotiation_info();
|
||||
foreach (language_types_info() as $type => $info) {
|
||||
if (isset($info['fixed'])) {
|
||||
$language_types[$type] = FALSE;
|
||||
$negotiation = array();
|
||||
foreach ($info['fixed'] as $weight => $id) {
|
||||
if (isset($defined_providers[$id])) {
|
||||
$negotiation[$id] = $weight;
|
||||
}
|
||||
}
|
||||
language_negotiation_set($type, $negotiation);
|
||||
}
|
||||
else {
|
||||
$language_types[$type] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Save language types.
|
||||
variable_set('language_types', $language_types);
|
||||
|
||||
// Ensure that subsequent calls of language_types_configurable() return the
|
||||
// updated language type information.
|
||||
drupal_static_reset('language_types_configurable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a language negotiation provider is enabled for a language type.
|
||||
*
|
||||
* This has two possible behaviors:
|
||||
* - If $provider_id is given return its ID if enabled, FALSE otherwise.
|
||||
* - If no ID is passed the first enabled language negotiation provider is
|
||||
* returned.
|
||||
*
|
||||
* @param $type
|
||||
* The language negotiation provider type.
|
||||
* @param $provider_id
|
||||
* The language negotiation provider ID.
|
||||
*
|
||||
* @return
|
||||
* The provider ID if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
function language_negotiation_get($type, $provider_id = NULL) {
|
||||
$negotiation = variable_get("language_negotiation_$type", array());
|
||||
|
||||
if (empty($negotiation)) {
|
||||
return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE;
|
||||
}
|
||||
|
||||
if (empty($provider_id)) {
|
||||
return key($negotiation);
|
||||
}
|
||||
|
||||
if (isset($negotiation[$provider_id])) {
|
||||
return $provider_id;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the language negotiation provider is enabled for any language type.
|
||||
*
|
||||
* @param $provider_id
|
||||
* The language negotiation provider ID.
|
||||
*
|
||||
* @return
|
||||
* TRUE if there is at least one language type for which the given language
|
||||
* provider is enabled, FALSE otherwise.
|
||||
*/
|
||||
function language_negotiation_get_any($provider_id) {
|
||||
foreach (language_types_configurable() as $type) {
|
||||
if (language_negotiation_get($type, $provider_id)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language switch links for the given language.
|
||||
*
|
||||
* @param $type
|
||||
* The language negotiation type.
|
||||
* @param $path
|
||||
* The internal path the switch links will be relative to.
|
||||
*
|
||||
* @return
|
||||
* A keyed array of links ready to be themed.
|
||||
*/
|
||||
function language_negotiation_get_switch_links($type, $path) {
|
||||
$links = FALSE;
|
||||
$negotiation = variable_get("language_negotiation_$type", array());
|
||||
|
||||
// Only get the languages if we have more than one.
|
||||
if (count(language_list()) >= 2) {
|
||||
$language = language_initialize($type);
|
||||
}
|
||||
|
||||
foreach ($negotiation as $id => $provider) {
|
||||
if (isset($provider['callbacks']['switcher'])) {
|
||||
if (isset($provider['file'])) {
|
||||
require_once DRUPAL_ROOT . '/' . $provider['file'];
|
||||
}
|
||||
|
||||
$callback = $provider['callbacks']['switcher'];
|
||||
$result = $callback($type, $path);
|
||||
|
||||
// Add support for WCAG 2.0's Language of Parts to add language identifiers.
|
||||
// http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html
|
||||
foreach ($result as $langcode => $link) {
|
||||
$result[$langcode]['attributes']['xml:lang'] = $langcode;
|
||||
}
|
||||
|
||||
if (!empty($result)) {
|
||||
// Allow modules to provide translations for specific links.
|
||||
drupal_alter('language_switch_links', $result, $type, $path);
|
||||
$links = (object) array('links' => $result, 'provider' => $id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any unused language negotiation providers from the configuration.
|
||||
*/
|
||||
function language_negotiation_purge() {
|
||||
// Ensure that we are getting the defined language negotiation information. An
|
||||
// invocation of module_enable() or module_disable() could outdate the cached
|
||||
// information.
|
||||
drupal_static_reset('language_negotiation_info');
|
||||
drupal_static_reset('language_types_info');
|
||||
|
||||
$defined_providers = language_negotiation_info();
|
||||
foreach (language_types_info() as $type => $type_info) {
|
||||
$weight = 0;
|
||||
$negotiation = array();
|
||||
foreach (variable_get("language_negotiation_$type", array()) as $id => $provider) {
|
||||
if (isset($defined_providers[$id])) {
|
||||
$negotiation[$id] = $weight++;
|
||||
}
|
||||
}
|
||||
language_negotiation_set($type, $negotiation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a list of language negotiation providers.
|
||||
*
|
||||
* @param $type
|
||||
* The language negotiation type.
|
||||
* @param $language_providers
|
||||
* An array of language negotiation provider weights keyed by provider ID.
|
||||
* @see language_provider_weight()
|
||||
*/
|
||||
function language_negotiation_set($type, $language_providers) {
|
||||
// Save only the necessary fields.
|
||||
$provider_fields = array('callbacks', 'file', 'cache');
|
||||
|
||||
$negotiation = array();
|
||||
$providers_weight = array();
|
||||
$defined_providers = language_negotiation_info();
|
||||
$default_types = language_types_configurable(FALSE);
|
||||
|
||||
// Initialize the providers weight list.
|
||||
foreach ($language_providers as $id => $provider) {
|
||||
$providers_weight[$id] = language_provider_weight($provider);
|
||||
}
|
||||
|
||||
// Order providers list by weight.
|
||||
asort($providers_weight);
|
||||
|
||||
foreach ($providers_weight as $id => $weight) {
|
||||
if (isset($defined_providers[$id])) {
|
||||
$provider = $defined_providers[$id];
|
||||
// If the provider does not express any preference about types, make it
|
||||
// available for any configurable type.
|
||||
$types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types);
|
||||
// Check whether the provider is defined and has the right type.
|
||||
if (isset($types[$type])) {
|
||||
$provider_data = array();
|
||||
foreach ($provider_fields as $field) {
|
||||
if (isset($provider[$field])) {
|
||||
$provider_data[$field] = $provider[$field];
|
||||
}
|
||||
}
|
||||
$negotiation[$id] = $provider_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable_set("language_negotiation_$type", $negotiation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the defined language negotiation providers.
|
||||
*
|
||||
* @return
|
||||
* An array of language negotiation providers.
|
||||
*/
|
||||
function language_negotiation_info() {
|
||||
$language_providers = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($language_providers)) {
|
||||
// Collect all the module-defined language negotiation providers.
|
||||
$language_providers = module_invoke_all('language_negotiation_info');
|
||||
|
||||
// Add the default language negotiation provider.
|
||||
$language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array(
|
||||
'callbacks' => array('language' => 'language_from_default'),
|
||||
'weight' => 10,
|
||||
'name' => t('Default'),
|
||||
'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->native)),
|
||||
);
|
||||
|
||||
// Let other modules alter the list of language negotiation providers.
|
||||
drupal_alter('language_negotiation_info', $language_providers);
|
||||
}
|
||||
|
||||
return $language_providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to cache the language negotiation providers results.
|
||||
*
|
||||
* @param $provider_id
|
||||
* The language negotiation provider's identifier.
|
||||
* @param $provider
|
||||
* (optional) An associative array of information about the provider to be
|
||||
* invoked (see hook_language_negotiation_info() for details). If not passed
|
||||
* in, it will be loaded through language_negotiation_info().
|
||||
*
|
||||
* @return
|
||||
* A language object representing the language chosen by the provider.
|
||||
*/
|
||||
function language_provider_invoke($provider_id, $provider = NULL) {
|
||||
$results = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($results[$provider_id])) {
|
||||
global $user;
|
||||
|
||||
// Get languages grouped by status and select only the enabled ones.
|
||||
$languages = language_list('enabled');
|
||||
$languages = $languages[1];
|
||||
|
||||
if (!isset($provider)) {
|
||||
$providers = language_negotiation_info();
|
||||
$provider = $providers[$provider_id];
|
||||
}
|
||||
|
||||
if (isset($provider['file'])) {
|
||||
require_once DRUPAL_ROOT . '/' . $provider['file'];
|
||||
}
|
||||
|
||||
// If the language negotiation provider has no cache preference or this is
|
||||
// satisfied we can execute the callback.
|
||||
$cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0);
|
||||
$callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE;
|
||||
$langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE;
|
||||
$results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE;
|
||||
}
|
||||
|
||||
// Since objects are resources, we need to return a clone to prevent the
|
||||
// language negotiation provider cache from being unintentionally altered. The
|
||||
// same providers might be used with different language types based on
|
||||
// configuration.
|
||||
return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passed language negotiation provider weight or a default value.
|
||||
*
|
||||
* @param $provider
|
||||
* A language negotiation provider data structure.
|
||||
*
|
||||
* @return
|
||||
* A numeric weight.
|
||||
*/
|
||||
function language_provider_weight($provider) {
|
||||
$default = is_numeric($provider) ? $provider : 0;
|
||||
return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses a language based on language negotiation provider settings.
|
||||
*
|
||||
* @param $type
|
||||
* The language type key to find the language for.
|
||||
*
|
||||
* @return
|
||||
* The negotiated language object.
|
||||
*/
|
||||
function language_initialize($type) {
|
||||
// Execute the language negotiation providers in the order they were set up and return the
|
||||
// first valid language found.
|
||||
$negotiation = variable_get("language_negotiation_$type", array());
|
||||
|
||||
foreach ($negotiation as $provider_id => $provider) {
|
||||
$language = language_provider_invoke($provider_id, $provider);
|
||||
if ($language) {
|
||||
$language->provider = $provider_id;
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
|
||||
// If no other language was found use the default one.
|
||||
$language = language_default();
|
||||
$language->provider = LANGUAGE_NEGOTIATION_DEFAULT;
|
||||
return $language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default language negotiation provider.
|
||||
*
|
||||
* @return
|
||||
* The default language code.
|
||||
*/
|
||||
function language_from_default() {
|
||||
return language_default()->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the given path into prefix and actual path.
|
||||
*
|
||||
* Parse the given path and return the language object identified by the prefix
|
||||
* and the actual path.
|
||||
*
|
||||
* @param $path
|
||||
* The path to split.
|
||||
* @param $languages
|
||||
* An array of valid languages.
|
||||
*
|
||||
* @return
|
||||
* An array composed of:
|
||||
* - A language object corresponding to the identified prefix on success,
|
||||
* FALSE otherwise.
|
||||
* - The path without the prefix on success, the given path otherwise.
|
||||
*/
|
||||
function language_url_split_prefix($path, $languages) {
|
||||
$args = empty($path) ? array() : explode('/', $path);
|
||||
$prefix = array_shift($args);
|
||||
|
||||
// Search prefix within enabled languages.
|
||||
foreach ($languages as $language) {
|
||||
if (!empty($language->prefix) && $language->prefix == $prefix) {
|
||||
// Rebuild $path with the language removed.
|
||||
return array($language, implode('/', $args));
|
||||
}
|
||||
}
|
||||
|
||||
return array(FALSE, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the possible fallback languages ordered by language weight.
|
||||
*
|
||||
* @param
|
||||
* (optional) The language type. Defaults to LANGUAGE_TYPE_CONTENT.
|
||||
*
|
||||
* @return
|
||||
* An array of language codes.
|
||||
*/
|
||||
function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) {
|
||||
$fallback_candidates = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($fallback_candidates)) {
|
||||
$fallback_candidates = array();
|
||||
|
||||
// Get languages ordered by weight.
|
||||
// Use array keys to avoid duplicated entries.
|
||||
foreach (language_list('weight') as $languages) {
|
||||
foreach ($languages as $language) {
|
||||
$fallback_candidates[$language->language] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
$fallback_candidates = array_keys($fallback_candidates);
|
||||
$fallback_candidates[] = LANGUAGE_NONE;
|
||||
|
||||
// Let other modules hook in and add/change candidates.
|
||||
drupal_alter('language_fallback_candidates', $fallback_candidates);
|
||||
}
|
||||
|
||||
return $fallback_candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "language_negotiation"
|
||||
*/
|
||||
+2492
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* A database-mediated implementation of a locking mechanism.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup lock Locking mechanisms
|
||||
* @{
|
||||
* Functions to coordinate long-running operations across requests.
|
||||
*
|
||||
* In most environments, multiple Drupal page requests (a.k.a. threads or
|
||||
* processes) will execute in parallel. This leads to potential conflicts or
|
||||
* race conditions when two requests execute the same code at the same time. A
|
||||
* common example of this is a rebuild like menu_rebuild() where we invoke many
|
||||
* hook implementations to get and process data from all active modules, and
|
||||
* then delete the current data in the database to insert the new afterwards.
|
||||
*
|
||||
* This is a cooperative, advisory lock system. Any long-running operation
|
||||
* that could potentially be attempted in parallel by multiple requests should
|
||||
* try to acquire a lock before proceeding. By obtaining a lock, one request
|
||||
* notifies any other requests that a specific operation is in progress which
|
||||
* must not be executed in parallel.
|
||||
*
|
||||
* To use this API, pick a unique name for the lock. A sensible choice is the
|
||||
* name of the function performing the operation. A very simple example use of
|
||||
* this API:
|
||||
* @code
|
||||
* function mymodule_long_operation() {
|
||||
* if (lock_acquire('mymodule_long_operation')) {
|
||||
* // Do the long operation here.
|
||||
* // ...
|
||||
* lock_release('mymodule_long_operation');
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* If a function acquires a lock it should always release it when the
|
||||
* operation is complete by calling lock_release(), as in the example.
|
||||
*
|
||||
* A function that has acquired a lock may attempt to renew a lock (extend the
|
||||
* duration of the lock) by calling lock_acquire() again during the operation.
|
||||
* Failure to renew a lock is indicative that another request has acquired
|
||||
* the lock, and that the current operation may need to be aborted.
|
||||
*
|
||||
* If a function fails to acquire a lock it may either immediately return, or
|
||||
* it may call lock_wait() if the rest of the current page request requires
|
||||
* that the operation in question be complete. After lock_wait() returns,
|
||||
* the function may again attempt to acquire the lock, or may simply allow the
|
||||
* page request to proceed on the assumption that a parallel request completed
|
||||
* the operation.
|
||||
*
|
||||
* lock_acquire() and lock_wait() will automatically break (delete) a lock
|
||||
* whose duration has exceeded the timeout specified when it was acquired.
|
||||
*
|
||||
* Alternative implementations of this API (such as APC) may be substituted
|
||||
* by setting the 'lock_inc' variable to an alternate include filepath. Since
|
||||
* this is an API intended to support alternative implementations, code using
|
||||
* this API should never rely upon specific implementation details (for example
|
||||
* no code should look for or directly modify a lock in the {semaphore} table).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize the locking system.
|
||||
*/
|
||||
function lock_initialize() {
|
||||
global $locks;
|
||||
|
||||
$locks = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get this request's unique id.
|
||||
*/
|
||||
function _lock_id() {
|
||||
// Do not use drupal_static(). This identifier refers to the current
|
||||
// client request, and must not be changed under any circumstances
|
||||
// else the shutdown handler may fail to release our locks.
|
||||
static $lock_id;
|
||||
|
||||
if (!isset($lock_id)) {
|
||||
// Assign a unique id.
|
||||
$lock_id = uniqid(mt_rand(), TRUE);
|
||||
// We only register a shutdown function if a lock is used.
|
||||
drupal_register_shutdown_function('lock_release_all', $lock_id);
|
||||
}
|
||||
return $lock_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire (or renew) a lock, but do not block if it fails.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the lock. Limit of name's length is 255 characters.
|
||||
* @param $timeout
|
||||
* A number of seconds (float) before the lock expires (minimum of 0.001).
|
||||
*
|
||||
* @return
|
||||
* TRUE if the lock was acquired, FALSE if it failed.
|
||||
*/
|
||||
function lock_acquire($name, $timeout = 30.0) {
|
||||
global $locks;
|
||||
|
||||
// Insure that the timeout is at least 1 ms.
|
||||
$timeout = max($timeout, 0.001);
|
||||
$expire = microtime(TRUE) + $timeout;
|
||||
if (isset($locks[$name])) {
|
||||
// Try to extend the expiration of a lock we already acquired.
|
||||
$success = (bool) db_update('semaphore')
|
||||
->fields(array('expire' => $expire))
|
||||
->condition('name', $name)
|
||||
->condition('value', _lock_id())
|
||||
->execute();
|
||||
if (!$success) {
|
||||
// The lock was broken.
|
||||
unset($locks[$name]);
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
else {
|
||||
// Optimistically try to acquire the lock, then retry once if it fails.
|
||||
// The first time through the loop cannot be a retry.
|
||||
$retry = FALSE;
|
||||
// We always want to do this code at least once.
|
||||
do {
|
||||
try {
|
||||
db_insert('semaphore')
|
||||
->fields(array(
|
||||
'name' => $name,
|
||||
'value' => _lock_id(),
|
||||
'expire' => $expire,
|
||||
))
|
||||
->execute();
|
||||
// We track all acquired locks in the global variable.
|
||||
$locks[$name] = TRUE;
|
||||
// We never need to try again.
|
||||
$retry = FALSE;
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
// Suppress the error. If this is our first pass through the loop,
|
||||
// then $retry is FALSE. In this case, the insert must have failed
|
||||
// meaning some other request acquired the lock but did not release it.
|
||||
// We decide whether to retry by checking lock_may_be_available()
|
||||
// Since this will break the lock in case it is expired.
|
||||
$retry = $retry ? FALSE : lock_may_be_available($name);
|
||||
}
|
||||
// We only retry in case the first attempt failed, but we then broke
|
||||
// an expired lock.
|
||||
} while ($retry);
|
||||
}
|
||||
return isset($locks[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if lock acquired by a different process may be available.
|
||||
*
|
||||
* If an existing lock has expired, it is removed.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the lock.
|
||||
*
|
||||
* @return
|
||||
* TRUE if there is no lock or it was removed, FALSE otherwise.
|
||||
*/
|
||||
function lock_may_be_available($name) {
|
||||
$lock = db_query('SELECT expire, value FROM {semaphore} WHERE name = :name', array(':name' => $name))->fetchAssoc();
|
||||
if (!$lock) {
|
||||
return TRUE;
|
||||
}
|
||||
$expire = (float) $lock['expire'];
|
||||
$now = microtime(TRUE);
|
||||
if ($now > $expire) {
|
||||
// We check two conditions to prevent a race condition where another
|
||||
// request acquired the lock and set a new expire time. We add a small
|
||||
// number to $expire to avoid errors with float to string conversion.
|
||||
return (bool) db_delete('semaphore')
|
||||
->condition('name', $name)
|
||||
->condition('value', $lock['value'])
|
||||
->condition('expire', 0.0001 + $expire, '<=')
|
||||
->execute();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a lock to be available.
|
||||
*
|
||||
* This function may be called in a request that fails to acquire a desired
|
||||
* lock. This will block further execution until the lock is available or the
|
||||
* specified delay in seconds is reached. This should not be used with locks
|
||||
* that are acquired very frequently, since the lock is likely to be acquired
|
||||
* again by a different request while waiting.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the lock.
|
||||
* @param $delay
|
||||
* The maximum number of seconds to wait, as an integer.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the lock holds, FALSE if it is available.
|
||||
*/
|
||||
function lock_wait($name, $delay = 30) {
|
||||
// Pause the process for short periods between calling
|
||||
// lock_may_be_available(). This prevents hitting the database with constant
|
||||
// database queries while waiting, which could lead to performance issues.
|
||||
// However, if the wait period is too long, there is the potential for a
|
||||
// large number of processes to be blocked waiting for a lock, especially
|
||||
// if the item being rebuilt is commonly requested. To address both of these
|
||||
// concerns, begin waiting for 25ms, then add 25ms to the wait period each
|
||||
// time until it reaches 500ms. After this point polling will continue every
|
||||
// 500ms until $delay is reached.
|
||||
|
||||
// $delay is passed in seconds, but we will be using usleep(), which takes
|
||||
// microseconds as a parameter. Multiply it by 1 million so that all
|
||||
// further numbers are equivalent.
|
||||
$delay = (int) $delay * 1000000;
|
||||
|
||||
// Begin sleeping at 25ms.
|
||||
$sleep = 25000;
|
||||
while ($delay > 0) {
|
||||
// This function should only be called by a request that failed to get a
|
||||
// lock, so we sleep first to give the parallel request a chance to finish
|
||||
// and release the lock.
|
||||
usleep($sleep);
|
||||
// After each sleep, increase the value of $sleep until it reaches
|
||||
// 500ms, to reduce the potential for a lock stampede.
|
||||
$delay = $delay - $sleep;
|
||||
$sleep = min(500000, $sleep + 25000, $delay);
|
||||
if (lock_may_be_available($name)) {
|
||||
// No longer need to wait.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// The caller must still wait longer to get the lock.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a lock previously acquired by lock_acquire().
|
||||
*
|
||||
* This will release the named lock if it is still held by the current request.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the lock.
|
||||
*/
|
||||
function lock_release($name) {
|
||||
global $locks;
|
||||
|
||||
unset($locks[$name]);
|
||||
db_delete('semaphore')
|
||||
->condition('name', $name)
|
||||
->condition('value', _lock_id())
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all previously acquired locks.
|
||||
*/
|
||||
function lock_release_all($lock_id = NULL) {
|
||||
global $locks;
|
||||
|
||||
$locks = array();
|
||||
if (empty($lock_id)) {
|
||||
$lock_id = _lock_id();
|
||||
}
|
||||
db_delete('semaphore')
|
||||
->condition('value', $lock_id)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup lock".
|
||||
*/
|
||||
@@ -0,0 +1,623 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API functions for processing and sending e-mail.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-detect appropriate line endings for e-mails.
|
||||
*
|
||||
* $conf['mail_line_endings'] will override this setting.
|
||||
*/
|
||||
define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE) ? "\r\n" : "\n");
|
||||
|
||||
/**
|
||||
* Composes and optionally sends an e-mail message.
|
||||
*
|
||||
* Sending an e-mail works with defining an e-mail template (subject, text
|
||||
* and possibly e-mail headers) and the replacement values to use in the
|
||||
* appropriate places in the template. Processed e-mail templates are
|
||||
* requested from hook_mail() from the module sending the e-mail. Any module
|
||||
* can modify the composed e-mail message array using hook_mail_alter().
|
||||
* Finally drupal_mail_system()->mail() sends the e-mail, which can
|
||||
* be reused if the exact same composed e-mail is to be sent to multiple
|
||||
* recipients.
|
||||
*
|
||||
* Finding out what language to send the e-mail with needs some consideration.
|
||||
* If you send e-mail to a user, her preferred language should be fine, so
|
||||
* use user_preferred_language(). If you send email based on form values
|
||||
* filled on the page, there are two additional choices if you are not
|
||||
* sending the e-mail to a user on the site. You can either use the language
|
||||
* used to generate the page ($language global variable) or the site default
|
||||
* language. See language_default(). The former is good if sending e-mail to
|
||||
* the person filling the form, the later is good if you send e-mail to an
|
||||
* address previously set up (like contact addresses in a contact form).
|
||||
*
|
||||
* Taking care of always using the proper language is even more important
|
||||
* when sending e-mails in a row to multiple users. Hook_mail() abstracts
|
||||
* whether the mail text comes from an administrator setting or is
|
||||
* static in the source code. It should also deal with common mail tokens,
|
||||
* only receiving $params which are unique to the actual e-mail at hand.
|
||||
*
|
||||
* An example:
|
||||
*
|
||||
* @code
|
||||
* function example_notify($accounts) {
|
||||
* foreach ($accounts as $account) {
|
||||
* $params['account'] = $account;
|
||||
* // example_mail() will be called based on the first drupal_mail() parameter.
|
||||
* drupal_mail('example', 'notice', $account->mail, user_preferred_language($account), $params);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* function example_mail($key, &$message, $params) {
|
||||
* $data['user'] = $params['account'];
|
||||
* $options['language'] = $message['language'];
|
||||
* user_mail_tokens($variables, $data, $options);
|
||||
* switch($key) {
|
||||
* case 'notice':
|
||||
* // If the recipient can receive such notices by instant-message, do
|
||||
* // not send by email.
|
||||
* if (example_im_send($key, $message, $params)) {
|
||||
* $message['send'] = FALSE;
|
||||
* break;
|
||||
* }
|
||||
* $langcode = $message['language']->language;
|
||||
* $message['subject'] = t('Notification from !site', $variables, array('langcode' => $langcode));
|
||||
* $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array('langcode' => $langcode));
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Another example, which uses drupal_mail() to format a message for sending
|
||||
* later:
|
||||
*
|
||||
* @code
|
||||
* $params = array('current_conditions' => $data);
|
||||
* $to = 'user@example.com';
|
||||
* $message = drupal_mail('example', 'notice', $to, $language, $params, FALSE);
|
||||
* // Only add to the spool if sending was not canceled.
|
||||
* if ($message['send']) {
|
||||
* example_spool_message($message);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param $module
|
||||
* A module name to invoke hook_mail() on. The {$module}_mail() hook will be
|
||||
* called to complete the $message structure which will already contain common
|
||||
* defaults.
|
||||
* @param $key
|
||||
* A key to identify the e-mail sent. The final e-mail id for e-mail altering
|
||||
* will be {$module}_{$key}.
|
||||
* @param $to
|
||||
* The e-mail address or addresses where the message will be sent to. The
|
||||
* formatting of this string will be validated with the
|
||||
* @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
|
||||
* Some examples are:
|
||||
* - user@example.com
|
||||
* - user@example.com, anotheruser@example.com
|
||||
* - User <user@example.com>
|
||||
* - User <user@example.com>, Another User <anotheruser@example.com>
|
||||
* @param $language
|
||||
* Language object to use to compose the e-mail.
|
||||
* @param $params
|
||||
* Optional parameters to build the e-mail.
|
||||
* @param $from
|
||||
* Sets From to this value, if given.
|
||||
* @param $send
|
||||
* If TRUE, drupal_mail() will call drupal_mail_system()->mail() to deliver
|
||||
* the message, and store the result in $message['result']. Modules
|
||||
* implementing hook_mail_alter() may cancel sending by setting
|
||||
* $message['send'] to FALSE.
|
||||
*
|
||||
* @return
|
||||
* The $message array structure containing all details of the
|
||||
* message. If already sent ($send = TRUE), then the 'result' element
|
||||
* will contain the success indicator of the e-mail, failure being already
|
||||
* written to the watchdog. (Success means nothing more than the message being
|
||||
* accepted at php-level, which still doesn't guarantee it to be delivered.)
|
||||
*/
|
||||
function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) {
|
||||
$default_from = variable_get('site_mail', ini_get('sendmail_from'));
|
||||
|
||||
// Bundle up the variables into a structured array for altering.
|
||||
$message = array(
|
||||
'id' => $module . '_' . $key,
|
||||
'module' => $module,
|
||||
'key' => $key,
|
||||
'to' => $to,
|
||||
'from' => isset($from) ? $from : $default_from,
|
||||
'language' => $language,
|
||||
'params' => $params,
|
||||
'send' => TRUE,
|
||||
'subject' => '',
|
||||
'body' => array()
|
||||
);
|
||||
|
||||
// Build the default headers
|
||||
$headers = array(
|
||||
'MIME-Version' => '1.0',
|
||||
'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
|
||||
'Content-Transfer-Encoding' => '8Bit',
|
||||
'X-Mailer' => 'Drupal'
|
||||
);
|
||||
if ($default_from) {
|
||||
// To prevent e-mail from looking like spam, the addresses in the Sender and
|
||||
// Return-Path headers should have a domain authorized to use the originating
|
||||
// SMTP server.
|
||||
$headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $default_from;
|
||||
}
|
||||
if ($from) {
|
||||
$headers['From'] = $from;
|
||||
}
|
||||
$message['headers'] = $headers;
|
||||
|
||||
// Build the e-mail (get subject and body, allow additional headers) by
|
||||
// invoking hook_mail() on this module. We cannot use module_invoke() as
|
||||
// we need to have $message by reference in hook_mail().
|
||||
if (function_exists($function = $module . '_mail')) {
|
||||
$function($key, $message, $params);
|
||||
}
|
||||
|
||||
// Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail.
|
||||
drupal_alter('mail', $message);
|
||||
|
||||
// Retrieve the responsible implementation for this message.
|
||||
$system = drupal_mail_system($module, $key);
|
||||
|
||||
// Format the message body.
|
||||
$message = $system->format($message);
|
||||
|
||||
// Optionally send e-mail.
|
||||
if ($send) {
|
||||
// The original caller requested sending. Sending was canceled by one or
|
||||
// more hook_mail_alter() implementations. We set 'result' to NULL, because
|
||||
// FALSE indicates an error in sending.
|
||||
if (empty($message['send'])) {
|
||||
$message['result'] = NULL;
|
||||
}
|
||||
// Sending was originally requested and was not canceled.
|
||||
else {
|
||||
$message['result'] = $system->mail($message);
|
||||
// Log errors.
|
||||
if (!$message['result']) {
|
||||
watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR);
|
||||
drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that implements the MailSystemInterface interface.
|
||||
*
|
||||
* Allows for one or more custom mail backends to format and send mail messages
|
||||
* composed using drupal_mail().
|
||||
*
|
||||
* An implementation needs to implement the following methods:
|
||||
* - format: Allows to preprocess, format, and postprocess a mail
|
||||
* message before it is passed to the sending system. By default, all messages
|
||||
* may contain HTML and are converted to plain-text by the DefaultMailSystem
|
||||
* implementation. For example, an alternative implementation could override
|
||||
* the default implementation and additionally sanitize the HTML for usage in
|
||||
* a MIME-encoded e-mail, but still invoking the DefaultMailSystem
|
||||
* implementation to generate an alternate plain-text version for sending.
|
||||
* - mail: Sends a message through a custom mail sending engine.
|
||||
* By default, all messages are sent via PHP's mail() function by the
|
||||
* DefaultMailSystem implementation.
|
||||
*
|
||||
* The selection of a particular implementation is controlled via the variable
|
||||
* 'mail_system', which is a keyed array. The default implementation
|
||||
* is the class whose name is the value of 'default-system' key. A more specific
|
||||
* match first to key and then to module will be used in preference to the
|
||||
* default. To specify a different class for all mail sent by one module, set
|
||||
* the class name as the value for the key corresponding to the module name. To
|
||||
* specify a class for a particular message sent by one module, set the class
|
||||
* name as the value for the array key that is the message id, which is
|
||||
* "${module}_${key}".
|
||||
*
|
||||
* For example to debug all mail sent by the user module by logging it to a
|
||||
* file, you might set the variable as something like:
|
||||
*
|
||||
* @code
|
||||
* array(
|
||||
* 'default-system' => 'DefaultMailSystem',
|
||||
* 'user' => 'DevelMailLog',
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* Finally, a different system can be specified for a specific e-mail ID (see
|
||||
* the $key param), such as one of the keys used by the contact module:
|
||||
*
|
||||
* @code
|
||||
* array(
|
||||
* 'default-system' => 'DefaultMailSystem',
|
||||
* 'user' => 'DevelMailLog',
|
||||
* 'contact_page_autoreply' => 'DrupalDevNullMailSend',
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* Other possible uses for system include a mail-sending class that actually
|
||||
* sends (or duplicates) each message to SMS, Twitter, instant message, etc, or
|
||||
* a class that queues up a large number of messages for more efficient bulk
|
||||
* sending or for sending via a remote gateway so as to reduce the load
|
||||
* on the local server.
|
||||
*
|
||||
* @param $module
|
||||
* The module name which was used by drupal_mail() to invoke hook_mail().
|
||||
* @param $key
|
||||
* A key to identify the e-mail sent. The final e-mail ID for the e-mail
|
||||
* alter hook in drupal_mail() would have been {$module}_{$key}.
|
||||
*
|
||||
* @return MailSystemInterface
|
||||
*/
|
||||
function drupal_mail_system($module, $key) {
|
||||
$instances = &drupal_static(__FUNCTION__, array());
|
||||
|
||||
$id = $module . '_' . $key;
|
||||
|
||||
$configuration = variable_get('mail_system', array('default-system' => 'DefaultMailSystem'));
|
||||
|
||||
// Look for overrides for the default class, starting from the most specific
|
||||
// id, and falling back to the module name.
|
||||
if (isset($configuration[$id])) {
|
||||
$class = $configuration[$id];
|
||||
}
|
||||
elseif (isset($configuration[$module])) {
|
||||
$class = $configuration[$module];
|
||||
}
|
||||
else {
|
||||
$class = $configuration['default-system'];
|
||||
}
|
||||
|
||||
if (empty($instances[$class])) {
|
||||
$interfaces = class_implements($class);
|
||||
if (isset($interfaces['MailSystemInterface'])) {
|
||||
$instances[$class] = new $class();
|
||||
}
|
||||
else {
|
||||
throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'MailSystemInterface')));
|
||||
}
|
||||
}
|
||||
return $instances[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for pluggable mail back-ends.
|
||||
*/
|
||||
interface MailSystemInterface {
|
||||
/**
|
||||
* Format a message composed by drupal_mail() prior sending.
|
||||
*
|
||||
* @param $message
|
||||
* A message array, as described in hook_mail_alter().
|
||||
*
|
||||
* @return
|
||||
* The formatted $message.
|
||||
*/
|
||||
public function format(array $message);
|
||||
|
||||
/**
|
||||
* Send a message composed by drupal_mail().
|
||||
*
|
||||
* @param $message
|
||||
* Message array with at least the following elements:
|
||||
* - id: A unique identifier of the e-mail type. Examples: 'contact_user_copy',
|
||||
* 'user_password_reset'.
|
||||
* - to: The mail address or addresses where the message will be sent to.
|
||||
* The formatting of this string will be validated with the
|
||||
* @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
|
||||
* Some examples are:
|
||||
* - user@example.com
|
||||
* - user@example.com, anotheruser@example.com
|
||||
* - User <user@example.com>
|
||||
* - User <user@example.com>, Another User <anotheruser@example.com>
|
||||
* - subject: Subject of the e-mail to be sent. This must not contain any
|
||||
* newline characters, or the mail may not be sent properly.
|
||||
* - body: Message to be sent. Accepts both CRLF and LF line-endings.
|
||||
* E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
|
||||
* smart plain text wrapping.
|
||||
* - headers: Associative array containing all additional mail headers not
|
||||
* defined by one of the other parameters. PHP's mail() looks for Cc and
|
||||
* Bcc headers and sends the mail to addresses in these headers too.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the mail was successfully accepted for delivery, otherwise FALSE.
|
||||
*/
|
||||
public function mail(array $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs format=flowed soft wrapping for mail (RFC 3676).
|
||||
*
|
||||
* We use delsp=yes wrapping, but only break non-spaced languages when
|
||||
* absolutely necessary to avoid compatibility issues.
|
||||
*
|
||||
* We deliberately use LF rather than CRLF, see drupal_mail().
|
||||
*
|
||||
* @param string $text
|
||||
* The plain text to process.
|
||||
* @param string $indent (optional)
|
||||
* A string to indent the text with. Only '>' characters are repeated on
|
||||
* subsequent wrapped lines. Others are replaced by spaces.
|
||||
*
|
||||
* @return string
|
||||
* The content of the email as a string with formatting applied.
|
||||
*/
|
||||
function drupal_wrap_mail($text, $indent = '') {
|
||||
// Convert CRLF into LF.
|
||||
$text = str_replace("\r", '', $text);
|
||||
// See if soft-wrapping is allowed.
|
||||
$clean_indent = _drupal_html_to_text_clean($indent);
|
||||
$soft = strpos($clean_indent, ' ') === FALSE;
|
||||
// Check if the string has line breaks.
|
||||
if (strpos($text, "\n") !== FALSE) {
|
||||
// Remove trailing spaces to make existing breaks hard, but leave signature
|
||||
// marker untouched (RFC 3676, Section 4.3).
|
||||
$text = preg_replace('/(?(?<!^--) +\n| +\n)/m', "\n", $text);
|
||||
// Wrap each line at the needed width.
|
||||
$lines = explode("\n", $text);
|
||||
array_walk($lines, '_drupal_wrap_mail_line', array('soft' => $soft, 'length' => strlen($indent)));
|
||||
$text = implode("\n", $lines);
|
||||
}
|
||||
else {
|
||||
// Wrap this line.
|
||||
_drupal_wrap_mail_line($text, 0, array('soft' => $soft, 'length' => strlen($indent)));
|
||||
}
|
||||
// Empty lines with nothing but spaces.
|
||||
$text = preg_replace('/^ +\n/m', "\n", $text);
|
||||
// Space-stuff special lines.
|
||||
$text = preg_replace('/^(>| |From)/m', ' $1', $text);
|
||||
// Apply indentation. We only include non-'>' indentation on the first line.
|
||||
$text = $indent . substr(preg_replace('/^/m', $clean_indent, $text), strlen($indent));
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an HTML string into plain text, preserving its structure.
|
||||
*
|
||||
* The output will be suitable for use as 'format=flowed; delsp=yes' text
|
||||
* (RFC 3676) and can be passed directly to drupal_mail() for sending.
|
||||
*
|
||||
* We deliberately use LF rather than CRLF, see drupal_mail().
|
||||
*
|
||||
* This function provides suitable alternatives for the following tags:
|
||||
* <a> <em> <i> <strong> <b> <br> <p> <blockquote> <ul> <ol> <li> <dl> <dt>
|
||||
* <dd> <h1> <h2> <h3> <h4> <h5> <h6> <hr>
|
||||
*
|
||||
* @param $string
|
||||
* The string to be transformed.
|
||||
* @param $allowed_tags (optional)
|
||||
* If supplied, a list of tags that will be transformed. If omitted, all
|
||||
* all supported tags are transformed.
|
||||
*
|
||||
* @return
|
||||
* The transformed string.
|
||||
*/
|
||||
function drupal_html_to_text($string, $allowed_tags = NULL) {
|
||||
// Cache list of supported tags.
|
||||
static $supported_tags;
|
||||
if (empty($supported_tags)) {
|
||||
$supported_tags = array('a', 'em', 'i', 'strong', 'b', 'br', 'p', 'blockquote', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr');
|
||||
}
|
||||
|
||||
// Make sure only supported tags are kept.
|
||||
$allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags;
|
||||
|
||||
// Make sure tags, entities and attributes are well-formed and properly nested.
|
||||
$string = _filter_htmlcorrector(filter_xss($string, $allowed_tags));
|
||||
|
||||
// Apply inline styles.
|
||||
$string = preg_replace('!</?(em|i)((?> +)[^>]*)?>!i', '/', $string);
|
||||
$string = preg_replace('!</?(strong|b)((?> +)[^>]*)?>!i', '*', $string);
|
||||
|
||||
// Replace inline <a> tags with the text of link and a footnote.
|
||||
// 'See <a href="http://drupal.org">the Drupal site</a>' becomes
|
||||
// 'See the Drupal site [1]' with the URL included as a footnote.
|
||||
_drupal_html_to_mail_urls(NULL, TRUE);
|
||||
$pattern = '@(<a[^>]+?href="([^"]*)"[^>]*?>(.+?)</a>)@i';
|
||||
$string = preg_replace_callback($pattern, '_drupal_html_to_mail_urls', $string);
|
||||
$urls = _drupal_html_to_mail_urls();
|
||||
$footnotes = '';
|
||||
if (count($urls)) {
|
||||
$footnotes .= "\n";
|
||||
for ($i = 0, $max = count($urls); $i < $max; $i++) {
|
||||
$footnotes .= '[' . ($i + 1) . '] ' . $urls[$i] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Split tags from text.
|
||||
$split = preg_split('/<([^>]+?)>/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
// Note: PHP ensures the array consists of alternating delimiters and literals
|
||||
// and begins and ends with a literal (inserting $null as required).
|
||||
|
||||
$tag = FALSE; // Odd/even counter (tag or no tag)
|
||||
$casing = NULL; // Case conversion function
|
||||
$output = '';
|
||||
$indent = array(); // All current indentation string chunks
|
||||
$lists = array(); // Array of counters for opened lists
|
||||
foreach ($split as $value) {
|
||||
$chunk = NULL; // Holds a string ready to be formatted and output.
|
||||
|
||||
// Process HTML tags (but don't output any literally).
|
||||
if ($tag) {
|
||||
list($tagname) = explode(' ', strtolower($value), 2);
|
||||
switch ($tagname) {
|
||||
// List counters
|
||||
case 'ul':
|
||||
array_unshift($lists, '*');
|
||||
break;
|
||||
case 'ol':
|
||||
array_unshift($lists, 1);
|
||||
break;
|
||||
case '/ul':
|
||||
case '/ol':
|
||||
array_shift($lists);
|
||||
$chunk = ''; // Ensure blank new-line.
|
||||
break;
|
||||
|
||||
// Quotation/list markers, non-fancy headers
|
||||
case 'blockquote':
|
||||
// Format=flowed indentation cannot be mixed with lists.
|
||||
$indent[] = count($lists) ? ' "' : '>';
|
||||
break;
|
||||
case 'li':
|
||||
$indent[] = isset($lists[0]) && is_numeric($lists[0]) ? ' ' . $lists[0]++ . ') ' : ' * ';
|
||||
break;
|
||||
case 'dd':
|
||||
$indent[] = ' ';
|
||||
break;
|
||||
case 'h3':
|
||||
$indent[] = '.... ';
|
||||
break;
|
||||
case 'h4':
|
||||
$indent[] = '.. ';
|
||||
break;
|
||||
case '/blockquote':
|
||||
if (count($lists)) {
|
||||
// Append closing quote for inline quotes (immediately).
|
||||
$output = rtrim($output, "> \n") . "\"\n";
|
||||
$chunk = ''; // Ensure blank new-line.
|
||||
}
|
||||
// Fall-through
|
||||
case '/li':
|
||||
case '/dd':
|
||||
array_pop($indent);
|
||||
break;
|
||||
case '/h3':
|
||||
case '/h4':
|
||||
array_pop($indent);
|
||||
case '/h5':
|
||||
case '/h6':
|
||||
$chunk = ''; // Ensure blank new-line.
|
||||
break;
|
||||
|
||||
// Fancy headers
|
||||
case 'h1':
|
||||
$indent[] = '======== ';
|
||||
$casing = 'drupal_strtoupper';
|
||||
break;
|
||||
case 'h2':
|
||||
$indent[] = '-------- ';
|
||||
$casing = 'drupal_strtoupper';
|
||||
break;
|
||||
case '/h1':
|
||||
case '/h2':
|
||||
$casing = NULL;
|
||||
// Pad the line with dashes.
|
||||
$output = _drupal_html_to_text_pad($output, ($tagname == '/h1') ? '=' : '-', ' ');
|
||||
array_pop($indent);
|
||||
$chunk = ''; // Ensure blank new-line.
|
||||
break;
|
||||
|
||||
// Horizontal rulers
|
||||
case 'hr':
|
||||
// Insert immediately.
|
||||
$output .= drupal_wrap_mail('', implode('', $indent)) . "\n";
|
||||
$output = _drupal_html_to_text_pad($output, '-');
|
||||
break;
|
||||
|
||||
// Paragraphs and definition lists
|
||||
case '/p':
|
||||
case '/dl':
|
||||
$chunk = ''; // Ensure blank new-line.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Process blocks of text.
|
||||
else {
|
||||
// Convert inline HTML text to plain text; not removing line-breaks or
|
||||
// white-space, since that breaks newlines when sanitizing plain-text.
|
||||
$value = trim(decode_entities($value));
|
||||
if (drupal_strlen($value)) {
|
||||
$chunk = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// See if there is something waiting to be output.
|
||||
if (isset($chunk)) {
|
||||
// Apply any necessary case conversion.
|
||||
if (isset($casing)) {
|
||||
$chunk = $casing($chunk);
|
||||
}
|
||||
// Format it and apply the current indentation.
|
||||
$output .= drupal_wrap_mail($chunk, implode('', $indent)) . MAIL_LINE_ENDINGS;
|
||||
// Remove non-quotation markers from indentation.
|
||||
$indent = array_map('_drupal_html_to_text_clean', $indent);
|
||||
}
|
||||
|
||||
$tag = !$tag;
|
||||
}
|
||||
|
||||
return $output . $footnotes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps words on a single line.
|
||||
*
|
||||
* Callback for array_walk() winthin drupal_wrap_mail().
|
||||
*/
|
||||
function _drupal_wrap_mail_line(&$line, $key, $values) {
|
||||
// Use soft-breaks only for purely quoted or unindented text.
|
||||
$line = wordwrap($line, 77 - $values['length'], $values['soft'] ? " \n" : "\n");
|
||||
// Break really long words at the maximum width allowed.
|
||||
$line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n", TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps track of URLs and replaces them with placeholder tokens.
|
||||
*
|
||||
* Callback for preg_replace_callback() within drupal_html_to_text().
|
||||
*/
|
||||
function _drupal_html_to_mail_urls($match = NULL, $reset = FALSE) {
|
||||
global $base_url, $base_path;
|
||||
static $urls = array(), $regexp;
|
||||
|
||||
if ($reset) {
|
||||
// Reset internal URL list.
|
||||
$urls = array();
|
||||
}
|
||||
else {
|
||||
if (empty($regexp)) {
|
||||
$regexp = '@^' . preg_quote($base_path, '@') . '@';
|
||||
}
|
||||
if ($match) {
|
||||
list(, , $url, $label) = $match;
|
||||
// Ensure all URLs are absolute.
|
||||
$urls[] = strpos($url, '://') ? $url : preg_replace($regexp, $base_url . '/', $url);
|
||||
return $label . ' [' . count($urls) . ']';
|
||||
}
|
||||
}
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces non-quotation markers from a given piece of indentation with spaces.
|
||||
*
|
||||
* Callback for array_map() within drupal_html_to_text().
|
||||
*/
|
||||
function _drupal_html_to_text_clean($indent) {
|
||||
return preg_replace('/[^>]/', ' ', $indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the last line with the given character.
|
||||
*
|
||||
* @see drupal_html_to_text()
|
||||
*/
|
||||
function _drupal_html_to_text_pad($text, $pad, $prefix = '') {
|
||||
// Remove last line break.
|
||||
$text = substr($text, 0, -1);
|
||||
// Calculate needed padding space and add it.
|
||||
if (($p = strrpos($text, "\n")) === FALSE) {
|
||||
$p = -1;
|
||||
}
|
||||
$n = max(0, 79 - (strlen($text) - $p) - strlen($prefix));
|
||||
// Add prefix and padding, and restore linebreak.
|
||||
return $text . $prefix . str_repeat($pad, $n) . "\n";
|
||||
}
|
||||
+3958
File diff suppressed because it is too large
Load Diff
+1173
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,694 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions to aid in presenting database results as a set of pages.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Query extender for pager queries.
|
||||
*
|
||||
* This is the "default" pager mechanism. It creates a paged query with a fixed
|
||||
* number of entries per page.
|
||||
*/
|
||||
class PagerDefault extends SelectQueryExtender {
|
||||
|
||||
/**
|
||||
* The highest element we've autogenerated so far.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
static $maxElement = 0;
|
||||
|
||||
/**
|
||||
* The number of elements per page to allow.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $limit = 10;
|
||||
|
||||
/**
|
||||
* The unique ID of this pager on this page.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $element = NULL;
|
||||
|
||||
/**
|
||||
* The count query that will be used for this pager.
|
||||
*
|
||||
* @var SelectQueryInterface
|
||||
*/
|
||||
protected $customCountQuery = FALSE;
|
||||
|
||||
public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) {
|
||||
parent::__construct($query, $connection);
|
||||
|
||||
// Add pager tag. Do this here to ensure that it is always added before
|
||||
// preExecute() is called.
|
||||
$this->addTag('pager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the execute method.
|
||||
*
|
||||
* Before we run the query, we need to add pager-based range() instructions
|
||||
* to it.
|
||||
*/
|
||||
public function execute() {
|
||||
|
||||
// Add convenience tag to mark that this is an extended query. We have to
|
||||
// do this in the constructor to ensure that it is set before preExecute()
|
||||
// gets called.
|
||||
if (!$this->preExecute($this)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// A NULL limit is the "kill switch" for pager queries.
|
||||
if (empty($this->limit)) {
|
||||
return;
|
||||
}
|
||||
$this->ensureElement();
|
||||
|
||||
$total_items = $this->getCountQuery()->execute()->fetchField();
|
||||
$current_page = pager_default_initialize($total_items, $this->limit, $this->element);
|
||||
$this->range($current_page * $this->limit, $this->limit);
|
||||
|
||||
// Now that we've added our pager-based range instructions, run the query normally.
|
||||
return $this->query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that there is an element associated with this query.
|
||||
* If an element was not specified previously, then the value of the
|
||||
* $maxElement counter is taken, after which the counter is incremented.
|
||||
*
|
||||
* After running this method, access $this->element to get the element for this
|
||||
* query.
|
||||
*/
|
||||
protected function ensureElement() {
|
||||
if (!isset($this->element)) {
|
||||
$this->element = self::$maxElement++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the count query object to use for this pager.
|
||||
*
|
||||
* You will rarely need to specify a count query directly. If not specified,
|
||||
* one is generated off of the pager query itself.
|
||||
*
|
||||
* @param SelectQueryInterface $query
|
||||
* The count query object. It must return a single row with a single column,
|
||||
* which is the total number of records.
|
||||
*/
|
||||
public function setCountQuery(SelectQueryInterface $query) {
|
||||
$this->customCountQuery = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the count query for this pager.
|
||||
*
|
||||
* The count query may be specified manually or, by default, taken from the
|
||||
* query we are extending.
|
||||
*
|
||||
* @return SelectQueryInterface
|
||||
* A count query object.
|
||||
*/
|
||||
public function getCountQuery() {
|
||||
if ($this->customCountQuery) {
|
||||
return $this->customCountQuery;
|
||||
}
|
||||
else {
|
||||
return $this->query->countQuery();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the maximum number of elements per page for this query.
|
||||
*
|
||||
* The default if not specified is 10 items per page.
|
||||
*
|
||||
* @param $limit
|
||||
* An integer specifying the number of elements per page. If passed a false
|
||||
* value (FALSE, 0, NULL), the pager is disabled.
|
||||
*/
|
||||
public function limit($limit = 10) {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the element ID for this pager query.
|
||||
*
|
||||
* The element is used to differentiate different pager queries on the same
|
||||
* page so that they may be operated independently. If you do not specify an
|
||||
* element, every pager query on the page will get a unique element. If for
|
||||
* whatever reason you want to explicitly define an element for a given query,
|
||||
* you may do so here.
|
||||
*
|
||||
* Setting the element here also increments the static $maxElement counter,
|
||||
* which is used for determining the $element when there's none specified.
|
||||
*
|
||||
* Note that no collision detection is done when setting an element ID
|
||||
* explicitly, so it is possible for two pagers to end up using the same ID
|
||||
* if both are set explicitly.
|
||||
*
|
||||
* @param $element
|
||||
*/
|
||||
public function element($element) {
|
||||
$this->element = $element;
|
||||
if ($element >= self::$maxElement) {
|
||||
self::$maxElement = $element + 1;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current page being requested for display within a pager.
|
||||
*
|
||||
* @param $element
|
||||
* An optional integer to distinguish between multiple pagers on one page.
|
||||
*
|
||||
* @return
|
||||
* The number of the current requested page, within the pager represented by
|
||||
* $element. This is determined from the URL query parameter $_GET['page'], or
|
||||
* 0 by default. Note that this number may differ from the actual page being
|
||||
* displayed. For example, if a search for "example text" brings up three
|
||||
* pages of results, but a users visits search/node/example+text?page=10, this
|
||||
* function will return 10, even though the default pager implementation
|
||||
* adjusts for this and still displays the third page of search results at
|
||||
* that URL.
|
||||
*
|
||||
* @see pager_default_initialize()
|
||||
*/
|
||||
function pager_find_page($element = 0) {
|
||||
$page = isset($_GET['page']) ? $_GET['page'] : '';
|
||||
$page_array = explode(',', $page);
|
||||
if (!isset($page_array[$element])) {
|
||||
$page_array[$element] = 0;
|
||||
}
|
||||
return (int) $page_array[$element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a pager for theme('pager').
|
||||
*
|
||||
* This function sets up the necessary global variables so that future calls
|
||||
* to theme('pager') will render a pager that correctly corresponds to the
|
||||
* items being displayed.
|
||||
*
|
||||
* If the items being displayed result from a database query performed using
|
||||
* Drupal's database API, and if you have control over the construction of the
|
||||
* database query, you do not need to call this function directly; instead, you
|
||||
* can simply extend the query object with the 'PagerDefault' extender before
|
||||
* executing it. For example:
|
||||
* @code
|
||||
* $query = db_select('some_table')->extend('PagerDefault');
|
||||
* @endcode
|
||||
*
|
||||
* However, if you are using a different method for generating the items to be
|
||||
* paged through, then you should call this function in preparation.
|
||||
*
|
||||
* The following example shows how this function can be used in a page callback
|
||||
* that invokes an external datastore with an SQL-like syntax:
|
||||
* @code
|
||||
* // First find the total number of items and initialize the pager.
|
||||
* $where = "status = 1";
|
||||
* $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
|
||||
* $num_per_page = variable_get('mymodule_num_per_page', 10);
|
||||
* $page = pager_default_initialize($total, $num_per_page);
|
||||
*
|
||||
* // Next, retrieve and display the items for the current page.
|
||||
* $offset = $num_per_page * $page;
|
||||
* $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll();
|
||||
* $output = theme('mymodule_results', array('result' => $result));
|
||||
*
|
||||
* // Finally, display the pager controls, and return.
|
||||
* $output .= theme('pager');
|
||||
* return $output;
|
||||
* @endcode
|
||||
*
|
||||
* A second example involves a page callback that invokes an external search
|
||||
* service where the total number of matching results is provided as part of
|
||||
* the returned set (so that we do not need a separate query in order to obtain
|
||||
* this information). Here, we call pager_find_page() to calculate the desired
|
||||
* offset before the search is invoked:
|
||||
* @code
|
||||
* // Perform the query, using the requested offset from pager_find_page().
|
||||
* // This comes from a URL parameter, so here we are assuming that the URL
|
||||
* // parameter corresponds to an actual page of results that will exist
|
||||
* // within the set.
|
||||
* $page = pager_find_page();
|
||||
* $num_per_page = variable_get('mymodule_num_per_page', 10);
|
||||
* $offset = $num_per_page * $page;
|
||||
* $result = mymodule_remote_search($keywords, $offset, $num_per_page);
|
||||
*
|
||||
* // Now that we have the total number of results, initialize the pager.
|
||||
* pager_default_initialize($result->total, $num_per_page);
|
||||
*
|
||||
* // Display the search results.
|
||||
* $output = theme('search_results', array('results' => $result->data, 'type' => 'remote'));
|
||||
*
|
||||
* // Finally, display the pager controls, and return.
|
||||
* $output .= theme('pager');
|
||||
* return $output;
|
||||
* @endcode
|
||||
*
|
||||
* @param $total
|
||||
* The total number of items to be paged.
|
||||
* @param $limit
|
||||
* The number of items the calling code will display per page.
|
||||
* @param $element
|
||||
* An optional integer to distinguish between multiple pagers on one page.
|
||||
*
|
||||
* @return
|
||||
* The number of the current page, within the pager represented by $element.
|
||||
* This is determined from the URL query parameter $_GET['page'], or 0 by
|
||||
* default. However, if a page that does not correspond to the actual range
|
||||
* of the result set was requested, this function will return the closest
|
||||
* page actually within the result set.
|
||||
*/
|
||||
function pager_default_initialize($total, $limit, $element = 0) {
|
||||
global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
|
||||
|
||||
$page = pager_find_page($element);
|
||||
|
||||
// We calculate the total of pages as ceil(items / limit).
|
||||
$pager_total_items[$element] = $total;
|
||||
$pager_total[$element] = ceil($pager_total_items[$element] / $limit);
|
||||
$pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1));
|
||||
$pager_limits[$element] = $limit;
|
||||
return $pager_page_array[$element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a URL query parameter array for pager links.
|
||||
*
|
||||
* @return
|
||||
* A URL query parameter array that consists of all components of the current
|
||||
* page request except for those pertaining to paging.
|
||||
*/
|
||||
function pager_get_query_parameters() {
|
||||
$query = &drupal_static(__FUNCTION__);
|
||||
if (!isset($query)) {
|
||||
$query = drupal_get_query_parameters($_GET, array('q', 'page'));
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for a query pager.
|
||||
*
|
||||
* Menu callbacks that display paged query results should call theme('pager') to
|
||||
* retrieve a pager control so that users can view other results. Format a list
|
||||
* of nearby pages with additional query results.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - tags: An array of labels for the controls in the pager.
|
||||
* - element: An optional integer to distinguish between multiple pagers on
|
||||
* one page.
|
||||
* - parameters: An associative array of query string parameters to append to
|
||||
* the pager links.
|
||||
* - quantity: The number of pages in the list.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_pager($variables) {
|
||||
$tags = $variables['tags'];
|
||||
$element = $variables['element'];
|
||||
$parameters = $variables['parameters'];
|
||||
$quantity = empty($variables['quantity']) ? 0 : $variables['quantity'];
|
||||
global $pager_page_array, $pager_total;
|
||||
|
||||
// Nothing to do if there is no pager.
|
||||
if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing to do if there is only one page.
|
||||
if ($pager_total[$element] <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate various markers within this pager piece:
|
||||
// Middle is used to "center" pages around the current page.
|
||||
$pager_middle = ceil($quantity / 2);
|
||||
// current is the page we are currently paged to
|
||||
$pager_current = $pager_page_array[$element] + 1;
|
||||
// first is the first page listed by this pager piece (re quantity)
|
||||
$pager_first = $pager_current - $pager_middle + 1;
|
||||
// last is the last page listed by this pager piece (re quantity)
|
||||
$pager_last = $pager_current + $quantity - $pager_middle;
|
||||
// max is the maximum page number
|
||||
$pager_max = $pager_total[$element];
|
||||
// End of marker calculations.
|
||||
|
||||
// Prepare for generation loop.
|
||||
$i = $pager_first;
|
||||
if ($pager_last > $pager_max) {
|
||||
// Adjust "center" if at end of query.
|
||||
$i = $i + ($pager_max - $pager_last);
|
||||
$pager_last = $pager_max;
|
||||
}
|
||||
if ($i <= 0) {
|
||||
// Adjust "center" if at start of query.
|
||||
$pager_last = $pager_last + (1 - $i);
|
||||
$i = 1;
|
||||
}
|
||||
// End of generation loop preparation.
|
||||
|
||||
$li_first = theme('pager_first', array('text' => (isset($tags[0]) ? $tags[0] : t('« first')), 'element' => $element, 'parameters' => $parameters));
|
||||
$li_previous = theme('pager_previous', array('text' => (isset($tags[1]) ? $tags[1] : t('‹ previous')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
|
||||
$li_next = theme('pager_next', array('text' => (isset($tags[3]) ? $tags[3] : t('next ›')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
|
||||
$li_last = theme('pager_last', array('text' => (isset($tags[4]) ? $tags[4] : t('last »')), 'element' => $element, 'parameters' => $parameters));
|
||||
|
||||
if ($pager_total[$element] > 1) {
|
||||
if ($li_first) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-first'),
|
||||
'data' => $li_first,
|
||||
);
|
||||
}
|
||||
if ($li_previous) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-previous'),
|
||||
'data' => $li_previous,
|
||||
);
|
||||
}
|
||||
|
||||
// When there is more than one page, create the pager list.
|
||||
if ($i != $pager_max) {
|
||||
if ($i > 1) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-ellipsis'),
|
||||
'data' => '…',
|
||||
);
|
||||
}
|
||||
// Now generate the actual pager piece.
|
||||
for (; $i <= $pager_last && $i <= $pager_max; $i++) {
|
||||
if ($i < $pager_current) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-item'),
|
||||
'data' => theme('pager_previous', array('text' => $i, 'element' => $element, 'interval' => ($pager_current - $i), 'parameters' => $parameters)),
|
||||
);
|
||||
}
|
||||
if ($i == $pager_current) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-current'),
|
||||
'data' => $i,
|
||||
);
|
||||
}
|
||||
if ($i > $pager_current) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-item'),
|
||||
'data' => theme('pager_next', array('text' => $i, 'element' => $element, 'interval' => ($i - $pager_current), 'parameters' => $parameters)),
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($i < $pager_max) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-ellipsis'),
|
||||
'data' => '…',
|
||||
);
|
||||
}
|
||||
}
|
||||
// End generation.
|
||||
if ($li_next) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-next'),
|
||||
'data' => $li_next,
|
||||
);
|
||||
}
|
||||
if ($li_last) {
|
||||
$items[] = array(
|
||||
'class' => array('pager-last'),
|
||||
'data' => $li_last,
|
||||
);
|
||||
}
|
||||
return '<h2 class="element-invisible">' . t('Pages') . '</h2>' . theme('item_list', array(
|
||||
'items' => $items,
|
||||
'attributes' => array('class' => array('pager')),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup pagerpieces Pager pieces
|
||||
* @{
|
||||
* Theme functions for customizing pager elements.
|
||||
*
|
||||
* Note that you should NOT modify this file to customize your pager.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns HTML for the "first page" link in a query pager.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - text: The name (or image) of the link.
|
||||
* - element: An optional integer to distinguish between multiple pagers on
|
||||
* one page.
|
||||
* - parameters: An associative array of query string parameters to append to
|
||||
* the pager links.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_pager_first($variables) {
|
||||
$text = $variables['text'];
|
||||
$element = $variables['element'];
|
||||
$parameters = $variables['parameters'];
|
||||
global $pager_page_array;
|
||||
$output = '';
|
||||
|
||||
// Nothing to do if there is no pager.
|
||||
if (!isset($pager_page_array[$element])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are anywhere but the first page
|
||||
if ($pager_page_array[$element] > 0) {
|
||||
$output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for the "previous page" link in a query pager.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - text: The name (or image) of the link.
|
||||
* - element: An optional integer to distinguish between multiple pagers on
|
||||
* one page.
|
||||
* - interval: The number of pages to move backward when the link is clicked.
|
||||
* - parameters: An associative array of query string parameters to append to
|
||||
* the pager links.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_pager_previous($variables) {
|
||||
$text = $variables['text'];
|
||||
$element = $variables['element'];
|
||||
$interval = $variables['interval'];
|
||||
$parameters = $variables['parameters'];
|
||||
global $pager_page_array;
|
||||
$output = '';
|
||||
|
||||
// Nothing to do if there is no pager.
|
||||
if (!isset($pager_page_array[$element])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are anywhere but the first page
|
||||
if ($pager_page_array[$element] > 0) {
|
||||
$page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
|
||||
|
||||
// If the previous page is the first page, mark the link as such.
|
||||
if ($page_new[$element] == 0) {
|
||||
$output = theme('pager_first', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
|
||||
}
|
||||
// The previous page is not the first page.
|
||||
else {
|
||||
$output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for the "next page" link in a query pager.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - text: The name (or image) of the link.
|
||||
* - element: An optional integer to distinguish between multiple pagers on
|
||||
* one page.
|
||||
* - interval: The number of pages to move forward when the link is clicked.
|
||||
* - parameters: An associative array of query string parameters to append to
|
||||
* the pager links.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_pager_next($variables) {
|
||||
$text = $variables['text'];
|
||||
$element = $variables['element'];
|
||||
$interval = $variables['interval'];
|
||||
$parameters = $variables['parameters'];
|
||||
global $pager_page_array, $pager_total;
|
||||
$output = '';
|
||||
|
||||
// Nothing to do if there is no pager.
|
||||
if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are anywhere but the last page
|
||||
if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
|
||||
$page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
|
||||
// If the next page is the last page, mark the link as such.
|
||||
if ($page_new[$element] == ($pager_total[$element] - 1)) {
|
||||
$output = theme('pager_last', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
|
||||
}
|
||||
// The next page is not the last page.
|
||||
else {
|
||||
$output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for the "last page" link in query pager.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - text: The name (or image) of the link.
|
||||
* - element: An optional integer to distinguish between multiple pagers on
|
||||
* one page.
|
||||
* - parameters: An associative array of query string parameters to append to
|
||||
* the pager links.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_pager_last($variables) {
|
||||
$text = $variables['text'];
|
||||
$element = $variables['element'];
|
||||
$parameters = $variables['parameters'];
|
||||
global $pager_page_array, $pager_total;
|
||||
$output = '';
|
||||
|
||||
// Nothing to do if there is no pager.
|
||||
if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are anywhere but the last page
|
||||
if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
|
||||
$output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns HTML for a link to a specific query result page.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - text: The link text. Also used to figure out the title attribute of the
|
||||
* link, if it is not provided in $variables['attributes']['title']; in
|
||||
* this case, $variables['text'] must be one of the standard pager link
|
||||
* text strings that would be generated by the pager theme functions, such
|
||||
* as a number or t('« first').
|
||||
* - page_new: The first result to display on the linked page.
|
||||
* - element: An optional integer to distinguish between multiple pagers on
|
||||
* one page.
|
||||
* - parameters: An associative array of query string parameters to append to
|
||||
* the pager link.
|
||||
* - attributes: An associative array of HTML attributes to apply to the
|
||||
* pager link.
|
||||
*
|
||||
* @see theme_pager()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_pager_link($variables) {
|
||||
$text = $variables['text'];
|
||||
$page_new = $variables['page_new'];
|
||||
$element = $variables['element'];
|
||||
$parameters = $variables['parameters'];
|
||||
$attributes = $variables['attributes'];
|
||||
|
||||
$page = isset($_GET['page']) ? $_GET['page'] : '';
|
||||
if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
|
||||
$parameters['page'] = $new_page;
|
||||
}
|
||||
|
||||
$query = array();
|
||||
if (count($parameters)) {
|
||||
$query = drupal_get_query_parameters($parameters, array());
|
||||
}
|
||||
if ($query_pager = pager_get_query_parameters()) {
|
||||
$query = array_merge($query, $query_pager);
|
||||
}
|
||||
|
||||
// Set each pager link title
|
||||
if (!isset($attributes['title'])) {
|
||||
static $titles = NULL;
|
||||
if (!isset($titles)) {
|
||||
$titles = array(
|
||||
t('« first') => t('Go to first page'),
|
||||
t('‹ previous') => t('Go to previous page'),
|
||||
t('next ›') => t('Go to next page'),
|
||||
t('last »') => t('Go to last page'),
|
||||
);
|
||||
}
|
||||
if (isset($titles[$text])) {
|
||||
$attributes['title'] = $titles[$text];
|
||||
}
|
||||
elseif (is_numeric($text)) {
|
||||
$attributes['title'] = t('Go to page @number', array('@number' => $text));
|
||||
}
|
||||
}
|
||||
|
||||
// @todo l() cannot be used here, since it adds an 'active' class based on the
|
||||
// path only (which is always the current path for pager links). Apparently,
|
||||
// none of the pager links is active at any time - but it should still be
|
||||
// possible to use l() here.
|
||||
// @see http://drupal.org/node/1410574
|
||||
$attributes['href'] = url($_GET['q'], array('query' => $query));
|
||||
return '<a' . drupal_attributes($attributes) . '>' . check_plain($text) . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "Pager pieces".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper function
|
||||
*
|
||||
* Copies $old_array to $new_array and sets $new_array[$element] = $value
|
||||
* Fills in $new_array[0 .. $element - 1] = 0
|
||||
*/
|
||||
function pager_load_array($value, $element, $old_array) {
|
||||
$new_array = $old_array;
|
||||
// Look for empty elements.
|
||||
for ($i = 0; $i < $element; $i++) {
|
||||
if (empty($new_array[$i])) {
|
||||
// Load found empty element with 0.
|
||||
$new_array[$i] = 0;
|
||||
}
|
||||
}
|
||||
// Update the changed element.
|
||||
$new_array[$element] = (int) $value;
|
||||
return $new_array;
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Secure password hashing functions for user authentication.
|
||||
*
|
||||
* Based on the Portable PHP password hashing framework.
|
||||
* @see http://www.openwall.com/phpass/
|
||||
*
|
||||
* An alternative or custom version of this password hashing API may be
|
||||
* used by setting the variable password_inc to the name of the PHP file
|
||||
* containing replacement user_hash_password(), user_check_password(), and
|
||||
* user_needs_new_hash() functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The standard log2 number of iterations for password stretching. This should
|
||||
* increase by 1 every Drupal version in order to counteract increases in the
|
||||
* speed and power of computers available to crack the hashes.
|
||||
*/
|
||||
define('DRUPAL_HASH_COUNT', 15);
|
||||
|
||||
/**
|
||||
* The minimum allowed log2 number of iterations for password stretching.
|
||||
*/
|
||||
define('DRUPAL_MIN_HASH_COUNT', 7);
|
||||
|
||||
/**
|
||||
* The maximum allowed log2 number of iterations for password stretching.
|
||||
*/
|
||||
define('DRUPAL_MAX_HASH_COUNT', 30);
|
||||
|
||||
/**
|
||||
* The expected (and maximum) number of characters in a hashed password.
|
||||
*/
|
||||
define('DRUPAL_HASH_LENGTH', 55);
|
||||
|
||||
/**
|
||||
* Returns a string for mapping an int to the corresponding base 64 character.
|
||||
*/
|
||||
function _password_itoa64() {
|
||||
return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes bytes into printable base 64 using the *nix standard from crypt().
|
||||
*
|
||||
* @param $input
|
||||
* The string containing bytes to encode.
|
||||
* @param $count
|
||||
* The number of characters (bytes) to encode.
|
||||
*
|
||||
* @return
|
||||
* Encoded string
|
||||
*/
|
||||
function _password_base64_encode($input, $count) {
|
||||
$output = '';
|
||||
$i = 0;
|
||||
$itoa64 = _password_itoa64();
|
||||
do {
|
||||
$value = ord($input[$i++]);
|
||||
$output .= $itoa64[$value & 0x3f];
|
||||
if ($i < $count) {
|
||||
$value |= ord($input[$i]) << 8;
|
||||
}
|
||||
$output .= $itoa64[($value >> 6) & 0x3f];
|
||||
if ($i++ >= $count) {
|
||||
break;
|
||||
}
|
||||
if ($i < $count) {
|
||||
$value |= ord($input[$i]) << 16;
|
||||
}
|
||||
$output .= $itoa64[($value >> 12) & 0x3f];
|
||||
if ($i++ >= $count) {
|
||||
break;
|
||||
}
|
||||
$output .= $itoa64[($value >> 18) & 0x3f];
|
||||
} while ($i < $count);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random base 64-encoded salt prefixed with settings for the hash.
|
||||
*
|
||||
* Proper use of salts may defeat a number of attacks, including:
|
||||
* - The ability to try candidate passwords against multiple hashes at once.
|
||||
* - The ability to use pre-hashed lists of candidate passwords.
|
||||
* - The ability to determine whether two users have the same (or different)
|
||||
* password without actually having to guess one of the passwords.
|
||||
*
|
||||
* @param $count_log2
|
||||
* Integer that determines the number of iterations used in the hashing
|
||||
* process. A larger value is more secure, but takes more time to complete.
|
||||
*
|
||||
* @return
|
||||
* A 12 character string containing the iteration count and a random salt.
|
||||
*/
|
||||
function _password_generate_salt($count_log2) {
|
||||
$output = '$S$';
|
||||
// Ensure that $count_log2 is within set bounds.
|
||||
$count_log2 = _password_enforce_log2_boundaries($count_log2);
|
||||
// We encode the final log2 iteration count in base 64.
|
||||
$itoa64 = _password_itoa64();
|
||||
$output .= $itoa64[$count_log2];
|
||||
// 6 bytes is the standard salt for a portable phpass hash.
|
||||
$output .= _password_base64_encode(drupal_random_bytes(6), 6);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that $count_log2 is within set bounds.
|
||||
*
|
||||
* @param $count_log2
|
||||
* Integer that determines the number of iterations used in the hashing
|
||||
* process. A larger value is more secure, but takes more time to complete.
|
||||
*
|
||||
* @return
|
||||
* Integer within set bounds that is closest to $count_log2.
|
||||
*/
|
||||
function _password_enforce_log2_boundaries($count_log2) {
|
||||
if ($count_log2 < DRUPAL_MIN_HASH_COUNT) {
|
||||
return DRUPAL_MIN_HASH_COUNT;
|
||||
}
|
||||
elseif ($count_log2 > DRUPAL_MAX_HASH_COUNT) {
|
||||
return DRUPAL_MAX_HASH_COUNT;
|
||||
}
|
||||
|
||||
return (int) $count_log2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using a secure stretched hash.
|
||||
*
|
||||
* By using a salt and repeated hashing the password is "stretched". Its
|
||||
* security is increased because it becomes much more computationally costly
|
||||
* for an attacker to try to break the hash by brute-force computation of the
|
||||
* hashes of a large number of plain-text words or strings to find a match.
|
||||
*
|
||||
* @param $algo
|
||||
* The string name of a hashing algorithm usable by hash(), like 'sha256'.
|
||||
* @param $password
|
||||
* Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to hash.
|
||||
* @param $setting
|
||||
* An existing hash or the output of _password_generate_salt(). Must be
|
||||
* at least 12 characters (the settings and salt).
|
||||
*
|
||||
* @return
|
||||
* A string containing the hashed password (and salt) or FALSE on failure.
|
||||
* The return string will be truncated at DRUPAL_HASH_LENGTH characters max.
|
||||
*/
|
||||
function _password_crypt($algo, $password, $setting) {
|
||||
// Prevent DoS attacks by refusing to hash large passwords.
|
||||
if (strlen($password) > 512) {
|
||||
return FALSE;
|
||||
}
|
||||
// The first 12 characters of an existing hash are its setting string.
|
||||
$setting = substr($setting, 0, 12);
|
||||
|
||||
if ($setting[0] != '$' || $setting[2] != '$') {
|
||||
return FALSE;
|
||||
}
|
||||
$count_log2 = _password_get_count_log2($setting);
|
||||
// Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
|
||||
if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) {
|
||||
return FALSE;
|
||||
}
|
||||
$salt = substr($setting, 4, 8);
|
||||
// Hashes must have an 8 character salt.
|
||||
if (strlen($salt) != 8) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Convert the base 2 logarithm into an integer.
|
||||
$count = 1 << $count_log2;
|
||||
|
||||
// We rely on the hash() function being available in PHP 5.2+.
|
||||
$hash = hash($algo, $salt . $password, TRUE);
|
||||
do {
|
||||
$hash = hash($algo, $hash . $password, TRUE);
|
||||
} while (--$count);
|
||||
|
||||
$len = strlen($hash);
|
||||
$output = $setting . _password_base64_encode($hash, $len);
|
||||
// _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
|
||||
// _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
|
||||
$expected = 12 + ceil((8 * $len) / 6);
|
||||
return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the log2 iteration count from a stored hash or setting string.
|
||||
*/
|
||||
function _password_get_count_log2($setting) {
|
||||
$itoa64 = _password_itoa64();
|
||||
return strpos($itoa64, $setting[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using a secure hash.
|
||||
*
|
||||
* @param $password
|
||||
* A plain-text password.
|
||||
* @param $count_log2
|
||||
* Optional integer to specify the iteration count. Generally used only during
|
||||
* mass operations where a value less than the default is needed for speed.
|
||||
*
|
||||
* @return
|
||||
* A string containing the hashed password (and a salt), or FALSE on failure.
|
||||
*/
|
||||
function user_hash_password($password, $count_log2 = 0) {
|
||||
if (empty($count_log2)) {
|
||||
// Use the standard iteration count.
|
||||
$count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
|
||||
}
|
||||
return _password_crypt('sha512', $password, _password_generate_salt($count_log2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a plain text password matches a stored hashed password.
|
||||
*
|
||||
* Alternative implementations of this function may use other data in the
|
||||
* $account object, for example the uid to look up the hash in a custom table
|
||||
* or remote database.
|
||||
*
|
||||
* @param $password
|
||||
* A plain-text password
|
||||
* @param $account
|
||||
* A user object with at least the fields from the {users} table.
|
||||
*
|
||||
* @return
|
||||
* TRUE or FALSE.
|
||||
*/
|
||||
function user_check_password($password, $account) {
|
||||
if (substr($account->pass, 0, 2) == 'U$') {
|
||||
// This may be an updated password from user_update_7000(). Such hashes
|
||||
// have 'U' added as the first character and need an extra md5().
|
||||
$stored_hash = substr($account->pass, 1);
|
||||
$password = md5($password);
|
||||
}
|
||||
else {
|
||||
$stored_hash = $account->pass;
|
||||
}
|
||||
|
||||
$type = substr($stored_hash, 0, 3);
|
||||
switch ($type) {
|
||||
case '$S$':
|
||||
// A normal Drupal 7 password using sha512.
|
||||
$hash = _password_crypt('sha512', $password, $stored_hash);
|
||||
break;
|
||||
case '$H$':
|
||||
// phpBB3 uses "$H$" for the same thing as "$P$".
|
||||
case '$P$':
|
||||
// A phpass password generated using md5. This is an
|
||||
// imported password or from an earlier Drupal version.
|
||||
$hash = _password_crypt('md5', $password, $stored_hash);
|
||||
break;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
return ($hash && $stored_hash == $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a user's hashed password needs to be replaced with a new hash.
|
||||
*
|
||||
* This is typically called during the login process when the plain text
|
||||
* password is available. A new hash is needed when the desired iteration count
|
||||
* has changed through a change in the variable password_count_log2 or
|
||||
* DRUPAL_HASH_COUNT or if the user's password hash was generated in an update
|
||||
* like user_update_7000().
|
||||
*
|
||||
* Alternative implementations of this function might use other criteria based
|
||||
* on the fields in $account.
|
||||
*
|
||||
* @param $account
|
||||
* A user object with at least the fields from the {users} table.
|
||||
*
|
||||
* @return
|
||||
* TRUE or FALSE.
|
||||
*/
|
||||
function user_needs_new_hash($account) {
|
||||
// Check whether this was an updated password.
|
||||
if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) {
|
||||
return TRUE;
|
||||
}
|
||||
// Ensure that $count_log2 is within set bounds.
|
||||
$count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT));
|
||||
// Check whether the iteration count used differs from the standard number.
|
||||
return (_password_get_count_log2($account->pass) !== $count_log2);
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions to handle paths in Drupal, including path aliasing.
|
||||
*
|
||||
* These functions are not loaded for cached pages, but modules that need
|
||||
* to use them in hook_boot() or hook exit() can make them available, by
|
||||
* executing "drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize the $_GET['q'] variable to the proper normal path.
|
||||
*/
|
||||
function drupal_path_initialize() {
|
||||
// Ensure $_GET['q'] is set before calling drupal_normal_path(), to support
|
||||
// path caching with hook_url_inbound_alter().
|
||||
if (empty($_GET['q'])) {
|
||||
$_GET['q'] = variable_get('site_frontpage', 'node');
|
||||
}
|
||||
$_GET['q'] = drupal_get_normal_path($_GET['q']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an alias, return its Drupal system URL if one exists. Given a Drupal
|
||||
* system URL return one of its aliases if such a one exists. Otherwise,
|
||||
* return FALSE.
|
||||
*
|
||||
* @param $action
|
||||
* One of the following values:
|
||||
* - wipe: delete the alias cache.
|
||||
* - alias: return an alias for a given Drupal system path (if one exists).
|
||||
* - source: return the Drupal system URL for a path alias (if one exists).
|
||||
* @param $path
|
||||
* The path to investigate for corresponding aliases or system URLs.
|
||||
* @param $path_language
|
||||
* Optional language code to search the path with. Defaults to the page language.
|
||||
* If there's no path defined for that language it will search paths without
|
||||
* language.
|
||||
*
|
||||
* @return
|
||||
* Either a Drupal system path, an aliased path, or FALSE if no path was
|
||||
* found.
|
||||
*/
|
||||
function drupal_lookup_path($action, $path = '', $path_language = NULL) {
|
||||
global $language_url;
|
||||
// Use the advanced drupal_static() pattern, since this is called very often.
|
||||
static $drupal_static_fast;
|
||||
if (!isset($drupal_static_fast)) {
|
||||
$drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
|
||||
}
|
||||
$cache = &$drupal_static_fast['cache'];
|
||||
|
||||
if (!isset($cache)) {
|
||||
$cache = array(
|
||||
'map' => array(),
|
||||
'no_source' => array(),
|
||||
'whitelist' => NULL,
|
||||
'system_paths' => array(),
|
||||
'no_aliases' => array(),
|
||||
'first_call' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
// Retrieve the path alias whitelist.
|
||||
if (!isset($cache['whitelist'])) {
|
||||
$cache['whitelist'] = variable_get('path_alias_whitelist', NULL);
|
||||
if (!isset($cache['whitelist'])) {
|
||||
$cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
// If no language is explicitly specified we default to the current URL
|
||||
// language. If we used a language different from the one conveyed by the
|
||||
// requested URL, we might end up being unable to check if there is a path
|
||||
// alias matching the URL path.
|
||||
$path_language = $path_language ? $path_language : $language_url->language;
|
||||
|
||||
if ($action == 'wipe') {
|
||||
$cache = array();
|
||||
$cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
|
||||
}
|
||||
elseif ($cache['whitelist'] && $path != '') {
|
||||
if ($action == 'alias') {
|
||||
// During the first call to drupal_lookup_path() per language, load the
|
||||
// expected system paths for the page from cache.
|
||||
if (!empty($cache['first_call'])) {
|
||||
$cache['first_call'] = FALSE;
|
||||
|
||||
$cache['map'][$path_language] = array();
|
||||
// Load system paths from cache.
|
||||
$cid = current_path();
|
||||
if ($cached = cache_get($cid, 'cache_path')) {
|
||||
$cache['system_paths'] = $cached->data;
|
||||
// Now fetch the aliases corresponding to these system paths.
|
||||
$args = array(
|
||||
':system' => $cache['system_paths'],
|
||||
':language' => $path_language,
|
||||
':language_none' => LANGUAGE_NONE,
|
||||
);
|
||||
// Always get the language-specific alias before the language-neutral
|
||||
// one. For example 'de' is less than 'und' so the order needs to be
|
||||
// ASC, while 'xx-lolspeak' is more than 'und' so the order needs to
|
||||
// be DESC. We also order by pid ASC so that fetchAllKeyed() returns
|
||||
// the most recently created alias for each source. Subsequent queries
|
||||
// using fetchField() must use pid DESC to have the same effect.
|
||||
// For performance reasons, the query builder is not used here.
|
||||
if ($path_language == LANGUAGE_NONE) {
|
||||
// Prevent PDO from complaining about a token the query doesn't use.
|
||||
unset($args[':language']);
|
||||
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language = :language_none ORDER BY pid ASC', $args);
|
||||
}
|
||||
elseif ($path_language < LANGUAGE_NONE) {
|
||||
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC', $args);
|
||||
}
|
||||
else {
|
||||
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language DESC, pid ASC', $args);
|
||||
}
|
||||
$cache['map'][$path_language] = $result->fetchAllKeyed();
|
||||
// Keep a record of paths with no alias to avoid querying twice.
|
||||
$cache['no_aliases'][$path_language] = array_flip(array_diff_key($cache['system_paths'], array_keys($cache['map'][$path_language])));
|
||||
}
|
||||
}
|
||||
// If the alias has already been loaded, return it.
|
||||
if (isset($cache['map'][$path_language][$path])) {
|
||||
return $cache['map'][$path_language][$path];
|
||||
}
|
||||
// Check the path whitelist, if the top_level part before the first /
|
||||
// is not in the list, then there is no need to do anything further,
|
||||
// it is not in the database.
|
||||
elseif (!isset($cache['whitelist'][strtok($path, '/')])) {
|
||||
return FALSE;
|
||||
}
|
||||
// For system paths which were not cached, query aliases individually.
|
||||
elseif (!isset($cache['no_aliases'][$path_language][$path])) {
|
||||
$args = array(
|
||||
':source' => $path,
|
||||
':language' => $path_language,
|
||||
':language_none' => LANGUAGE_NONE,
|
||||
);
|
||||
// See the queries above.
|
||||
if ($path_language == LANGUAGE_NONE) {
|
||||
unset($args[':language']);
|
||||
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language = :language_none ORDER BY pid DESC", $args)->fetchField();
|
||||
}
|
||||
elseif ($path_language > LANGUAGE_NONE) {
|
||||
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args)->fetchField();
|
||||
}
|
||||
else {
|
||||
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args)->fetchField();
|
||||
}
|
||||
$cache['map'][$path_language][$path] = $alias;
|
||||
return $alias;
|
||||
}
|
||||
}
|
||||
// Check $no_source for this $path in case we've already determined that there
|
||||
// isn't a path that has this alias
|
||||
elseif ($action == 'source' && !isset($cache['no_source'][$path_language][$path])) {
|
||||
// Look for the value $path within the cached $map
|
||||
$source = FALSE;
|
||||
if (!isset($cache['map'][$path_language]) || !($source = array_search($path, $cache['map'][$path_language]))) {
|
||||
$args = array(
|
||||
':alias' => $path,
|
||||
':language' => $path_language,
|
||||
':language_none' => LANGUAGE_NONE,
|
||||
);
|
||||
// See the queries above.
|
||||
if ($path_language == LANGUAGE_NONE) {
|
||||
unset($args[':language']);
|
||||
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language = :language_none ORDER BY pid DESC", $args);
|
||||
}
|
||||
elseif ($path_language > LANGUAGE_NONE) {
|
||||
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args);
|
||||
}
|
||||
else {
|
||||
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args);
|
||||
}
|
||||
if ($source = $result->fetchField()) {
|
||||
$cache['map'][$path_language][$source] = $path;
|
||||
}
|
||||
else {
|
||||
// We can't record anything into $map because we do not have a valid
|
||||
// index and there is no need because we have not learned anything
|
||||
// about any Drupal path. Thus cache to $no_source.
|
||||
$cache['no_source'][$path_language][$path] = TRUE;
|
||||
}
|
||||
}
|
||||
return $source;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache system paths for a page.
|
||||
*
|
||||
* Cache an array of the system paths available on each page. We assume
|
||||
* that aliases will be needed for the majority of these paths during
|
||||
* subsequent requests, and load them in a single query during
|
||||
* drupal_lookup_path().
|
||||
*/
|
||||
function drupal_cache_system_paths() {
|
||||
// Check if the system paths for this page were loaded from cache in this
|
||||
// request to avoid writing to cache on every request.
|
||||
$cache = &drupal_static('drupal_lookup_path', array());
|
||||
if (empty($cache['system_paths']) && !empty($cache['map'])) {
|
||||
// Generate a cache ID (cid) specifically for this page.
|
||||
$cid = current_path();
|
||||
// The static $map array used by drupal_lookup_path() includes all
|
||||
// system paths for the page request.
|
||||
if ($paths = current($cache['map'])) {
|
||||
$data = array_keys($paths);
|
||||
$expire = REQUEST_TIME + (60 * 60 * 24);
|
||||
cache_set($cid, $data, 'cache_path', $expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an internal Drupal path, return the alias set by the administrator.
|
||||
*
|
||||
* If no path is provided, the function will return the alias of the current
|
||||
* page.
|
||||
*
|
||||
* @param $path
|
||||
* An internal Drupal path.
|
||||
* @param $path_language
|
||||
* An optional language code to look up the path in.
|
||||
*
|
||||
* @return
|
||||
* An aliased path if one was found, or the original path if no alias was
|
||||
* found.
|
||||
*/
|
||||
function drupal_get_path_alias($path = NULL, $path_language = NULL) {
|
||||
// If no path is specified, use the current page's path.
|
||||
if ($path == NULL) {
|
||||
$path = $_GET['q'];
|
||||
}
|
||||
$result = $path;
|
||||
if ($alias = drupal_lookup_path('alias', $path, $path_language)) {
|
||||
$result = $alias;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path alias, return the internal path it represents.
|
||||
*
|
||||
* @param $path
|
||||
* A Drupal path alias.
|
||||
* @param $path_language
|
||||
* An optional language code to look up the path in.
|
||||
*
|
||||
* @return
|
||||
* The internal path represented by the alias, or the original alias if no
|
||||
* internal path was found.
|
||||
*/
|
||||
function drupal_get_normal_path($path, $path_language = NULL) {
|
||||
$original_path = $path;
|
||||
|
||||
// Lookup the path alias first.
|
||||
if ($source = drupal_lookup_path('source', $path, $path_language)) {
|
||||
$path = $source;
|
||||
}
|
||||
|
||||
// Allow other modules to alter the inbound URL. We cannot use drupal_alter()
|
||||
// here because we need to run hook_url_inbound_alter() in the reverse order
|
||||
// of hook_url_outbound_alter().
|
||||
foreach (array_reverse(module_implements('url_inbound_alter')) as $module) {
|
||||
$function = $module . '_url_inbound_alter';
|
||||
$function($path, $original_path, $path_language);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current page is the front page.
|
||||
*
|
||||
* @return
|
||||
* Boolean value: TRUE if the current page is the front page; FALSE if otherwise.
|
||||
*/
|
||||
function drupal_is_front_page() {
|
||||
// Use the advanced drupal_static() pattern, since this is called very often.
|
||||
static $drupal_static_fast;
|
||||
if (!isset($drupal_static_fast)) {
|
||||
$drupal_static_fast['is_front_page'] = &drupal_static(__FUNCTION__);
|
||||
}
|
||||
$is_front_page = &$drupal_static_fast['is_front_page'];
|
||||
|
||||
if (!isset($is_front_page)) {
|
||||
// As drupal_path_initialize updates $_GET['q'] with the 'site_frontpage' path,
|
||||
// we can check it against the 'site_frontpage' variable.
|
||||
$is_front_page = ($_GET['q'] == variable_get('site_frontpage', 'node'));
|
||||
}
|
||||
|
||||
return $is_front_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path matches any pattern in a set of patterns.
|
||||
*
|
||||
* @param $path
|
||||
* The path to match.
|
||||
* @param $patterns
|
||||
* String containing a set of patterns separated by \n, \r or \r\n.
|
||||
*
|
||||
* @return
|
||||
* Boolean value: TRUE if the path matches a pattern, FALSE otherwise.
|
||||
*/
|
||||
function drupal_match_path($path, $patterns) {
|
||||
$regexps = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($regexps[$patterns])) {
|
||||
// Convert path settings to a regular expression.
|
||||
// Therefore replace newlines with a logical or, /* with asterisks and the <front> with the frontpage.
|
||||
$to_replace = array(
|
||||
'/(\r\n?|\n)/', // newlines
|
||||
'/\\\\\*/', // asterisks
|
||||
'/(^|\|)\\\\<front\\\\>($|\|)/' // <front>
|
||||
);
|
||||
$replacements = array(
|
||||
'|',
|
||||
'.*',
|
||||
'\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\2'
|
||||
);
|
||||
$patterns_quoted = preg_quote($patterns, '/');
|
||||
$regexps[$patterns] = '/^(' . preg_replace($to_replace, $replacements, $patterns_quoted) . ')$/';
|
||||
}
|
||||
return (bool)preg_match($regexps[$patterns], $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current URL path of the page being viewed.
|
||||
*
|
||||
* Examples:
|
||||
* - http://example.com/node/306 returns "node/306".
|
||||
* - http://example.com/drupalfolder/node/306 returns "node/306" while
|
||||
* base_path() returns "/drupalfolder/".
|
||||
* - http://example.com/path/alias (which is a path alias for node/306) returns
|
||||
* "node/306" as opposed to the path alias.
|
||||
*
|
||||
* This function is not available in hook_boot() so use $_GET['q'] instead.
|
||||
* However, be careful when doing that because in the case of Example #3
|
||||
* $_GET['q'] will contain "path/alias". If "node/306" is needed, calling
|
||||
* drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available.
|
||||
*
|
||||
* @return
|
||||
* The current Drupal URL path. The path is untrusted user input and must be
|
||||
* treated as such.
|
||||
*
|
||||
* @see request_path()
|
||||
*/
|
||||
function current_path() {
|
||||
return $_GET['q'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the path alias white list.
|
||||
*
|
||||
* @param $source
|
||||
* An optional system path for which an alias is being inserted.
|
||||
*
|
||||
* @return
|
||||
* An array containing a white list of path aliases.
|
||||
*/
|
||||
function drupal_path_alias_whitelist_rebuild($source = NULL) {
|
||||
// When paths are inserted, only rebuild the whitelist if the system path
|
||||
// has a top level component which is not already in the whitelist.
|
||||
if (!empty($source)) {
|
||||
$whitelist = variable_get('path_alias_whitelist', NULL);
|
||||
if (isset($whitelist[strtok($source, '/')])) {
|
||||
return $whitelist;
|
||||
}
|
||||
}
|
||||
// For each alias in the database, get the top level component of the system
|
||||
// path it corresponds to. This is the portion of the path before the first
|
||||
// '/', if present, otherwise the whole path itself.
|
||||
$whitelist = array();
|
||||
$result = db_query("SELECT DISTINCT SUBSTRING_INDEX(source, '/', 1) AS path FROM {url_alias}");
|
||||
foreach ($result as $row) {
|
||||
$whitelist[$row->path] = TRUE;
|
||||
}
|
||||
variable_set('path_alias_whitelist', $whitelist);
|
||||
return $whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a specific URL alias from the database.
|
||||
*
|
||||
* @param $conditions
|
||||
* A string representing the source, a number representing the pid, or an
|
||||
* array of query conditions.
|
||||
*
|
||||
* @return
|
||||
* FALSE if no alias was found or an associative array containing the
|
||||
* following keys:
|
||||
* - source: The internal system path.
|
||||
* - alias: The URL alias.
|
||||
* - pid: Unique path alias identifier.
|
||||
* - language: The language of the alias.
|
||||
*/
|
||||
function path_load($conditions) {
|
||||
if (is_numeric($conditions)) {
|
||||
$conditions = array('pid' => $conditions);
|
||||
}
|
||||
elseif (is_string($conditions)) {
|
||||
$conditions = array('source' => $conditions);
|
||||
}
|
||||
elseif (!is_array($conditions)) {
|
||||
return FALSE;
|
||||
}
|
||||
$select = db_select('url_alias');
|
||||
foreach ($conditions as $field => $value) {
|
||||
$select->condition($field, $value);
|
||||
}
|
||||
return $select
|
||||
->fields('url_alias')
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a path alias to the database.
|
||||
*
|
||||
* @param $path
|
||||
* An associative array containing the following keys:
|
||||
* - source: The internal system path.
|
||||
* - alias: The URL alias.
|
||||
* - pid: (optional) Unique path alias identifier.
|
||||
* - language: (optional) The language of the alias.
|
||||
*/
|
||||
function path_save(&$path) {
|
||||
$path += array('language' => LANGUAGE_NONE);
|
||||
|
||||
// Load the stored alias, if any.
|
||||
if (!empty($path['pid']) && !isset($path['original'])) {
|
||||
$path['original'] = path_load($path['pid']);
|
||||
}
|
||||
|
||||
if (empty($path['pid'])) {
|
||||
drupal_write_record('url_alias', $path);
|
||||
module_invoke_all('path_insert', $path);
|
||||
}
|
||||
else {
|
||||
drupal_write_record('url_alias', $path, array('pid'));
|
||||
module_invoke_all('path_update', $path);
|
||||
}
|
||||
|
||||
// Clear internal properties.
|
||||
unset($path['original']);
|
||||
|
||||
// Clear the static alias cache.
|
||||
drupal_clear_path_cache($path['source']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a URL alias.
|
||||
*
|
||||
* @param $criteria
|
||||
* A number representing the pid or an array of criteria.
|
||||
*/
|
||||
function path_delete($criteria) {
|
||||
if (!is_array($criteria)) {
|
||||
$criteria = array('pid' => $criteria);
|
||||
}
|
||||
$path = path_load($criteria);
|
||||
if (isset($path['source'])) {
|
||||
$query = db_delete('url_alias');
|
||||
foreach ($criteria as $field => $value) {
|
||||
$query->condition($field, $value);
|
||||
}
|
||||
$query->execute();
|
||||
module_invoke_all('path_delete', $path);
|
||||
drupal_clear_path_cache($path['source']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path is in the administrative section of the site.
|
||||
*
|
||||
* By default, paths are considered to be non-administrative. If a path does
|
||||
* not match any of the patterns in path_get_admin_paths(), or if it matches
|
||||
* both administrative and non-administrative patterns, it is considered
|
||||
* non-administrative.
|
||||
*
|
||||
* @param $path
|
||||
* A Drupal path.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the path is administrative, FALSE otherwise.
|
||||
*
|
||||
* @see path_get_admin_paths()
|
||||
* @see hook_admin_paths()
|
||||
* @see hook_admin_paths_alter()
|
||||
*/
|
||||
function path_is_admin($path) {
|
||||
$path_map = &drupal_static(__FUNCTION__);
|
||||
if (!isset($path_map['admin'][$path])) {
|
||||
$patterns = path_get_admin_paths();
|
||||
$path_map['admin'][$path] = drupal_match_path($path, $patterns['admin']);
|
||||
$path_map['non_admin'][$path] = drupal_match_path($path, $patterns['non_admin']);
|
||||
}
|
||||
return $path_map['admin'][$path] && !$path_map['non_admin'][$path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of administrative and non-administrative paths.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the following keys:
|
||||
* 'admin': An array of administrative paths and regular expressions
|
||||
* in a format suitable for drupal_match_path().
|
||||
* 'non_admin': An array of non-administrative paths and regular expressions.
|
||||
*
|
||||
* @see hook_admin_paths()
|
||||
* @see hook_admin_paths_alter()
|
||||
*/
|
||||
function path_get_admin_paths() {
|
||||
$patterns = &drupal_static(__FUNCTION__);
|
||||
if (!isset($patterns)) {
|
||||
$paths = module_invoke_all('admin_paths');
|
||||
drupal_alter('admin_paths', $paths);
|
||||
// Combine all admin paths into one array, and likewise for non-admin paths,
|
||||
// for easier handling.
|
||||
$patterns = array();
|
||||
$patterns['admin'] = array();
|
||||
$patterns['non_admin'] = array();
|
||||
foreach ($paths as $path => $enabled) {
|
||||
if ($enabled) {
|
||||
$patterns['admin'][] = $path;
|
||||
}
|
||||
else {
|
||||
$patterns['non_admin'][] = $path;
|
||||
}
|
||||
}
|
||||
$patterns['admin'] = implode("\n", $patterns['admin']);
|
||||
$patterns['non_admin'] = implode("\n", $patterns['non_admin']);
|
||||
}
|
||||
return $patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a path exists and the current user has access to it.
|
||||
*
|
||||
* @param $path
|
||||
* The path to check.
|
||||
* @param $dynamic_allowed
|
||||
* Whether paths with menu wildcards (like user/%) should be allowed.
|
||||
*
|
||||
* @return
|
||||
* TRUE if it is a valid path AND the current user has access permission,
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
function drupal_valid_path($path, $dynamic_allowed = FALSE) {
|
||||
global $menu_admin;
|
||||
// We indicate that a menu administrator is running the menu access check.
|
||||
$menu_admin = TRUE;
|
||||
if ($path == '<front>' || url_is_external($path)) {
|
||||
$item = array('access' => TRUE);
|
||||
}
|
||||
elseif ($dynamic_allowed && preg_match('/\/\%/', $path)) {
|
||||
// Path is dynamic (ie 'user/%'), so check directly against menu_router table.
|
||||
if ($item = db_query("SELECT * FROM {menu_router} where path = :path", array(':path' => $path))->fetchAssoc()) {
|
||||
$item['link_path'] = $item['path'];
|
||||
$item['link_title'] = $item['title'];
|
||||
$item['external'] = FALSE;
|
||||
$item['options'] = '';
|
||||
_menu_link_translate($item);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$item = menu_get_item($path);
|
||||
}
|
||||
$menu_admin = FALSE;
|
||||
return $item && $item['access'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the path cache.
|
||||
*
|
||||
* @param $source
|
||||
* An optional system path for which an alias is being changed.
|
||||
*/
|
||||
function drupal_clear_path_cache($source = NULL) {
|
||||
// Clear the drupal_lookup_path() static cache.
|
||||
drupal_static_reset('drupal_lookup_path');
|
||||
drupal_path_alias_whitelist_rebuild($source);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user