Compare commits
817 commits
be6a3e28ca
...
59416eec4d
Author | SHA1 | Date | |
---|---|---|---|
59416eec4d | |||
|
df121586c1 | ||
|
00a9ddd013 | ||
|
326870f216 | ||
|
ca07f265f1 | ||
|
4557abb981 | ||
|
c88bc1d760 | ||
|
bd3e71cacb | ||
|
1697e7a4bd | ||
|
db71d2fcd4 | ||
|
2caa586a4d | ||
|
c60a42890b | ||
|
beb683e463 | ||
|
408b7e7d0a | ||
|
646f63e413 | ||
|
a98553b601 | ||
|
2ef90ec791 | ||
|
0222e62ea4 | ||
|
5b21103d76 | ||
|
728169f185 | ||
|
78f7a338c1 | ||
|
bfdbb62cd1 | ||
|
ba21aa60d9 | ||
|
484ee2bbb0 | ||
|
e5545436b0 | ||
|
00db4f6bcc | ||
|
e7a2d06a63 | ||
|
00ccae495c | ||
|
1e7419eca9 | ||
|
2a88f7a526 | ||
|
8d3e224936 | ||
|
4e653e1e97 | ||
|
00264cb4a1 | ||
|
ee5ee294b2 | ||
|
81cfed8be4 | ||
|
0992c86f16 | ||
|
256cffb289 | ||
|
62552cc413 | ||
|
a4b3430078 | ||
|
5dd9bc93e1 | ||
|
9230af464d | ||
|
b7719cdf0a | ||
|
db1532214e | ||
|
c4d3e2d7ec | ||
|
5db074fdef | ||
|
ea3cda543c | ||
|
280e6138fc | ||
|
4b94854200 | ||
|
775fcb44f9 | ||
|
25d2a9b744 | ||
|
572c55f0b1 | ||
|
add670b8ab | ||
|
5a6515c988 | ||
|
ea57088177 | ||
|
638a7c5141 | ||
|
c937d3e184 | ||
|
4cbba2fe64 | ||
|
949e03ca1f | ||
|
e91984b507 | ||
|
f812581234 | ||
|
5af1c9dbae | ||
|
e7ff18c864 | ||
|
66982b6f79 | ||
|
6d16b6c8a8 | ||
|
910b695d4d | ||
|
d24fc65695 | ||
|
c44a0d200d | ||
|
9fd6739a1f | ||
|
3b5923b7e1 | ||
|
467b28c237 | ||
|
0eee6a2ba1 | ||
|
27db81ab07 | ||
|
f64b4666ea | ||
|
a1c6460077 | ||
|
a8c7c5c4bf | ||
|
eb340c7eb6 | ||
|
22b4044986 | ||
|
d48e06f438 | ||
|
88b76c44f7 | ||
|
1cd642619f | ||
|
5f69e17310 | ||
|
e0a8f961ba | ||
|
739776a754 | ||
|
08d9347e9a | ||
|
050bb50cdf | ||
|
ef9d019ccd | ||
|
a7e361e22a | ||
|
9a1ad45e90 | ||
|
fd4379992d | ||
|
9c9d6298bf | ||
|
625235787e | ||
|
062698c123 | ||
|
0a47d89193 | ||
|
6624f00c05 | ||
|
c0c743f9c6 | ||
|
9e24bb317c | ||
|
ebe9417981 | ||
|
8cd369aee7 | ||
|
b858332f9f | ||
|
8457001294 | ||
|
f15ac5957a | ||
|
721c090198 | ||
|
9195ce0378 | ||
|
4b88cbb56c | ||
|
e4ee672404 | ||
|
169755c6a9 | ||
|
4c76d4eea9 | ||
|
99485504d0 | ||
|
cc2ea94d06 | ||
|
1222aa62c5 | ||
|
50c27108c7 | ||
|
0d70e5e44f | ||
|
a9acad9e1a | ||
|
f3316d707a | ||
|
b9938cf084 | ||
|
8226308d67 | ||
|
1ba35135a1 | ||
|
de895c73b7 | ||
|
3c39cea735 | ||
|
ab16f6a826 | ||
|
c0ace02c3a | ||
|
6d4e397805 | ||
|
965f8948ee | ||
|
bc470ef8c7 | ||
|
ff4d8b5198 | ||
|
8f8abf2025 | ||
|
7bcf4255a7 | ||
|
733b40446e | ||
|
e957f8fb23 | ||
|
c971344ddb | ||
|
f28b9d550e | ||
|
00cce1f8c7 | ||
|
cd618bd8be | ||
|
84b37c7baa | ||
|
4242f6955a | ||
|
611b794034 | ||
|
5abf19faf2 | ||
|
ac8966566e | ||
|
0a058bba45 | ||
|
864e0bf775 | ||
|
75e659e3a2 | ||
|
15fb1bdeef | ||
|
89a945a1c9 | ||
|
bd0d639c60 | ||
|
75fe26dbdb | ||
|
a54795f8eb | ||
|
6775e2a2af | ||
|
a265865124 | ||
|
ea1810d4da | ||
|
a66f036ece | ||
|
288b25ae61 | ||
|
93a826f0a4 | ||
|
dc589b7490 | ||
|
5177e036fa | ||
|
74883e373e | ||
|
8b1f9115ad | ||
|
e0a9d66d99 | ||
|
bd81f94287 | ||
|
b5988ce296 | ||
|
1da6caedfb | ||
|
db33cd79b5 | ||
|
6180c859d3 | ||
|
dbd99f310f | ||
|
221a2534b2 | ||
|
8c4c408072 | ||
|
51603fe265 | ||
|
0f896033fa | ||
|
dc08e17996 | ||
|
7c6df1c80f | ||
|
3665594d36 | ||
|
8a7a09df90 | ||
|
bde1fdcb48 | ||
|
10b460fbd2 | ||
|
130008da03 | ||
|
b59cdb3871 | ||
|
d5b218eed4 | ||
|
22d43c303b | ||
|
8b10110c2b | ||
|
32777d9fd2 | ||
|
46a104e433 | ||
|
11cf6e9b49 | ||
|
39c89d84f5 | ||
|
bf8bec322b | ||
|
91dbe63e00 | ||
|
fdfc09f73f | ||
|
0abe2f0dcc | ||
|
40a26241de | ||
|
e22c550cca | ||
|
3813b39a4c | ||
|
21af53f795 | ||
|
026ed40d90 | ||
|
7e39b5ee36 | ||
|
febe0a6487 | ||
|
803f6a7e36 | ||
|
1387e059eb | ||
|
9d99925615 | ||
|
6ecc4745f4 | ||
|
f6076a9275 | ||
|
abf5a0fb9f | ||
|
c2c8990f23 | ||
|
f2cd2215f0 | ||
|
441b755712 | ||
|
cbb140a9af | ||
|
30cf6bf92e | ||
|
6067508bb1 | ||
|
39a3749f3b | ||
|
cf9031da22 | ||
|
aeda845b3c | ||
|
7ae4b2f8b0 | ||
|
df0ca43873 | ||
|
21550cda7d | ||
|
e0f4fd4537 | ||
|
163cd8ce2d | ||
|
d2019d3a09 | ||
|
c6f27b1ade | ||
|
6202038c65 | ||
|
a7d43caccb | ||
|
6e0f92acb4 | ||
|
6ca3980812 | ||
|
a276e0c4e6 | ||
|
b1f026b674 | ||
|
a908ed6866 | ||
|
6ca8ab31b2 | ||
|
f33af48b59 | ||
|
dea58d5abd | ||
|
36e16b3d17 | ||
|
3b1818dfa6 | ||
|
9a62827452 | ||
|
dba8975f85 | ||
|
4dfabbf8a7 | ||
|
65d9afa9b8 | ||
|
142370f0a1 | ||
|
a0eed0546b | ||
|
ab6a4c9f7b | ||
|
63fe4881cc | ||
|
a79707258f | ||
|
1a36e314ba | ||
|
4cfaa67e07 | ||
|
338648093f | ||
|
d1fed70609 | ||
|
4a63c2bbb4 | ||
|
f841121a6e | ||
|
2a569f1e93 | ||
|
bf63992617 | ||
|
eeaabc05a7 | ||
|
11c6fc418d | ||
|
4f6a4f6107 | ||
|
a047b62c2b | ||
|
fbedbd8ee2 | ||
|
0b97c4279e | ||
|
8887f9826a | ||
|
2ce2ef3708 | ||
|
d8fe0e7276 | ||
|
3d27ecbb37 | ||
|
fbb33c723e | ||
|
bbb2dd30ac | ||
|
62c9b3a6d9 | ||
|
bbcb18931c | ||
|
d4da270289 | ||
|
832739e58f | ||
|
0f4cd55599 | ||
|
e660450a2b | ||
|
45b3c7cb7a | ||
|
723e93f439 | ||
|
6225cc9c86 | ||
|
824f3c6dff | ||
|
4ca1b33b0e | ||
|
c273f511aa | ||
|
a5482cf9af | ||
|
060e6d9498 | ||
|
e00b9dc099 | ||
|
0ed6614981 | ||
|
9700d9ea3e | ||
|
9f87e82cdb | ||
|
5bf3deb815 | ||
|
ba4fa9460a | ||
|
8b428dabea | ||
|
af764dfd4e | ||
|
44b0825860 | ||
|
830a73dcf6 | ||
|
0681511699 | ||
|
9665870b39 | ||
|
b2a43bc861 | ||
|
bd7ed438fa | ||
|
b29f14d67e | ||
|
0a47426f88 | ||
|
2c2c349e8a | ||
|
2ea2c99dcb | ||
|
4543717881 | ||
|
fe170cb571 | ||
|
755c094bdd | ||
|
8b8ed23ae1 | ||
|
28123e7283 | ||
|
546c776acd | ||
|
e1847ae5a7 | ||
|
fb0d610c80 | ||
|
c5cd2f16f4 | ||
|
3bb6205e7f | ||
|
94dadd85a0 | ||
|
9db1ccdf2c | ||
|
bcba6bd353 | ||
|
8997ae6c8e | ||
|
11edc143b4 | ||
|
a1cd7a3b2f | ||
|
83b4eb1795 | ||
|
d496fd857d | ||
|
e570cc8b1a | ||
|
9b8c0a4560 | ||
|
055d97f9a9 | ||
|
9ef8555ad2 | ||
|
dafd3f081a | ||
|
6b76ce6f62 | ||
|
baac4388b1 | ||
|
5b5d22a3df | ||
|
3d6278e86f | ||
|
93175b6e9d | ||
|
47ac77adbb | ||
|
ffa39719a1 | ||
|
31c4be2b60 | ||
|
2fbdb7d657 | ||
|
8fbc29de02 | ||
|
9e55beebfd | ||
|
ccd1862d5f | ||
|
544bbdaf83 | ||
|
bf02f8ba8e | ||
|
e6754f2154 | ||
|
ed4ee8f029 | ||
|
20ba77a2dc | ||
|
0640c1a6db | ||
|
035a002edc | ||
|
fe58bdcd9e | ||
|
8ed5fbef8f | ||
|
b01b3b83a7 | ||
|
f2e309b67d | ||
|
151fa1e450 | ||
|
f00600a283 | ||
|
88a8e284b2 | ||
|
ab4c170672 | ||
|
bd11879018 | ||
|
8f423eb11c | ||
|
b1d78519a8 | ||
|
3d5f05052f | ||
|
51dea3b87e | ||
|
6a3a78d023 | ||
|
5b74c67461 | ||
|
2a20e67f76 | ||
|
ee07df357b | ||
|
495545f2f0 | ||
|
8a6b7e96b7 | ||
|
e4b8330e45 | ||
|
05c616f7a0 | ||
|
70507b8603 | ||
|
b2eb77e1f7 | ||
|
51580efbff | ||
|
da950241f3 | ||
|
a6e9c08499 | ||
|
2883c6d0a7 | ||
|
1595d0e2b3 | ||
|
150f2a0f24 | ||
|
831e974ea5 | ||
|
6f9e0609f4 | ||
|
a6935feb22 | ||
|
5a09b5fffd | ||
|
302662797c | ||
|
c94c32d1a3 | ||
|
39b75ea983 | ||
|
7e78237fc9 | ||
|
325cc8adad | ||
|
8affa22431 | ||
|
80c8889bfe | ||
|
85c09fe379 | ||
|
336f15e8ba | ||
|
1e49a65a2a | ||
|
2f4df75304 | ||
|
5c856a6923 | ||
|
b99e00f7cd | ||
|
53054b2bf6 | ||
|
b7c50a58de | ||
|
e09bb93e18 | ||
|
d9d71b10c3 | ||
|
c51d65238b | ||
|
8d8fa898ab | ||
|
00d3dd91ef | ||
|
8a9796014c | ||
|
9952de2fe0 | ||
|
ce901a5828 | ||
|
8c5f6c786d | ||
|
cfdd209440 | ||
|
8a1ce1da15 | ||
|
df9aac5b64 | ||
|
67339338af | ||
|
b3bd8c3e8d | ||
|
48df9f45b8 | ||
|
c61d8a85b7 | ||
|
a5a4fb1793 | ||
|
5f987a64d8 | ||
|
8bbf57a2d0 | ||
|
47d1581850 | ||
|
a4a59e183e | ||
|
330ac859fb | ||
|
740b32b520 | ||
|
1a94978e44 | ||
|
38b55fbf3d | ||
|
b7ec15790e | ||
|
b862705947 | ||
|
dff039092d | ||
|
b37ca79072 | ||
|
14c9370b4f | ||
|
114a43b20e | ||
|
1ca7ddd76b | ||
|
d3f6d52525 | ||
|
d2bb40cc7c | ||
|
156061d445 | ||
|
06734af130 | ||
|
ff9686066e | ||
|
358cb20bcb | ||
|
b2b5ef3122 | ||
|
34c8f558e5 | ||
|
6a71675887 | ||
|
c609944cb9 | ||
|
25e90d8d75 | ||
|
5d8de7587d | ||
|
b8e5a253ab | ||
|
54afb1d6f6 | ||
|
36e6d88dbf | ||
|
c2cd15dac2 | ||
|
977db7eabc | ||
|
9c04921a8c | ||
|
e6215a2ad9 | ||
|
034c1ce526 | ||
|
e69e3fef7b | ||
|
42a72c02fa | ||
|
820cae27cf | ||
|
49da2ffbce | ||
|
8f6e3d51cc | ||
|
2f87bfdc69 | ||
|
0cf76ccb47 | ||
|
3445443349 | ||
|
5c06c0870f | ||
|
b38a1b0209 | ||
|
ca5e98da48 | ||
|
d8030c8155 | ||
|
21e72da9ee | ||
|
9b3c1270bc | ||
|
552c3b942a | ||
|
5256b42870 | ||
|
52e27a96a4 | ||
|
6866ed766f | ||
|
f1a148ab92 | ||
|
4e3875c0ce | ||
|
64cac25626 | ||
|
3cb4e8a44c | ||
|
81c9df1363 | ||
|
7836ed9b2e | ||
|
3adbdc2a83 | ||
|
7f5250421b | ||
|
cd2878edee | ||
|
4b3aca6623 | ||
|
5334090be0 | ||
|
4cf3564d28 | ||
|
f34554c6c2 | ||
|
ec45749187 | ||
|
4a26974a4b | ||
|
efb7d21b52 | ||
|
29c31b7ec6 | ||
|
fd1ddad98d | ||
|
458b6b9918 | ||
|
543b16b4f4 | ||
|
8f269b49d7 | ||
|
8fabcd0224 | ||
|
84045ffbb1 | ||
|
64152387d6 | ||
|
2d015d79b7 | ||
|
3020310dd0 | ||
|
b028f0869f | ||
|
7f1bb5553b | ||
|
72fbbcd679 | ||
|
df25b28dcd | ||
|
fc4d1b6796 | ||
|
bb176441cb | ||
|
7b18876361 | ||
|
ee07b7283f | ||
|
f4ea7cd563 | ||
|
1db2ebbd79 | ||
|
255b2264a1 | ||
|
80a3efe116 | ||
|
25cb75552b | ||
|
d8ef4a893f | ||
|
95158e7565 | ||
|
22e75f062d | ||
|
c3fca560b6 | ||
|
769a28833b | ||
|
1ea09a1b8b | ||
|
d246e2c512 | ||
|
3a49307c3d | ||
|
d018755b45 | ||
|
f447edb73b | ||
|
ab58f25420 | ||
|
a5a9cf23ac | ||
|
2b7a7bc928 | ||
|
676571dab9 | ||
|
6cdca9562c | ||
|
b1baca99f2 | ||
|
8f60e1206e | ||
|
24225f6332 | ||
|
e011be0170 | ||
|
d0ae1ba273 | ||
|
1cb5be5d0c | ||
|
585fc700fa | ||
|
85b972baf6 | ||
|
71eb87353c | ||
|
0f686afe11 | ||
|
6ec24b3605 | ||
|
cdb96276c1 | ||
|
9192a48be3 | ||
|
98325d646e | ||
|
96746d7165 | ||
|
abe033be85 | ||
|
5baafe5001 | ||
|
98e7a59ca2 | ||
|
2785d85e0a | ||
|
76fe68d924 | ||
|
6f199ee489 | ||
|
4488ea4bb9 | ||
|
0d930454a2 | ||
|
4af591ff3c | ||
|
b93cfeba7b | ||
|
650a5f09cb | ||
|
e809908f9e | ||
|
97870f3512 | ||
|
68855686db | ||
|
a5e9f2d6c9 | ||
|
61f0c4b679 | ||
|
f682f1b899 | ||
|
083b28021a | ||
|
19489e92d7 | ||
|
5eece37b0a | ||
|
d8847936d4 | ||
|
9417f1337e | ||
|
1a19c921a9 | ||
|
78b5b44d8f | ||
|
ff2b5f5bd8 | ||
|
48b19a7014 | ||
|
02117f7ea3 | ||
|
e21df1e729 | ||
|
f3ab261631 | ||
|
38d66e1a40 | ||
|
ecdae2237f | ||
|
45203c0bca | ||
|
46e019a132 | ||
|
78f319fa6b | ||
|
6c4cae378e | ||
|
1aeefe1088 | ||
|
e0fe33f90b | ||
|
b6c9a2db30 | ||
|
5cacf290f0 | ||
|
56ae25f11f | ||
|
f5afa87c38 | ||
|
dfe14f264b | ||
|
74c2ae4088 | ||
|
328c215a8a | ||
|
dfed9b2dd5 | ||
|
881bd96f15 | ||
|
778add2c9c | ||
|
538fb324a8 | ||
|
c84d143047 | ||
|
30255b794a | ||
|
41b93897f3 | ||
|
6384447d1d | ||
|
a32e6665d0 | ||
|
fe007f94e4 | ||
|
91a21c2729 | ||
|
4ff703e369 | ||
|
d52ab0b1e9 | ||
|
6128ab6a55 | ||
|
da7acb9830 | ||
|
e2dff28b44 | ||
|
949a095310 | ||
|
27ddfec3c3 | ||
|
a5dd7d58d2 | ||
|
707a1d237a | ||
|
a3cb851d0c | ||
|
7b05dd71f3 | ||
|
e2a2441d3b | ||
|
2ba51040c7 | ||
|
d33cffdb2e | ||
|
2cd0509b50 | ||
|
80b708a878 | ||
|
46d3f8162b | ||
|
0386a84d82 | ||
|
ce7918386a | ||
|
21163a3329 | ||
|
865f0a0e01 | ||
|
9e6371a6fd | ||
|
0a286f6946 | ||
|
2835ac7cbe | ||
|
ca636b898c | ||
|
d95624add4 | ||
|
43582975dc | ||
|
82fcace8fc | ||
|
14fcfb5213 | ||
|
cd10bc23e7 | ||
|
11aa4a7a29 | ||
|
9e2d47e519 | ||
|
aca995e09c | ||
|
0e60b7f174 | ||
|
4479aff18f | ||
|
63b0059ed5 | ||
|
06f05c923a | ||
|
a975d97a8d | ||
|
e813934ae1 | ||
|
816ffba74b | ||
|
b06fc28aa3 | ||
|
a8e210faa6 | ||
|
972daa4513 | ||
|
8af1d2da60 | ||
|
ebc027ec0a | ||
|
3eba6bd318 | ||
|
bea062149e | ||
|
2d8a0a71a8 | ||
|
46237c9788 | ||
|
6152a26790 | ||
|
109ebf318f | ||
|
7e3dc0ba98 | ||
|
af41d5ab5d | ||
|
0c6fdbe12b | ||
|
bedbb845ee | ||
|
1a68ae5a29 | ||
|
d6e5f04d39 | ||
|
af074f9030 | ||
|
f7f08ceec1 | ||
|
624123177f | ||
|
301c7ab1a0 | ||
|
b725eb047d | ||
|
a285668ec4 | ||
|
9fbc42294e | ||
|
bc583903ad | ||
|
204035bd3c | ||
|
87ae3c4f08 | ||
|
8e9169ceba | ||
|
3ee8351e43 | ||
|
fabff3835d | ||
|
a8c11451e8 | ||
|
c4ad3d4f06 | ||
|
1a8ac737e5 | ||
|
6132d64748 | ||
|
764d34a7d3 | ||
|
1b8620b1ad | ||
|
78657347c5 | ||
|
c70ff64a61 | ||
|
e8a10f312a | ||
|
3447d888d7 | ||
|
7b8a6f2858 | ||
|
1ab675445e | ||
|
baa6979194 | ||
|
9c75f87793 | ||
|
818b3193ff | ||
|
c22fa57a55 | ||
|
8eac2e5488 | ||
|
66063ed1a1 | ||
|
465033230d | ||
|
fdedbfd4a7 | ||
|
ef00f9d203 | ||
|
ba43064ddb | ||
|
2899ebb5b5 | ||
|
af290059d1 | ||
|
893f5159c6 | ||
|
dd09ec52b2 | ||
|
5ec4708ced | ||
|
7b2ba6ef82 | ||
|
f4929b1188 | ||
|
c56a540c6e | ||
|
029ada5a07 | ||
|
c4d5be53c2 | ||
|
e3d28be967 | ||
|
07f99432b7 | ||
|
69e29ff65e | ||
|
60ae241251 | ||
|
3772298ee7 | ||
|
c79473bd84 | ||
|
72caf4e84c | ||
|
c266a89d0f | ||
|
03340c18ea | ||
|
8e47af2b36 | ||
|
b0428aa9b0 | ||
|
485b168a96 | ||
|
bee33239ed | ||
|
b8e3630f2e | ||
|
c909f5d5cc | ||
|
2a891ca6b1 | ||
|
df46c28208 | ||
|
7e884740f1 | ||
|
78c2f122e0 | ||
|
8694e8411b | ||
|
e1231265bc | ||
|
a69cfe0dd2 | ||
|
9ba6982ea3 | ||
|
21e5df5ee8 | ||
|
c0d750b9e5 | ||
|
8f80821820 | ||
|
50c9543f7b | ||
|
8f6202deb0 | ||
|
cf01113cad | ||
|
752bc4c5e6 | ||
|
2dd6ecb126 | ||
|
dbbcb0c6cf | ||
|
273453900a | ||
|
1ea8aeef76 | ||
|
46846fd4fc | ||
|
cc2ded54e1 | ||
|
424530d9af | ||
|
810f0f6c96 | ||
|
df7c286d91 | ||
|
db206aaaca | ||
|
1f02ae8076 | ||
|
922341aaa7 | ||
|
82e7b56f29 | ||
|
52964ec873 | ||
|
f6637392a9 | ||
|
b495d5c92a | ||
|
c653ae3bfb | ||
|
27ceea2aee | ||
|
0498b209b5 | ||
|
9e4cc28e29 | ||
|
dd51f653d0 | ||
|
6c50a6ccce | ||
|
1410dce2db | ||
|
20433ea72b | ||
|
529fc750b3 | ||
|
0d42b21200 | ||
|
fc6c701774 | ||
|
09390a50cd | ||
|
04a816f648 | ||
|
3b04d19a62 | ||
|
54ab5636e3 | ||
|
a8a38401f0 | ||
|
7a7a523782 | ||
|
6fa3c87d32 | ||
|
57bd9780c8 | ||
|
dd452c5691 | ||
|
1001cc108f | ||
|
12523aea34 | ||
|
fecfc73b3f | ||
|
a39acb2518 | ||
|
7e3648ad87 | ||
|
4869d535b5 | ||
|
3fb29fdda0 | ||
|
e26e2060f5 | ||
|
cf92b4dd15 | ||
|
336a28fa4a | ||
|
796c4c57d0 | ||
|
def39d0dd7 | ||
|
ef02885753 | ||
|
7d0db8b567 | ||
|
d9bfceaddf | ||
|
74c1d02079 | ||
|
5256f83d02 | ||
|
eb0a0f77cc | ||
|
af8a03d1ab | ||
|
ac2214bdb5 | ||
|
72539044fb | ||
|
99c7d66384 | ||
|
3575fe5bcf | ||
|
54b065c253 | ||
|
4b15c49198 | ||
|
83ef0ff176 | ||
|
f4c6625962 | ||
|
b4665de89b | ||
|
5ed0d9f54d | ||
|
bda0d35a51 | ||
|
31d691649a | ||
|
0b631e69d1 | ||
|
954b3c81ce | ||
|
1df5e9ca86 | ||
|
c139a6e9ed | ||
|
b04ff6cf16 | ||
|
7eb196723e | ||
|
fc66e61ca9 | ||
|
0baa658130 | ||
|
51837fe8ba | ||
|
e0e24335f7 | ||
|
3b0f03770b | ||
|
90ea2cb62e | ||
|
8d4cada793 | ||
|
424ae4b001 | ||
|
188a99db99 | ||
|
354fb98cc9 | ||
|
5669f474f3 | ||
|
b5e2b23c99 | ||
|
0a4bc5a17d | ||
|
b405a44f29 | ||
|
5f1617a480 | ||
|
bd1adc8df6 | ||
|
7ff3ed1d63 | ||
|
14a7d73c2d | ||
|
edcfe54c45 | ||
|
9f9627059a | ||
|
8ba951640c | ||
|
1a6d61766a | ||
|
3a52dfcc5c | ||
|
f400ba291d | ||
|
31c788ddcf | ||
|
e29a9a73b4 | ||
|
df40879739 | ||
|
91af3e6b19 | ||
|
5e61546a32 | ||
|
211d93307b | ||
|
58c2701e54 | ||
|
419eef1f56 | ||
|
fa7625219d | ||
|
67d4029fee | ||
|
9f3bdf5895 | ||
|
f9b99c7217 | ||
|
06a8992737 | ||
|
ad8099a7af | ||
|
d1bcf28db3 | ||
|
719ef8e896 |
|
@ -1,17 +0,0 @@
|
||||||
options:
|
|
||||||
max-warnings: 0
|
|
||||||
rules:
|
|
||||||
property-sort-order:
|
|
||||||
- 0
|
|
||||||
# Sort order rule does not work with CSS variables: https://github.com/sasstools/sass-lint/issues/1161
|
|
||||||
# - 1
|
|
||||||
# -
|
|
||||||
# order: 'concentric'
|
|
||||||
no-important:
|
|
||||||
- 0
|
|
||||||
no-vendor-prefixes:
|
|
||||||
- 0 # this will be fixed with v2: see https://github.com/sasstools/sass-lint/pull/1137
|
|
||||||
nesting-depth:
|
|
||||||
- 1
|
|
||||||
-
|
|
||||||
max-depth: 4
|
|
15
.dev/.stylelintrc.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: 'stylelint-config-standard',
|
||||||
|
plugins: [
|
||||||
|
"stylelint-scss"
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
"indentation": [2],
|
||||||
|
"number-leading-zero": null,
|
||||||
|
// Replace CSS @ with SASS ones
|
||||||
|
"at-rule-no-unknown": null,
|
||||||
|
"scss/at-rule-no-unknown": true,
|
||||||
|
// not compatible with SASS apparently
|
||||||
|
"no-descending-specificity": null
|
||||||
|
},
|
||||||
|
}
|
|
@ -17,27 +17,14 @@ http {
|
||||||
index index.html index.php;
|
index index.html index.php;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
root /var/www/shaarli;
|
listen [::]:80;
|
||||||
|
root /var/www/shaarli;
|
||||||
|
|
||||||
access_log /var/log/nginx/shaarli.access.log;
|
access_log /var/log/nginx/shaarli.access.log;
|
||||||
error_log /var/log/nginx/shaarli.error.log;
|
error_log /var/log/nginx/shaarli.error.log;
|
||||||
|
|
||||||
location ~ /\. {
|
location ~* \.(?:ico|css|js|gif|jpe?g|png|ttf|oet|woff2?)$ {
|
||||||
# deny access to dotfiles
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ~$ {
|
|
||||||
# deny access to temp editor files, e.g. "script.php~"
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
|
|
||||||
# cache static assets
|
# cache static assets
|
||||||
expires max;
|
expires max;
|
||||||
add_header Pragma public;
|
add_header Pragma public;
|
||||||
|
@ -49,25 +36,25 @@ http {
|
||||||
alias /var/www/shaarli/images/favicon.ico;
|
alias /var/www/shaarli/images/favicon.ico;
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location /doc/html/ {
|
||||||
# Slim - rewrite URLs
|
default_type "text/html";
|
||||||
try_files $uri /index.php$is_args$args;
|
try_files $uri $uri/ $uri.html =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ (index)\.php$ {
|
location / {
|
||||||
|
# Slim - rewrite URLs & do NOT serve static files through this location
|
||||||
|
try_files _ /index.php$is_args$args;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ index\.php$ {
|
||||||
# Slim - split URL path into (script_filename, path_info)
|
# Slim - split URL path into (script_filename, path_info)
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
fastcgi_split_path_info ^(index.php)(/.+)$;
|
||||||
|
|
||||||
# filter and proxy PHP requests to PHP-FPM
|
# filter and proxy PHP requests to PHP-FPM
|
||||||
fastcgi_pass unix:/var/run/php-fpm.sock;
|
fastcgi_pass unix:/var/run/php-fpm.sock;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi.conf;
|
include fastcgi.conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ \.php$ {
|
|
||||||
# deny access to all other PHP scripts
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
#!/bin/execlineb -P
|
#!/bin/execlineb -P
|
||||||
php-fpm7 -F
|
php-fpm8 -F
|
||||||
|
|
|
@ -2,8 +2,15 @@
|
||||||
.dev
|
.dev
|
||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
|
.gitattributes
|
||||||
|
.gitignore
|
||||||
tests
|
tests
|
||||||
|
|
||||||
|
# Docker related resources are not needed inside the container
|
||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
||||||
|
Dockerfile.armhf
|
||||||
|
|
||||||
# Docker Compose resources
|
# Docker Compose resources
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
|
||||||
|
@ -13,6 +20,9 @@ data/*
|
||||||
pagecache/*
|
pagecache/*
|
||||||
tmp/*
|
tmp/*
|
||||||
|
|
||||||
|
# Shaarli's docs are created during the build
|
||||||
|
doc/html/
|
||||||
|
|
||||||
# Eclipse project files
|
# Eclipse project files
|
||||||
.settings
|
.settings
|
||||||
.buildpath
|
.buildpath
|
||||||
|
|
|
@ -14,7 +14,7 @@ indent_size = 4
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.php]
|
[*.php]
|
||||||
max_line_length = 100
|
max_line_length = 120
|
||||||
|
|
||||||
[Dockerfile]
|
[Dockerfile]
|
||||||
max_line_length = 80
|
max_line_length = 80
|
||||||
|
|
4
.gitattributes
vendored
|
@ -40,6 +40,8 @@ Dockerfile* export-ignore
|
||||||
Doxyfile export-ignore
|
Doxyfile export-ignore
|
||||||
Makefile export-ignore
|
Makefile export-ignore
|
||||||
node_modules/ export-ignore
|
node_modules/ export-ignore
|
||||||
mkdocs.yml export-ignore
|
doc/conf.py export-ignore
|
||||||
|
doc/requirements.txt export-ignore
|
||||||
|
doc/html/.doctrees/ export-ignore
|
||||||
phpunit.xml export-ignore
|
phpunit.xml export-ignore
|
||||||
tests/ export-ignore
|
tests/ export-ignore
|
||||||
|
|
7
.github/mailmap
vendored
|
@ -1,13 +1,18 @@
|
||||||
ArthurHoaro <arthur@hoa.ro>
|
ArthurHoaro <arthur@hoa.ro> <arthur.hoareau@wizacha.com>
|
||||||
|
ArthurHoaro <arthur@hoa.ro> Arthur
|
||||||
Florian Eula <eula.florian@gmail.com> feula
|
Florian Eula <eula.florian@gmail.com> feula
|
||||||
Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com>
|
Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com>
|
||||||
Immánuel Fodor <immanuelfactor+github@gmail.com>
|
Immánuel Fodor <immanuelfactor+github@gmail.com>
|
||||||
|
Immánuel Fodor <immanuelfactor+github@gmail.com> Immánuel! <21174107+immanuelfodor@users.noreply.github.com>
|
||||||
kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com>
|
kalvn <kalvnthereal@gmail.com> <kalvn@users.noreply.github.com>
|
||||||
|
kalvn <kalvnthereal@gmail.com> <kalvn@pm.me>
|
||||||
|
Neros <contact@neros.fr> <NerosTie@users.noreply.github.com>
|
||||||
Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm
|
Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm
|
||||||
Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar>
|
Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar>
|
||||||
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com>
|
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com>
|
||||||
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@users.noreply.github.com>
|
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@users.noreply.github.com>
|
||||||
Sébastien Sauvage <sebsauvage@sebsauvage.net>
|
Sébastien Sauvage <sebsauvage@sebsauvage.net>
|
||||||
|
Sébastien NOBILI <code@pipoprods.org> <s-code-github@pipoprods.org>
|
||||||
Timo Van Neerden <fire@lehollandaisvolant.net>
|
Timo Van Neerden <fire@lehollandaisvolant.net>
|
||||||
Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurhollandais@gmail.com>
|
Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurhollandais@gmail.com>
|
||||||
VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com>
|
VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com>
|
||||||
|
|
106
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
name: Shaarli CI
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
php:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php-versions: ['7.4', '8.0', '8.1', '8.2']
|
||||||
|
name: PHP ${{ matrix.php-versions }}
|
||||||
|
steps:
|
||||||
|
- name: Set locales
|
||||||
|
run: |
|
||||||
|
sudo locale-gen de_DE.utf8 && \
|
||||||
|
sudo locale-gen en_US.utf8 && \
|
||||||
|
sudo locale-gen fr_FR.utf8 && \
|
||||||
|
sudo dpkg-reconfigure --frontend=noninteractive locales
|
||||||
|
|
||||||
|
- name: Install Gettext
|
||||||
|
run: sudo apt-get install gettext
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
extensions: gd, xml, curl, mbstring, intl, gettext
|
||||||
|
tools: composer:v2
|
||||||
|
|
||||||
|
- name: Check PHP version
|
||||||
|
run: php -v
|
||||||
|
|
||||||
|
- name: Setup Composer from PHP version + update
|
||||||
|
run: composer config --unset platform && composer config platform.php ${{ matrix.php-versions }}
|
||||||
|
|
||||||
|
- name: Update dependencies for PHP 8.x
|
||||||
|
if: ${{ matrix.php-versions == '8.0' || matrix.php-versions == '8.1' }}
|
||||||
|
run: |
|
||||||
|
composer update && \
|
||||||
|
composer remove --dev phpunit/phpunit && \
|
||||||
|
composer require --dev phpunit/php-text-template ^2.0 && \
|
||||||
|
composer require --dev phpunit/phpunit ^9.0
|
||||||
|
|
||||||
|
- name: Update dependencies for PHP 7.x
|
||||||
|
if: ${{ matrix.php-versions != '8.0' && matrix.php-versions != '8.1' }}
|
||||||
|
run: composer update
|
||||||
|
|
||||||
|
- name: Clean up
|
||||||
|
run: make clean
|
||||||
|
|
||||||
|
- name: Check permissions
|
||||||
|
run: make check_permissions
|
||||||
|
|
||||||
|
- name: Run PHPCS
|
||||||
|
run: make code_sniffer
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: make all_tests
|
||||||
|
|
||||||
|
node:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Yarn install
|
||||||
|
run: yarnpkg install
|
||||||
|
|
||||||
|
- name: Verify successful frontend builds
|
||||||
|
run: yarnpkg run build
|
||||||
|
|
||||||
|
- name: JS static analysis
|
||||||
|
run: make eslint
|
||||||
|
|
||||||
|
- name: Linter for SASS syntax
|
||||||
|
run: make sasslint
|
||||||
|
|
||||||
|
python:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
|
||||||
|
- name: Build documentation
|
||||||
|
run: make htmldoc
|
||||||
|
|
||||||
|
trivy-repo:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Run trivy scanner on repository (non-blocking)
|
||||||
|
run: make test_trivy_repo TRIVY_EXIT_CODE=0
|
45
.github/workflows/docker-latest.yml
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
name: Build/push Docker image (master/latest)
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
jobs:
|
||||||
|
docker-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set shaarli version to the latest commit hash
|
||||||
|
run: sed -i "s/dev/$(git rev-parse --short HEAD)/" shaarli_version.php
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
tags: |
|
||||||
|
${{ secrets.DOCKER_IMAGE }}:latest
|
||||||
|
ghcr.io/${{ secrets.DOCKER_IMAGE }}:latest
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||||
|
- name: Run trivy scanner on latest docker image
|
||||||
|
run: make test_trivy_docker TRIVY_TARGET_DOCKER_IMAGE=ghcr.io/${{ secrets.DOCKER_IMAGE }}:latest
|
21
.github/workflows/docker-pr.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
name: Build Docker image (Pull Request)
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Build Docker image
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
push: false
|
||||||
|
tags: shaarli/shaarli:pr-${{ github.event.number }}
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
43
.github/workflows/docker-tags.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
name: Build/push Docker image (tags/releases)
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
branches:
|
||||||
|
- "v*.*"
|
||||||
|
- release
|
||||||
|
jobs:
|
||||||
|
docker-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get the tag name
|
||||||
|
run: echo "REF=${GITHUB_REF##*/}" >> $GITHUB_ENV
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm/v7
|
||||||
|
tags: |
|
||||||
|
${{ secrets.DOCKER_IMAGE }}:${{ env.REF }}
|
||||||
|
ghcr.io/${{ secrets.DOCKER_IMAGE }}:${{ env.REF }}
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
13
.gitignore
vendored
|
@ -25,8 +25,21 @@ coverage
|
||||||
sandbox
|
sandbox
|
||||||
phpmd.html
|
phpmd.html
|
||||||
phpdoc.xml
|
phpdoc.xml
|
||||||
|
.phpunit.result.cache
|
||||||
|
trivy
|
||||||
|
|
||||||
# User plugin configuration
|
# User plugin configuration
|
||||||
|
plugins/*
|
||||||
|
!addlink_toolbar
|
||||||
|
!archiveorg
|
||||||
|
!default_colors
|
||||||
|
!demo_plugin
|
||||||
|
!isso
|
||||||
|
!piwik
|
||||||
|
!playvideos
|
||||||
|
!pubsubhubbub
|
||||||
|
!qrcode
|
||||||
|
!wallabag
|
||||||
plugins/*/config.php
|
plugins/*/config.php
|
||||||
plugins/default_colors/default_colors.css
|
plugins/default_colors/default_colors.css
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,19 @@
|
||||||
# Required
|
# Required
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
# Build documentation with MkDocs
|
# Build documentation in the "docs/" directory with Sphinx
|
||||||
mkdocs:
|
sphinx:
|
||||||
configuration: mkdocs.yml
|
configuration: doc/conf.py
|
||||||
|
builder: html
|
||||||
|
|
||||||
|
build:
|
||||||
|
os: ubuntu-22.04
|
||||||
|
tools:
|
||||||
|
python: "3.11"
|
||||||
|
commands:
|
||||||
|
- pip install sphinx==7.1.0 furo==2023.7.26 myst-parser sphinx-design
|
||||||
|
- sphinx-build -b html -c doc/ doc/md/ _readthedocs/html/
|
||||||
|
|
||||||
# Optionally set the version of Python and requirements required to build your docs
|
|
||||||
# https://github.com/rtfd/readthedocs.org/issues/5250
|
|
||||||
python:
|
python:
|
||||||
version: 3.5
|
install:
|
||||||
|
- requirements: doc/requirements.txt
|
||||||
|
|
56
.travis.yml
|
@ -1,56 +0,0 @@
|
||||||
sudo: false
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- language: php
|
|
||||||
php: 7.3
|
|
||||||
- language: php
|
|
||||||
php: 7.2
|
|
||||||
- language: php
|
|
||||||
php: 7.1
|
|
||||||
- language: php
|
|
||||||
php: 7.0
|
|
||||||
- language: php
|
|
||||||
php: 5.6
|
|
||||||
- language: node_js
|
|
||||||
node_js: 8
|
|
||||||
cache:
|
|
||||||
yarn: true
|
|
||||||
directories:
|
|
||||||
- $HOME/.cache/yarn
|
|
||||||
|
|
||||||
install:
|
|
||||||
- yarn install
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- PATH=${PATH//:\.\/node_modules\/\.bin/}
|
|
||||||
|
|
||||||
script:
|
|
||||||
- yarn run build # Just to be sure that the build isn't broken
|
|
||||||
- make eslint
|
|
||||||
- make sasslint
|
|
||||||
- language: python
|
|
||||||
python: 3.6
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.cache/pip
|
|
||||||
install:
|
|
||||||
- pip install mkdocs
|
|
||||||
script:
|
|
||||||
- mkdocs build --clean
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.composer/cache
|
|
||||||
|
|
||||||
install:
|
|
||||||
- composer install --prefer-dist
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- PATH=${PATH//:\.\/node_modules\/\.bin/}
|
|
||||||
|
|
||||||
script:
|
|
||||||
- make clean
|
|
||||||
- make check_permissions
|
|
||||||
- make all_tests
|
|
46
AUTHORS
|
@ -1,8 +1,8 @@
|
||||||
1206 ArthurHoaro <arthur@hoa.ro>
|
1216 ArthurHoaro <arthur@hoa.ro>
|
||||||
|
456 nodiscc <nodiscc@gmail.com>
|
||||||
405 VirtualTam <virtualtam@flibidi.net>
|
405 VirtualTam <virtualtam@flibidi.net>
|
||||||
384 nodiscc <nodiscc@gmail.com>
|
|
||||||
56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
|
56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
|
||||||
23 dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
27 dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||||
19 Keith Carangelo <mail@kcaran.com>
|
19 Keith Carangelo <mail@kcaran.com>
|
||||||
16 Luce Carević <lcarevic@access42.net>
|
16 Luce Carević <lcarevic@access42.net>
|
||||||
15 Florian Eula <eula.florian@gmail.com>
|
15 Florian Eula <eula.florian@gmail.com>
|
||||||
|
@ -15,32 +15,38 @@
|
||||||
6 YFdyh000 <yfdyh000@gmail.com>
|
6 YFdyh000 <yfdyh000@gmail.com>
|
||||||
6 kalvn <kalvnthereal@gmail.com>
|
6 kalvn <kalvnthereal@gmail.com>
|
||||||
6 B. van Berkum <dev@dotmpe.com>
|
6 B. van Berkum <dev@dotmpe.com>
|
||||||
|
6 Immánuel Fodor <immanuelfactor+github@gmail.com>
|
||||||
|
6 YFdyh000 <yfdyh000@gmail.com>
|
||||||
|
6 kalvn <kalvnthereal@gmail.com>
|
||||||
6 llune <llune@users.noreply.github.com>
|
6 llune <llune@users.noreply.github.com>
|
||||||
5 Mark Schmitz <kramred@gmail.com>
|
5 Mark Schmitz <kramred@gmail.com>
|
||||||
5 Sébastien NOBILI <code@pipoprods.org>
|
5 Sébastien NOBILI <code@pipoprods.org>
|
||||||
4 Alexandre Alapetite <alexandre@alapetite.fr>
|
4 Alexandre Alapetite <alexandre@alapetite.fr>
|
||||||
4 yude <yudesleepy@gmail.com>
|
4 yude <yudesleepy@gmail.com>
|
||||||
4 David Sferruzza <david.sferruzza@gmail.com>
|
4 David Sferruzza <david.sferruzza@gmail.com>
|
||||||
|
4 yude <yudesleepy@gmail.com>
|
||||||
|
3 Agurato <mail.vmonot@gmail.com>
|
||||||
|
3 Christoph Stoettner <christoph.stoettner@stoeps.de>
|
||||||
|
3 Olivier <bourreauolivier@gmail.com>
|
||||||
3 Teromene <teromene@teromene.fr>
|
3 Teromene <teromene@teromene.fr>
|
||||||
3 yudete <yu@yude.moe>
|
3 yudete <yu@yude.moe>
|
||||||
3 Agurato <mail.vmonot@gmail.com>
|
2 Alexander Railean <alexandr.railean@arculus.de>
|
||||||
3 Olivier <bourreauolivier@gmail.com>
|
2 Alexandre G.-Raymond <alex@ndre.gr>
|
||||||
3 Christoph Stoettner <christoph.stoettner@stoeps.de>
|
2 Chris Kuethe <chris.kuethe@gmail.com>
|
||||||
|
2 Doug Breaux <25640850+dougbreaux@users.noreply.github.com>
|
||||||
2 Felix Bartels <felix@host-consultants.de>
|
2 Felix Bartels <felix@host-consultants.de>
|
||||||
|
2 Ganesh Kandu <kanduganesh@gmail.com>
|
||||||
|
2 Gregory <gregory@nosheep.fr>
|
||||||
|
2 Guillaume Virlet <github@virlet.org>
|
||||||
|
2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
|
||||||
2 Mathieu Chabanon <git@matchab.fr>
|
2 Mathieu Chabanon <git@matchab.fr>
|
||||||
2 Miloš Jovanović <mjovanovic@gmail.com>
|
2 Miloš Jovanović <mjovanovic@gmail.com>
|
||||||
2 Neros <contact@neros.fr>
|
2 Neros <contact@neros.fr>
|
||||||
2 Alexandre G.-Raymond <alex@ndre.gr>
|
|
||||||
2 Qwerty <champlywood@free.fr>
|
2 Qwerty <champlywood@free.fr>
|
||||||
2 Guillaume Virlet <github@virlet.org>
|
|
||||||
2 Sebastien Wains <sebw@users.noreply.github.com>
|
2 Sebastien Wains <sebw@users.noreply.github.com>
|
||||||
2 Stephen Muth <smuth4@gmail.com>
|
2 Stephen Muth <smuth4@gmail.com>
|
||||||
2 Timo Van Neerden <fire@lehollandaisvolant.net>
|
2 Timo Van Neerden <fire@lehollandaisvolant.net>
|
||||||
2 Alexander Railean <alexandr.railean@arculus.de>
|
|
||||||
2 Doug Breaux <25640850+dougbreaux@users.noreply.github.com>
|
|
||||||
2 flow.gunso <flow.gunso@gmail.com>
|
2 flow.gunso <flow.gunso@gmail.com>
|
||||||
2 Chris Kuethe <chris.kuethe@gmail.com>
|
|
||||||
2 Ganesh Kandu <kanduganesh@gmail.com>
|
|
||||||
2 julienCXX <software@chmodplusx.eu>
|
2 julienCXX <software@chmodplusx.eu>
|
||||||
2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
|
2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
|
||||||
2 philipp-r <philipp-r@users.noreply.github.com>
|
2 philipp-r <philipp-r@users.noreply.github.com>
|
||||||
|
@ -58,6 +64,8 @@
|
||||||
1 Adrien le Maire <adrien@alemaire.be>
|
1 Adrien le Maire <adrien@alemaire.be>
|
||||||
1 Ajabep <ajabep@users.noreply.github.com>
|
1 Ajabep <ajabep@users.noreply.github.com>
|
||||||
1 Alexis J <alexis@effingo.be>
|
1 Alexis J <alexis@effingo.be>
|
||||||
|
1 Alistair Young <avatar@arkane-systems.net>
|
||||||
|
1 Amadeous <amadeous@users.noreply.github.com>
|
||||||
1 Angristan <angristan@users.noreply.github.com>
|
1 Angristan <angristan@users.noreply.github.com>
|
||||||
1 Bish Erbas <42714627+bisherbas@users.noreply.github.com>
|
1 Bish Erbas <42714627+bisherbas@users.noreply.github.com>
|
||||||
1 BoboTiG <bobotig@gmail.com>
|
1 BoboTiG <bobotig@gmail.com>
|
||||||
|
@ -66,6 +74,7 @@
|
||||||
1 Buster One <37770318+buster-one@users.noreply.github.com>
|
1 Buster One <37770318+buster-one@users.noreply.github.com>
|
||||||
1 D Low <daniellowtw@gmail.com>
|
1 D Low <daniellowtw@gmail.com>
|
||||||
1 Daniel Jakots <vigdis@chown.me>
|
1 Daniel Jakots <vigdis@chown.me>
|
||||||
|
1 David <dajare@gmail.com>
|
||||||
1 David Foucher <dev@tyjak.net>
|
1 David Foucher <dev@tyjak.net>
|
||||||
1 Denis Renning <denis@devtty.de>
|
1 Denis Renning <denis@devtty.de>
|
||||||
1 Dennis Verspuij <dennisverspuij@users.noreply.github.com>
|
1 Dennis Verspuij <dennisverspuij@users.noreply.github.com>
|
||||||
|
@ -75,7 +84,6 @@
|
||||||
1 Florian Voigt <flvoigt@me.com>
|
1 Florian Voigt <flvoigt@me.com>
|
||||||
1 Franck Kerbiriou <FranckKe@users.noreply.github.com>
|
1 Franck Kerbiriou <FranckKe@users.noreply.github.com>
|
||||||
1 Gary Marigliano <gmarigliano93@gmail.com>
|
1 Gary Marigliano <gmarigliano93@gmail.com>
|
||||||
1 Gregory <gregory@nosheep.fr>
|
|
||||||
1 Hazhar Galeh <78073762+hazhargaleh@users.noreply.github.com>
|
1 Hazhar Galeh <78073762+hazhargaleh@users.noreply.github.com>
|
||||||
1 Hg <dev@indigo.re>
|
1 Hg <dev@indigo.re>
|
||||||
1 Jens Kubieziel <github@kubieziel.de>
|
1 Jens Kubieziel <github@kubieziel.de>
|
||||||
|
@ -90,16 +98,26 @@
|
||||||
1 Mark Gerarts <mark.gerarts@gmail.com>
|
1 Mark Gerarts <mark.gerarts@gmail.com>
|
||||||
1 Marsup <marsup@gmail.com>
|
1 Marsup <marsup@gmail.com>
|
||||||
1 Nicolas Friedli <nicolas@theologique.ch>
|
1 Nicolas Friedli <nicolas@theologique.ch>
|
||||||
|
1 Nicolas Le Gaillart <nicolas@legaillart.fr>
|
||||||
1 Paul van den Burg <github@paulvandenburg.nl>
|
1 Paul van den Burg <github@paulvandenburg.nl>
|
||||||
1 Adrien Oliva <adrien.oliva@yapbreak.fr>
|
1 Rajat Hans <rajathans9@gmail.com>
|
||||||
1 Sbgodin <Sbgodin@users.noreply.github.com>
|
1 Sbgodin <Sbgodin@users.noreply.github.com>
|
||||||
1 ToM <tom@leloop.org>
|
1 ToM <tom@leloop.org>
|
||||||
1 TsT <tst2005@gmail.com>
|
1 TsT <tst2005@gmail.com>
|
||||||
1 agentcobra <agentcobra@free.fr>
|
1 agentcobra <agentcobra@free.fr>
|
||||||
1 aguy <aguytech@users.noreply.github.com>
|
1 aguy <aguytech@users.noreply.github.com>
|
||||||
|
1 bschwede <bschwede@users.noreply.github.com>
|
||||||
1 bschwede <gummibando@gmx.net>
|
1 bschwede <gummibando@gmx.net>
|
||||||
|
1 clach04 <clach04@gmail.com>
|
||||||
1 dimtion <zizou.xena@gmail.com>
|
1 dimtion <zizou.xena@gmail.com>
|
||||||
1 durcheinandr <jochen@durcheinandr.de>
|
1 durcheinandr <jochen@durcheinandr.de>
|
||||||
1 heimpogo <hypertexthome@googlemail.com>
|
1 heimpogo <hypertexthome@googlemail.com>
|
||||||
1 jalr <mail@jalr.de>
|
1 jalr <mail@jalr.de>
|
||||||
1 lapineige <lapineige@users.noreply.github.com>
|
1 lapineige <lapineige@users.noreply.github.com>
|
||||||
|
1 leyrer <gitlab@leyrer.priv.at>
|
||||||
|
1 locness3 <37651007+locness3@users.noreply.github.com>
|
||||||
|
1 owen bell <66233223+xfnw@users.noreply.github.com>
|
||||||
|
1 philipp <philipp@philipp.PC.Ubuntu>
|
||||||
|
1 rfolo9li <50079896+rfolo9li@users.noreply.github.com>
|
||||||
|
1 sprak3000 <sprak3000+github@gmail.com>
|
||||||
|
1 yudejp <i@yude.jp>
|
||||||
|
|
51
CHANGELOG.md
|
@ -4,6 +4,57 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [v0.13.0](https://github.com/shaarli/Shaarli/releases/tag/v0.12.3) - 2023-11-22
|
||||||
|
|
||||||
|
> Major changes:
|
||||||
|
> - Security: Fix XSS vulnerability in tag search
|
||||||
|
> - Drop support for PHP 7.1, 7.2 and 7.3
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Docker build: add ARM64 platform and bump Github action version by @ArthurHoaro in https://github.com/shaarli/Shaarli/pull/1965
|
||||||
|
* github actions: build OCI images that contain both amd64 and armv7 by @nodiscc in https://github.com/shaarli/Shaarli/pull/1962
|
||||||
|
* Expose tags_separator config through /info API by @amadeous in https://github.com/shaarli/Shaarli/pull/1997
|
||||||
|
* tools: github actions: build docker images on pull requests by @nodiscc in https://github.com/shaarli/Shaarli/pull/2014
|
||||||
|
* doc: server configuration: add PHP 8.2 to PHP compatibility table by @nodiscc in https://github.com/shaarli/Shaarli/pull/2021
|
||||||
|
* Add shaarli-stack theme to Community-and-related-software.md by @dajare in https://github.com/shaarli/Shaarli/pull/2028
|
||||||
|
* doc: document general.download_max_size/timeout configuration settings by @nodiscc in https://github.com/shaarli/Shaarli/pull/2036
|
||||||
|
* doc: troubleshooting: automatic title retrieval fails when it is set by javascript by @nodiscc in https://github.com/shaarli/Shaarli/pull/2037
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* doc: update release procedure (merge the latest release to the release branch) + use the release branch for latest release version detection by @nodiscc in https://github.com/shaarli/Shaarli/pull/1960
|
||||||
|
* Update german translation by @bschwede in https://github.com/shaarli/Shaarli/pull/1969
|
||||||
|
* Update Server-configuration.md by @reinboldg in https://github.com/shaarli/Shaarli/pull/1973
|
||||||
|
* Update Community-and-related-software.md by @nlegaillart in https://github.com/shaarli/Shaarli/pull/1984
|
||||||
|
* doc: improve docs on usage of OR operator in tags search by @nodiscc in https://github.com/shaarli/Shaarli/pull/1987
|
||||||
|
* docker: nginx: listen on IPv6 in addition to IPv4 by @cerebrate in https://github.com/shaarli/Shaarli/pull/1983
|
||||||
|
* Doc update, WebSub (formerly PubSubHubbub) plugin by @clach04 in https://github.com/shaarli/Shaarli/pull/2008
|
||||||
|
* doc: community/related software/integration with other platforms: add link to shaarli debian package by @nodiscc in https://github.com/shaarli/Shaarli/pull/2018
|
||||||
|
* replace mkdocs with sphinx/myst-parser for HTML documentation generation, documentation improvements by @nodiscc in https://github.com/shaarli/Shaarli/pull/2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Makefile: Use GNU tar if available by @ArthurHoaro in https://github.com/shaarli/Shaarli/pull/1957
|
||||||
|
* Support: ignore disk_free_space if the function is unavailable by @ArthurHoaro in https://github.com/shaarli/Shaarli/pull/1970
|
||||||
|
* Documentation: fix broken link to 3rd party plugins by @ArthurHoaro in https://github.com/shaarli/Shaarli/pull/1975
|
||||||
|
* Fix autofocus: load bulk action input on linklist only by @ArthurHoaro in https://github.com/shaarli/Shaarli/pull/1976
|
||||||
|
* doc: fix mkdocs build warnings/relative links by @nodiscc in https://github.com/shaarli/Shaarli/pull/2015
|
||||||
|
* correct usage of hyphens in all occurences of 'super fast, database-free' by @nodiscc in https://github.com/shaarli/Shaarli/pull/2003
|
||||||
|
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* Drop support for PHP 7.1, 7.2 and 7.3 by @ArthurHoaro in https://github.com/shaarli/Shaarli/pull/1958
|
||||||
|
* doc: themes: remove unmaintained themes by @nodiscc in https://github.com/shaarli/Shaarli/pull/2030
|
||||||
|
* doc: remove bountysource badge by @nodiscc in https://github.com/shaarli/Shaarli/pull/2035
|
||||||
|
|
||||||
|
### Security
|
||||||
|
* Fix XSS vulnerability in tag search by @ArthurHoaro in https://github.com/shaarli/Shaarli/pull/2039
|
||||||
|
* tools: run trivy vulnerability scanner on the 'latest' docker image by @nodiscc in https://github.com/shaarli/Shaarli/pull/1980
|
||||||
|
* github actions: fix value of TRIVY_TARGET_DOCKER_IMAGE by @nodiscc in https://github.com/shaarli/Shaarli/pull/1989
|
||||||
|
* tools/CI: scan repository with trivy security scanner (yarn.lock, composer.lock) by @nodiscc in https://github.com/shaarli/Shaarli/pull/1998
|
||||||
|
* tools/tests: update trivy to v0.44.0 by @nodiscc in https://github.com/shaarli/Shaarli/pull/2012
|
||||||
|
* docker: update base alpine docker image to 3.16.7 by @nodiscc in https://github.com/shaarli/Shaarli/pull/2024
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/shaarli/Shaarli/compare/v0.12.2...v0.12.3
|
||||||
|
|
||||||
## [v0.12.2](https://github.com/shaarli/Shaarli/releases/tag/v0.12.2) - 2023-03-18
|
## [v0.12.2](https://github.com/shaarli/Shaarli/releases/tag/v0.12.2) - 2023-03-18
|
||||||
|
|
||||||
> Docker: use `ghcr.io/shaarli/shaarli` as Docker image instead of `shaarli/shaarli`.
|
> Docker: use `ghcr.io/shaarli/shaarli` as Docker image instead of `shaarli/shaarli`.
|
||||||
|
|
49
Dockerfile
|
@ -4,8 +4,8 @@
|
||||||
FROM python:3-alpine as docs
|
FROM python:3-alpine as docs
|
||||||
ADD . /usr/src/app/shaarli
|
ADD . /usr/src/app/shaarli
|
||||||
RUN cd /usr/src/app/shaarli \
|
RUN cd /usr/src/app/shaarli \
|
||||||
&& pip install --no-cache-dir mkdocs \
|
&& apk add --no-cache gcc musl-dev make bash \
|
||||||
&& mkdocs build --clean
|
&& make htmldoc
|
||||||
|
|
||||||
# Stage 2:
|
# Stage 2:
|
||||||
# - Resolve PHP dependencies with Composer
|
# - Resolve PHP dependencies with Composer
|
||||||
|
@ -16,43 +16,46 @@ RUN cd shaarli \
|
||||||
|
|
||||||
# Stage 3:
|
# Stage 3:
|
||||||
# - Frontend dependencies
|
# - Frontend dependencies
|
||||||
FROM node:9.9-alpine as node
|
FROM node:12-alpine as node
|
||||||
COPY --from=composer /app/shaarli shaarli
|
COPY --from=composer /app/shaarli shaarli
|
||||||
RUN cd shaarli \
|
RUN cd shaarli \
|
||||||
&& yarn install \
|
&& yarnpkg install \
|
||||||
&& yarn run build \
|
&& yarnpkg run build \
|
||||||
&& rm -rf node_modules
|
&& rm -rf node_modules
|
||||||
|
|
||||||
# Stage 4:
|
# Stage 4:
|
||||||
# - Shaarli image
|
# - Shaarli image
|
||||||
FROM alpine:3.8
|
FROM alpine:3.16.7
|
||||||
LABEL maintainer="Shaarli Community"
|
LABEL maintainer="Shaarli Community"
|
||||||
|
|
||||||
RUN apk --update --no-cache add \
|
RUN apk --update --no-cache add \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
nginx \
|
nginx \
|
||||||
php7 \
|
php8 \
|
||||||
php7-ctype \
|
php8-ctype \
|
||||||
php7-curl \
|
php8-curl \
|
||||||
php7-fpm \
|
php8-fpm \
|
||||||
php7-gd \
|
php8-gd \
|
||||||
php7-iconv \
|
php8-gettext \
|
||||||
php7-intl \
|
php8-iconv \
|
||||||
php7-json \
|
php8-intl \
|
||||||
php7-mbstring \
|
php8-json \
|
||||||
php7-openssl \
|
php8-ldap \
|
||||||
php7-session \
|
php8-mbstring \
|
||||||
php7-xml \
|
php8-openssl \
|
||||||
php7-zlib \
|
php8-session \
|
||||||
|
php8-xml \
|
||||||
|
php8-simplexml \
|
||||||
|
php8-zlib \
|
||||||
s6
|
s6
|
||||||
|
|
||||||
COPY .docker/nginx.conf /etc/nginx/nginx.conf
|
COPY .docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
COPY .docker/php-fpm.conf /etc/php7/php-fpm.conf
|
COPY .docker/php-fpm.conf /etc/php8/php-fpm.conf
|
||||||
COPY .docker/services.d /etc/services.d
|
COPY .docker/services.d /etc/services.d
|
||||||
|
|
||||||
RUN rm -rf /etc/php7/php-fpm.d/www.conf \
|
RUN rm -rf /etc/php8/php-fpm.d/www.conf \
|
||||||
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \
|
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php8/php.ini \
|
||||||
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini
|
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php8/php.ini
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /var/www
|
WORKDIR /var/www
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
# Stage 1:
|
|
||||||
# - Copy Shaarli sources
|
|
||||||
# - Build documentation
|
|
||||||
FROM arm32v6/alpine:3.8 as docs
|
|
||||||
ADD . /usr/src/app/shaarli
|
|
||||||
RUN apk --update --no-cache add py2-pip \
|
|
||||||
&& cd /usr/src/app/shaarli \
|
|
||||||
&& pip install --no-cache-dir mkdocs \
|
|
||||||
&& mkdocs build --clean
|
|
||||||
|
|
||||||
# Stage 2:
|
|
||||||
# - Resolve PHP dependencies with Composer
|
|
||||||
FROM arm32v6/alpine:3.8 as composer
|
|
||||||
COPY --from=docs /usr/src/app/shaarli /app/shaarli
|
|
||||||
RUN apk --update --no-cache add php7-curl php7-mbstring composer \
|
|
||||||
&& cd /app/shaarli \
|
|
||||||
&& composer --prefer-dist --no-dev install
|
|
||||||
|
|
||||||
# Stage 3:
|
|
||||||
# - Frontend dependencies
|
|
||||||
FROM arm32v6/alpine:3.8 as node
|
|
||||||
COPY --from=composer /app/shaarli /shaarli
|
|
||||||
RUN apk --update --no-cache add yarn nodejs-current python2 build-base \
|
|
||||||
&& cd /shaarli \
|
|
||||||
&& yarn install \
|
|
||||||
&& yarn run build \
|
|
||||||
&& rm -rf node_modules
|
|
||||||
|
|
||||||
# Stage 4:
|
|
||||||
# - Shaarli image
|
|
||||||
FROM arm32v6/alpine:3.8
|
|
||||||
LABEL maintainer="Shaarli Community"
|
|
||||||
MAINTAINER Shaarli Community
|
|
||||||
|
|
||||||
RUN apk --update --no-cache add \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
nginx \
|
|
||||||
php7 \
|
|
||||||
php7-ctype \
|
|
||||||
php7-curl \
|
|
||||||
php7-fpm \
|
|
||||||
php7-gd \
|
|
||||||
php7-iconv \
|
|
||||||
php7-intl \
|
|
||||||
php7-json \
|
|
||||||
php7-mbstring \
|
|
||||||
php7-openssl \
|
|
||||||
php7-phar \
|
|
||||||
php7-session \
|
|
||||||
php7-xml \
|
|
||||||
php7-zlib \
|
|
||||||
s6
|
|
||||||
|
|
||||||
COPY .docker/nginx.conf /etc/nginx/nginx.conf
|
|
||||||
COPY .docker/php-fpm.conf /etc/php7/php-fpm.conf
|
|
||||||
COPY .docker/services.d /etc/services.d
|
|
||||||
|
|
||||||
RUN curl -sS https://getcomposer.org/installer | php7 -- --install-dir=/usr/local/bin --filename=composer \
|
|
||||||
&& rm -rf /etc/php7/php-fpm.d/www.conf \
|
|
||||||
&& sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php7/php.ini \
|
|
||||||
&& sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php7/php.ini
|
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /var/www
|
|
||||||
RUN curl -L https://github.com/shaarli/Shaarli/archive/latest.tar.gz | tar xzf - \
|
|
||||||
&& mv Shaarli-latest shaarli \
|
|
||||||
&& cd shaarli \
|
|
||||||
&& composer --prefer-dist --no-dev install \
|
|
||||||
&& rm -rf ~/.composer \
|
|
||||||
&& chown -R nginx:nginx . \
|
|
||||||
&& ln -sf /dev/stdout /var/log/nginx/shaarli.access.log \
|
|
||||||
&& ln -sf /dev/stderr /var/log/nginx/shaarli.error.log
|
|
||||||
|
|
||||||
VOLUME /var/www/shaarli/data
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
|
|
||||||
CMD []
|
|
87
Makefile
|
@ -1,9 +1,9 @@
|
||||||
# The personal, minimalist, super-fast, database free, bookmarking service.
|
# The personal, minimalist, super fast, database-free, bookmarking service.
|
||||||
# Makefile for PHP code analysis & testing, documentation and release generation
|
# Makefile for PHP code analysis & testing, documentation and release generation
|
||||||
|
|
||||||
BIN = vendor/bin
|
BIN = vendor/bin
|
||||||
|
|
||||||
all: static_analysis_summary check_permissions test
|
all: check_permissions test
|
||||||
|
|
||||||
##
|
##
|
||||||
# Docker test adapter
|
# Docker test adapter
|
||||||
|
@ -24,13 +24,16 @@ docker_%:
|
||||||
##
|
##
|
||||||
PHPCS := $(BIN)/phpcs
|
PHPCS := $(BIN)/phpcs
|
||||||
|
|
||||||
|
# Use GNU Tar where available
|
||||||
|
ifneq (, $(shell which gtar))
|
||||||
|
TAR := gtar
|
||||||
|
else
|
||||||
|
TAR := tar
|
||||||
|
endif
|
||||||
|
|
||||||
code_sniffer:
|
code_sniffer:
|
||||||
@$(PHPCS)
|
@$(PHPCS)
|
||||||
|
|
||||||
### - errors filtered by coding standard: PEAR, PSR1, PSR2, Zend...
|
|
||||||
PHPCS_%:
|
|
||||||
@$(PHPCS) --report-full --report-width=200 --standard=$*
|
|
||||||
|
|
||||||
### - errors by Git author
|
### - errors by Git author
|
||||||
code_sniffer_blame:
|
code_sniffer_blame:
|
||||||
@$(PHPCS) --report-gitblame
|
@$(PHPCS) --report-gitblame
|
||||||
|
@ -80,10 +83,15 @@ locale_test_%:
|
||||||
--testsuite language-$(firstword $(subst _, ,$*))
|
--testsuite language-$(firstword $(subst _, ,$*))
|
||||||
|
|
||||||
all_tests: test locale_test_de_DE locale_test_en_US locale_test_fr_FR
|
all_tests: test locale_test_de_DE locale_test_en_US locale_test_fr_FR
|
||||||
@$(BIN)/phpcov merge --html coverage coverage
|
@# --The current version is not compatible with PHP 7.2
|
||||||
|
@#$(BIN)/phpcov merge --html coverage coverage
|
||||||
@# --text doesn't work with phpunit 4.* (v5 requires PHP 5.6)
|
@# --text doesn't work with phpunit 4.* (v5 requires PHP 5.6)
|
||||||
@#$(BIN)/phpcov merge --text coverage/txt coverage
|
@#$(BIN)/phpcov merge --text coverage/txt coverage
|
||||||
|
|
||||||
|
### download 3rd-party PHP libraries, including dev dependencies
|
||||||
|
composer_dependencies_dev: clean
|
||||||
|
composer install --prefer-dist
|
||||||
|
|
||||||
##
|
##
|
||||||
# Custom release archive generation
|
# Custom release archive generation
|
||||||
#
|
#
|
||||||
|
@ -105,24 +113,25 @@ composer_dependencies: clean
|
||||||
|
|
||||||
### download 3rd-party frontend libraries
|
### download 3rd-party frontend libraries
|
||||||
frontend_dependencies:
|
frontend_dependencies:
|
||||||
yarn install
|
yarnpkg install
|
||||||
|
|
||||||
### Build frontend dependencies
|
### Build frontend dependencies
|
||||||
build_frontend: frontend_dependencies
|
build_frontend: frontend_dependencies
|
||||||
yarn run build
|
yarnpkg run build
|
||||||
|
|
||||||
### generate a release tarball and include 3rd-party dependencies and translations
|
### generate a release tarball and include 3rd-party dependencies and translations
|
||||||
release_tar: composer_dependencies htmldoc translate build_frontend
|
release_tar: composer_dependencies htmldoc translate build_frontend
|
||||||
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD
|
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD
|
||||||
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/
|
$(TAR) rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/
|
||||||
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/
|
$(TAR) rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/
|
||||||
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^tpl|$(ARCHIVE_PREFIX)tpl|" tpl/
|
$(TAR) rvf $(ARCHIVE_VERSION).tar --transform "s|^tpl|$(ARCHIVE_PREFIX)tpl|" tpl/
|
||||||
gzip $(ARCHIVE_VERSION).tar
|
gzip $(ARCHIVE_VERSION).tar
|
||||||
|
|
||||||
### generate a release zip and include 3rd-party dependencies and translations
|
### generate a release zip and include 3rd-party dependencies and translations
|
||||||
release_zip: composer_dependencies htmldoc translate build_frontend
|
release_zip: composer_dependencies htmldoc translate build_frontend
|
||||||
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD
|
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD
|
||||||
mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor}
|
mkdir -p $(ARCHIVE_PREFIX)/doc
|
||||||
|
mkdir -p $(ARCHIVE_PREFIX)/vendor
|
||||||
rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/
|
rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/
|
||||||
zip -r $(ARCHIVE_VERSION).zip $(ARCHIVE_PREFIX)doc/
|
zip -r $(ARCHIVE_VERSION).zip $(ARCHIVE_PREFIX)doc/
|
||||||
rsync -a vendor/ $(ARCHIVE_PREFIX)vendor/
|
rsync -a vendor/ $(ARCHIVE_PREFIX)vendor/
|
||||||
|
@ -138,10 +147,10 @@ release_zip: composer_dependencies htmldoc translate build_frontend
|
||||||
### remove all unversioned files
|
### remove all unversioned files
|
||||||
clean:
|
clean:
|
||||||
@git clean -df
|
@git clean -df
|
||||||
@rm -rf sandbox
|
@rm -rf sandbox trivy*
|
||||||
|
|
||||||
### generate the AUTHORS file from Git commit information
|
### generate the AUTHORS file from Git commit information
|
||||||
authors:
|
generate_authors:
|
||||||
@cp .github/mailmap .mailmap
|
@cp .github/mailmap .mailmap
|
||||||
@git shortlog -sne > AUTHORS
|
@git shortlog -sne > AUTHORS
|
||||||
@rm .mailmap
|
@rm .mailmap
|
||||||
|
@ -150,25 +159,57 @@ authors:
|
||||||
phpdoc: clean
|
phpdoc: clean
|
||||||
@docker run --rm -v $(PWD):/data -u `id -u`:`id -g` phpdoc/phpdoc
|
@docker run --rm -v $(PWD):/data -u `id -u`:`id -g` phpdoc/phpdoc
|
||||||
|
|
||||||
### generate HTML documentation from Markdown pages with MkDocs
|
### generate HTML documentation from Markdown pages with Sphinx
|
||||||
htmldoc:
|
htmldoc:
|
||||||
python3 -m venv venv/
|
python3 -m venv venv/
|
||||||
bash -c 'source venv/bin/activate; \
|
bash -c 'source venv/bin/activate; \
|
||||||
pip install mkdocs; \
|
pip install wheel; \
|
||||||
mkdocs build --clean'
|
pip install sphinx==7.1.0 furo==2023.7.26 myst-parser sphinx-design; \
|
||||||
|
sphinx-build -b html -c doc/ doc/md/ doc/html/'
|
||||||
find doc/html/ -type f -exec chmod a-x '{}' \;
|
find doc/html/ -type f -exec chmod a-x '{}' \;
|
||||||
rm -r venv
|
rm -r venv
|
||||||
|
|
||||||
|
|
||||||
### Generate Shaarli's translation compiled file (.mo)
|
### Generate Shaarli's translation compiled file (.mo)
|
||||||
translate:
|
translate:
|
||||||
@find inc/languages/ -name shaarli.po -execdir msgfmt shaarli.po -o shaarli.mo \;
|
@echo "----------------------"
|
||||||
|
@echo "Compile translation files"
|
||||||
|
@echo "----------------------"
|
||||||
|
@for pofile in `find inc/languages/ -name shaarli.po`; do \
|
||||||
|
echo "Compiling $$pofile"; \
|
||||||
|
msgfmt -v "$$pofile" -o "`dirname "$$pofile"`/`basename "$$pofile" .po`.mo"; \
|
||||||
|
done;
|
||||||
|
|
||||||
### Run ESLint check against Shaarli's JS files
|
### Run ESLint check against Shaarli's JS files
|
||||||
eslint:
|
eslint:
|
||||||
@yarn run eslint -c .dev/.eslintrc.js assets/vintage/js/
|
@yarnpkg run eslint -c .dev/.eslintrc.js assets/vintage/js/
|
||||||
@yarn run eslint -c .dev/.eslintrc.js assets/default/js/
|
@yarnpkg run eslint -c .dev/.eslintrc.js assets/default/js/
|
||||||
|
@yarnpkg run eslint -c .dev/.eslintrc.js assets/common/js/
|
||||||
|
|
||||||
### Run CSSLint check against Shaarli's SCSS files
|
### Run CSSLint check against Shaarli's SCSS files
|
||||||
sasslint:
|
sasslint:
|
||||||
@yarn run sass-lint -c .dev/.sasslintrc 'assets/default/scss/*.scss' -v -q
|
@yarnpkg run stylelint --config .dev/.stylelintrc.js 'assets/default/scss/*.scss'
|
||||||
|
|
||||||
|
##
|
||||||
|
# Security scans
|
||||||
|
##
|
||||||
|
|
||||||
|
# trivy version (https://github.com/aquasecurity/trivy/releases)
|
||||||
|
TRIVY_VERSION=0.44.0
|
||||||
|
# default trivy exit code when vulnerabilities are found
|
||||||
|
TRIVY_EXIT_CODE=1
|
||||||
|
# default docker image to scan with trivy
|
||||||
|
TRIVY_TARGET_DOCKER_IMAGE=ghcr.io/shaarli/shaarli:latest
|
||||||
|
|
||||||
|
### download trivy vulneravbility scanner
|
||||||
|
download_trivy:
|
||||||
|
wget --quiet --continue -O trivy_$(TRIVY_VERSION)_Linux-64bit.tar.gz https://github.com/aquasecurity/trivy/releases/download/v$(TRIVY_VERSION)/trivy_$(TRIVY_VERSION)_Linux-64bit.tar.gz
|
||||||
|
tar -z -x trivy -f trivy_$(TRIVY_VERSION)_Linux-64bit.tar.gz
|
||||||
|
|
||||||
|
### run trivy vulnerability scanner on docker image
|
||||||
|
test_trivy_docker: download_trivy
|
||||||
|
./trivy --exit-code $(TRIVY_EXIT_CODE) image $(TRIVY_TARGET_DOCKER_IMAGE)
|
||||||
|
|
||||||
|
### run trivy vulnerability scanner on composer/yarn dependency trees
|
||||||
|
test_trivy_repo: download_trivy
|
||||||
|
./trivy --exit-code $(TRIVY_EXIT_CODE) fs composer.lock
|
||||||
|
./trivy --exit-code $(TRIVY_EXIT_CODE) fs yarn.lock
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
![Shaarli logo](doc/md/images/doc-logo.png)
|
![Shaarli logo](doc/md/images/doc-logo.png)
|
||||||
|
|
||||||
The personal, minimalist, super-fast, database free, bookmarking service.
|
The personal, minimalist, super fast, database-free, bookmarking service.
|
||||||
|
|
||||||
_Do you want to share the links you discover?_
|
_Do you want to share the links you discover?_
|
||||||
_Shaarli is a minimalist link sharing service that you can install on your own server._
|
_Shaarli is a minimalist link sharing service that you can install on your own server._
|
||||||
_It is designed to be personal (single-user), fast and handy._
|
_It is designed to be personal (single-user), fast and handy._
|
||||||
|
|
||||||
[![](https://img.shields.io/badge/stable-v0.11.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.11.1)
|
[![](https://img.shields.io/badge/stable-v0.12.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.11.1)
|
||||||
[![](https://img.shields.io/badge/latest-v0.12.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.12.1)
|
[![](https://img.shields.io/badge/latest-v0.13.0-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.12.2)
|
||||||
[![](https://img.shields.io/badge/master-v0.12.x-blue.svg)](https://github.com/shaarli/Shaarli)
|
[![](https://img.shields.io/badge/master-v0.13.x-blue.svg)](https://github.com/shaarli/Shaarli)
|
||||||
[![](https://github.com/shaarli/Shaarli/actions/workflows/ci.yml/badge.svg)](https://github.com/shaarli/Shaarli/actions)
|
[![](https://github.com/shaarli/Shaarli/actions/workflows/ci.yml/badge.svg)](https://github.com/shaarli/Shaarli/actions)
|
||||||
[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli)
|
[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli)
|
||||||
[![Bountysource](https://www.bountysource.com/badge/team?team_id=19583&style=bounties_received)](https://www.bountysource.com/teams/shaarli/issues)
|
|
||||||
[![Docker repository](https://img.shields.io/docker/pulls/shaarli/shaarli.svg)](https://github.com/shaarli/Shaarli/pkgs/container/shaarli)
|
[![Docker repository](https://img.shields.io/docker/pulls/shaarli/shaarli.svg)](https://github.com/shaarli/Shaarli/pkgs/container/shaarli)
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
|
@ -1,246 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Shaarli;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Shaarli\Config\ConfigManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shaarli (application) utilities
|
|
||||||
*/
|
|
||||||
class ApplicationUtils
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string File containing the current version
|
|
||||||
*/
|
|
||||||
public static $VERSION_FILE = 'shaarli_version.php';
|
|
||||||
|
|
||||||
private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli';
|
|
||||||
private static $GIT_BRANCHES = array('latest', 'stable');
|
|
||||||
private static $VERSION_START_TAG = '<?php /* ';
|
|
||||||
private static $VERSION_END_TAG = ' */ ?>';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the latest version code from the Git repository
|
|
||||||
*
|
|
||||||
* The code is read from the raw content of the version file on the Git server.
|
|
||||||
*
|
|
||||||
* @param string $url URL to reach to get the latest version.
|
|
||||||
* @param int $timeout Timeout to check the URL (in seconds).
|
|
||||||
*
|
|
||||||
* @return mixed the version code from the repository if available, else 'false'
|
|
||||||
*/
|
|
||||||
public static function getLatestGitVersionCode($url, $timeout = 2)
|
|
||||||
{
|
|
||||||
list($headers, $data) = get_http_response($url, $timeout);
|
|
||||||
|
|
||||||
if (strpos($headers[0], '200 OK') === false) {
|
|
||||||
error_log('Failed to retrieve ' . $url);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the version from a remote URL or a file.
|
|
||||||
*
|
|
||||||
* @param string $remote URL or file to fetch.
|
|
||||||
* @param int $timeout For URLs fetching.
|
|
||||||
*
|
|
||||||
* @return bool|string The version or false if it couldn't be retrieved.
|
|
||||||
*/
|
|
||||||
public static function getVersion($remote, $timeout = 2)
|
|
||||||
{
|
|
||||||
if (startsWith($remote, 'http')) {
|
|
||||||
if (($data = static::getLatestGitVersionCode($remote, $timeout)) === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!is_file($remote)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$data = file_get_contents($remote);
|
|
||||||
}
|
|
||||||
|
|
||||||
return str_replace(
|
|
||||||
array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL),
|
|
||||||
array('', '', ''),
|
|
||||||
$data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a new Shaarli version has been published on the Git repository
|
|
||||||
*
|
|
||||||
* Updates checks are run periodically, according to the following criteria:
|
|
||||||
* - the update checks are enabled (install, global config);
|
|
||||||
* - the user is logged in (or this is an open instance);
|
|
||||||
* - the last check is older than a given interval;
|
|
||||||
* - the check is non-blocking if the HTTPS connection to Git fails;
|
|
||||||
* - in case of failure, the update file's modification date is updated,
|
|
||||||
* to avoid intempestive connection attempts.
|
|
||||||
*
|
|
||||||
* @param string $currentVersion the current version code
|
|
||||||
* @param string $updateFile the file where to store the latest version code
|
|
||||||
* @param int $checkInterval the minimum interval between update checks (in seconds
|
|
||||||
* @param bool $enableCheck whether to check for new versions
|
|
||||||
* @param bool $isLoggedIn whether the user is logged in
|
|
||||||
* @param string $branch check update for the given branch
|
|
||||||
*
|
|
||||||
* @throws Exception an invalid branch has been set for update checks
|
|
||||||
*
|
|
||||||
* @return mixed the new version code if available and greater, else 'false'
|
|
||||||
*/
|
|
||||||
public static function checkUpdate(
|
|
||||||
$currentVersion,
|
|
||||||
$updateFile,
|
|
||||||
$checkInterval,
|
|
||||||
$enableCheck,
|
|
||||||
$isLoggedIn,
|
|
||||||
$branch = 'stable'
|
|
||||||
) {
|
|
||||||
// Do not check versions for visitors
|
|
||||||
// Do not check if the user doesn't want to
|
|
||||||
// Do not check with dev version
|
|
||||||
if (!$isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_file($updateFile) && (filemtime($updateFile) > time() - $checkInterval)) {
|
|
||||||
// Shaarli has checked for updates recently - skip HTTP query
|
|
||||||
$latestKnownVersion = file_get_contents($updateFile);
|
|
||||||
|
|
||||||
if (version_compare($latestKnownVersion, $currentVersion) == 1) {
|
|
||||||
return $latestKnownVersion;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array($branch, self::$GIT_BRANCHES)) {
|
|
||||||
throw new Exception(
|
|
||||||
'Invalid branch selected for updates: "' . $branch . '"'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Late Static Binding allows overriding within tests
|
|
||||||
// See http://php.net/manual/en/language.oop5.late-static-bindings.php
|
|
||||||
$latestVersion = static::getVersion(
|
|
||||||
self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$latestVersion) {
|
|
||||||
// Only update the file's modification date
|
|
||||||
file_put_contents($updateFile, $currentVersion);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the file's content and modification date
|
|
||||||
file_put_contents($updateFile, $latestVersion);
|
|
||||||
|
|
||||||
if (version_compare($latestVersion, $currentVersion) == 1) {
|
|
||||||
return $latestVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the PHP version to ensure Shaarli can run
|
|
||||||
*
|
|
||||||
* @param string $minVersion minimum PHP required version
|
|
||||||
* @param string $curVersion current PHP version (use PHP_VERSION)
|
|
||||||
*
|
|
||||||
* @throws Exception the PHP version is not supported
|
|
||||||
*/
|
|
||||||
public static function checkPHPVersion($minVersion, $curVersion)
|
|
||||||
{
|
|
||||||
if (version_compare($curVersion, $minVersion) < 0) {
|
|
||||||
$msg = t(
|
|
||||||
'Your PHP version is obsolete!'
|
|
||||||
. ' Shaarli requires at least PHP %s, and thus cannot run.'
|
|
||||||
. ' Your PHP version has known security vulnerabilities and should be'
|
|
||||||
. ' updated as soon as possible.'
|
|
||||||
);
|
|
||||||
throw new Exception(sprintf($msg, $minVersion));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks Shaarli has the proper access permissions to its resources
|
|
||||||
*
|
|
||||||
* @param ConfigManager $conf Configuration Manager instance.
|
|
||||||
*
|
|
||||||
* @return array A list of the detected configuration issues
|
|
||||||
*/
|
|
||||||
public static function checkResourcePermissions($conf)
|
|
||||||
{
|
|
||||||
$errors = array();
|
|
||||||
$rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/');
|
|
||||||
|
|
||||||
// Check script and template directories are readable
|
|
||||||
foreach (array(
|
|
||||||
'application',
|
|
||||||
'inc',
|
|
||||||
'plugins',
|
|
||||||
$rainTplDir,
|
|
||||||
$rainTplDir . '/' . $conf->get('resource.theme'),
|
|
||||||
) as $path) {
|
|
||||||
if (!is_readable(realpath($path))) {
|
|
||||||
$errors[] = '"' . $path . '" ' . t('directory is not readable');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cache and data directories are readable and writable
|
|
||||||
foreach (array(
|
|
||||||
$conf->get('resource.thumbnails_cache'),
|
|
||||||
$conf->get('resource.data_dir'),
|
|
||||||
$conf->get('resource.page_cache'),
|
|
||||||
$conf->get('resource.raintpl_tmp'),
|
|
||||||
) as $path) {
|
|
||||||
if (!is_readable(realpath($path))) {
|
|
||||||
$errors[] = '"' . $path . '" ' . t('directory is not readable');
|
|
||||||
}
|
|
||||||
if (!is_writable(realpath($path))) {
|
|
||||||
$errors[] = '"' . $path . '" ' . t('directory is not writable');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check configuration files are readable and writable
|
|
||||||
foreach (array(
|
|
||||||
$conf->getConfigFileExt(),
|
|
||||||
$conf->get('resource.datastore'),
|
|
||||||
$conf->get('resource.ban_file'),
|
|
||||||
$conf->get('resource.log'),
|
|
||||||
$conf->get('resource.update_check'),
|
|
||||||
) as $path) {
|
|
||||||
if (!is_file(realpath($path))) {
|
|
||||||
# the file may not exist yet
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_readable(realpath($path))) {
|
|
||||||
$errors[] = '"' . $path . '" ' . t('file is not readable');
|
|
||||||
}
|
|
||||||
if (!is_writable(realpath($path))) {
|
|
||||||
$errors[] = '"' . $path . '" ' . t('file is not writable');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a salted hash representing the current Shaarli version.
|
|
||||||
*
|
|
||||||
* Useful for assets browser cache.
|
|
||||||
*
|
|
||||||
* @param string $currentVersion of Shaarli
|
|
||||||
* @param string $salt User personal salt, also used for the authentication
|
|
||||||
*
|
|
||||||
* @return string version hash
|
|
||||||
*/
|
|
||||||
public static function getVersionHash($currentVersion, $salt)
|
|
||||||
{
|
|
||||||
return hash_hmac('sha256', $currentVersion, $salt);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Shaarli;
|
|
||||||
|
|
||||||
use Shaarli\Exceptions\IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class FileUtils
|
|
||||||
*
|
|
||||||
* Utility class for file manipulation.
|
|
||||||
*/
|
|
||||||
class FileUtils
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected static $phpPrefix = '<?php /* ';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected static $phpSuffix = ' */ ?>';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write data into a file (Shaarli database format).
|
|
||||||
* The data is stored in a PHP file, as a comment, in compressed base64 format.
|
|
||||||
*
|
|
||||||
* The file will be created if it doesn't exist.
|
|
||||||
*
|
|
||||||
* @param string $file File path.
|
|
||||||
* @param mixed $content Content to write.
|
|
||||||
*
|
|
||||||
* @return int|bool Number of bytes written or false if it fails.
|
|
||||||
*
|
|
||||||
* @throws IOException The destination file can't be written.
|
|
||||||
*/
|
|
||||||
public static function writeFlatDB($file, $content)
|
|
||||||
{
|
|
||||||
if (is_file($file) && !is_writeable($file)) {
|
|
||||||
// The datastore exists but is not writeable
|
|
||||||
throw new IOException($file);
|
|
||||||
} elseif (!is_file($file) && !is_writeable(dirname($file))) {
|
|
||||||
// The datastore does not exist and its parent directory is not writeable
|
|
||||||
throw new IOException(dirname($file));
|
|
||||||
}
|
|
||||||
|
|
||||||
return file_put_contents(
|
|
||||||
$file,
|
|
||||||
self::$phpPrefix . base64_encode(gzdeflate(serialize($content))) . self::$phpSuffix
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read data from a file containing Shaarli database format content.
|
|
||||||
*
|
|
||||||
* If the file isn't readable or doesn't exist, default data will be returned.
|
|
||||||
*
|
|
||||||
* @param string $file File path.
|
|
||||||
* @param mixed $default The default value to return if the file isn't readable.
|
|
||||||
*
|
|
||||||
* @return mixed The content unserialized, or default if the file isn't readable, or false if it fails.
|
|
||||||
*/
|
|
||||||
public static function readFlatDB($file, $default = null)
|
|
||||||
{
|
|
||||||
// Note that gzinflate is faster than gzuncompress.
|
|
||||||
// See: http://www.php.net/manual/en/function.gzdeflate.php#96439
|
|
||||||
if (!is_readable($file)) {
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = file_get_contents($file);
|
|
||||||
if ($data == '') {
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return unserialize(
|
|
||||||
gzinflate(
|
|
||||||
base64_decode(
|
|
||||||
substr($data, strlen(self::$phpPrefix), -strlen(self::$phpSuffix))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Shaarli;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Router
|
|
||||||
*
|
|
||||||
* (only displayable pages here)
|
|
||||||
*/
|
|
||||||
class Router
|
|
||||||
{
|
|
||||||
public static $AJAX_THUMB_UPDATE = 'ajax_thumb_update';
|
|
||||||
|
|
||||||
public static $PAGE_LOGIN = 'login';
|
|
||||||
|
|
||||||
public static $PAGE_PICWALL = 'picwall';
|
|
||||||
|
|
||||||
public static $PAGE_TAGCLOUD = 'tagcloud';
|
|
||||||
|
|
||||||
public static $PAGE_TAGLIST = 'taglist';
|
|
||||||
|
|
||||||
public static $PAGE_DAILY = 'daily';
|
|
||||||
|
|
||||||
public static $PAGE_FEED_ATOM = 'atom';
|
|
||||||
|
|
||||||
public static $PAGE_FEED_RSS = 'rss';
|
|
||||||
|
|
||||||
public static $PAGE_TOOLS = 'tools';
|
|
||||||
|
|
||||||
public static $PAGE_CHANGEPASSWORD = 'changepasswd';
|
|
||||||
|
|
||||||
public static $PAGE_CONFIGURE = 'configure';
|
|
||||||
|
|
||||||
public static $PAGE_CHANGETAG = 'changetag';
|
|
||||||
|
|
||||||
public static $PAGE_ADDLINK = 'addlink';
|
|
||||||
|
|
||||||
public static $PAGE_EDITLINK = 'edit_link';
|
|
||||||
|
|
||||||
public static $PAGE_DELETELINK = 'delete_link';
|
|
||||||
|
|
||||||
public static $PAGE_CHANGE_VISIBILITY = 'change_visibility';
|
|
||||||
|
|
||||||
public static $PAGE_PINLINK = 'pin';
|
|
||||||
|
|
||||||
public static $PAGE_EXPORT = 'export';
|
|
||||||
|
|
||||||
public static $PAGE_IMPORT = 'import';
|
|
||||||
|
|
||||||
public static $PAGE_OPENSEARCH = 'opensearch';
|
|
||||||
|
|
||||||
public static $PAGE_LINKLIST = 'linklist';
|
|
||||||
|
|
||||||
public static $PAGE_PLUGINSADMIN = 'pluginadmin';
|
|
||||||
|
|
||||||
public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin';
|
|
||||||
|
|
||||||
public static $PAGE_THUMBS_UPDATE = 'thumbs_update';
|
|
||||||
|
|
||||||
public static $GET_TOKEN = 'token';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reproducing renderPage() if hell, to avoid regression.
|
|
||||||
*
|
|
||||||
* This highlights how bad this needs to be rewrite,
|
|
||||||
* but let's focus on plugins for now.
|
|
||||||
*
|
|
||||||
* @param string $query $_SERVER['QUERY_STRING'].
|
|
||||||
* @param array $get $_SERVER['GET'].
|
|
||||||
* @param bool $loggedIn true if authenticated user.
|
|
||||||
*
|
|
||||||
* @return string page found.
|
|
||||||
*/
|
|
||||||
public static function findPage($query, $get, $loggedIn)
|
|
||||||
{
|
|
||||||
$loggedIn = ($loggedIn === true) ? true : false;
|
|
||||||
|
|
||||||
if (empty($query) && !isset($get['edit_link']) && !isset($get['post'])) {
|
|
||||||
return self::$PAGE_LINKLIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_LOGIN) && $loggedIn === false) {
|
|
||||||
return self::$PAGE_LOGIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_PICWALL)) {
|
|
||||||
return self::$PAGE_PICWALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_TAGCLOUD)) {
|
|
||||||
return self::$PAGE_TAGCLOUD;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_TAGLIST)) {
|
|
||||||
return self::$PAGE_TAGLIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_OPENSEARCH)) {
|
|
||||||
return self::$PAGE_OPENSEARCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_DAILY)) {
|
|
||||||
return self::$PAGE_DAILY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_FEED_ATOM)) {
|
|
||||||
return self::$PAGE_FEED_ATOM;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_FEED_RSS)) {
|
|
||||||
return self::$PAGE_FEED_RSS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_THUMBS_UPDATE)) {
|
|
||||||
return self::$PAGE_THUMBS_UPDATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$AJAX_THUMB_UPDATE)) {
|
|
||||||
return self::$AJAX_THUMB_UPDATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, only loggedin pages.
|
|
||||||
if (!$loggedIn) {
|
|
||||||
return self::$PAGE_LINKLIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_TOOLS)) {
|
|
||||||
return self::$PAGE_TOOLS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_CHANGEPASSWORD)) {
|
|
||||||
return self::$PAGE_CHANGEPASSWORD;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_CONFIGURE)) {
|
|
||||||
return self::$PAGE_CONFIGURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_CHANGETAG)) {
|
|
||||||
return self::$PAGE_CHANGETAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_ADDLINK)) {
|
|
||||||
return self::$PAGE_ADDLINK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($get['edit_link']) || isset($get['post'])) {
|
|
||||||
return self::$PAGE_EDITLINK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($get['delete_link'])) {
|
|
||||||
return self::$PAGE_DELETELINK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($get[self::$PAGE_CHANGE_VISIBILITY])) {
|
|
||||||
return self::$PAGE_CHANGE_VISIBILITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_PINLINK)) {
|
|
||||||
return self::$PAGE_PINLINK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_EXPORT)) {
|
|
||||||
return self::$PAGE_EXPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_IMPORT)) {
|
|
||||||
return self::$PAGE_IMPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_PLUGINSADMIN)) {
|
|
||||||
return self::$PAGE_PLUGINSADMIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$PAGE_SAVE_PLUGINSADMIN)) {
|
|
||||||
return self::$PAGE_SAVE_PLUGINSADMIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith($query, 'do=' . self::$GET_TOKEN)) {
|
|
||||||
return self::$GET_TOKEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$PAGE_LINKLIST;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -168,6 +168,10 @@ class BookmarkIO
|
||||||
*/
|
*/
|
||||||
public function checkDiskSpace(string $data): bool
|
public function checkDiskSpace(string $data): bool
|
||||||
{
|
{
|
||||||
|
if (function_exists('disk_free_space') === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return disk_free_space(dirname($this->datastore)) > (strlen($data) + 1024 * 500);
|
return disk_free_space(dirname($this->datastore)) > (strlen($data) + 1024 * 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ Markdown also supports tables:
|
||||||
|
|
||||||
$bookmark = new Bookmark();
|
$bookmark = new Bookmark();
|
||||||
$bookmark->setTitle(
|
$bookmark->setTitle(
|
||||||
'Shaarli - ' . t('The personal, minimalist, super-fast, database free, bookmarking service')
|
'Shaarli - ' . t('The personal, minimalist, super fast, database-free, bookmarking service')
|
||||||
);
|
);
|
||||||
$bookmark->setDescription(t(
|
$bookmark->setDescription(t(
|
||||||
'Welcome to Shaarli!
|
'Welcome to Shaarli!
|
||||||
|
|
|
@ -1,577 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Shaarli\Bookmark;
|
|
||||||
|
|
||||||
use ArrayAccess;
|
|
||||||
use Countable;
|
|
||||||
use DateTime;
|
|
||||||
use Iterator;
|
|
||||||
use Shaarli\Bookmark\Exception\LinkNotFoundException;
|
|
||||||
use Shaarli\Exceptions\IOException;
|
|
||||||
use Shaarli\FileUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data storage for links.
|
|
||||||
*
|
|
||||||
* This object behaves like an associative array.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* $myLinks = new LinkDB();
|
|
||||||
* echo $myLinks[350]['title'];
|
|
||||||
* foreach ($myLinks as $link)
|
|
||||||
* echo $link['title'].' at url '.$link['url'].'; description:'.$link['description'];
|
|
||||||
*
|
|
||||||
* Available keys:
|
|
||||||
* - id: primary key, incremental integer identifier (persistent)
|
|
||||||
* - description: description of the entry
|
|
||||||
* - created: creation date of this entry, DateTime object.
|
|
||||||
* - updated: last modification date of this entry, DateTime object.
|
|
||||||
* - private: Is this link private? 0=no, other value=yes
|
|
||||||
* - tags: tags attached to this entry (separated by spaces)
|
|
||||||
* - title Title of the link
|
|
||||||
* - url URL of the link. Used for displayable links.
|
|
||||||
* Can be absolute or relative in the database but the relative links
|
|
||||||
* will be converted to absolute ones in templates.
|
|
||||||
* - real_url Raw URL in stored in the DB (absolute or relative).
|
|
||||||
* - shorturl Permalink smallhash
|
|
||||||
*
|
|
||||||
* Implements 3 interfaces:
|
|
||||||
* - ArrayAccess: behaves like an associative array;
|
|
||||||
* - Countable: there is a count() method;
|
|
||||||
* - Iterator: usable in foreach () loops.
|
|
||||||
*
|
|
||||||
* ID mechanism:
|
|
||||||
* ArrayAccess is implemented in a way that will allow to access a link
|
|
||||||
* with the unique identifier ID directly with $link[ID].
|
|
||||||
* Note that it's not the real key of the link array attribute.
|
|
||||||
* This mechanism is in place to have persistent link IDs,
|
|
||||||
* even though the internal array is reordered by date.
|
|
||||||
* Example:
|
|
||||||
* - DB: link #1 (2010-01-01) link #2 (2016-01-01)
|
|
||||||
* - Order: #2 #1
|
|
||||||
* - Import links containing: link #3 (2013-01-01)
|
|
||||||
* - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01)
|
|
||||||
* - Real order: #2 #3 #1
|
|
||||||
*/
|
|
||||||
class LinkDB implements Iterator, Countable, ArrayAccess
|
|
||||||
{
|
|
||||||
// Links are stored as a PHP serialized string
|
|
||||||
private $datastore;
|
|
||||||
|
|
||||||
// Link date storage format
|
|
||||||
const LINK_DATE_FORMAT = 'Ymd_His';
|
|
||||||
|
|
||||||
// List of links (associative array)
|
|
||||||
// - key: link date (e.g. "20110823_124546"),
|
|
||||||
// - value: associative array (keys: title, description...)
|
|
||||||
private $links;
|
|
||||||
|
|
||||||
// List of all recorded URLs (key=url, value=link offset)
|
|
||||||
// for fast reserve search (url-->link offset)
|
|
||||||
private $urls;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array List of all links IDS mapped with their array offset.
|
|
||||||
* Map: id->offset.
|
|
||||||
*/
|
|
||||||
protected $ids;
|
|
||||||
|
|
||||||
// List of offset keys (for the Iterator interface implementation)
|
|
||||||
private $keys;
|
|
||||||
|
|
||||||
// Position in the $this->keys array (for the Iterator interface)
|
|
||||||
private $position;
|
|
||||||
|
|
||||||
// Is the user logged in? (used to filter private links)
|
|
||||||
private $loggedIn;
|
|
||||||
|
|
||||||
// Hide public links
|
|
||||||
private $hidePublicLinks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new LinkDB
|
|
||||||
*
|
|
||||||
* Checks if the datastore exists; else, attempts to create a dummy one.
|
|
||||||
*
|
|
||||||
* @param string $datastore datastore file path.
|
|
||||||
* @param boolean $isLoggedIn is the user logged in?
|
|
||||||
* @param boolean $hidePublicLinks if true all links are private.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
$datastore,
|
|
||||||
$isLoggedIn,
|
|
||||||
$hidePublicLinks
|
|
||||||
) {
|
|
||||||
|
|
||||||
$this->datastore = $datastore;
|
|
||||||
$this->loggedIn = $isLoggedIn;
|
|
||||||
$this->hidePublicLinks = $hidePublicLinks;
|
|
||||||
$this->check();
|
|
||||||
$this->read();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Countable - Counts elements of an object
|
|
||||||
*/
|
|
||||||
public function count()
|
|
||||||
{
|
|
||||||
return count($this->links);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ArrayAccess - Assigns a value to the specified offset
|
|
||||||
*/
|
|
||||||
public function offsetSet($offset, $value)
|
|
||||||
{
|
|
||||||
// TODO: use exceptions instead of "die"
|
|
||||||
if (!$this->loggedIn) {
|
|
||||||
die(t('You are not authorized to add a link.'));
|
|
||||||
}
|
|
||||||
if (!isset($value['id']) || empty($value['url'])) {
|
|
||||||
die(t('Internal Error: A link should always have an id and URL.'));
|
|
||||||
}
|
|
||||||
if (($offset !== null && !is_int($offset)) || !is_int($value['id'])) {
|
|
||||||
die(t('You must specify an integer as a key.'));
|
|
||||||
}
|
|
||||||
if ($offset !== null && $offset !== $value['id']) {
|
|
||||||
die(t('Array offset and link ID must be equal.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the link exists, we reuse the real offset, otherwise new entry
|
|
||||||
$existing = $this->getLinkOffset($offset);
|
|
||||||
if ($existing !== null) {
|
|
||||||
$offset = $existing;
|
|
||||||
} else {
|
|
||||||
$offset = count($this->links);
|
|
||||||
}
|
|
||||||
$this->links[$offset] = $value;
|
|
||||||
$this->urls[$value['url']] = $offset;
|
|
||||||
$this->ids[$value['id']] = $offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ArrayAccess - Whether or not an offset exists
|
|
||||||
*/
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
return array_key_exists($this->getLinkOffset($offset), $this->links);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ArrayAccess - Unsets an offset
|
|
||||||
*/
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
if (!$this->loggedIn) {
|
|
||||||
// TODO: raise an exception
|
|
||||||
die('You are not authorized to delete a link.');
|
|
||||||
}
|
|
||||||
$realOffset = $this->getLinkOffset($offset);
|
|
||||||
$url = $this->links[$realOffset]['url'];
|
|
||||||
unset($this->urls[$url]);
|
|
||||||
unset($this->ids[$realOffset]);
|
|
||||||
unset($this->links[$realOffset]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ArrayAccess - Returns the value at specified offset
|
|
||||||
*/
|
|
||||||
public function offsetGet($offset)
|
|
||||||
{
|
|
||||||
$realOffset = $this->getLinkOffset($offset);
|
|
||||||
return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterator - Returns the current element
|
|
||||||
*/
|
|
||||||
public function current()
|
|
||||||
{
|
|
||||||
return $this[$this->keys[$this->position]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterator - Returns the key of the current element
|
|
||||||
*/
|
|
||||||
public function key()
|
|
||||||
{
|
|
||||||
return $this->keys[$this->position];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterator - Moves forward to next element
|
|
||||||
*/
|
|
||||||
public function next()
|
|
||||||
{
|
|
||||||
++$this->position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterator - Rewinds the Iterator to the first element
|
|
||||||
*
|
|
||||||
* Entries are sorted by date (latest first)
|
|
||||||
*/
|
|
||||||
public function rewind()
|
|
||||||
{
|
|
||||||
$this->keys = array_keys($this->ids);
|
|
||||||
$this->position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterator - Checks if current position is valid
|
|
||||||
*/
|
|
||||||
public function valid()
|
|
||||||
{
|
|
||||||
return isset($this->keys[$this->position]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the DB directory and file exist
|
|
||||||
*
|
|
||||||
* If no DB file is found, creates a dummy DB.
|
|
||||||
*/
|
|
||||||
private function check()
|
|
||||||
{
|
|
||||||
if (file_exists($this->datastore)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a dummy database for example
|
|
||||||
$this->links = array();
|
|
||||||
$link = array(
|
|
||||||
'id' => 1,
|
|
||||||
'title' => t('The personal, minimalist, super-fast, database free, bookmarking service'),
|
|
||||||
'url' => 'https://shaarli.readthedocs.io',
|
|
||||||
'description' => t(
|
|
||||||
'Welcome to Shaarli! This is your first public bookmark. '
|
|
||||||
. 'To edit or delete me, you must first login.
|
|
||||||
|
|
||||||
To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
|
|
||||||
|
|
||||||
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
|
|
||||||
),
|
|
||||||
'private' => 0,
|
|
||||||
'created' => new DateTime(),
|
|
||||||
'tags' => 'opensource software',
|
|
||||||
'sticky' => false,
|
|
||||||
);
|
|
||||||
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
|
|
||||||
$this->links[1] = $link;
|
|
||||||
|
|
||||||
$link = array(
|
|
||||||
'id' => 0,
|
|
||||||
'title' => t('My secret stuff... - Pastebin.com'),
|
|
||||||
'url' => 'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
|
|
||||||
'description' => t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
|
|
||||||
'private' => 1,
|
|
||||||
'created' => new DateTime('1 minute ago'),
|
|
||||||
'tags' => 'secretstuff',
|
|
||||||
'sticky' => false,
|
|
||||||
);
|
|
||||||
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
|
|
||||||
$this->links[0] = $link;
|
|
||||||
|
|
||||||
// Write database to disk
|
|
||||||
$this->write();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads database from disk to memory
|
|
||||||
*/
|
|
||||||
private function read()
|
|
||||||
{
|
|
||||||
// Public links are hidden and user not logged in => nothing to show
|
|
||||||
if ($this->hidePublicLinks && !$this->loggedIn) {
|
|
||||||
$this->links = array();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->urls = [];
|
|
||||||
$this->ids = [];
|
|
||||||
$this->links = FileUtils::readFlatDB($this->datastore, []);
|
|
||||||
|
|
||||||
$toremove = array();
|
|
||||||
foreach ($this->links as $key => &$link) {
|
|
||||||
if (!$this->loggedIn && $link['private'] != 0) {
|
|
||||||
// Transition for not upgraded databases.
|
|
||||||
unset($this->links[$key]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize data fields.
|
|
||||||
sanitizeLink($link);
|
|
||||||
|
|
||||||
// Remove private tags if the user is not logged in.
|
|
||||||
if (!$this->loggedIn) {
|
|
||||||
$link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$link['real_url'] = $link['url'];
|
|
||||||
|
|
||||||
$link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
|
|
||||||
|
|
||||||
$link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
|
|
||||||
|
|
||||||
// To be able to load links before running the update, and prepare the update
|
|
||||||
if (!isset($link['created'])) {
|
|
||||||
$link['id'] = $link['linkdate'];
|
|
||||||
$link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
|
|
||||||
if (!empty($link['updated'])) {
|
|
||||||
$link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
|
|
||||||
}
|
|
||||||
$link['shorturl'] = smallHash($link['linkdate']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->urls[$link['url']] = $key;
|
|
||||||
$this->ids[$link['id']] = $key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the database from memory to disk
|
|
||||||
*
|
|
||||||
* @throws IOException the datastore is not writable
|
|
||||||
*/
|
|
||||||
private function write()
|
|
||||||
{
|
|
||||||
$this->reorder();
|
|
||||||
FileUtils::writeFlatDB($this->datastore, $this->links);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the database from memory to disk
|
|
||||||
*
|
|
||||||
* @param string $pageCacheDir page cache directory
|
|
||||||
*/
|
|
||||||
public function save($pageCacheDir)
|
|
||||||
{
|
|
||||||
if (!$this->loggedIn) {
|
|
||||||
// TODO: raise an Exception instead
|
|
||||||
die('You are not authorized to change the database.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->write();
|
|
||||||
|
|
||||||
invalidateCaches($pageCacheDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the link for a given URL, or False if it does not exist.
|
|
||||||
*
|
|
||||||
* @param string $url URL to search for
|
|
||||||
*
|
|
||||||
* @return mixed the existing link if it exists, else 'false'
|
|
||||||
*/
|
|
||||||
public function getLinkFromUrl($url)
|
|
||||||
{
|
|
||||||
if (isset($this->urls[$url])) {
|
|
||||||
return $this->links[$this->urls[$url]];
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the shaare corresponding to a smallHash.
|
|
||||||
*
|
|
||||||
* @param string $request QUERY_STRING server parameter.
|
|
||||||
*
|
|
||||||
* @return array $filtered array containing permalink data.
|
|
||||||
*
|
|
||||||
* @throws LinkNotFoundException if the smallhash is malformed or doesn't match any link.
|
|
||||||
*/
|
|
||||||
public function filterHash($request)
|
|
||||||
{
|
|
||||||
$request = substr($request, 0, 6);
|
|
||||||
$linkFilter = new LinkFilter($this->links);
|
|
||||||
return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of articles for a given day.
|
|
||||||
*
|
|
||||||
* @param string $request day to filter. Format: YYYYMMDD.
|
|
||||||
*
|
|
||||||
* @return array list of shaare found.
|
|
||||||
*/
|
|
||||||
public function filterDay($request)
|
|
||||||
{
|
|
||||||
$linkFilter = new LinkFilter($this->links);
|
|
||||||
return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter links according to search parameters.
|
|
||||||
*
|
|
||||||
* @param array $filterRequest Search request content. Supported keys:
|
|
||||||
* - searchtags: list of tags
|
|
||||||
* - searchterm: term search
|
|
||||||
* @param bool $casesensitive Optional: Perform case sensitive filter
|
|
||||||
* @param string $visibility return only all/private/public links
|
|
||||||
* @param bool $untaggedonly return only untagged links
|
|
||||||
*
|
|
||||||
* @return array filtered links, all links if no suitable filter was provided.
|
|
||||||
*/
|
|
||||||
public function filterSearch(
|
|
||||||
$filterRequest = array(),
|
|
||||||
$casesensitive = false,
|
|
||||||
$visibility = 'all',
|
|
||||||
$untaggedonly = false
|
|
||||||
) {
|
|
||||||
|
|
||||||
// Filter link database according to parameters.
|
|
||||||
$searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
|
|
||||||
$searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
|
|
||||||
|
|
||||||
// Search tags + fullsearch - blank string parameter will return all links.
|
|
||||||
$type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; // == "vuotext"
|
|
||||||
$request = [$searchtags, $searchterm];
|
|
||||||
|
|
||||||
$linkFilter = new LinkFilter($this);
|
|
||||||
return $linkFilter->filter($type, $request, $casesensitive, $visibility, $untaggedonly);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list tags appearing in the links with the given tags
|
|
||||||
*
|
|
||||||
* @param array $filteringTags tags selecting the links to consider
|
|
||||||
* @param string $visibility process only all/private/public links
|
|
||||||
*
|
|
||||||
* @return array tag => linksCount
|
|
||||||
*/
|
|
||||||
public function linksCountPerTag($filteringTags = [], $visibility = 'all')
|
|
||||||
{
|
|
||||||
$links = $this->filterSearch(['searchtags' => $filteringTags], false, $visibility);
|
|
||||||
$tags = [];
|
|
||||||
$caseMapping = [];
|
|
||||||
foreach ($links as $link) {
|
|
||||||
foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
|
|
||||||
if (empty($tag)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// The first case found will be displayed.
|
|
||||||
if (!isset($caseMapping[strtolower($tag)])) {
|
|
||||||
$caseMapping[strtolower($tag)] = $tag;
|
|
||||||
$tags[$caseMapping[strtolower($tag)]] = 0;
|
|
||||||
}
|
|
||||||
$tags[$caseMapping[strtolower($tag)]]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Formerly used arsort(), which doesn't define the sort behaviour for equal values.
|
|
||||||
* Also, this function doesn't produce the same result between PHP 5.6 and 7.
|
|
||||||
*
|
|
||||||
* So we now use array_multisort() to sort tags by DESC occurrences,
|
|
||||||
* then ASC alphabetically for equal values.
|
|
||||||
*
|
|
||||||
* @see https://github.com/shaarli/Shaarli/issues/1142
|
|
||||||
*/
|
|
||||||
$keys = array_keys($tags);
|
|
||||||
$tmpTags = array_combine($keys, $keys);
|
|
||||||
array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
|
|
||||||
return $tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rename or delete a tag across all links.
|
|
||||||
*
|
|
||||||
* @param string $from Tag to rename
|
|
||||||
* @param string $to New tag. If none is provided, the from tag will be deleted
|
|
||||||
*
|
|
||||||
* @return array|bool List of altered links or false on error
|
|
||||||
*/
|
|
||||||
public function renameTag($from, $to)
|
|
||||||
{
|
|
||||||
if (empty($from)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$delete = empty($to);
|
|
||||||
// True for case-sensitive tag search.
|
|
||||||
$linksToAlter = $this->filterSearch(['searchtags' => $from], true);
|
|
||||||
foreach ($linksToAlter as $key => &$value) {
|
|
||||||
$tags = preg_split('/\s+/', trim($value['tags']));
|
|
||||||
if (($pos = array_search($from, $tags)) !== false) {
|
|
||||||
if ($delete) {
|
|
||||||
unset($tags[$pos]); // Remove tag.
|
|
||||||
} else {
|
|
||||||
$tags[$pos] = trim($to);
|
|
||||||
}
|
|
||||||
$value['tags'] = trim(implode(' ', array_unique($tags)));
|
|
||||||
$this[$value['id']] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $linksToAlter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of days containing articles (oldest first)
|
|
||||||
* Output: An array containing days (in format YYYYMMDD).
|
|
||||||
*/
|
|
||||||
public function days()
|
|
||||||
{
|
|
||||||
$linkDays = array();
|
|
||||||
foreach ($this->links as $link) {
|
|
||||||
$linkDays[$link['created']->format('Ymd')] = 0;
|
|
||||||
}
|
|
||||||
$linkDays = array_keys($linkDays);
|
|
||||||
sort($linkDays);
|
|
||||||
|
|
||||||
return $linkDays;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reorder links by creation date (newest first).
|
|
||||||
*
|
|
||||||
* Also update the urls and ids mapping arrays.
|
|
||||||
*
|
|
||||||
* @param string $order ASC|DESC
|
|
||||||
*/
|
|
||||||
public function reorder($order = 'DESC')
|
|
||||||
{
|
|
||||||
$order = $order === 'ASC' ? -1 : 1;
|
|
||||||
// Reorder array by dates.
|
|
||||||
usort($this->links, function ($a, $b) use ($order) {
|
|
||||||
if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) {
|
|
||||||
return $a['sticky'] ? -1 : 1;
|
|
||||||
}
|
|
||||||
return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->urls = [];
|
|
||||||
$this->ids = [];
|
|
||||||
foreach ($this->links as $key => $link) {
|
|
||||||
$this->urls[$link['url']] = $key;
|
|
||||||
$this->ids[$link['id']] = $key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the next key for link creation.
|
|
||||||
* E.g. If the last ID is 597, the next will be 598.
|
|
||||||
*
|
|
||||||
* @return int next ID.
|
|
||||||
*/
|
|
||||||
public function getNextId()
|
|
||||||
{
|
|
||||||
if (!empty($this->ids)) {
|
|
||||||
return max(array_keys($this->ids)) + 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a link offset in links array from its unique ID.
|
|
||||||
*
|
|
||||||
* @param int $id Persistent ID of a link.
|
|
||||||
*
|
|
||||||
* @return int Real offset in local array, or null if doesn't exist.
|
|
||||||
*/
|
|
||||||
protected function getLinkOffset($id)
|
|
||||||
{
|
|
||||||
if (isset($this->ids[$id])) {
|
|
||||||
return $this->ids[$id];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,449 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Shaarli\Bookmark;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Shaarli\Bookmark\Exception\LinkNotFoundException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class LinkFilter.
|
|
||||||
*
|
|
||||||
* Perform search and filter operation on link data list.
|
|
||||||
*/
|
|
||||||
class LinkFilter
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string permalinks.
|
|
||||||
*/
|
|
||||||
public static $FILTER_HASH = 'permalink';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string text search.
|
|
||||||
*/
|
|
||||||
public static $FILTER_TEXT = 'fulltext';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string tag filter.
|
|
||||||
*/
|
|
||||||
public static $FILTER_TAG = 'tags';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string filter by day.
|
|
||||||
*/
|
|
||||||
public static $FILTER_DAY = 'FILTER_DAY';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string Allowed characters for hashtags (regex syntax).
|
|
||||||
*/
|
|
||||||
public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var LinkDB all available links.
|
|
||||||
*/
|
|
||||||
private $links;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param LinkDB $links initialization.
|
|
||||||
*/
|
|
||||||
public function __construct($links)
|
|
||||||
{
|
|
||||||
$this->links = $links;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter links according to parameters.
|
|
||||||
*
|
|
||||||
* @param string $type Type of filter (eg. tags, permalink, etc.).
|
|
||||||
* @param mixed $request Filter content.
|
|
||||||
* @param bool $casesensitive Optional: Perform case sensitive filter if true.
|
|
||||||
* @param string $visibility Optional: return only all/private/public links
|
|
||||||
* @param string $untaggedonly Optional: return only untagged links. Applies only if $type includes FILTER_TAG
|
|
||||||
*
|
|
||||||
* @return array filtered link list.
|
|
||||||
*/
|
|
||||||
public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false)
|
|
||||||
{
|
|
||||||
if (!in_array($visibility, ['all', 'public', 'private'])) {
|
|
||||||
$visibility = 'all';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($type) {
|
|
||||||
case self::$FILTER_HASH:
|
|
||||||
return $this->filterSmallHash($request);
|
|
||||||
case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext"
|
|
||||||
$noRequest = empty($request) || (empty($request[0]) && empty($request[1]));
|
|
||||||
if ($noRequest) {
|
|
||||||
if ($untaggedonly) {
|
|
||||||
return $this->filterUntagged($visibility);
|
|
||||||
}
|
|
||||||
return $this->noFilter($visibility);
|
|
||||||
}
|
|
||||||
if ($untaggedonly) {
|
|
||||||
$filtered = $this->filterUntagged($visibility);
|
|
||||||
} else {
|
|
||||||
$filtered = $this->links;
|
|
||||||
}
|
|
||||||
if (!empty($request[0])) {
|
|
||||||
$filtered = (new LinkFilter($filtered))->filterTags($request[0], $casesensitive, $visibility);
|
|
||||||
}
|
|
||||||
if (!empty($request[1])) {
|
|
||||||
$filtered = (new LinkFilter($filtered))->filterFulltext($request[1], $visibility);
|
|
||||||
}
|
|
||||||
return $filtered;
|
|
||||||
case self::$FILTER_TEXT:
|
|
||||||
return $this->filterFulltext($request, $visibility);
|
|
||||||
case self::$FILTER_TAG:
|
|
||||||
if ($untaggedonly) {
|
|
||||||
return $this->filterUntagged($visibility);
|
|
||||||
} else {
|
|
||||||
return $this->filterTags($request, $casesensitive, $visibility);
|
|
||||||
}
|
|
||||||
case self::$FILTER_DAY:
|
|
||||||
return $this->filterDay($request);
|
|
||||||
default:
|
|
||||||
return $this->noFilter($visibility);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown filter, but handle private only.
|
|
||||||
*
|
|
||||||
* @param string $visibility Optional: return only all/private/public links
|
|
||||||
*
|
|
||||||
* @return array filtered links.
|
|
||||||
*/
|
|
||||||
private function noFilter($visibility = 'all')
|
|
||||||
{
|
|
||||||
if ($visibility === 'all') {
|
|
||||||
return $this->links;
|
|
||||||
}
|
|
||||||
|
|
||||||
$out = array();
|
|
||||||
foreach ($this->links as $key => $value) {
|
|
||||||
if ($value['private'] && $visibility === 'private') {
|
|
||||||
$out[$key] = $value;
|
|
||||||
} elseif (!$value['private'] && $visibility === 'public') {
|
|
||||||
$out[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the shaare corresponding to a smallHash.
|
|
||||||
*
|
|
||||||
* @param string $smallHash permalink hash.
|
|
||||||
*
|
|
||||||
* @return array $filtered array containing permalink data.
|
|
||||||
*
|
|
||||||
* @throws \Shaarli\Bookmark\Exception\LinkNotFoundException if the smallhash doesn't match any link.
|
|
||||||
*/
|
|
||||||
private function filterSmallHash($smallHash)
|
|
||||||
{
|
|
||||||
$filtered = array();
|
|
||||||
foreach ($this->links as $key => $l) {
|
|
||||||
if ($smallHash == $l['shorturl']) {
|
|
||||||
// Yes, this is ugly and slow
|
|
||||||
$filtered[$key] = $l;
|
|
||||||
return $filtered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($filtered)) {
|
|
||||||
throw new LinkNotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of links corresponding to a full-text search
|
|
||||||
*
|
|
||||||
* Searches:
|
|
||||||
* - in the URLs, title and description;
|
|
||||||
* - are case-insensitive;
|
|
||||||
* - terms surrounded by quotes " are exact terms search.
|
|
||||||
* - terms starting with a dash - are excluded (except exact terms).
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* print_r($mydb->filterFulltext('hollandais'));
|
|
||||||
*
|
|
||||||
* mb_convert_case($val, MB_CASE_LOWER, 'UTF-8')
|
|
||||||
* - allows to perform searches on Unicode text
|
|
||||||
* - see https://github.com/shaarli/Shaarli/issues/75 for examples
|
|
||||||
*
|
|
||||||
* @param string $searchterms search query.
|
|
||||||
* @param string $visibility Optional: return only all/private/public links.
|
|
||||||
*
|
|
||||||
* @return array search results.
|
|
||||||
*/
|
|
||||||
private function filterFulltext($searchterms, $visibility = 'all')
|
|
||||||
{
|
|
||||||
if (empty($searchterms)) {
|
|
||||||
return $this->noFilter($visibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
$filtered = array();
|
|
||||||
$search = mb_convert_case(html_entity_decode($searchterms), MB_CASE_LOWER, 'UTF-8');
|
|
||||||
$exactRegex = '/"([^"]+)"/';
|
|
||||||
// Retrieve exact search terms.
|
|
||||||
preg_match_all($exactRegex, $search, $exactSearch);
|
|
||||||
$exactSearch = array_values(array_filter($exactSearch[1]));
|
|
||||||
|
|
||||||
// Remove exact search terms to get AND terms search.
|
|
||||||
$explodedSearchAnd = explode(' ', trim(preg_replace($exactRegex, '', $search)));
|
|
||||||
$explodedSearchAnd = array_values(array_filter($explodedSearchAnd));
|
|
||||||
|
|
||||||
// Filter excluding terms and update andSearch.
|
|
||||||
$excludeSearch = array();
|
|
||||||
$andSearch = array();
|
|
||||||
foreach ($explodedSearchAnd as $needle) {
|
|
||||||
if ($needle[0] == '-' && strlen($needle) > 1) {
|
|
||||||
$excludeSearch[] = substr($needle, 1);
|
|
||||||
} else {
|
|
||||||
$andSearch[] = $needle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$keys = array('title', 'description', 'url', 'tags');
|
|
||||||
|
|
||||||
// Iterate over every stored link.
|
|
||||||
foreach ($this->links as $id => $link) {
|
|
||||||
// ignore non private links when 'privatonly' is on.
|
|
||||||
if ($visibility !== 'all') {
|
|
||||||
if (!$link['private'] && $visibility === 'private') {
|
|
||||||
continue;
|
|
||||||
} elseif ($link['private'] && $visibility === 'public') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenate link fields to search across fields.
|
|
||||||
// Adds a '\' separator for exact search terms.
|
|
||||||
$content = '';
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
$content .= mb_convert_case($link[$key], MB_CASE_LOWER, 'UTF-8') . '\\';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Be optimistic
|
|
||||||
$found = true;
|
|
||||||
|
|
||||||
// First, we look for exact term search
|
|
||||||
for ($i = 0; $i < count($exactSearch) && $found; $i++) {
|
|
||||||
$found = strpos($content, $exactSearch[$i]) !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over keywords, if keyword is not found,
|
|
||||||
// no need to check for the others. We want all or nothing.
|
|
||||||
for ($i = 0; $i < count($andSearch) && $found; $i++) {
|
|
||||||
$found = strpos($content, $andSearch[$i]) !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exclude terms.
|
|
||||||
for ($i = 0; $i < count($excludeSearch) && $found; $i++) {
|
|
||||||
$found = strpos($content, $excludeSearch[$i]) === false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($found) {
|
|
||||||
$filtered[$id] = $link;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* generate a regex fragment out of a tag
|
|
||||||
*
|
|
||||||
* @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard
|
|
||||||
*
|
|
||||||
* @return string generated regex fragment
|
|
||||||
*/
|
|
||||||
private static function tag2regex($tag)
|
|
||||||
{
|
|
||||||
$len = strlen($tag);
|
|
||||||
if (!$len || $tag === "-" || $tag === "*") {
|
|
||||||
// nothing to search, return empty regex
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if ($tag[0] === "-") {
|
|
||||||
// query is negated
|
|
||||||
$i = 1; // use offset to start after '-' character
|
|
||||||
$regex = '(?!'; // create negative lookahead
|
|
||||||
} else {
|
|
||||||
$i = 0; // start at first character
|
|
||||||
$regex = '(?='; // use positive lookahead
|
|
||||||
}
|
|
||||||
$regex .= '.*(?:^| )'; // before tag may only be a space or the beginning
|
|
||||||
// iterate over string, separating it into placeholder and content
|
|
||||||
for (; $i < $len; $i++) {
|
|
||||||
if ($tag[$i] === '*') {
|
|
||||||
// placeholder found
|
|
||||||
$regex .= '[^ ]*?';
|
|
||||||
} else {
|
|
||||||
// regular characters
|
|
||||||
$offset = strpos($tag, '*', $i);
|
|
||||||
if ($offset === false) {
|
|
||||||
// no placeholder found, set offset to end of string
|
|
||||||
$offset = $len;
|
|
||||||
}
|
|
||||||
// subtract one, as we want to get before the placeholder or end of string
|
|
||||||
$offset -= 1;
|
|
||||||
// we got a tag name that we want to search for. escape any regex characters to prevent conflicts.
|
|
||||||
$regex .= preg_quote(substr($tag, $i, $offset - $i + 1), '/');
|
|
||||||
// move $i on
|
|
||||||
$i = $offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$regex .= '(?:$| ))'; // after the tag may only be a space or the end
|
|
||||||
return $regex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of links associated with a given list of tags
|
|
||||||
*
|
|
||||||
* You can specify one or more tags, separated by space or a comma, e.g.
|
|
||||||
* print_r($mydb->filterTags('linux programming'));
|
|
||||||
*
|
|
||||||
* @param string $tags list of tags separated by commas or blank spaces.
|
|
||||||
* @param bool $casesensitive ignore case if false.
|
|
||||||
* @param string $visibility Optional: return only all/private/public links.
|
|
||||||
*
|
|
||||||
* @return array filtered links.
|
|
||||||
*/
|
|
||||||
public function filterTags($tags, $casesensitive = false, $visibility = 'all')
|
|
||||||
{
|
|
||||||
// get single tags (we may get passed an array, even though the docs say different)
|
|
||||||
$inputTags = $tags;
|
|
||||||
if (!is_array($tags)) {
|
|
||||||
// we got an input string, split tags
|
|
||||||
$inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!count($inputTags)) {
|
|
||||||
// no input tags
|
|
||||||
return $this->noFilter($visibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build regex from all tags
|
|
||||||
$re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/';
|
|
||||||
if (!$casesensitive) {
|
|
||||||
// make regex case insensitive
|
|
||||||
$re .= 'i';
|
|
||||||
}
|
|
||||||
|
|
||||||
// create resulting array
|
|
||||||
$filtered = array();
|
|
||||||
|
|
||||||
// iterate over each link
|
|
||||||
foreach ($this->links as $key => $link) {
|
|
||||||
// check level of visibility
|
|
||||||
// ignore non private links when 'privateonly' is on.
|
|
||||||
if ($visibility !== 'all') {
|
|
||||||
if (!$link['private'] && $visibility === 'private') {
|
|
||||||
continue;
|
|
||||||
} elseif ($link['private'] && $visibility === 'public') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$search = $link['tags']; // build search string, start with tags of current link
|
|
||||||
if (strlen(trim($link['description'])) && strpos($link['description'], '#') !== false) {
|
|
||||||
// description given and at least one possible tag found
|
|
||||||
$descTags = array();
|
|
||||||
// find all tags in the form of #tag in the description
|
|
||||||
preg_match_all(
|
|
||||||
'/(?<![' . self::$HASHTAG_CHARS . '])#([' . self::$HASHTAG_CHARS . ']+?)\b/sm',
|
|
||||||
$link['description'],
|
|
||||||
$descTags
|
|
||||||
);
|
|
||||||
if (count($descTags[1])) {
|
|
||||||
// there were some tags in the description, add them to the search string
|
|
||||||
$search .= ' ' . implode(' ', $descTags[1]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// match regular expression with search string
|
|
||||||
if (!preg_match($re, $search)) {
|
|
||||||
// this entry does _not_ match our regex
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$filtered[$key] = $link;
|
|
||||||
}
|
|
||||||
return $filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return only links without any tag.
|
|
||||||
*
|
|
||||||
* @param string $visibility return only all/private/public links.
|
|
||||||
*
|
|
||||||
* @return array filtered links.
|
|
||||||
*/
|
|
||||||
public function filterUntagged($visibility)
|
|
||||||
{
|
|
||||||
$filtered = [];
|
|
||||||
foreach ($this->links as $key => $link) {
|
|
||||||
if ($visibility !== 'all') {
|
|
||||||
if (!$link['private'] && $visibility === 'private') {
|
|
||||||
continue;
|
|
||||||
} elseif ($link['private'] && $visibility === 'public') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty(trim($link['tags']))) {
|
|
||||||
$filtered[$key] = $link;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of articles for a given day, chronologically sorted
|
|
||||||
*
|
|
||||||
* Day must be in the form 'YYYYMMDD' (e.g. '20120125'), e.g.
|
|
||||||
* print_r($mydb->filterDay('20120125'));
|
|
||||||
*
|
|
||||||
* @param string $day day to filter.
|
|
||||||
*
|
|
||||||
* @return array all link matching given day.
|
|
||||||
*
|
|
||||||
* @throws Exception if date format is invalid.
|
|
||||||
*/
|
|
||||||
public function filterDay($day)
|
|
||||||
{
|
|
||||||
if (!checkDateFormat('Ymd', $day)) {
|
|
||||||
throw new Exception('Invalid date format');
|
|
||||||
}
|
|
||||||
|
|
||||||
$filtered = array();
|
|
||||||
foreach ($this->links as $key => $l) {
|
|
||||||
if ($l['created']->format('Ymd') == $day) {
|
|
||||||
$filtered[$key] = $l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by date ASC
|
|
||||||
return array_reverse($filtered, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a list of tags (str) to an array. Also
|
|
||||||
* - handle case sensitivity.
|
|
||||||
* - accepts spaces commas as separator.
|
|
||||||
*
|
|
||||||
* @param string $tags string containing a list of tags.
|
|
||||||
* @param bool $casesensitive will convert everything to lowercase if false.
|
|
||||||
*
|
|
||||||
* @return array filtered tags string.
|
|
||||||
*/
|
|
||||||
public static function tagsStrToArray($tags, $casesensitive)
|
|
||||||
{
|
|
||||||
// We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
|
|
||||||
$tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8');
|
|
||||||
$tagsOut = str_replace(',', ' ', $tagsOut);
|
|
||||||
|
|
||||||
return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Shaarli\Bookmark\Exception;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
class LinkNotFoundException extends Exception
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* LinkNotFoundException constructor.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->message = t('The link you are trying to reach does not exist or has been deleted.');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Cache utilities
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Purges all cached pages
|
|
||||||
*
|
|
||||||
* @param string $pageCacheDir page cache directory
|
|
||||||
*
|
|
||||||
* @return mixed an error string if the directory is missing
|
|
||||||
*/
|
|
||||||
function purgeCachedPages($pageCacheDir)
|
|
||||||
{
|
|
||||||
if (! is_dir($pageCacheDir)) {
|
|
||||||
$error = sprintf(t('Cannot purge %s: no directory'), $pageCacheDir);
|
|
||||||
error_log($error);
|
|
||||||
return $error;
|
|
||||||
}
|
|
||||||
|
|
||||||
array_map('unlink', glob($pageCacheDir.'/*.cache'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates caches when the database is changed or the user logs out.
|
|
||||||
*
|
|
||||||
* @param string $pageCacheDir page cache directory
|
|
||||||
*/
|
|
||||||
function invalidateCaches($pageCacheDir)
|
|
||||||
{
|
|
||||||
// Purge cache attached to session.
|
|
||||||
if (isset($_SESSION['tags'])) {
|
|
||||||
unset($_SESSION['tags']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge page cache shared by sessions.
|
|
||||||
purgeCachedPages($pageCacheDir);
|
|
||||||
}
|
|
|
@ -28,7 +28,7 @@ class ServerController extends ShaarliAdminController
|
||||||
$releaseUrl = ApplicationUtils::$GITHUB_URL . '/releases/';
|
$releaseUrl = ApplicationUtils::$GITHUB_URL . '/releases/';
|
||||||
if ($this->container->conf->get('updates.check_updates', true)) {
|
if ($this->container->conf->get('updates.check_updates', true)) {
|
||||||
$latestVersion = 'v' . ApplicationUtils::getVersion(
|
$latestVersion = 'v' . ApplicationUtils::getVersion(
|
||||||
ApplicationUtils::$GIT_RAW_URL . '/latest/' . ApplicationUtils::$VERSION_FILE
|
ApplicationUtils::$GIT_RAW_URL . '/release/' . ApplicationUtils::$VERSION_FILE
|
||||||
);
|
);
|
||||||
$releaseUrl .= 'tag/' . $latestVersion;
|
$releaseUrl .= 'tag/' . $latestVersion;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -82,6 +82,9 @@ class BookmarkListController extends ShaarliVisitorController
|
||||||
$searchTagsUrlEncoded = array_map('urlencode', tags_str2array($searchTags, $tagsSeparator));
|
$searchTagsUrlEncoded = array_map('urlencode', tags_str2array($searchTags, $tagsSeparator));
|
||||||
$searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : '';
|
$searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : '';
|
||||||
|
|
||||||
|
$searchTags = !empty($searchTags) ? escape($searchTags) : '';
|
||||||
|
$searchTerm = !empty($searchTerm) ? escape($searchTerm) : '';
|
||||||
|
|
||||||
// Fill all template fields.
|
// Fill all template fields.
|
||||||
$data = array_merge(
|
$data = array_merge(
|
||||||
$this->initializeTemplateVars(),
|
$this->initializeTemplateVars(),
|
||||||
|
@ -91,8 +94,8 @@ class BookmarkListController extends ShaarliVisitorController
|
||||||
'page_current' => $page,
|
'page_current' => $page,
|
||||||
'page_max' => $searchResult->getLastPage(),
|
'page_max' => $searchResult->getLastPage(),
|
||||||
'result_count' => $searchResult->getTotalCount(),
|
'result_count' => $searchResult->getTotalCount(),
|
||||||
'search_term' => escape($searchTerm),
|
'search_term' => $searchTerm,
|
||||||
'search_tags' => escape($searchTags),
|
'search_tags' => $searchTags,
|
||||||
'search_tags_url' => $searchTagsUrlEncoded,
|
'search_tags_url' => $searchTagsUrlEncoded,
|
||||||
'visibility' => $visibility,
|
'visibility' => $visibility,
|
||||||
'links' => $links,
|
'links' => $links,
|
||||||
|
|
|
@ -329,7 +329,9 @@ class ApplicationUtils
|
||||||
'7.2' => '2020-11-30',
|
'7.2' => '2020-11-30',
|
||||||
'7.3' => '2021-12-06',
|
'7.3' => '2021-12-06',
|
||||||
'7.4' => '2022-11-28',
|
'7.4' => '2022-11-28',
|
||||||
'8.0' => '2023-12-01',
|
'8.0' => '2023-11-26',
|
||||||
|
'8.1' => '2024-11-25',
|
||||||
|
'8.2' => '2025-12-08',
|
||||||
][$matches[1]] ?? (new \DateTime('+2 year'))->format('Y-m-d');
|
][$matches[1]] ?? (new \DateTime('+2 year'))->format('Y-m-d');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<<<<<<< HEAD
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Shaarli\Legacy;
|
namespace Shaarli\Legacy;
|
||||||
|
@ -583,3 +584,599 @@ You use the community supported version of the original Shaarli project, by Seba
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
||||||| d7dead56
|
||||||
|
=======
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shaarli\Legacy;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Countable;
|
||||||
|
use DateTime;
|
||||||
|
use Iterator;
|
||||||
|
use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
|
||||||
|
use Shaarli\Exceptions\IOException;
|
||||||
|
use Shaarli\Helper\FileUtils;
|
||||||
|
use Shaarli\Render\PageCacheManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data storage for bookmarks.
|
||||||
|
*
|
||||||
|
* This object behaves like an associative array.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* $myLinks = new LinkDB();
|
||||||
|
* echo $myLinks[350]['title'];
|
||||||
|
* foreach ($myLinks as $link)
|
||||||
|
* echo $link['title'].' at url '.$link['url'].'; description:'.$link['description'];
|
||||||
|
*
|
||||||
|
* Available keys:
|
||||||
|
* - id: primary key, incremental integer identifier (persistent)
|
||||||
|
* - description: description of the entry
|
||||||
|
* - created: creation date of this entry, DateTime object.
|
||||||
|
* - updated: last modification date of this entry, DateTime object.
|
||||||
|
* - private: Is this link private? 0=no, other value=yes
|
||||||
|
* - tags: tags attached to this entry (separated by spaces)
|
||||||
|
* - title Title of the link
|
||||||
|
* - url URL of the link. Used for displayable bookmarks.
|
||||||
|
* Can be absolute or relative in the database but the relative bookmarks
|
||||||
|
* will be converted to absolute ones in templates.
|
||||||
|
* - real_url Raw URL in stored in the DB (absolute or relative).
|
||||||
|
* - shorturl Permalink smallhash
|
||||||
|
*
|
||||||
|
* Implements 3 interfaces:
|
||||||
|
* - ArrayAccess: behaves like an associative array;
|
||||||
|
* - Countable: there is a count() method;
|
||||||
|
* - Iterator: usable in foreach () loops.
|
||||||
|
*
|
||||||
|
* ID mechanism:
|
||||||
|
* ArrayAccess is implemented in a way that will allow to access a link
|
||||||
|
* with the unique identifier ID directly with $link[ID].
|
||||||
|
* Note that it's not the real key of the link array attribute.
|
||||||
|
* This mechanism is in place to have persistent link IDs,
|
||||||
|
* even though the internal array is reordered by date.
|
||||||
|
* Example:
|
||||||
|
* - DB: link #1 (2010-01-01) link #2 (2016-01-01)
|
||||||
|
* - Order: #2 #1
|
||||||
|
* - Import bookmarks containing: link #3 (2013-01-01)
|
||||||
|
* - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01)
|
||||||
|
* - Real order: #2 #3 #1
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
class LegacyLinkDB implements Iterator, Countable, ArrayAccess
|
||||||
|
{
|
||||||
|
// Links are stored as a PHP serialized string
|
||||||
|
private $datastore;
|
||||||
|
|
||||||
|
// Link date storage format
|
||||||
|
public const LINK_DATE_FORMAT = 'Ymd_His';
|
||||||
|
|
||||||
|
// List of bookmarks (associative array)
|
||||||
|
// - key: link date (e.g. "20110823_124546"),
|
||||||
|
// - value: associative array (keys: title, description...)
|
||||||
|
private $links;
|
||||||
|
|
||||||
|
// List of all recorded URLs (key=url, value=link offset)
|
||||||
|
// for fast reserve search (url-->link offset)
|
||||||
|
private $urls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array List of all bookmarks IDS mapped with their array offset.
|
||||||
|
* Map: id->offset.
|
||||||
|
*/
|
||||||
|
protected $ids;
|
||||||
|
|
||||||
|
// List of offset keys (for the Iterator interface implementation)
|
||||||
|
private $keys;
|
||||||
|
|
||||||
|
// Position in the $this->keys array (for the Iterator interface)
|
||||||
|
private $position;
|
||||||
|
|
||||||
|
// Is the user logged in? (used to filter private bookmarks)
|
||||||
|
private $loggedIn;
|
||||||
|
|
||||||
|
// Hide public bookmarks
|
||||||
|
private $hidePublicLinks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new LinkDB
|
||||||
|
*
|
||||||
|
* Checks if the datastore exists; else, attempts to create a dummy one.
|
||||||
|
*
|
||||||
|
* @param string $datastore datastore file path.
|
||||||
|
* @param boolean $isLoggedIn is the user logged in?
|
||||||
|
* @param boolean $hidePublicLinks if true all bookmarks are private.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
$datastore,
|
||||||
|
$isLoggedIn,
|
||||||
|
$hidePublicLinks
|
||||||
|
) {
|
||||||
|
|
||||||
|
$this->datastore = $datastore;
|
||||||
|
$this->loggedIn = $isLoggedIn;
|
||||||
|
$this->hidePublicLinks = $hidePublicLinks;
|
||||||
|
$this->check();
|
||||||
|
$this->read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Countable - Counts elements of an object
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return count($this->links);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArrayAccess - Assigns a value to the specified offset
|
||||||
|
*/
|
||||||
|
public function offsetSet($offset, $value): void
|
||||||
|
{
|
||||||
|
// TODO: use exceptions instead of "die"
|
||||||
|
if (!$this->loggedIn) {
|
||||||
|
die(t('You are not authorized to add a link.'));
|
||||||
|
}
|
||||||
|
if (!isset($value['id']) || empty($value['url'])) {
|
||||||
|
die(t('Internal Error: A link should always have an id and URL.'));
|
||||||
|
}
|
||||||
|
if (($offset !== null && !is_int($offset)) || !is_int($value['id'])) {
|
||||||
|
die(t('You must specify an integer as a key.'));
|
||||||
|
}
|
||||||
|
if ($offset !== null && $offset !== $value['id']) {
|
||||||
|
die(t('Array offset and link ID must be equal.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the link exists, we reuse the real offset, otherwise new entry
|
||||||
|
$existing = $this->getLinkOffset($offset);
|
||||||
|
if ($existing !== null) {
|
||||||
|
$offset = $existing;
|
||||||
|
} else {
|
||||||
|
$offset = count($this->links);
|
||||||
|
}
|
||||||
|
$this->links[$offset] = $value;
|
||||||
|
$this->urls[$value['url']] = $offset;
|
||||||
|
$this->ids[$value['id']] = $offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArrayAccess - Whether or not an offset exists
|
||||||
|
*/
|
||||||
|
public function offsetExists($offset): bool
|
||||||
|
{
|
||||||
|
return array_key_exists($this->getLinkOffset($offset), $this->links);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArrayAccess - Unsets an offset
|
||||||
|
*/
|
||||||
|
public function offsetUnset($offset): void
|
||||||
|
{
|
||||||
|
if (!$this->loggedIn) {
|
||||||
|
// TODO: raise an exception
|
||||||
|
die('You are not authorized to delete a link.');
|
||||||
|
}
|
||||||
|
$realOffset = $this->getLinkOffset($offset);
|
||||||
|
$url = $this->links[$realOffset]['url'];
|
||||||
|
unset($this->urls[$url]);
|
||||||
|
unset($this->ids[$realOffset]);
|
||||||
|
unset($this->links[$realOffset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArrayAccess - Returns the value at specified offset
|
||||||
|
*/
|
||||||
|
public function offsetGet($offset): ?array
|
||||||
|
{
|
||||||
|
$realOffset = $this->getLinkOffset($offset);
|
||||||
|
return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator - Returns the current element
|
||||||
|
*/
|
||||||
|
public function current(): array
|
||||||
|
{
|
||||||
|
return $this[$this->keys[$this->position]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator - Returns the key of the current element
|
||||||
|
*
|
||||||
|
* @return int|string
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
return $this->keys[$this->position];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator - Moves forward to next element
|
||||||
|
*/
|
||||||
|
public function next(): void
|
||||||
|
{
|
||||||
|
++$this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator - Rewinds the Iterator to the first element
|
||||||
|
*
|
||||||
|
* Entries are sorted by date (latest first)
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->keys = array_keys($this->ids);
|
||||||
|
$this->position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator - Checks if current position is valid
|
||||||
|
*/
|
||||||
|
public function valid(): bool
|
||||||
|
{
|
||||||
|
return isset($this->keys[$this->position]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the DB directory and file exist
|
||||||
|
*
|
||||||
|
* If no DB file is found, creates a dummy DB.
|
||||||
|
*/
|
||||||
|
private function check()
|
||||||
|
{
|
||||||
|
if (file_exists($this->datastore)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a dummy database for example
|
||||||
|
$this->links = [];
|
||||||
|
$link = [
|
||||||
|
'id' => 1,
|
||||||
|
'title' => t('The personal, minimalist, super fast, database-free, bookmarking service'),
|
||||||
|
'url' => 'https://shaarli.readthedocs.io',
|
||||||
|
'description' => t(
|
||||||
|
'Welcome to Shaarli! This is your first public bookmark. '
|
||||||
|
. 'To edit or delete me, you must first login.
|
||||||
|
|
||||||
|
To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
|
||||||
|
|
||||||
|
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
|
||||||
|
),
|
||||||
|
'private' => 0,
|
||||||
|
'created' => new DateTime(),
|
||||||
|
'tags' => 'opensource software',
|
||||||
|
'sticky' => false,
|
||||||
|
];
|
||||||
|
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
|
||||||
|
$this->links[1] = $link;
|
||||||
|
|
||||||
|
$link = [
|
||||||
|
'id' => 0,
|
||||||
|
'title' => t('My secret stuff... - Pastebin.com'),
|
||||||
|
'url' => 'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
|
||||||
|
'description' => t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
|
||||||
|
'private' => 1,
|
||||||
|
'created' => new DateTime('1 minute ago'),
|
||||||
|
'tags' => 'secretstuff',
|
||||||
|
'sticky' => false,
|
||||||
|
];
|
||||||
|
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
|
||||||
|
$this->links[0] = $link;
|
||||||
|
|
||||||
|
// Write database to disk
|
||||||
|
$this->write();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads database from disk to memory
|
||||||
|
*/
|
||||||
|
private function read()
|
||||||
|
{
|
||||||
|
// Public bookmarks are hidden and user not logged in => nothing to show
|
||||||
|
if ($this->hidePublicLinks && !$this->loggedIn) {
|
||||||
|
$this->links = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->urls = [];
|
||||||
|
$this->ids = [];
|
||||||
|
$this->links = FileUtils::readFlatDB($this->datastore, []);
|
||||||
|
|
||||||
|
$toremove = [];
|
||||||
|
foreach ($this->links as $key => &$link) {
|
||||||
|
if (!$this->loggedIn && $link['private'] != 0) {
|
||||||
|
// Transition for not upgraded databases.
|
||||||
|
unset($this->links[$key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize data fields.
|
||||||
|
sanitizeLink($link);
|
||||||
|
|
||||||
|
// Remove private tags if the user is not logged in.
|
||||||
|
if (!$this->loggedIn) {
|
||||||
|
$link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$link['real_url'] = $link['url'];
|
||||||
|
|
||||||
|
$link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
|
||||||
|
|
||||||
|
<<<<<<<< HEAD:application/bookmark/LinkDB.php
|
||||||
|
$link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
|
||||||
|
|
||||||
|
// To be able to load links before running the update, and prepare the update
|
||||||
|
|||||||| d7dead56:application/bookmark/LinkDB.php
|
||||||
|
// To be able to load links before running the update, and prepare the update
|
||||||
|
========
|
||||||
|
// To be able to load bookmarks before running the update, and prepare the update
|
||||||
|
>>>>>>>> github/v0.13:application/legacy/LegacyLinkDB.php
|
||||||
|
if (!isset($link['created'])) {
|
||||||
|
$link['id'] = $link['linkdate'];
|
||||||
|
$link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
|
||||||
|
if (!empty($link['updated'])) {
|
||||||
|
$link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
|
||||||
|
}
|
||||||
|
$link['shorturl'] = smallHash($link['linkdate']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->urls[$link['url']] = $key;
|
||||||
|
$this->ids[$link['id']] = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the database from memory to disk
|
||||||
|
*
|
||||||
|
* @throws IOException the datastore is not writable
|
||||||
|
*/
|
||||||
|
private function write()
|
||||||
|
{
|
||||||
|
$this->reorder();
|
||||||
|
FileUtils::writeFlatDB($this->datastore, $this->links);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the database from memory to disk
|
||||||
|
*
|
||||||
|
* @param string $pageCacheDir page cache directory
|
||||||
|
*/
|
||||||
|
public function save($pageCacheDir)
|
||||||
|
{
|
||||||
|
if (!$this->loggedIn) {
|
||||||
|
// TODO: raise an Exception instead
|
||||||
|
die('You are not authorized to change the database.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->write();
|
||||||
|
|
||||||
|
$pageCacheManager = new PageCacheManager($pageCacheDir, $this->loggedIn);
|
||||||
|
$pageCacheManager->invalidateCaches();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the link for a given URL, or False if it does not exist.
|
||||||
|
*
|
||||||
|
* @param string $url URL to search for
|
||||||
|
*
|
||||||
|
* @return mixed the existing link if it exists, else 'false'
|
||||||
|
*/
|
||||||
|
public function getLinkFromUrl($url)
|
||||||
|
{
|
||||||
|
if (isset($this->urls[$url])) {
|
||||||
|
return $this->links[$this->urls[$url]];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the shaare corresponding to a smallHash.
|
||||||
|
*
|
||||||
|
* @param string $request QUERY_STRING server parameter.
|
||||||
|
*
|
||||||
|
* @return array $filtered array containing permalink data.
|
||||||
|
*
|
||||||
|
* @throws BookmarkNotFoundException if the smallhash is malformed or doesn't match any link.
|
||||||
|
*/
|
||||||
|
public function filterHash($request)
|
||||||
|
{
|
||||||
|
$request = substr($request, 0, 6);
|
||||||
|
$linkFilter = new LegacyLinkFilter($this->links);
|
||||||
|
return $linkFilter->filter(LegacyLinkFilter::$FILTER_HASH, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of articles for a given day.
|
||||||
|
*
|
||||||
|
* @param string $request day to filter. Format: YYYYMMDD.
|
||||||
|
*
|
||||||
|
* @return array list of shaare found.
|
||||||
|
*/
|
||||||
|
public function filterDay($request)
|
||||||
|
{
|
||||||
|
$linkFilter = new LegacyLinkFilter($this->links);
|
||||||
|
return $linkFilter->filter(LegacyLinkFilter::$FILTER_DAY, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter bookmarks according to search parameters.
|
||||||
|
*
|
||||||
|
* @param array $filterRequest Search request content. Supported keys:
|
||||||
|
* - searchtags: list of tags
|
||||||
|
* - searchterm: term search
|
||||||
|
* @param bool $casesensitive Optional: Perform case sensitive filter
|
||||||
|
* @param string $visibility return only all/private/public bookmarks
|
||||||
|
* @param bool $untaggedonly return only untagged bookmarks
|
||||||
|
*
|
||||||
|
* @return array filtered bookmarks, all bookmarks if no suitable filter was provided.
|
||||||
|
*/
|
||||||
|
public function filterSearch(
|
||||||
|
$filterRequest = [],
|
||||||
|
$casesensitive = false,
|
||||||
|
$visibility = 'all',
|
||||||
|
$untaggedonly = false
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Filter link database according to parameters.
|
||||||
|
$searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
|
||||||
|
$searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
|
||||||
|
|
||||||
|
// Search tags + fullsearch - blank string parameter will return all bookmarks.
|
||||||
|
$type = LegacyLinkFilter::$FILTER_TAG | LegacyLinkFilter::$FILTER_TEXT; // == "vuotext"
|
||||||
|
$request = [$searchtags, $searchterm];
|
||||||
|
|
||||||
|
$linkFilter = new LegacyLinkFilter($this);
|
||||||
|
return $linkFilter->filter($type, $request, $casesensitive, $visibility, $untaggedonly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list tags appearing in the bookmarks with the given tags
|
||||||
|
*
|
||||||
|
* @param array $filteringTags tags selecting the bookmarks to consider
|
||||||
|
* @param string $visibility process only all/private/public bookmarks
|
||||||
|
*
|
||||||
|
* @return array tag => linksCount
|
||||||
|
*/
|
||||||
|
public function linksCountPerTag($filteringTags = [], $visibility = 'all')
|
||||||
|
{
|
||||||
|
$links = $this->filterSearch(['searchtags' => $filteringTags], false, $visibility);
|
||||||
|
$tags = [];
|
||||||
|
$caseMapping = [];
|
||||||
|
foreach ($links as $link) {
|
||||||
|
foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
|
||||||
|
if (empty($tag)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// The first case found will be displayed.
|
||||||
|
if (!isset($caseMapping[strtolower($tag)])) {
|
||||||
|
$caseMapping[strtolower($tag)] = $tag;
|
||||||
|
$tags[$caseMapping[strtolower($tag)]] = 0;
|
||||||
|
}
|
||||||
|
$tags[$caseMapping[strtolower($tag)]]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Formerly used arsort(), which doesn't define the sort behaviour for equal values.
|
||||||
|
* Also, this function doesn't produce the same result between PHP 5.6 and 7.
|
||||||
|
*
|
||||||
|
* So we now use array_multisort() to sort tags by DESC occurrences,
|
||||||
|
* then ASC alphabetically for equal values.
|
||||||
|
*
|
||||||
|
* @see https://github.com/shaarli/Shaarli/issues/1142
|
||||||
|
*/
|
||||||
|
$keys = array_keys($tags);
|
||||||
|
$tmpTags = array_combine($keys, $keys);
|
||||||
|
array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename or delete a tag across all bookmarks.
|
||||||
|
*
|
||||||
|
* @param string $from Tag to rename
|
||||||
|
* @param string $to New tag. If none is provided, the from tag will be deleted
|
||||||
|
*
|
||||||
|
* @return array|bool List of altered bookmarks or false on error
|
||||||
|
*/
|
||||||
|
public function renameTag($from, $to)
|
||||||
|
{
|
||||||
|
if (empty($from)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$delete = empty($to);
|
||||||
|
// True for case-sensitive tag search.
|
||||||
|
$linksToAlter = $this->filterSearch(['searchtags' => $from], true);
|
||||||
|
foreach ($linksToAlter as $key => &$value) {
|
||||||
|
$tags = preg_split('/\s+/', trim($value['tags']));
|
||||||
|
if (($pos = array_search($from, $tags)) !== false) {
|
||||||
|
if ($delete) {
|
||||||
|
unset($tags[$pos]); // Remove tag.
|
||||||
|
} else {
|
||||||
|
$tags[$pos] = trim($to);
|
||||||
|
}
|
||||||
|
$value['tags'] = trim(implode(' ', array_unique($tags)));
|
||||||
|
$this[$value['id']] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $linksToAlter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of days containing articles (oldest first)
|
||||||
|
* Output: An array containing days (in format YYYYMMDD).
|
||||||
|
*/
|
||||||
|
public function days()
|
||||||
|
{
|
||||||
|
$linkDays = [];
|
||||||
|
foreach ($this->links as $link) {
|
||||||
|
$linkDays[$link['created']->format('Ymd')] = 0;
|
||||||
|
}
|
||||||
|
$linkDays = array_keys($linkDays);
|
||||||
|
sort($linkDays);
|
||||||
|
|
||||||
|
return $linkDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder bookmarks by creation date (newest first).
|
||||||
|
*
|
||||||
|
* Also update the urls and ids mapping arrays.
|
||||||
|
*
|
||||||
|
* @param string $order ASC|DESC
|
||||||
|
*/
|
||||||
|
public function reorder($order = 'DESC')
|
||||||
|
{
|
||||||
|
$order = $order === 'ASC' ? -1 : 1;
|
||||||
|
// Reorder array by dates.
|
||||||
|
usort($this->links, function ($a, $b) use ($order) {
|
||||||
|
if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) {
|
||||||
|
return $a['sticky'] ? -1 : 1;
|
||||||
|
}
|
||||||
|
if ($a['created'] == $b['created']) {
|
||||||
|
return $a['id'] < $b['id'] ? 1 * $order : -1 * $order;
|
||||||
|
}
|
||||||
|
return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->urls = [];
|
||||||
|
$this->ids = [];
|
||||||
|
foreach ($this->links as $key => $link) {
|
||||||
|
$this->urls[$link['url']] = $key;
|
||||||
|
$this->ids[$link['id']] = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the next key for link creation.
|
||||||
|
* E.g. If the last ID is 597, the next will be 598.
|
||||||
|
*
|
||||||
|
* @return int next ID.
|
||||||
|
*/
|
||||||
|
public function getNextId()
|
||||||
|
{
|
||||||
|
if (!empty($this->ids)) {
|
||||||
|
return max(array_keys($this->ids)) + 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a link offset in bookmarks array from its unique ID.
|
||||||
|
*
|
||||||
|
* @param int $id Persistent ID of a link.
|
||||||
|
*
|
||||||
|
* @return int Real offset in local array, or null if doesn't exist.
|
||||||
|
*/
|
||||||
|
protected function getLinkOffset($id)
|
||||||
|
{
|
||||||
|
if (isset($this->ids[$id])) {
|
||||||
|
return $this->ids[$id];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>>>>>>> github/v0.13
|
||||||
|
|
|
@ -438,7 +438,7 @@ class LegacyUpdater
|
||||||
|
|
||||||
// Get latest branch major version digit
|
// Get latest branch major version digit
|
||||||
$latestVersion = ApplicationUtils::getLatestGitVersionCode(
|
$latestVersion = ApplicationUtils::getLatestGitVersionCode(
|
||||||
'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php',
|
'https://raw.githubusercontent.com/shaarli/Shaarli/release/shaarli_version.php',
|
||||||
5
|
5
|
||||||
);
|
);
|
||||||
if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) {
|
if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "shaarli/shaarli",
|
"name": "shaarli/shaarli",
|
||||||
"description": "The personal, minimalist, super-fast, database-free bookmarking service",
|
"description": "The personal, minimalist, super fast, database-free bookmarking service",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/shaarli/Shaarli",
|
"homepage": "https://github.com/shaarli/Shaarli",
|
||||||
|
@ -12,11 +12,11 @@
|
||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.1.29"
|
"php": "7.4.33"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1",
|
"php": ">=7.4",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-zlib": "*",
|
"ext-zlib": "*",
|
||||||
"arthurhoaro/web-thumbnailer": "^2.0",
|
"arthurhoaro/web-thumbnailer": "^2.0",
|
||||||
|
@ -31,8 +31,8 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"squizlabs/php_codesniffer": "3.*",
|
"squizlabs/php_codesniffer": "^3.0",
|
||||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
|
"phpunit/phpunit": "^9.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-curl": "Allows fetching web pages and thumbnails in a more robust way",
|
"ext-curl": "Allows fetching web pages and thumbnails in a more robust way",
|
||||||
|
|
1696
composer.lock
generated
49
doc/conf.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
project = 'shaarli'
|
||||||
|
author = 'shaarli community'
|
||||||
|
version = '0.13.0'
|
||||||
|
release = '0.13.0'
|
||||||
|
copyright = '2011-2023, the shaarli community'
|
||||||
|
language = 'en'
|
||||||
|
html_title = 'Shaarli documentation'
|
||||||
|
html_theme = 'furo'
|
||||||
|
html_show_sphinx = False
|
||||||
|
html_show_search_summary = True
|
||||||
|
html_copy_source = False
|
||||||
|
html_show_copyright = True
|
||||||
|
html_use_opensearch = 'https://shaarli.readthedocs.io/'
|
||||||
|
html_logo = 'md/images/doc-logo.svg'
|
||||||
|
html_favicon = 'md/images/icon.png'
|
||||||
|
extensions = ['myst_parser', 'sphinx_design']
|
||||||
|
source_suffix = ['.md']
|
||||||
|
templates_path = ['_templates']
|
||||||
|
exclude_patterns = []
|
||||||
|
suppress_warnings = ["myst.xref_missing"]
|
||||||
|
|
||||||
|
# myst-parser configuration (https://myst-parser.readthedocs.io/en/latest/configuration.html)
|
||||||
|
myst_enable_extensions = ['fieldlist']
|
||||||
|
myst_html_meta = {
|
||||||
|
"description lang=en": "The personal, minimalist, super fast, database-free bookmarking service",
|
||||||
|
"charset": "UTF-8"
|
||||||
|
}
|
||||||
|
|
||||||
|
# theme configuration (https://pradyunsg.me/furo/customisation/)
|
||||||
|
html_theme_options = {
|
||||||
|
"top_of_page_button": None,
|
||||||
|
# "announcement": "Example announcement!"
|
||||||
|
"source_repository": "https://github.com/shaarli/shaarli",
|
||||||
|
"source_branch": "master",
|
||||||
|
"footer_icons": [
|
||||||
|
{
|
||||||
|
"name": "GitHub",
|
||||||
|
"url": "https://github.com/shaarli/shaarli",
|
||||||
|
"html": """
|
||||||
|
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path>
|
||||||
|
</svg>
|
||||||
|
""",
|
||||||
|
"class": "",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{#
|
|
||||||
The entry point for the ReadTheDocs Theme.
|
|
||||||
|
|
||||||
Any theme customisations should override this file to redefine blocks defined in
|
|
||||||
the various templates. The custom theme should only need to define a main.html
|
|
||||||
which `{% extends "base.html" %}` and defines various blocks which will replace
|
|
||||||
the blocks defined in base.html and its included child templates.
|
|
||||||
#}
|
|
||||||
|
|
||||||
{%- block site_meta %}
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
|
|
||||||
{%- if config.extra_css|length and 'media.readthedocs.org' not in config.extra_css[0] %}
|
|
||||||
<meta name="robots" content="noindex, nofollow">
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{% if page and page.is_homepage %}<meta name="description" content="{{ config.site_description }}">{% endif %}
|
|
||||||
{% if config.site_author %}<meta name="author" content="{{ config.site_author }}">{% endif %}
|
|
||||||
{%- endblock %}
|
|
|
@ -1,21 +0,0 @@
|
||||||
## CSS
|
|
||||||
|
|
||||||
- Yahoo UI [CSS Reset](http://yuilibrary.com/yui/docs/cssreset/) - standardize cross-browser rendering
|
|
||||||
|
|
||||||
## Javascript
|
|
||||||
|
|
||||||
- [Awesomeplete](https://leaverou.github.io/awesomplete/) ([GitHub](https://github.com/LeaVerou/awesomplete)) - autocompletion in input forms
|
|
||||||
- [bLazy](http://dinbror.dk/blazy/) ([GitHub](https://github.com/dinbror/blazy)) - lazy loading for thumbnails
|
|
||||||
- [qr.js](http://neocotic.com/qr.js/) ([GitHub](https://github.com/neocotic/qr.js)) - QR code generation
|
|
||||||
|
|
||||||
## PHP
|
|
||||||
|
|
||||||
- [RainTPL](https://github.com/rainphp/raintpl) - HTML templating for PHP
|
|
||||||
|
|
||||||
### Composer
|
|
||||||
|
|
||||||
Library | Usage
|
|
||||||
---|---
|
|
||||||
[`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) | Import bookmarks from Netscape files
|
|
||||||
[`erusev/parsedown`](https://packagist.org/packages/erusev/parsedown) | Parse MarkDown syntax for the MarkDown plugin
|
|
||||||
[`slim/slim`](https://packagist.org/packages/slim/slim) | Handle routes and middleware for the REST API
|
|
11
doc/md/Backup-and-restore.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
## Backup and restore
|
||||||
|
|
||||||
|
All data and [configuration](Shaarli-configuration.md) is kept in the `data` directory. Backup this directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rsync -avzP my.server.com:/var/www/shaarli.mydomain.org/data ~/backups/shaarli-data-$(date +%Y-%m-%d_%H%M)
|
||||||
|
```
|
||||||
|
|
||||||
|
It is strongly recommended to do periodic, automatic backups to a separate machine. You can automate the command above using a cron job or full-featured backup solutions such as [rsnapshot](https://rsnapshot.org/)
|
||||||
|
|
||||||
|
To restore a backup, simply put back the `data/` directory in place, overwriting any existing files.
|
|
@ -1,37 +0,0 @@
|
||||||
## Plain text search
|
|
||||||
|
|
||||||
Use the `Search text` field to search in _any_ of the fields of all links (Title, URL, Description...)
|
|
||||||
|
|
||||||
**Exclude text/tags:** Use the `-` operator before a word or tag (example `-uninteresting`) to prevent entries containing (or tagged) `uninteresting` from showing up in the search results.
|
|
||||||
|
|
||||||
**Exact text search:** Use double-quotes (example `"exact search"`) to search for the exact expression.
|
|
||||||
|
|
||||||
Both exclude patterns and exact searches can be combined with normal searches (example `"exact search" term otherterm -notthis "very exact" stuff -notagain`)
|
|
||||||
|
|
||||||
## Tags search
|
|
||||||
|
|
||||||
Use the `Filter by tags` field to restrict displayed links to entries tagged with one or multiple tags (use space to separate tags).
|
|
||||||
|
|
||||||
**Hidden tags:** Tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in.
|
|
||||||
|
|
||||||
### Tag cloud
|
|
||||||
|
|
||||||
The `Tag cloud` page diplays a "cloud" view of all tags in your Shaarli.
|
|
||||||
|
|
||||||
* The most frequently used tags are displayed with a bigger font size.
|
|
||||||
* When sorting by `Most used` or `Alphabetical`, tags are displayed as a _list_, along with counters and edit/delete buttons for each tag.
|
|
||||||
* Clicking on any tag will display a list of all Shaares matching this tag.
|
|
||||||
* Clicking on the counter next to a tag `example`, will filter the tag cloud to only display tags found in Shaares tagged `example`. Repeat this any number of times to further filter the tag cloud. Click `List all links with those tags` to display Shaares matching your current tag filter.
|
|
||||||
|
|
||||||
## Filtering RSS feeds/Picture wall
|
|
||||||
|
|
||||||
RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds).
|
|
||||||
|
|
||||||
## Filter buttons
|
|
||||||
|
|
||||||
Filter buttons can be found at the top left of the link list. They allow you to apply different filters to the list:
|
|
||||||
|
|
||||||
* **Private links:** When this toggle button is enabled, only shaares set to `private` will be shown.
|
|
||||||
* **Untagged links:** When the this toggle button is enabled (top left of the link list), only shaares _without any tags_ will be shown in the link list.
|
|
||||||
|
|
||||||
Filter buttons are only available when logged in.
|
|
|
@ -1,76 +0,0 @@
|
||||||
_Unofficial but related work on Shaarli. If you maintain one of these,
|
|
||||||
please get in touch with us to help us find a way to adapt your work to our fork._
|
|
||||||
|
|
||||||
## Related software
|
|
||||||
|
|
||||||
|
|
||||||
### REST API clients
|
|
||||||
See [REST API](REST-API) for a list of official and community clients.
|
|
||||||
|
|
||||||
|
|
||||||
### Third party plugins
|
|
||||||
- [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.
|
|
||||||
- [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.
|
|
||||||
- [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.
|
|
||||||
- [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.
|
|
||||||
- [twemoji](https://github.com/NerosTie/twemoji) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli (Twemoji version)
|
|
||||||
- [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support
|
|
||||||
- [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.
|
|
||||||
- [markdown-toolbar](https://github.com/immanuelfodor/shaarli-markdown-toolbar) by [@immanuelfodor](https://github.com/immanuelfodor) - Easily insert markdown syntax into the Description field when editing a link.
|
|
||||||
- [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related links based on the number of identical tags.
|
|
||||||
- [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.
|
|
||||||
- [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli
|
|
||||||
- [shaarli2mastodon](https://github.com/kalvn/shaarli2mastodon) by [@kalvn](https://github.com/kalvn) - This Shaarli plugin allows you to automatically publish links you post on your Mastodon timeline.
|
|
||||||
- [shaarli-descriptor](https://github.com/immanuelfodor/shaarli-descriptor) by [@immanuelfodor](https://github.com/immanuelfodor) - Customize the default height/number of rows of the Description field when editing a link.
|
|
||||||
- [urlextern](https://github.com/trailjeep/shaarli-urlextern) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to open external links in a new tab/window.
|
|
||||||
- [favicons](https://github.com/trailjeep/shaarli-favicons) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to add favicon/filetype icons to links.
|
|
||||||
|
|
||||||
### Third-party themes
|
|
||||||
See [Theming](Theming) for a list of community-contributed themes, and an installation guide.
|
|
||||||
|
|
||||||
|
|
||||||
### Integration with other platforms
|
|
||||||
- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [Tiny-Tiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli
|
|
||||||
- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar
|
|
||||||
- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle
|
|
||||||
- [Shaarli app for Cloudron](https://git.cloudron.io/cloudron/shaarli-app) - Effortlessly run Shaarli with the help of [Cloudron](https://cloudron.io/) [![Install](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=com.github.shaarli)
|
|
||||||
- [Shaarli_ynh](https://github.com/YunoHost-Apps/shaarli_ynh) - Shaarli is available as a [Yunohost](https://yunohost.org) app [![Install Shaarli with YunoHost](https://install-app.yunohost.org/install-with-yunohost.png)](https://install-app.yunohost.org/?app=shaarli)
|
|
||||||
|
|
||||||
### Mobile Apps
|
|
||||||
- [ShaarliOS](https://github.com/mro/ShaarliOS) - Apple iOS share extension.
|
|
||||||
- [Shaarli for Android](http://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider
|
|
||||||
- [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - Android application to simply add links directly into your Shaarli
|
|
||||||
- [Stakali for Android](https://stakali.toneiv.eu) - Stakali is a personal bookmark manager which synchronizes with Shaarli
|
|
||||||
|
|
||||||
### Browser addons
|
|
||||||
- [Shaarli Firefox Extension](https://github.com/ikipatang/shaarli-web-extension) - toolbar button to share your current tab with Shaarli.
|
|
||||||
- [Shaarli Chrome Extension](https://github.com/octplane/Shiny-Shaarli) - toolbar button to share your current tab with Shaarli.
|
|
||||||
|
|
||||||
### Server apps
|
|
||||||
- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content
|
|
||||||
- [shaarli-river](https://github.com/mknexen/shaarli-river) - An aggregator for shaarlis with many features
|
|
||||||
- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among French shaarliers: [shaarli.fr](http://shaarli.fr/))
|
|
||||||
- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis
|
|
||||||
- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli
|
|
||||||
- [Self dead link](https://framagit.org/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://framagit.org/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).
|
|
||||||
- [Bookmark Archiver](https://github.com/pirate/bookmark-archiver) - Save an archived copy of all websites starred using browser bookmarks/Shaarli/Delicious/Instapaper/Unmark.it/Pocket/Pinboard. Outputs browseable html.
|
|
||||||
|
|
||||||
## Alternatives to Shaarli
|
|
||||||
See [awesome-selfhosted: bookmarks & link sharing](https://github.com/Kickball/awesome-selfhosted/#bookmarks--link-sharing).
|
|
||||||
|
|
||||||
## Community
|
|
||||||
- [Liens en vrac de sebsauvage](http://sebsauvage.net/links/) - the original Shaarli
|
|
||||||
- [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html)
|
|
||||||
- [A list of working Shaarli aggregators](https://raw.githubusercontent.com/Oros42/find_shaarlis/master/annuaires.json)
|
|
||||||
- [A list of some known Shaarlis](https://github.com/Oros42/shaarlis_list)
|
|
||||||
- [Adieu Delicious, Diigo et StumbleUpon. Salut Shaarli ! - sebsauvage.net](http://sebsauvage.net/rhaa/index.php?2011/09/16/09/29/58-adieu-delicious-diigo-et-stumbleupon-salut-shaarli-) (fr) _16/09/2011 - the original post about Shaarli_
|
|
||||||
- [Original ideas/fixme/TODO page](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:ideas)
|
|
||||||
- [Original discussion page](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:discussion) (fr)
|
|
||||||
- [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
|
|
||||||
- [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni)
|
|
||||||
|
|
||||||
### Articles and social media discussions
|
|
||||||
- 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176
|
|
||||||
- 2015-08-15 - Reddit - [Question about migrating from WordPress to Shaarli.](https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/)
|
|
||||||
- 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366
|
|
||||||
- 2015-05-12 - Reddit - [shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)](https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/)
|
|
117
doc/md/Community-and-related-software.md
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# Community & related software
|
||||||
|
|
||||||
|
_Unofficial but related work on Shaarli. If you maintain one of these,
|
||||||
|
please get in touch with us to help us find a way to adapt your work to our fork._
|
||||||
|
|
||||||
|
|
||||||
|
## Related software
|
||||||
|
|
||||||
|
### REST API clients
|
||||||
|
See [REST API](REST-API.md) for a list of official and community clients.
|
||||||
|
|
||||||
|
|
||||||
|
### Third party plugins
|
||||||
|
|
||||||
|
- [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a Shaare to avoid any loss in case of crash or unexpected shutdown.
|
||||||
|
- [clickat](https://forge.tourmentine.com/n/shaarli-plugin-clickat) by [@n](https://forge.tourmentine.com/n): Makes Twitter and Fediverse addresses clickable.
|
||||||
|
- [code-coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.
|
||||||
|
- [custom-css](https://github.com/immanuelfodor/shaarli-custom-css) by [@immanuelfodor](https://github.com/immanuelfodor) - Customize the look and feel of the UI with custom CSS rules
|
||||||
|
- [disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.
|
||||||
|
- [emojione](https://github.com/immanuelfodor/emojione) by [@immanuelfodor](https://github.com/immanuelfodor) - Resurrected fork of the original emojione project
|
||||||
|
- [favicons](https://github.com/trailjeep/shaarli-favicons) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to add favicon/filetype icons to Shaares.
|
||||||
|
- [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](https://github.com/ericjuden): Adds Google Analytics tracking support
|
||||||
|
- [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.
|
||||||
|
- [markdown-toolbar](https://github.com/immanuelfodor/shaarli-markdown-toolbar) by [@immanuelfodor](https://github.com/immanuelfodor) - Easily insert markdown syntax into the Description field when editing a Shaare.
|
||||||
|
- [related](https://github.com/ilesinge/shaarli-related) by [@ilesinge](https://github.com/ilesinge) - Show related Shaares based on the number of identical tags.
|
||||||
|
- [shaargem](https://forge.tourmentine.com/n/shaarli-plugin-shaargem) by [@n](https://forge.tourmentine.com/n): Allow to shaare gemini capsules.
|
||||||
|
- [shaarli-descriptor](https://github.com/immanuelfodor/shaarli-descriptor) by [@immanuelfodor](https://github.com/immanuelfodor) - Customize the default height/number of rows of the Description field when editing a Shaare.
|
||||||
|
- [shaarli2mastodon](https://github.com/kalvn/shaarli2mastodon) by [@kalvn](https://github.com/kalvn) - This Shaarli plugin allows you to automatically publish links you post on your Mastodon timeline.
|
||||||
|
- [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your Shaares from Shaarli
|
||||||
|
- [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.
|
||||||
|
- [urlextern](https://github.com/trailjeep/shaarli-urlextern) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to open external links in a new tab/window.
|
||||||
|
- [webhooks](https://gitlab.com/flow.gunso/shaarli-webhooks) by [@flow.gunso](https://gitlab.com/flow.gunso) - Shaarli plugin that enables user-defined callback URL, i.e. webhooks, for specific Shaarli events (link saving, deletion...)
|
||||||
|
|
||||||
|
|
||||||
|
### Themes
|
||||||
|
|
||||||
|
- [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone
|
||||||
|
- [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site
|
||||||
|
- [RolandTi/shaarli-stack](https://github.com/RolandTi/shaarli-stack) - A miminalist template/theme for Shaarli
|
||||||
|
- [xfnw/shaarli-default-dark](https://github.com/xfnw/shaarli-default-dark) - The default theme but nice and dark for your eyeballs
|
||||||
|
- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for Shaarli (custom CSS)
|
||||||
|
|
||||||
|
|
||||||
|
### Integration with other platforms
|
||||||
|
|
||||||
|
- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [Tiny-Tiny RSS](https://tt-rss.org/) plugin that adds support for sharing articles with Shaarli
|
||||||
|
- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli Shaares on the sidebar
|
||||||
|
- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle
|
||||||
|
- [Shaarli app for Cloudron](https://www.cloudron.io/button.html?app=com.github.shaarli) - [![](https://img.shields.io/badge/Cloudron-03A9F4?logo=Buffer)](https://www.cloudron.io/button.html?app=com.github.shaarli) Effortlessly run Shaarli with the help of [Cloudron](https://www.cloudron.io/)
|
||||||
|
- [Shaarli_ynh](https://github.com/YunoHost-Apps/shaarli_ynh) - [![](https://img.shields.io/badge/YunoHost-black?logo=Buffer)](https://install-app.yunohost.org/?app=shaarli) Shaarli is available as a [Yunohost](https://yunohost.org) app
|
||||||
|
- [shaarli_poster](https://github.com/getpelican/pelican-plugins/tree/master/shaarli_poster) - [pelican](https://getpelican.com) static blog generator plugin to auto-post articles on a Shaarli instance
|
||||||
|
- [shaarli .deb package](https://packages.debian.org/search?keywords=shaarli) - for [Debian](https://www.debian.org/) and [Freedombox](https://www.freedombox.org/)
|
||||||
|
|
||||||
|
|
||||||
|
### Mobile apps
|
||||||
|
|
||||||
|
- [ShaarliOS](https://github.com/mro/ShaarliOS) - [![](https://img.shields.io/badge/App%20Store-black?logo=apple)](https://apps.apple.com/app/shaarlios/id1027441388) Apple iOS share extension.
|
||||||
|
- [Shaarlier for Android](https://github.com/dimtion/Shaarlier) - [![](https://img.shields.io/badge/Play%20Store-3EB7C0?logo=Google%20Play)](https://play.google.com/store/apps/details?id=com.dimtion.shaarlier) Android application to simply add Shaares directly into your Shaarli via Sharing. Free software.
|
||||||
|
- [Stakali for Android](https://stakali.toneiv.eu) - [![](https://img.shields.io/badge/Play%20Store-3EB7C0?logo=Google%20Play)](https://play.google.com/store/apps/details?id=eu.toneiv.stakali) Stakali is a personal bookmark manager which synchronizes with Shaarli. Support offline work without instance, sync, browsing within app. Free use of up to 20 results.
|
||||||
|
- [Shaarli for Android](https://sebsauvage.net/links/?ZAyDzg) - Android application that adds Shaarli as a sharing provider.
|
||||||
|
|
||||||
|
|
||||||
|
### Desktop apps
|
||||||
|
|
||||||
|
- [Ulauncher Extension](https://github.com/sebw/ulauncher-shaarli) - Ulauncher is an an application launcher for Linux, this extension allows research in your Shaarli
|
||||||
|
|
||||||
|
|
||||||
|
### Browser extensions
|
||||||
|
|
||||||
|
- [Shaarli Firefox Extension](https://github.com/ikipatang/shaarli-web-extension) - [![](https://img.shields.io/badge/Firefox%20Add--ons-414141?logo=Firefox)](https://addons.mozilla.org/en-US/firefox/addon/shaarli/) toolbar button to share your current tab with Shaarli.
|
||||||
|
- [Add to Shaarli](https://github.com/burgyl/AddToShaarli) - [![](https://img.shields.io/badge/Chrome%20Web%20Store-white?logo=Google%20Chrome)](https://chrome.google.com/webstore/detail/add-to-shaarli/jhfblapoehcfajokolimghdfmeeakbee) lets you add the current tab or a note to your Shaarli instance.
|
||||||
|
- [WebLinks Ext](https://chrome.google.com/webstore/detail/weblinks-ext/pnejcofgljlklmmjkocipnanfdbaclke) - [![](https://img.shields.io/badge/Chrome%20Web%20Store-white?logo=Google%20Chrome)](https://chrome.google.com/webstore/detail/weblinks-ext/pnejcofgljlklmmjkocipnanfdbaclke) Save, edit and use your Shaarli. Support work offline, sync, trash, etc. Free software.
|
||||||
|
- [Shaarli Chrome Extension](https://github.com/octplane/Shiny-Shaarli) - toolbar button to share your current tab with Shaarli. (Removed from Chrome web store).
|
||||||
|
|
||||||
|
|
||||||
|
### Server apps
|
||||||
|
|
||||||
|
- [Shaarli Archiver](https://github.com/sebw/shaarli-archiver) - Shaarli and SingleFile to archive your bookmarks
|
||||||
|
- [shaarchiver](https://github.com/nodiscc/shaarchiver) - Archive your Shaarli bookmarks and their content
|
||||||
|
- [shaarli-river](https://github.com/mknexen/shaarli-river) - An aggregator for shaarlis with many features
|
||||||
|
- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features
|
||||||
|
- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis
|
||||||
|
- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli
|
||||||
|
- [Self dead link](https://framagit.org/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://framagit.org/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).
|
||||||
|
- [Bookmark Archiver](https://github.com/ArchiveBox/ArchiveBox) - Save an archived copy of all websites starred using browser bookmarks/Shaarli/Delicious/Instapaper/Unmark.it/Pocket/Pinboard. Outputs browseable html.
|
||||||
|
|
||||||
|
|
||||||
|
## Alternatives to Shaarli
|
||||||
|
|
||||||
|
See [awesome-selfhosted: bookmarks & link sharing](https://awesome-selfhosted.net/tags/bookmarks-and-link-sharing.html).
|
||||||
|
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
- [Liens en vrac de sebsauvage](https://sebsauvage.net/links/) - the original Shaarli
|
||||||
|
- [A large list of Shaarlis](http://porneia.free.fr/pub/links/ou-est-shaarli.html)
|
||||||
|
- [A list of working Shaarli aggregators](https://raw.githubusercontent.com/Oros42/find_shaarlis/master/annuaires.json)
|
||||||
|
- [A list of some known Shaarlis](https://github.com/Oros42/shaarlis_list)
|
||||||
|
- [Adieu Delicious, Diigo et StumbleUpon. Salut Shaarli ! - sebsauvage.net](https://sebsauvage.net/rhaa/index.php?2011/09/16/09/29/58-adieu-delicious-diigo-et-stumbleupon-salut-shaarli-) (fr) _16/09/2011 - the original post about Shaarli_
|
||||||
|
- [Original ideas/fixme/TODO page](https://sebsauvage.net/wiki/doku.php?id=php:shaarli:ideas)
|
||||||
|
- [Original discussion page](https://sebsauvage.net/wiki/doku.php?id=php:shaarli:discussion) (fr)
|
||||||
|
- [Original revisions history](https://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
|
||||||
|
|
||||||
|
|
||||||
|
### Articles and social media discussions
|
||||||
|
- 2020-04-05 - Hacker News - [Self-hosted instance of Shaarli - it is simple, fast and reliable](https://news.ycombinator.com/item?id=22780219)
|
||||||
|
- 2016-10-10 - Framasoft - [MyFrama : vos favoris partout, avec vous, rien qu’à vous !](https://framablog.org/2016/10/10/myframa-vos-favoris-et-framasofteries-partout-avec-vous-rien-qua-vous/)
|
||||||
|
- 2016-09-22 - Hacker News - [Shaarli – Personal, minimalist, database-free, bookmarking service (github.com)](https://news.ycombinator.com/item?id=12552176)
|
||||||
|
- 2015-08-15 - Reddit - [Question about migrating from WordPress to Shaarli.](https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/)
|
||||||
|
- 2015-06-22 - Hacker News - [Shaarli: Self-hosted del.icio.us alternative (sebsauvage.net)](https://news.ycombinator.com/item?id=9755366)
|
||||||
|
- 2015-05-12 - Reddit - [shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)](https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/)
|
||||||
|
- 2014-10-15 - OpenSource.com - [Five open source alternatives to popular web apps](https://opensource.com/life/14/10/five-open-source-alternatives-popular-web-apps)
|
||||||
|
|
||||||
|
It also appears in the following recommendation lists:
|
||||||
|
- [AlternativeTo](https://alternativeto.net/software/shaarli/)
|
||||||
|
- [FramaLibre](https://framalibre.org/content/shaarli)
|
||||||
|
- [Awesome-Selfhosted - Bookmarks and Link Sharing](https://awesome-selfhosted.net/tags/bookmarks-and-link-sharing.html)
|
|
@ -1,29 +0,0 @@
|
||||||
## Local development
|
|
||||||
A [`Makefile`](https://github.com/shaarli/Shaarli/blob/master/Makefile) is available to perform project-related operations:
|
|
||||||
|
|
||||||
- Documentation - generate a local HTML copy of the GitHub wiki
|
|
||||||
- [Static analysis](Static-analysis) - check that the code is compliant to PHP conventions
|
|
||||||
- [Unit tests](Unit-tests) - ensure there are no regressions introduced by new commits
|
|
||||||
|
|
||||||
## Automatic builds
|
|
||||||
[Travis CI](http://docs.travis-ci.com/) is a Continuous Integration build server, that runs a build:
|
|
||||||
|
|
||||||
- each time a commit is merged to the mainline (`master` branch)
|
|
||||||
- each time a Pull Request is submitted or updated
|
|
||||||
|
|
||||||
A build is composed of several jobs: one for each supported PHP version (see [Server requirements](Server requirements)).
|
|
||||||
|
|
||||||
Each build job:
|
|
||||||
|
|
||||||
- updates Composer
|
|
||||||
- installs 3rd-party test dependencies with Composer
|
|
||||||
- runs [Unit tests](Unit-tests)
|
|
||||||
- runs ESLint check
|
|
||||||
|
|
||||||
After all jobs have finished, Travis returns the results to GitHub:
|
|
||||||
|
|
||||||
- a status icon represents the result for the `master` branch: [![](https://api.travis-ci.org/shaarli/Shaarli.svg)](https://travis-ci.org/shaarli/Shaarli)
|
|
||||||
- Pull Requests are updated with the Travis result
|
|
||||||
- Green: all tests have passed
|
|
||||||
- Red: some tests failed
|
|
||||||
- Orange: tests are pending
|
|
|
@ -1,13 +0,0 @@
|
||||||
## Development guidelines
|
|
||||||
|
|
||||||
Please have a look at the following pages:
|
|
||||||
|
|
||||||
- [Contributing to Shaarli](https://github.com/shaarli/Shaarli/tree/master/CONTRIBUTING.md)
|
|
||||||
- [Static analysis](Static-analysis) - patches should try to stick to the
|
|
||||||
[PHP Standard Recommendations](http://www.php-fig.org/psr/) (PSR), especially:
|
|
||||||
- [PSR-1](http://www.php-fig.org/psr/psr-1/) - Basic Coding Standard
|
|
||||||
- [PSR-2](http://www.php-fig.org/psr/psr-2/) - Coding Style Guide
|
|
||||||
- [Unit tests](Unit-tests)
|
|
||||||
- Javascript linting - Shaarli uses [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript).
|
|
||||||
Run `make eslint` to check JS style.
|
|
||||||
- [GnuPG signature](GnuPG-signature) for tags/releases
|
|
|
@ -1,54 +0,0 @@
|
||||||
## Directory structure
|
|
||||||
|
|
||||||
Here is the directory structure of Shaarli and the purpose of the different files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
index.php # Main program
|
|
||||||
application/ # Shaarli classes
|
|
||||||
├── LinkDB.php
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
└── Utils.php
|
|
||||||
tests/ # Shaarli unitary & functional tests
|
|
||||||
├── LinkDBTest.php
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
├── utils # utilities to ease testing
|
|
||||||
│ └── ReferenceLinkDB.php
|
|
||||||
└── UtilsTest.php
|
|
||||||
assets/
|
|
||||||
├── common/ # Assets shared by multiple themes
|
|
||||||
├── ...
|
|
||||||
├── default/ # Assets for the default template, before compilation
|
|
||||||
├── fonts/ # Font files
|
|
||||||
├── img/ # Images used by the default theme
|
|
||||||
├── js/ # JavaScript files in ES6 syntax
|
|
||||||
├── scss/ # SASS files
|
|
||||||
└── vintage/ # Assets for the vintage template, before compilation
|
|
||||||
└── ...
|
|
||||||
COPYING # Shaarli license
|
|
||||||
inc/ # static assets and 3rd party libraries
|
|
||||||
└── rain.tpl.class.php # RainTPL templating library
|
|
||||||
images/ # Images and icons used in Shaarli
|
|
||||||
data/ # data storage: bookmark database, configuration, logs, banlist...
|
|
||||||
├── config.json.php # Shaarli configuration (login, password, timezone, title...)
|
|
||||||
├── datastore.php # Your link database (compressed).
|
|
||||||
├── ipban.php # IP address ban system data
|
|
||||||
├── lastupdatecheck.txt # Update check timestamp file
|
|
||||||
└── log.txt # login/IPban log.
|
|
||||||
tpl/ # RainTPL templates for Shaarli. They are used to build the pages.
|
|
||||||
├── default/ # Default Shaarli theme
|
|
||||||
├── fonts/ # Font files
|
|
||||||
├── img/ # Images
|
|
||||||
├── js/ # JavaScript files compiled by Babel and compatible with all browsers
|
|
||||||
├── css/ # CSS files compiled with SASS
|
|
||||||
└── vintage/ # Legacy Shaarli theme
|
|
||||||
└── ...
|
|
||||||
cache/ # thumbnails cache
|
|
||||||
# This directory is automatically created. You can erase it anytime you want.
|
|
||||||
tmp/ # Temporary directory for compiled RainTPL templates.
|
|
||||||
# This directory is automatically created. You can erase it anytime you want.
|
|
||||||
vendor/ # Third-party dependencies. This directory is created by Composer
|
|
||||||
```
|
|
227
doc/md/Docker.md
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
|
||||||
|
[Docker](https://docs.docker.com/get-started/overview/) is an open platform for developing, shipping, and running applications
|
||||||
|
|
||||||
|
## Install Docker
|
||||||
|
|
||||||
|
Install [Docker](https://docs.docker.com/engine/install/), by following the instructions relevant to your OS / distribution, and start the service. For example on [Debian](https://docs.docker.com/engine/install/debian/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# update your package lists
|
||||||
|
sudo apt update
|
||||||
|
# remove old versions
|
||||||
|
sudo apt-get remove docker docker-engine docker.io containerd runc
|
||||||
|
# install requirements
|
||||||
|
sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
|
||||||
|
# add docker's GPG signing key
|
||||||
|
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
|
||||||
|
# add the repository
|
||||||
|
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
|
||||||
|
# install docker engine
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install docker-ce docker-ce-cli containerd.io
|
||||||
|
# Start and enable Docker service
|
||||||
|
sudo systemctl enable docker && sudo systemctl start docker
|
||||||
|
# verify that Docker is properly configured
|
||||||
|
sudo docker run hello-world
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to run Docker commands as a non-root user, you must add the `docker` group to this user:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add docker group as secondary group
|
||||||
|
sudo usermod -aG docker your-user
|
||||||
|
# Reboot or logout
|
||||||
|
# Then verify that Docker is properly configured, as "your-user"
|
||||||
|
docker run hello-world
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get and run a Shaarli image
|
||||||
|
|
||||||
|
Shaarli images are available on [GitHub Container Registry](https://github.com/shaarli/Shaarli/pkgs/container/shaarli) `ghcr.io/shaarli/shaarli`:
|
||||||
|
|
||||||
|
- `latest`: master (development) branch
|
||||||
|
- `vX.Y.Z`: shaarli [releases](https://github.com/shaarli/Shaarli/releases)
|
||||||
|
- `release`: always points to the last release
|
||||||
|
- `stable` and `master`: **deprecated**. These tags are no longer maintained and may be removed without notice
|
||||||
|
|
||||||
|
These images are built automatically on Github Actions and rely on:
|
||||||
|
|
||||||
|
- [Alpine Linux](https://www.alpinelinux.org/)
|
||||||
|
- [PHP7-FPM](https://php-fpm.org/)
|
||||||
|
- [Nginx](https://nginx.org/)
|
||||||
|
|
||||||
|
Additional Dockerfiles are provided for the `arm32v7` platform, relying on [Linuxserver.io Alpine armhf images](https://hub.docker.com/r/lsiobase/alpine.armhf/). These images must be built using [`docker build`](https://docs.docker.com/engine/reference/commandline/build/) on an `arm32v7` machine or using an emulator such as [qemu](https://blog.balena.io/building-arm-containers-on-any-x86-machine-even-dockerhub/).
|
||||||
|
|
||||||
|
Here is an example of how to run Shaarli latest image using Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# download the 'latest' image from GitHub Container Registry
|
||||||
|
docker pull ghcr.io/shaarli/shaarli
|
||||||
|
|
||||||
|
# create persistent data volumes/directories on the host
|
||||||
|
docker volume create shaarli-data
|
||||||
|
docker volume create shaarli-cache
|
||||||
|
|
||||||
|
# create a new container using the Shaarli image
|
||||||
|
# --detach: run the container in background
|
||||||
|
# --name: name of the created container/instance
|
||||||
|
# --publish: map the host's :8000 port to the container's :80 port
|
||||||
|
# --rm: automatically remove the container when it exits
|
||||||
|
# --volume: mount persistent volumes in the container ($volume_name:$volume_mountpoint)
|
||||||
|
docker run --detach \
|
||||||
|
--name myshaarli \
|
||||||
|
--publish 8000:80 \
|
||||||
|
--rm \
|
||||||
|
--volume shaarli-data:/var/www/shaarli/data \
|
||||||
|
--volume shaarli-cache:/var/www/shaarli/cache \
|
||||||
|
ghcr.io/shaarli/shaarli:latest
|
||||||
|
|
||||||
|
# verify that the container is running
|
||||||
|
docker ps | grep myshaarli
|
||||||
|
|
||||||
|
# to completely remove the container
|
||||||
|
docker stop myshaarli # stop the running container
|
||||||
|
docker ps | grep myshaarli # verify the container is no longer running
|
||||||
|
docker ps -a | grep myshaarli # verify the container is stopped
|
||||||
|
docker rm myshaarli # destroy the container
|
||||||
|
docker ps -a | grep myshaarli # verify th container has been destroyed
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After running `docker run` command, your Shaarli instance should be available on the host machine at [localhost:8000](http://localhost:8000). In order to access your instance through a reverse proxy, we recommend using our [Docker Compose](#docker-compose) build.
|
||||||
|
|
||||||
|
## Docker Compose
|
||||||
|
|
||||||
|
A [Compose file](https://docs.docker.com/compose/compose-file/) is a common format for defining and running multi-container Docker applications.
|
||||||
|
|
||||||
|
A `docker-compose.yml` file can be used to run a persistent/autostarted shaarli service using [Docker Compose](https://docs.docker.com/compose/) or in a [Docker stack](https://docs.docker.com/engine/reference/commandline/stack_deploy/).
|
||||||
|
|
||||||
|
Shaarli provides configuration file for Docker Compose, that will setup a Shaarli instance, a [Træfik](https://traefik.io/traefik/) instance (reverse proxy) with [Let's Encrypt](https://letsencrypt.org/) certificates, a Docker network, and volumes for Shaarli data and Træfik TLS configuration and certificates.
|
||||||
|
|
||||||
|
Download docker-compose from the [release page](https://docs.docker.com/compose/install/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
$ sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
```
|
||||||
|
|
||||||
|
To run Shaarli container and its reverse proxy, you can execute the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# create a new directory to store the configuration:
|
||||||
|
$ mkdir shaarli && cd shaarli
|
||||||
|
# Download the latest version of Shaarli's docker-compose.yml
|
||||||
|
$ curl -L https://raw.githubusercontent.com/shaarli/Shaarli/latest/docker-compose.yml -o docker-compose.yml
|
||||||
|
# Create the .env file and fill in your VPS and domain information
|
||||||
|
# (replace <shaarli.mydomain.org>, <admin@mydomain.org> and <latest> with your actual information)
|
||||||
|
$ echo 'SHAARLI_VIRTUAL_HOST=shaarli.mydomain.org' > .env
|
||||||
|
$ echo 'SHAARLI_LETSENCRYPT_EMAIL=admin@mydomain.org' >> .env
|
||||||
|
# Available Docker tags can be found at https://github.com/shaarli/Shaarli/pkgs/container/shaarli/versions?filters%5Bversion_type%5D=tagged
|
||||||
|
$ echo 'SHAARLI_DOCKER_TAG=latest' >> .env
|
||||||
|
# Pull the Docker images
|
||||||
|
$ docker-compose pull
|
||||||
|
# Run!
|
||||||
|
$ docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
After a few seconds, you should be able to access your Shaarli instance at [https://shaarli.mydomain.org](https://shaarli.mydomain.org) (replace your own domain name).
|
||||||
|
|
||||||
|
## Running dockerized Shaarli as a systemd service
|
||||||
|
|
||||||
|
It is possible to start a dockerized Shaarli instance as a systemd service (systemd is the service management tool on several distributions). After installing Docker, use the following steps to run your shaarli container Shaarli to run on system start.
|
||||||
|
|
||||||
|
As root, create `/etc/systemd/system/docker.shaarli.service`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Shaarli Bookmark Manager Container
|
||||||
|
After=docker.service
|
||||||
|
Requires=docker.service
|
||||||
|
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
# Put any environment you want in an included file, like $host- or $domainname in this example
|
||||||
|
EnvironmentFile=/etc/sysconfig/box-environment
|
||||||
|
|
||||||
|
# It's just an example..
|
||||||
|
ExecStart=/usr/bin/docker run \
|
||||||
|
-p 28010:80 \
|
||||||
|
--name ${hostname}-shaarli \
|
||||||
|
--hostname shaarli.${domainname} \
|
||||||
|
-v /srv/docker-volumes-local/shaarli-data:/var/www/shaarli/data:rw \
|
||||||
|
-v /etc/localtime:/etc/localtime:ro \
|
||||||
|
ghcr.io/shaarli/shaarli:latest
|
||||||
|
|
||||||
|
ExecStop=/usr/bin/docker rm -f ${hostname}-shaarli
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# reload systemd services definitions
|
||||||
|
systemctl daemon-reload
|
||||||
|
# start the servie and enable it a boot time
|
||||||
|
systemctl enable docker.shaarli.service --now
|
||||||
|
# verify that the service is running
|
||||||
|
systemctl status docker.*
|
||||||
|
# inspect system log if needed
|
||||||
|
journalctl -f
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Docker cheatsheet
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# pull/update an image
|
||||||
|
$ docker pull ghcr.io/shaarli/shaarli:release
|
||||||
|
# run a container from an image
|
||||||
|
$ docker run ghcr.io/shaarli/shaarli:latest
|
||||||
|
# list available images
|
||||||
|
$ docker images ls
|
||||||
|
# list running containers
|
||||||
|
$ docker ps
|
||||||
|
# list running AND stopped containers
|
||||||
|
$ docker ps -a
|
||||||
|
# run a command in a running container
|
||||||
|
$ docker exec -ti <container-name-or-first-letters-of-id> bash
|
||||||
|
# follow logs of a running container
|
||||||
|
$ docker logs -f <container-name-or-first-letters-of-id>
|
||||||
|
# delete unused images to free up disk space
|
||||||
|
$ docker system prune --images
|
||||||
|
# delete unused volumes to free up disk space (CAUTION all data in unused volumes will be lost)
|
||||||
|
$ docker system prune --volumes
|
||||||
|
# delete unused containers
|
||||||
|
$ docker system prune
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Docker: using volumes](https://docs.docker.com/storage/volumes/)
|
||||||
|
- [Dockerfile best practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)
|
||||||
|
- [Dockerfile reference](https://docs.docker.com/engine/reference/builder/)
|
||||||
|
- [GitHub Container Registry](https://github.com/features/packages)
|
||||||
|
- [GithHub Packages documentation](https://docs.github.com/en/packages)
|
||||||
|
- [DockerHub: Teams and organizations](https://docs.docker.com/docker-hub/orgs/), [End of Docker free teams](https://www.docker.com/blog/we-apologize-we-did-a-terrible-job-announcing-the-end-of-docker-free-teams/)
|
||||||
|
- [Get Docker CE for Debian](https://docs.docker.com/engine/install/debian/)
|
||||||
|
- [Install Docker Compose](https://docs.docker.com/compose/install/)
|
||||||
|
- [Service management: Nginx in the foreground](https://nginx.org/en/docs/ngx_core_module.html#daemon)
|
||||||
|
- [Service management: Run multiple services in a container](https://docs.docker.com/config/containers/multi-service_container/)
|
||||||
|
- [Volumes](https://docs.docker.com/storage/volumes/)
|
||||||
|
- [Where are Docker images stored?](https://blog.thoward37.me/articles/where-are-docker-images-stored/)
|
||||||
|
- [docker create](https://docs.docker.com/engine/reference/commandline/create/)
|
||||||
|
- [Docker Documentation](https://docs.docker.com/)
|
||||||
|
- [docker exec](https://docs.docker.com/engine/reference/commandline/exec/)
|
||||||
|
- [docker images](https://docs.docker.com/engine/reference/commandline/images/)
|
||||||
|
- [docker logs](https://docs.docker.com/engine/reference/commandline/logs/)
|
||||||
|
- [Docker Overview](https://docs.docker.com/get-started/overview/)
|
||||||
|
- [docker ps](https://docs.docker.com/engine/reference/commandline/ps/)
|
||||||
|
- [docker pull](https://docs.docker.com/engine/reference/commandline/pull/)
|
||||||
|
- [docker run](https://docs.docker.com/engine/reference/commandline/run/)
|
||||||
|
- Træfik: [Documentation](https://doc.traefik.io/traefik/), [Docker image](https://hub.docker.com/_/traefik/)
|
|
@ -1,124 +0,0 @@
|
||||||
To install Shaarli, simply place the files in a directory under your webserver's
|
|
||||||
Document Root (or directly at the document root).
|
|
||||||
|
|
||||||
Also, please make sure your server is properly [configured](Server-configuration).
|
|
||||||
|
|
||||||
Multiple releases branches are available:
|
|
||||||
|
|
||||||
- latest (last release)
|
|
||||||
- stable (previous major release)
|
|
||||||
- master (development)
|
|
||||||
|
|
||||||
Using one of the following methods:
|
|
||||||
|
|
||||||
- by downloading full release archives including all dependencies
|
|
||||||
- by downloading Github archives
|
|
||||||
- by cloning the Git repository
|
|
||||||
- using Docker: [see the documentation](docker/shaarli-images.md)
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Latest release (recommended)
|
|
||||||
|
|
||||||
### Download as an archive
|
|
||||||
|
|
||||||
In most cases, you should download the latest Shaarli release from the [releases](https://github.com/shaarli/Shaarli/releases) page. Download our **shaarli-full** archive to include dependencies.
|
|
||||||
|
|
||||||
The current latest released version is `v0.10.4`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ wget https://github.com/shaarli/Shaarli/releases/download/v0.10.4/shaarli-v0.10.4-full.zip
|
|
||||||
$ unzip shaarli-v0.10.4-full.zip
|
|
||||||
$ mv Shaarli /path/to/shaarli/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using git
|
|
||||||
|
|
||||||
Cloning using `git` or downloading Github branches as zip files requires additional steps:
|
|
||||||
|
|
||||||
* Install [Composer](Unit-tests.md#install_composer) to manage third-party [PHP dependencies](3rd-party-libraries.md#composer).
|
|
||||||
* Install [yarn](https://yarnpkg.com/lang/en/docs/install/) to build the frontend dependencies.
|
|
||||||
* Install [python3-virtualenv](https://pypi.python.org/pypi/virtualenv) to build the local HTML documentation.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ mkdir -p /path/to/shaarli && cd /path/to/shaarli/
|
|
||||||
$ git clone -b latest https://github.com/shaarli/Shaarli.git .
|
|
||||||
$ composer install --no-dev --prefer-dist
|
|
||||||
$ make build_frontend
|
|
||||||
$ make translate
|
|
||||||
$ make htmldoc
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Stable version
|
|
||||||
|
|
||||||
The stable version has been experienced by Shaarli users, and will receive security updates.
|
|
||||||
|
|
||||||
|
|
||||||
### Download as an archive
|
|
||||||
|
|
||||||
As a .zip archive:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ wget https://github.com/shaarli/Shaarli/archive/stable.zip
|
|
||||||
$ unzip stable.zip
|
|
||||||
$ mv Shaarli-stable /path/to/shaarli/
|
|
||||||
```
|
|
||||||
|
|
||||||
As a .tar.gz archive :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ wget https://github.com/shaarli/Shaarli/archive/stable.tar.gz
|
|
||||||
$ tar xvf stable.tar.gz
|
|
||||||
$ mv Shaarli-stable /path/to/shaarli/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using git
|
|
||||||
|
|
||||||
Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/
|
|
||||||
# install/update third-party dependencies
|
|
||||||
$ cd /path/to/shaarli/
|
|
||||||
$ composer install --no-dev --prefer-dist
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Development version (mainline)
|
|
||||||
|
|
||||||
_Use at your own risk!_
|
|
||||||
|
|
||||||
Install [Composer](Unit-tests.md#install_composer) to manage Shaarli PHP dependencies,
|
|
||||||
and [yarn](https://yarnpkg.com/lang/en/docs/install/)
|
|
||||||
for front-end dependencies.
|
|
||||||
|
|
||||||
To get the latest changes from the `master` branch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# clone the repository
|
|
||||||
$ git clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/
|
|
||||||
# install/update third-party dependencies
|
|
||||||
$ cd /path/to/shaarli
|
|
||||||
$ composer install --no-dev --prefer-dist
|
|
||||||
$ make build_frontend
|
|
||||||
$ make translate
|
|
||||||
$ make htmldoc
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Finish Installation
|
|
||||||
|
|
||||||
Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.
|
|
||||||
|
|
||||||
![install screenshot](images/install-shaarli.png)
|
|
||||||
|
|
||||||
Setup your Shaarli installation, and it's ready to use!
|
|
||||||
|
|
||||||
## Updating Shaarli
|
|
||||||
|
|
||||||
See [Upgrade and Migration](Upgrade-and-migration)
|
|
|
@ -1,46 +0,0 @@
|
||||||
### Why did you create Shaarli ?
|
|
||||||
|
|
||||||
I was a StumbleUpon user. Then I got fed up with they big toolbar. I switched to delicious, which was lighter, faster and more beautiful. Until Yahoo bought it. Then the export API broke all the time, delicious became slow and was ditched by Yahoo. I switched to Diigo, which is not bad, but does too much. And Diigo is sslllooooowww and their Firefox extension a bit buggy. And… oh… **their Firefox addon sends to Diigo every single URL you visit** (Don't believe me ? Use [Tamper Data](https://addons.mozilla.org/en-US/firefox/addon/tamper-data/) and open any page).
|
|
||||||
|
|
||||||
Enough is enough. Saving simple links should not be a complicated heavy thing. I ditched them all and wrote my own: Shaarli. It's simple, but it does the job and does it well. And my data is not hosted on a foreign server, but on my server.
|
|
||||||
|
|
||||||
### Why use Shaarli and not Delicious/Diigo ?
|
|
||||||
|
|
||||||
With Shaarli:
|
|
||||||
|
|
||||||
- The data is yours: It's hosted on your server.
|
|
||||||
- Never fear of having your data locked-in.
|
|
||||||
- Never fear to have your data sold to third party.
|
|
||||||
- Your private links are not hosted on a third party server.
|
|
||||||
- You are not tracked by browser addons (like Diigo does)
|
|
||||||
- You can change the look and feel of the pages if you want.
|
|
||||||
- You can change the behaviour of the program.
|
|
||||||
- It's magnitude faster than most bookmarking services.
|
|
||||||
|
|
||||||
### What does Shaarli mean?
|
|
||||||
|
|
||||||
Shaarli stands for _shaaring_ your _links_.
|
|
||||||
|
|
||||||
### My Shaarli is broken!
|
|
||||||
First of all, ensure that both the [web server](Server-configuration) and
|
|
||||||
[Shaarli](Shaarli-configuration) are correctly configured, and that your
|
|
||||||
installation is [supported](Server-configuration).
|
|
||||||
|
|
||||||
If everything looks right but the issue(s) remain(s), please:
|
|
||||||
|
|
||||||
- take a look at the [troubleshooting](Troubleshooting) section
|
|
||||||
- come [chat with us](https://gitter.im/shaarli/Shaarli) on Gitter, we'll be happy to help ;-)
|
|
||||||
- browse active [issues](https://github.com/shaarli/Shaarli/issues) and [Pull Requests](https://github.com/shaarli/Shaarli/pulls)
|
|
||||||
- if you find one that is related to the issue, feel free to comment and provide additional details (host/Shaarli setup)
|
|
||||||
- else, [open a new issue](https://github.com/shaarli/Shaarli/issues/new), and provide information about the problem:
|
|
||||||
- _what happens?_ - display glitches, invalid data, security flaws...
|
|
||||||
- _what is your configuration?_ - OS, server version, activated extensions, web browser...
|
|
||||||
- _is it reproducible?_
|
|
||||||
|
|
||||||
### Why not use a real database? Files are slow!
|
|
||||||
|
|
||||||
Does browsing [this page](http://sebsauvage.net/links/) feel slow? Try browsing older pages, too.
|
|
||||||
|
|
||||||
It's not slow at all, is it? And don't forget the database contains more than 16000 links, and it's on a shared host, with 32000 visitors/day for my website alone. And it's still damn fast. Why?
|
|
||||||
|
|
||||||
The data file is only 3.7 Mb. It's read 99% of the time, and is probably already in the operation system disk cache. So generating a page involves no I/O at all most of the time.
|
|
|
@ -1,78 +0,0 @@
|
||||||
## Introduction
|
|
||||||
### PGP and GPG
|
|
||||||
[Gnu Privacy Guard](https://gnupg.org/) (GnuPG) is an Open Source implementation of the
|
|
||||||
[Pretty Good Privacy](https://en.wikipedia.org/wiki/Pretty_Good_Privacy#OpenPGP)
|
|
||||||
(OpenPGP) specification. Its main purposes are digital authentication, signature and encryption.
|
|
||||||
|
|
||||||
It is often used by the [FLOSS](https://en.wikipedia.org/wiki/Free_and_open-source_software) community to verify:
|
|
||||||
|
|
||||||
- Linux package signatures: Debian [SecureApt](https://wiki.debian.org/SecureApt), ArchLinux [Master
|
|
||||||
Keys](https://www.archlinux.org/master-keys/)
|
|
||||||
- [SCM](https://en.wikipedia.org/wiki/Revision_control) releases & maintainer identity
|
|
||||||
|
|
||||||
### Trust
|
|
||||||
To quote Phil Pennock (the author of the [SKS](https://bitbucket.org/skskeyserver/sks-keyserver/wiki/Home) key server - http://sks.spodhuis.org/):
|
|
||||||
|
|
||||||
> You MUST understand that presence of data in the keyserver (pools) in no way connotes trust. Anyone can generate a key, with any name or email address, and upload it. All security and trust comes from evaluating security at the “object level”, via PGP Web-Of-Trust signatures. This keyserver makes it possible to retrieve keys, looking them up via various indices, but the collection of keys in this public pool is KNOWN to contain malicious and fraudulent keys. It is the common expectation of server operators that users understand this and use software which, like all known common OpenPGP implementations, evaluates trust accordingly. This expectation is so common that it is not normally explicitly stated.
|
|
||||||
|
|
||||||
Trust can be gained by having your key signed by other people (and signing their key back, too :) ), for instance during [key signing parties](https://en.wikipedia.org/wiki/Key_signing_party), see:
|
|
||||||
|
|
||||||
- [The Keysigning party HOWTO](http://www.cryptnet.net/fdp/crypto/keysigning_party/en/keysigning_party.html)
|
|
||||||
- [Web of trust](https://en.wikipedia.org/wiki/Web_of_trust)
|
|
||||||
|
|
||||||
## Generate a GPG key
|
|
||||||
- [Generating a GPG key for Git tagging](http://stackoverflow.com/a/16725717) (StackOverflow)
|
|
||||||
- [Generating a GPG key](https://help.github.com/articles/generating-a-gpg-key/) (GitHub)
|
|
||||||
|
|
||||||
### gpg - provide identity information
|
|
||||||
```bash
|
|
||||||
$ gpg --gen-key
|
|
||||||
|
|
||||||
gpg (GnuPG) 2.1.6; Copyright (C) 2015 Free Software Foundation, Inc.
|
|
||||||
This is free software: you are free to change and redistribute it.
|
|
||||||
There is NO WARRANTY, to the extent permitted by law.
|
|
||||||
|
|
||||||
Note: Use "gpg2 --full-gen-key" for a full featured key generation dialog.
|
|
||||||
|
|
||||||
GnuPG needs to construct a user ID to identify your key.
|
|
||||||
|
|
||||||
Real name: Marvin the Paranoid Android
|
|
||||||
Email address: marvin@h2g2.net
|
|
||||||
You selected this USER-ID:
|
|
||||||
"Marvin the Paranoid Android <marvin@h2g2.net>"
|
|
||||||
|
|
||||||
Change (N)ame, (E)mail, or (O)kay/(Q)uit? o
|
|
||||||
We need to generate a lot of random bytes. It is a good idea to perform
|
|
||||||
some other action (type on the keyboard, move the mouse, utilize the
|
|
||||||
disks) during the prime generation; this gives the random number
|
|
||||||
generator a better chance to gain enough entropy.
|
|
||||||
```
|
|
||||||
|
|
||||||
### gpg - entropy interlude
|
|
||||||
At this point, you will:
|
|
||||||
- be prompted for a secure password to protect your key (the input method will depend on your Desktop Environment and configuration)
|
|
||||||
- be asked to use your machine's input devices (mouse, keyboard, etc.) to generate random entropy; this step _may take some time_
|
|
||||||
|
|
||||||
### gpg - key creation confirmation
|
|
||||||
```bash
|
|
||||||
gpg: key A9D53A3E marked as ultimately trusted
|
|
||||||
public and secret key created and signed.
|
|
||||||
|
|
||||||
gpg: checking the trustdb
|
|
||||||
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
|
|
||||||
gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u
|
|
||||||
pub rsa2048/A9D53A3E 2015-07-31
|
|
||||||
Key fingerprint = AF2A 5381 E54B 2FD2 14C4 A9A3 0E35 ACA4 A9D5 3A3E
|
|
||||||
uid [ultimate] Marvin the Paranoid Android <marvin@h2g2.net>
|
|
||||||
sub rsa2048/8C0EACF1 2015-07-31
|
|
||||||
```
|
|
||||||
|
|
||||||
### gpg - submit your public key to a PGP server (Optional)
|
|
||||||
``` bash
|
|
||||||
$ gpg --keyserver pgp.mit.edu --send-keys A9D53A3E
|
|
||||||
gpg: sending key A9D53A3E to hkp server pgp.mit.edu
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create and push a GPG-signed tag
|
|
||||||
|
|
||||||
See [Release Shaarli](Release Shaarli).
|
|
83
doc/md/Installation.md
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Once your server is [configured](Server-configuration.md), install Shaarli:
|
||||||
|
|
||||||
|
## From release ZIP
|
||||||
|
|
||||||
|
To install Shaarli, simply place the files from the latest [release .zip archive](https://github.com/shaarli/Shaarli/releases) under your webserver's document root (directly at the document root, or in a subdirectory). Download the **shaarli-vX.X.X-full** archive to include dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget https://github.com/shaarli/Shaarli/releases/download/v0.11.1/shaarli-v0.11.1-full.zip
|
||||||
|
unzip shaarli-v0.11.1-full.zip
|
||||||
|
sudo rsync -avP Shaarli/ /var/www/shaarli.mydomain.org/
|
||||||
|
```
|
||||||
|
|
||||||
|
## From sources
|
||||||
|
|
||||||
|
These components are required to build Shaarli:
|
||||||
|
|
||||||
|
- [Composer](dev/Development.md#install-composer) to manage third-party [PHP dependencies](dev/Development#third-party-libraries).
|
||||||
|
- [yarn](https://classic.yarnpkg.com/en/docs/install/) to build frontend dependencies.
|
||||||
|
- [python3-virtualenv](https://pypi.org/project/virtualenv/) to build local HTML documentation.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# example from a Debian-based build machine
|
||||||
|
sudo apt install composer yarnpkg python3-virtualenv
|
||||||
|
```
|
||||||
|
|
||||||
|
Clone the repository, either pointing to:
|
||||||
|
|
||||||
|
- any [tagged release](https://github.com/shaarli/Shaarli/releases)
|
||||||
|
- `latest`: the latest tagged release
|
||||||
|
- `master`: development branch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# clone the branch/tag of your choice
|
||||||
|
$ git clone -b latest https://github.com/shaarli/Shaarli.git /home/me/Shaarli
|
||||||
|
# OR download/extract the tar.gz/zip: wget https://github.com/shaarli/Shaarli/archive/latest.tar.gz...
|
||||||
|
|
||||||
|
# enter the directory
|
||||||
|
$ cd /home/me/Shaarli
|
||||||
|
# install 3rd-party PHP dependencies
|
||||||
|
$ composer install --no-dev --prefer-dist
|
||||||
|
# build frontend static assets
|
||||||
|
$ make build_frontend
|
||||||
|
# build translations
|
||||||
|
$ make translate
|
||||||
|
# build HTML documentation
|
||||||
|
$ make htmldoc
|
||||||
|
# copy the resulting shaarli directory under your webserver's document root
|
||||||
|
$ rsync -avP /home/me/Shaarli/ /var/www/shaarli.mydomain.org/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Set file permissions
|
||||||
|
|
||||||
|
Regardless of the installation method, appropriate [file permissions](dev/Development.md#directory-structure) must be set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo chown -R root:www-data /var/www/shaarli.mydomain.org
|
||||||
|
sudo chmod -R g+rX /var/www/shaarli.mydomain.org
|
||||||
|
sudo chmod -R g+rwX /var/www/shaarli.mydomain.org/{cache/,data/,pagecache/,tmp/}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Docker
|
||||||
|
|
||||||
|
[See the documentation](Docker.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Finish Installation
|
||||||
|
|
||||||
|
Once Shaarli is downloaded and files have been placed at the correct location, open this location your web browser.
|
||||||
|
|
||||||
|
Enter basic settings for your Shaarli installation, and it's ready to use!
|
||||||
|
|
||||||
|
![](images/07-installation.jpg)
|
||||||
|
|
||||||
|
Congratulations! Your Shaarli is now available at `https://shaarli.mydomain.org`.
|
||||||
|
|
||||||
|
You can further [configure Shaarli](Shaarli-configuration.md), setup [Plugins](Plugins.md) or [additional software](Community-and-related-software.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Upgrading Shaarli
|
||||||
|
|
||||||
|
See [Upgrade and Migration](Upgrade-and-migration)
|
|
@ -1,18 +0,0 @@
|
||||||
## Link structure
|
|
||||||
|
|
||||||
Every link available through the `LinkDB` object is represented as an array
|
|
||||||
containing the following fields:
|
|
||||||
|
|
||||||
* `id` (integer): Unique identifier.
|
|
||||||
* `title` (string): Title of the link.
|
|
||||||
* `url` (string): URL of the link. Used for displayable links (without redirector, url encoding, etc.).
|
|
||||||
Can be absolute or relative for Notes.
|
|
||||||
* `real_url` (string): Real destination URL, can be redirected, encoded, etc.
|
|
||||||
* `shorturl` (string): Permalink small hash.
|
|
||||||
* `description` (string): Link text description.
|
|
||||||
* `private` (boolean): whether the link is private or not.
|
|
||||||
* `tags` (string): all link tags separated by a single space
|
|
||||||
* `thumbnail` (string|boolean): relative path of the thumbnail cache file, or false if there isn't any.
|
|
||||||
* `created` (DateTime): link creation date time.
|
|
||||||
* `updated` (DateTime): last modification date time.
|
|
||||||
|
|
|
@ -1,725 +0,0 @@
|
||||||
[**I am a developer: ** Developer API](#developer-api)
|
|
||||||
|
|
||||||
[**I am a template designer: ** Guide for template designers](#guide-for-template-designer)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Developer API
|
|
||||||
|
|
||||||
### What can I do with plugins?
|
|
||||||
|
|
||||||
The plugin system let you:
|
|
||||||
|
|
||||||
- insert content into specific places across templates.
|
|
||||||
- alter data before templates rendering.
|
|
||||||
- alter data before saving new links.
|
|
||||||
|
|
||||||
### How can I create a plugin for Shaarli?
|
|
||||||
|
|
||||||
First, chose a plugin name, such as `demo_plugin`.
|
|
||||||
|
|
||||||
Under `plugin` folder, create a folder named with your plugin name. Then create a <plugin_name>.php file in that folder.
|
|
||||||
|
|
||||||
You should have the following tree view:
|
|
||||||
|
|
||||||
```
|
|
||||||
| index.php
|
|
||||||
| plugins/
|
|
||||||
|---| demo_plugin/
|
|
||||||
| |---| demo_plugin.php
|
|
||||||
```
|
|
||||||
|
|
||||||
### Plugin initialization
|
|
||||||
|
|
||||||
At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an `init()` function to execute and run it if it exists. This function must be named this way, and takes the `ConfigManager` as parameter.
|
|
||||||
|
|
||||||
<plugin_name>_init($conf)
|
|
||||||
|
|
||||||
This function can be used to create initial data, load default settings, etc. But also to set *plugin errors*. If the initialization function returns an array of strings, they will be understand as errors, and displayed in the header to logged in users.
|
|
||||||
|
|
||||||
### Understanding hooks
|
|
||||||
|
|
||||||
A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution.
|
|
||||||
|
|
||||||
These functions need to be named with this pattern:
|
|
||||||
|
|
||||||
```
|
|
||||||
hook_<plugin_name>_<hook_name>($data, $conf)
|
|
||||||
```
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
|
|
||||||
- data: see [$data section](https://shaarli.readthedocs.io/en/master/Plugin-System/#plugins-data)
|
|
||||||
- conf: the `ConfigManager` instance.
|
|
||||||
|
|
||||||
For example, if my plugin want to add data to the header, this function is needed:
|
|
||||||
|
|
||||||
hook_demo_plugin_render_header
|
|
||||||
|
|
||||||
If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header.
|
|
||||||
|
|
||||||
### Plugin's data
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
Every hook function has a `$data` parameter. Its content differs for each hooks.
|
|
||||||
|
|
||||||
**This parameter needs to be returned every time**, otherwise data is lost.
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
|
|
||||||
#### Filling templates placeholder
|
|
||||||
|
|
||||||
Template placeholders are displayed in template in specific places.
|
|
||||||
|
|
||||||
RainTPL displays every element contained in the placeholder's array. These element can be added by plugins.
|
|
||||||
|
|
||||||
For example, let's add a value in the placeholder `top_placeholder` which is displayed at the top of my page:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$data['top_placeholder'][] = 'My content';
|
|
||||||
# OR
|
|
||||||
array_push($data['top_placeholder'], 'My', 'content');
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Data manipulation
|
|
||||||
|
|
||||||
When a page is displayed, every variable send to the template engine is passed to plugins before that in `$data`.
|
|
||||||
|
|
||||||
The data contained by this array can be altered before template rendering.
|
|
||||||
|
|
||||||
For exemple, in linklist, it is possible to alter every title:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// mind the reference if you want $data to be altered
|
|
||||||
foreach ($data['links'] as &$value) {
|
|
||||||
// String reverse every title.
|
|
||||||
$value['title'] = strrev($value['title']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Metadata
|
|
||||||
|
|
||||||
Every plugin needs a `<plugin_name>.meta` file, which is in fact an `.ini` file (`KEY="VALUE"`), to be listed in plugin administration.
|
|
||||||
|
|
||||||
Each file contain two keys:
|
|
||||||
|
|
||||||
- `description`: plugin description
|
|
||||||
- `parameters`: user parameter names, separated by a `;`.
|
|
||||||
- `parameter.<PARAMETER_NAME>`: add a text description the specified parameter.
|
|
||||||
|
|
||||||
> Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file.
|
|
||||||
|
|
||||||
### It's not working!
|
|
||||||
|
|
||||||
Use `demo_plugin` as a functional example. It covers most of the plugin system features.
|
|
||||||
|
|
||||||
If it's still not working, please [open an issue](https://github.com/shaarli/Shaarli/issues/new).
|
|
||||||
|
|
||||||
### Hooks
|
|
||||||
|
|
||||||
| Hooks | Description |
|
|
||||||
| ------------- |:-------------:|
|
|
||||||
| [render_header](#render_header) | Allow plugin to add content in page headers. |
|
|
||||||
| [render_includes](#render_includes) | Allow plugin to include their own CSS files. |
|
|
||||||
| [render_footer](#render_footer) | Allow plugin to add content in page footer and include their own JS files. |
|
|
||||||
| [render_linklist](#render_linklist) | It allows to add content at the begining and end of the page, after every link displayed and to alter link data. |
|
|
||||||
| [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. |
|
|
||||||
| [render_tools](#render_tools) | Allow to add content at the end of the page. |
|
|
||||||
| [render_picwall](#render_picwall) | Allow to add content at the top and bottom of the page. |
|
|
||||||
| [render_tagcloud](#render_tagcloud) | Allow to add content at the top and bottom of the page, and after all tags. |
|
|
||||||
| [render_taglist](#render_taglist) | Allow to add content at the top and bottom of the page, and after all tags. |
|
|
||||||
| [render_daily](#render_daily) | Allow to add content at the top and bottom of the page, the bottom of each link and to alter data. |
|
|
||||||
| [render_feed](#render_feed) | Allow to do add tags in RSS and ATOM feeds. |
|
|
||||||
| [save_link](#save_link) | Allow to alter the link being saved in the datastore. |
|
|
||||||
| [delete_link](#delete_link) | Allow to do an action before a link is deleted from the datastore. |
|
|
||||||
| [save_plugin_parameters](#save_plugin_parameters) | Allow to manipulate plugin parameters before they're saved. |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### render_header
|
|
||||||
|
|
||||||
Triggered on every page.
|
|
||||||
|
|
||||||
Allow plugin to add content in page headers.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_PAGE_`: current target page (eg: `linklist`, `picwall`, etc.).
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `buttons_toolbar`: after the list of buttons in the header.
|
|
||||||
|
|
||||||
![buttons_toolbar_example](http://i.imgur.com/ssJUOrt.png)
|
|
||||||
|
|
||||||
- `fields_toolbar`: after search fields in the header.
|
|
||||||
|
|
||||||
> Note: This will only be called in linklist.
|
|
||||||
|
|
||||||
![fields_toolbar_example](http://i.imgur.com/3GMifI2.png)
|
|
||||||
|
|
||||||
#### render_includes
|
|
||||||
|
|
||||||
Triggered on every page.
|
|
||||||
|
|
||||||
Allow plugin to include their own CSS files.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_PAGE_`: current target page (eg: `linklist`, `picwall`, etc.).
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `css_files`: called after loading default CSS.
|
|
||||||
|
|
||||||
> Note: only add the path of the CSS file. E.g: `plugins/demo_plugin/custom_demo.css`.
|
|
||||||
|
|
||||||
#### render_footer
|
|
||||||
|
|
||||||
Triggered on every page.
|
|
||||||
|
|
||||||
Allow plugin to add content in page footer and include their own JS files.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_PAGE_`: current target page (eg: `linklist`, `picwall`, etc.).
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `text`: called after the end of the footer text.
|
|
||||||
- `endofpage`: called at the end of the page.
|
|
||||||
|
|
||||||
![text_example](http://i.imgur.com/L5S2YEH.png)
|
|
||||||
|
|
||||||
- `js_files`: called at the end of the page, to include custom JS scripts.
|
|
||||||
|
|
||||||
> Note: only add the path of the JS file. E.g: `plugins/demo_plugin/custom_demo.js`.
|
|
||||||
|
|
||||||
#### render_linklist
|
|
||||||
|
|
||||||
Triggered when `linklist` is displayed (list of links, permalink, search, tag filtered, etc.).
|
|
||||||
|
|
||||||
It allows to add content at the begining and end of the page, after every link displayed and to alter link data.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
- All templates data, including links.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `action_plugin`: next to the button "private only" at the top and bottom of the page.
|
|
||||||
|
|
||||||
![action_plugin_example](http://i.imgur.com/Q12PWg0.png)
|
|
||||||
|
|
||||||
- `link_plugin`: for every link, between permalink and link URL.
|
|
||||||
|
|
||||||
![link_plugin_example](http://i.imgur.com/3oDPhWx.png)
|
|
||||||
|
|
||||||
- `plugin_start_zone`: before displaying the template content.
|
|
||||||
|
|
||||||
![plugin_start_zone_example](http://i.imgur.com/OVBkGy3.png)
|
|
||||||
|
|
||||||
- `plugin_end_zone`: after displaying the template content.
|
|
||||||
|
|
||||||
![plugin_end_zone_example](http://i.imgur.com/6IoRuop.png)
|
|
||||||
|
|
||||||
#### render_editlink
|
|
||||||
|
|
||||||
Triggered when the link edition form is displayed.
|
|
||||||
|
|
||||||
Allow to add fields in the form, or display elements.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- All templates data.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `edit_link_plugin`: after tags field.
|
|
||||||
|
|
||||||
![edit_link_plugin_example](http://i.imgur.com/5u17Ens.png)
|
|
||||||
|
|
||||||
#### render_tools
|
|
||||||
|
|
||||||
Triggered when the "tools" page is displayed.
|
|
||||||
|
|
||||||
Allow to add content at the end of the page.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- All templates data.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `tools_plugin`: at the end of the page.
|
|
||||||
|
|
||||||
![tools_plugin_example](http://i.imgur.com/Bqhu9oQ.png)
|
|
||||||
|
|
||||||
#### render_picwall
|
|
||||||
|
|
||||||
Triggered when picwall is displayed.
|
|
||||||
|
|
||||||
Allow to add content at the top and bottom of the page.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
- All templates data.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `plugin_start_zone`: before displaying the template content.
|
|
||||||
- `plugin_end_zone`: after displaying the template content.
|
|
||||||
|
|
||||||
![plugin_start_end_zone_example](http://i.imgur.com/tVTQFER.png)
|
|
||||||
|
|
||||||
#### render_tagcloud
|
|
||||||
|
|
||||||
Triggered when tagcloud is displayed.
|
|
||||||
|
|
||||||
Allow to add content at the top and bottom of the page.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
- All templates data.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `plugin_start_zone`: before displaying the template content.
|
|
||||||
- `plugin_end_zone`: after displaying the template content.
|
|
||||||
|
|
||||||
For each tag, the following placeholder can be used:
|
|
||||||
|
|
||||||
- `tag_plugin`: after each tag
|
|
||||||
|
|
||||||
![plugin_start_end_zone_example](http://i.imgur.com/vHmyT3a.png)
|
|
||||||
|
|
||||||
|
|
||||||
#### render_taglist
|
|
||||||
|
|
||||||
Triggered when taglist is displayed.
|
|
||||||
|
|
||||||
Allow to add content at the top and bottom of the page.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
- All templates data.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `plugin_start_zone`: before displaying the template content.
|
|
||||||
- `plugin_end_zone`: after displaying the template content.
|
|
||||||
|
|
||||||
For each tag, the following placeholder can be used:
|
|
||||||
|
|
||||||
- `tag_plugin`: after each tag
|
|
||||||
|
|
||||||
#### render_daily
|
|
||||||
|
|
||||||
Triggered when tagcloud is displayed.
|
|
||||||
|
|
||||||
Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
- All templates data, including links.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `link_plugin`: used at bottom of each link.
|
|
||||||
|
|
||||||
![link_plugin_example](http://i.imgur.com/hzhMfSZ.png)
|
|
||||||
|
|
||||||
- `plugin_start_zone`: before displaying the template content.
|
|
||||||
- `plugin_end_zone`: after displaying the template content.
|
|
||||||
|
|
||||||
#### render_feed
|
|
||||||
|
|
||||||
Triggered when the ATOM or RSS feed is displayed.
|
|
||||||
|
|
||||||
Allow to add tags in the feed, either in the header or for each items. Items (links) can also be altered before being rendered.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing:
|
|
||||||
|
|
||||||
- `_LOGGEDIN_`: true if user is logged in, false otherwise.
|
|
||||||
- `_PAGE_`: containing either `rss` or `atom`.
|
|
||||||
- All templates data, including links.
|
|
||||||
|
|
||||||
##### Template placeholders
|
|
||||||
|
|
||||||
Tags can be added in feeds by adding an entry in `$data['<placeholder>']` array.
|
|
||||||
|
|
||||||
List of placeholders:
|
|
||||||
|
|
||||||
- `feed_plugins_header`: used as a header tag in the feed.
|
|
||||||
|
|
||||||
For each links:
|
|
||||||
|
|
||||||
- `feed_plugins`: additional tag for every link entry.
|
|
||||||
|
|
||||||
#### save_link
|
|
||||||
|
|
||||||
Triggered when a link is save (new link or edit).
|
|
||||||
|
|
||||||
Allow to alter the link being saved in the datastore.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing the link being saved:
|
|
||||||
|
|
||||||
- id
|
|
||||||
- title
|
|
||||||
- url
|
|
||||||
- shorturl
|
|
||||||
- description
|
|
||||||
- private
|
|
||||||
- tags
|
|
||||||
- created
|
|
||||||
- updated
|
|
||||||
|
|
||||||
|
|
||||||
#### delete_link
|
|
||||||
|
|
||||||
Triggered when a link is deleted.
|
|
||||||
|
|
||||||
Allow to execute any action before the link is actually removed from the datastore
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` is an array containing the link being saved:
|
|
||||||
|
|
||||||
- id
|
|
||||||
- title
|
|
||||||
- url
|
|
||||||
- shorturl
|
|
||||||
- description
|
|
||||||
- private
|
|
||||||
- tags
|
|
||||||
- created
|
|
||||||
- updated
|
|
||||||
|
|
||||||
|
|
||||||
#### save_plugin_parameters
|
|
||||||
|
|
||||||
Triggered when the plugin parameters are saved from the plugin administration page.
|
|
||||||
|
|
||||||
Plugins can perform an action every times their settings are updated.
|
|
||||||
For example it is used to update the CSS file of the `default_colors` plugins.
|
|
||||||
|
|
||||||
##### Data
|
|
||||||
|
|
||||||
`$data` input contains the `$_POST` array.
|
|
||||||
|
|
||||||
So if the plugin has a parameter called `MYPLUGIN_PARAMETER`,
|
|
||||||
the array will contain an entry with `MYPLUGIN_PARAMETER` as a key.
|
|
||||||
|
|
||||||
|
|
||||||
## Guide for template designer
|
|
||||||
|
|
||||||
### Plugin administration
|
|
||||||
|
|
||||||
Your theme must include a plugin administration page: `pluginsadmin.html`.
|
|
||||||
|
|
||||||
> Note: repo's template link needs to be added when the PR is merged.
|
|
||||||
|
|
||||||
Use the default one as an example.
|
|
||||||
|
|
||||||
Aside from classic RainTPL loops, plugins order is handle by JavaScript. You can just include `plugin_admin.js`, only if:
|
|
||||||
|
|
||||||
- you're using a table.
|
|
||||||
- you call orderUp() and orderUp() onclick on arrows.
|
|
||||||
- you add data-line and data-order to your rows.
|
|
||||||
|
|
||||||
Otherwise, you can use your own JS as long as this field is send by the form:
|
|
||||||
|
|
||||||
<input type="hidden" name="order_{$key}" value="{$counter}">
|
|
||||||
|
|
||||||
### Placeholder system
|
|
||||||
|
|
||||||
In order to make plugins work with every custom themes, you need to add variable placeholder in your templates.
|
|
||||||
|
|
||||||
It's a RainTPL loop like this:
|
|
||||||
|
|
||||||
{loop="$plugin_variable"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
|
|
||||||
You should enable `demo_plugin` for testing purpose, since it uses every placeholder available.
|
|
||||||
|
|
||||||
### List of placeholders
|
|
||||||
|
|
||||||
**page.header.html**
|
|
||||||
|
|
||||||
At the end of the menu:
|
|
||||||
|
|
||||||
{loop="$plugins_header.buttons_toolbar"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
|
|
||||||
At the end of file, before clearing floating blocks:
|
|
||||||
|
|
||||||
{if="!empty($plugin_errors) && isLoggedIn()"}
|
|
||||||
<ul class="errors">
|
|
||||||
{loop="plugin_errors"}
|
|
||||||
<li>{$value}</li>
|
|
||||||
{/loop}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
**includes.html**
|
|
||||||
|
|
||||||
At the end of the file:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$plugins_includes.css_files"}
|
|
||||||
<link type="text/css" rel="stylesheet" href="{$value}#"/>
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
**page.footer.html**
|
|
||||||
|
|
||||||
At the end of your footer notes:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$plugins_footer.text"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
At the end of file:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$plugins_footer.js_files"}
|
|
||||||
<script src="{$value}#"></script>
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
**linklist.html**
|
|
||||||
|
|
||||||
After search fields:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$plugins_header.fields_toolbar"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
Before displaying the link list (after paging):
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$plugin_start_zone"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
For every links (icons):
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$value.link_plugin"}
|
|
||||||
<span>{$value}</span>
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
Before end paging:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$plugin_end_zone"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
**linklist.paging.html**
|
|
||||||
|
|
||||||
After the "private only" icon:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$action_plugin"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
**editlink.html**
|
|
||||||
|
|
||||||
After tags field:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$edit_link_plugin"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
**tools.html**
|
|
||||||
|
|
||||||
After the last tool:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{loop="$tools_plugin"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
**picwall.html**
|
|
||||||
|
|
||||||
Top:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="plugin_zone_start_picwall" class="plugin_zone">
|
|
||||||
{loop="$plugin_start_zone"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Bottom:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="plugin_zone_end_picwall" class="plugin_zone">
|
|
||||||
{loop="$plugin_end_zone"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**tagcloud.html**
|
|
||||||
|
|
||||||
Top:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="plugin_zone_start_tagcloud" class="plugin_zone">
|
|
||||||
{loop="$plugin_start_zone"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Bottom:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="plugin_zone_end_tagcloud" class="plugin_zone">
|
|
||||||
{loop="$plugin_end_zone"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**daily.html**
|
|
||||||
|
|
||||||
Top:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="plugin_zone_start_picwall" class="plugin_zone">
|
|
||||||
{loop="$plugin_start_zone"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
After every link:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="dailyEntryFooter">
|
|
||||||
{loop="$link.link_plugin"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Bottom:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="plugin_zone_end_picwall" class="plugin_zone">
|
|
||||||
{loop="$plugin_end_zone"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**feed.atom.xml** and **feed.rss.xml**:
|
|
||||||
|
|
||||||
In headers tags section:
|
|
||||||
```xml
|
|
||||||
{loop="$feed_plugins_header"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
||||||
|
|
||||||
After each entry:
|
|
||||||
```xml
|
|
||||||
{loop="$value.feed_plugins"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
```
|
|
|
@ -1,14 +1,13 @@
|
||||||
## Plugin installation
|
# Plugins
|
||||||
|
|
||||||
There is a bunch of plugins shipped with Shaarli, where there is nothing to do to install them.
|
## Installation
|
||||||
|
|
||||||
If you want to install a third party plugin:
|
For plugins shipped with Shaarli, no installation is required.
|
||||||
|
|
||||||
- Download it.
|
If you want to install a third party plugin, download it to the `plugins` directory in Shaarli's installation folder:
|
||||||
- Put it in the `plugins` directory in Shaarli's installation folder.
|
|
||||||
- Make sure you put it correctly:
|
|
||||||
|
|
||||||
```
|
```bash
|
||||||
|
# example directory structure
|
||||||
| index.php
|
| index.php
|
||||||
| plugins/
|
| plugins/
|
||||||
|---| custom_plugin/
|
|---| custom_plugin/
|
||||||
|
@ -17,63 +16,47 @@ If you want to install a third party plugin:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* Make sure your webserver can read and write the files in your plugin folder.
|
Make sure your webserver can read and write the files in your plugin folder.
|
||||||
|
|
||||||
## Plugin configuration
|
|
||||||
|
|
||||||
In Shaarli's administration page (`Tools` link), go to `Plugin administration`.
|
## Configuration
|
||||||
|
|
||||||
Here you can enable and disable all plugins available, and configure them.
|
From Shaarli's administration page (`Tools` link), go to `Plugin administration`. Here you can enable and disable all plugins available, and configure them.
|
||||||
|
|
||||||
![administration screenshot](https://camo.githubusercontent.com/5da68e191969007492ca0fbeb25f3b2357b748cc/687474703a2f2f692e696d6775722e636f6d2f766837544643712e706e67)
|
![administration screenshot](https://camo.githubusercontent.com/5da68e191969007492ca0fbeb25f3b2357b748cc/687474703a2f2f692e696d6775722e636f6d2f766837544643712e706e67)
|
||||||
|
|
||||||
## Plugin order
|
|
||||||
|
## Order
|
||||||
|
|
||||||
In the plugin administration page, you can move enabled plugins to the top or bottom of the list. The first plugins in the list will be processed first.
|
In the plugin administration page, you can move enabled plugins to the top or bottom of the list. The first plugins in the list will be processed first.
|
||||||
|
|
||||||
This is important in case plugins are depending on each other. Read plugins README details for more information.
|
This is important in case plugins depend on each other. Read plugins READMEs for more information.
|
||||||
|
|
||||||
**Use case**: The (non existent) plugin `shaares_footer` adds a footer to every shaare in Markdown syntax. It needs to be processed *before* (higher in the list) the Markdown plugin. Otherwise its syntax won't be translated in HTML.
|
**Use case**: The (non existent) plugin `shaares_footer` adds a footer to every shaare in Markdown syntax. It needs to be processed *before* (higher in the list) the Markdown plugin. Otherwise its syntax won't be translated in HTML.
|
||||||
|
|
||||||
## File mode
|
|
||||||
|
|
||||||
Enabled plugin are stored in your `config.json.php` parameters file, under the `array`:
|
## Configuration file
|
||||||
|
|
||||||
```php
|
Enabled plugins are stored in your [Configuration file](Shaarli-configuration.md).
|
||||||
$GLOBALS['config']['ENABLED_PLUGINS']
|
|
||||||
```
|
|
||||||
|
|
||||||
You can edit them manually here.
|
## Usage
|
||||||
Example:
|
|
||||||
|
|
||||||
```php
|
### Official plugins
|
||||||
$GLOBALS['config']['ENABLED_PLUGINS'] = array(
|
|
||||||
'qrcode',
|
|
||||||
'archiveorg',
|
|
||||||
'wallabag',
|
|
||||||
'markdown',
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Plugin usage
|
|
||||||
|
|
||||||
#### Official plugins
|
|
||||||
|
|
||||||
Usage of each plugin is documented in it's README file:
|
Usage of each plugin is documented in it's README file:
|
||||||
|
|
||||||
* `addlink-toolbar`: Adds the addlink input on the linklist page
|
* `addlink-toolbar`: Adds the addlink input on the Shaares list page
|
||||||
* `archiveorg`: For each link, add an Archive.org icon
|
* `archiveorg`: For each Shaare, add a link to the archived page on Archive.org
|
||||||
* `default_colors`: Override default theme colors.
|
* `default_colors`: Override default theme colors.
|
||||||
* `isso`: Let visitor comment your shaares on permalinks with Isso.
|
* `isso`: Let visitor comment your shaares on permalinks with Isso.
|
||||||
* [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax.
|
|
||||||
* `piwik`: A plugin that adds Piwik tracking code to Shaarli pages.
|
* `piwik`: A plugin that adds Piwik tracking code to Shaarli pages.
|
||||||
* [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.
|
* [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.
|
||||||
* `pubsubhubbub`: Enable PubSubHubbub feed publishing
|
* `pubsubhubbub`: Enable PubSubHubbub feed publishing
|
||||||
* `qrcode`: For each link, add a QRCode icon.
|
* `qrcode`: For each Shaare, add a QRCode icon.
|
||||||
* [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each link, add a Wallabag icon to save it in your instance.
|
* `readitlater`: Mark bookmarks to read them later, with bookmark list highlight and filter.
|
||||||
|
* [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each Shaare, add a Wallabag icon to save it in your instance.
|
||||||
|
|
||||||
|
|
||||||
|
### Third party plugins
|
||||||
|
|
||||||
#### Third party plugins
|
See [Community & related software](Community-and-related-software.md)
|
||||||
|
|
||||||
See [Community & related software](https://shaarli.readthedocs.io/en/master/Community-&-Related-software/)
|
|
||||||
|
|
|
@ -1,103 +1,26 @@
|
||||||
## Usage and Prerequisites
|
# REST API
|
||||||
|
|
||||||
See the [REST API documentation](http://shaarli.github.io/api-documentation/)
|
## Server requirements
|
||||||
for a list of available endpoints and parameters.
|
|
||||||
|
|
||||||
Please ensure that your server meets the
|
See the **[REST API documentation](https://shaarli.github.io/api-documentation/)** for a list of available endpoints and parameters.
|
||||||
[requirements](Server-configuration#prerequisites) and is properly
|
|
||||||
[configured](Server-configuration):
|
Please ensure that your server meets the requirements and is properly [configured](Server-configuration.md):
|
||||||
|
|
||||||
- URL rewriting is enabled (see specific Apache and Nginx sections)
|
- URL rewriting is enabled (see specific Apache and Nginx sections)
|
||||||
- the server's timezone is properly defined
|
- the server's timezone is properly defined
|
||||||
- the server's clock is synchronized with
|
- the server's clock is synchronized with [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol)
|
||||||
[NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol)
|
|
||||||
|
|
||||||
The host where the API client is invoked should also be synchronized with NTP,
|
The host where the API client is invoked should also be synchronized with NTP, see _payload/token expiration_
|
||||||
see [token expiration](#payload).
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
All requests to Shaarli's API must include a JWT token to verify their authenticity.
|
|
||||||
|
|
||||||
This token has to be included as an HTTP header called `Authentication: Bearer <jwt token>`.
|
|
||||||
|
|
||||||
JWT resources :
|
|
||||||
|
|
||||||
- [jwt.io](https://jwt.io) (including a list of client per language).
|
|
||||||
- RFC : https://tools.ietf.org/html/rfc7519
|
|
||||||
- https://float-middle.com/json-web-tokens-jwt-vs-sessions/
|
|
||||||
- HackerNews thread: https://news.ycombinator.com/item?id=11929267
|
|
||||||
|
|
||||||
|
|
||||||
### Shaarli JWT Token
|
|
||||||
|
|
||||||
JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64:
|
|
||||||
|
|
||||||
```
|
|
||||||
[header].[payload].[signature]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Header
|
|
||||||
|
|
||||||
Shaarli only allow one hash algorithm, so the header will always be the same:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"typ": "JWT",
|
|
||||||
"alg": "HS512"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Encoded in base64, it gives:
|
|
||||||
|
|
||||||
```
|
|
||||||
ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Payload
|
|
||||||
|
|
||||||
**Token expiration**
|
|
||||||
|
|
||||||
To avoid infinite token validity, JWT tokens must include their creation date
|
|
||||||
in UNIX timestamp format (timezone independent - UTC) under the key `iat` (issued at).
|
|
||||||
This token will be valid during **9 minutes**.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"iat": 1468663519
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See [RFC reference](https://tools.ietf.org/html/rfc7519#section-4.1.6).
|
|
||||||
|
|
||||||
|
|
||||||
#### Signature
|
|
||||||
|
|
||||||
The signature authenticate the token validity. It contains the base64 of the header and the body, separated by a dot `.`, hashed in SHA512 with the API secret available in Shaarli administration page.
|
|
||||||
|
|
||||||
Signature example with PHP:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$content = base64_encode($header) . '.' . base64_encode($payload);
|
|
||||||
$signature = hash_hmac('sha512', $content, $secret);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Clients and examples
|
## Clients and examples
|
||||||
### Android, Java, Kotlin
|
|
||||||
|
|
||||||
- [Android client example with Kotlin](https://gitlab.com/snippets/1665808)
|
- **[python-shaarli-client](https://github.com/shaarli/python-shaarli-client)** - the reference API client ([Documentation](https://python-shaarli-client.readthedocs.io/en/latest/))
|
||||||
by [Braincoke](https://github.com/Braincoke)
|
- [shaarli-client](https://www.npmjs.com/package/shaarli-client) - NodeJs client ([source code](https://github.com/laBecasse/shaarli-client)) by [laBecasse](https://github.com/laBecasse)
|
||||||
|
- [Android client example with Kotlin](https://gitlab.com/-/snippets/1665808) by [Braincoke](https://github.com/Braincoke)
|
||||||
|
|
||||||
### Javascript, NodeJS
|
|
||||||
|
|
||||||
- [shaarli-client](https://www.npmjs.com/package/shaarli-client)
|
This example uses the [PHP cURL](https://www.php.net/manual/en/book.curl.php) library.
|
||||||
([source code](https://github.com/laBecasse/shaarli-client))
|
|
||||||
by [laBecasse](https://github.com/laBecasse)
|
|
||||||
|
|
||||||
### PHP
|
|
||||||
|
|
||||||
This example uses the [PHP cURL](http://php.net/manual/en/book.curl.php) library.
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
@ -145,13 +68,57 @@ function getInfo($baseUrl, $secret) {
|
||||||
var_dump(getInfo($baseUrl, $secret));
|
var_dump(getInfo($baseUrl, $secret));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
### Python
|
### Authentication
|
||||||
|
|
||||||
|
- All requests to Shaarli's API must include a **JWT token** to verify their authenticity.
|
||||||
|
- This token must be included as an HTTP header called `Authorization: Bearer <jwt token>`.
|
||||||
|
- JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64:
|
||||||
|
|
||||||
|
```
|
||||||
|
[header].[payload].[signature]
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Header
|
||||||
|
|
||||||
|
Shaarli only allow one hash algorithm, so the header will always be the same:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"typ": "JWT",
|
||||||
|
"alg": "HS512"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Encoded in base64, it gives:
|
||||||
|
|
||||||
|
```
|
||||||
|
ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Payload
|
||||||
|
|
||||||
|
Token expiration: To avoid infinite token validity, JWT tokens must include their creation date in UNIX timestamp format (timezone independent - UTC) under the key `iat` (issued at) field ([1](https://datatracker.ietf.org/doc/html/rfc7519)). This token will be valid during **9 minutes**.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"iat": 1468663519
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Signature
|
||||||
|
|
||||||
|
The signature authenticates the token validity. It contains the base64 of the header and the body, separated by a dot `.`, hashed in SHA512 with the API secret available in Shaarli administration page.
|
||||||
|
|
||||||
|
Example signature with PHP:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$content = base64_encode($header) . '.' . base64_encode($payload);
|
||||||
|
$signature = hash_hmac('sha512', $content, $secret);
|
||||||
|
```
|
||||||
|
|
||||||
See the reference API client:
|
|
||||||
|
|
||||||
- [Documentation](http://python-shaarli-client.readthedocs.io/en/latest/) on ReadTheDocs
|
|
||||||
- [python-shaarli-client](https://github.com/shaarli/python-shaarli-client) on Github
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
@ -171,3 +138,13 @@ to get the actual error message in the HTTP response body with:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [jwt.io](https://jwt.io) (including a list of client per language).
|
||||||
|
- [RFC - JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519)
|
||||||
|
- [JSON Web Tokens (JWT) vs Sessions](https://float-middle.com/json-web-tokens-jwt-vs-sessions/), [HackerNews thread](https://news.ycombinator.com/item?id=11929267)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
### Feeds options
|
|
||||||
|
|
||||||
Feeds are available in ATOM with `?do=atom` and RSS with `do=RSS`.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
- You can use `permalinks` in the feed URL to get permalink to Shaares instead of direct link to shaared URL.
|
|
||||||
- E.G. `https://my.shaarli.domain/?do=atom&permalinks`.
|
|
||||||
- You can use `nb` parameter in the feed URL to specify the number of Shaares you want in a feed (default if not specified: `50`). The keyword `all` is available if you want everything.
|
|
||||||
- `https://my.shaarli.domain/?do=atom&permalinks&nb=42`
|
|
||||||
- `https://my.shaarli.domain/?do=atom&permalinks&nb=all`
|
|
||||||
|
|
||||||
### RSS Feeds or Picture Wall for a specific search/tag
|
|
||||||
|
|
||||||
It is possible to filter RSS/ATOM feeds and Picture Wall on a Shaarli to **only display results of a specific search, or for a specific tag**.
|
|
||||||
|
|
||||||
For example, if you want to subscribe only to links tagged `photography`:
|
|
||||||
|
|
||||||
- Go to the desired Shaarli instance.
|
|
||||||
- Search for the `photography` tag in the _Filter by tag_ box. Links tagged `photography` are displayed.
|
|
||||||
- Click on the `RSS Feed` button.
|
|
||||||
- You are presented with an RSS feed showing only these links. Subscribe to it to receive only updates with this tag.
|
|
||||||
- The same method **also works for a full-text search** (_Search_ box) **and for the Picture Wall** (want to only see pictures about `nature`?)
|
|
||||||
- You can also build the URLs manually:
|
|
||||||
- `https://my.shaarli.domain/?do=rss&searchtags=nature`
|
|
||||||
- `https://my.shaarli.domain/links/?do=picwall&searchterm=poney`
|
|
||||||
|
|
||||||
![](images/rss-filter-1.png) ![](images/rss-filter-2.png)
|
|
|
@ -1,161 +0,0 @@
|
||||||
See [Git - Maintaining a project - Tagging your
|
|
||||||
releases](http://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project#Tagging-Your-Releases).
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
This guide assumes that you have:
|
|
||||||
|
|
||||||
- a GPG key matching your GitHub authentication credentials
|
|
||||||
- i.e., the email address identified by the GPG key is the same as the one in your `~/.gitconfig`
|
|
||||||
- a GitHub fork of Shaarli
|
|
||||||
- a local clone of your Shaarli fork, with the following remotes:
|
|
||||||
- `origin` pointing to your GitHub fork
|
|
||||||
- `upstream` pointing to the main Shaarli repository
|
|
||||||
- maintainer permissions on the main Shaarli repository, to:
|
|
||||||
- push the signed tag
|
|
||||||
- create a new release
|
|
||||||
- [Composer](https://getcomposer.org/) needs to be installed
|
|
||||||
- The [venv](https://docs.python.org/3/library/venv.html) Python 3 module needs to be installed for HTML documentation generation.
|
|
||||||
|
|
||||||
## GitHub release draft and `CHANGELOG.md`
|
|
||||||
See http://keepachangelog.com/en/0.3.0/ for changelog formatting.
|
|
||||||
|
|
||||||
### GitHub release draft
|
|
||||||
GitHub allows drafting the release note for the upcoming release, from the [Releases](https://github.com/shaarli/Shaarli/releases) page. This way, the release note can be drafted while contributions are merged to `master`.
|
|
||||||
|
|
||||||
### `CHANGELOG.md`
|
|
||||||
This file should contain the same information as the release note draft for the upcoming version.
|
|
||||||
|
|
||||||
Update it to:
|
|
||||||
|
|
||||||
- add new entries (additions, fixes, etc.)
|
|
||||||
- mark the current version as released by setting its date and link
|
|
||||||
- add a new section for the future unreleased version
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd /path/to/shaarli
|
|
||||||
|
|
||||||
$ nano CHANGELOG.md
|
|
||||||
|
|
||||||
[...]
|
|
||||||
## vA.B.C - UNRELEASED
|
|
||||||
TBA
|
|
||||||
|
|
||||||
## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD
|
|
||||||
[...]
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Increment the version code, update docs, create and push a signed tag
|
|
||||||
### Update the list of Git contributors
|
|
||||||
```bash
|
|
||||||
$ make authors
|
|
||||||
$ git commit -s -m "Update AUTHORS"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create and merge a Pull Request
|
|
||||||
This one is pretty straightforward ;-)
|
|
||||||
|
|
||||||
### Bump Shaarli version to v0.x branch
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git checkout master
|
|
||||||
$ git fetch upstream
|
|
||||||
$ git pull upstream master
|
|
||||||
|
|
||||||
# IF the branch doesn't exists
|
|
||||||
$ git checkout -b v0.5
|
|
||||||
# OR if the branch already exists
|
|
||||||
$ git checkout v0.5
|
|
||||||
$ git rebase upstream/master
|
|
||||||
|
|
||||||
# Bump shaarli version from dev to 0.5.0, **without the `v`**
|
|
||||||
$ vim shaarli_version.php
|
|
||||||
$ git add shaarli_version
|
|
||||||
$ git commit -s -m "Bump Shaarli version to v0.5.0"
|
|
||||||
$ git push upstream v0.5
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create and push a signed tag
|
|
||||||
```bash
|
|
||||||
# update your local copy
|
|
||||||
$ git checkout v0.5
|
|
||||||
$ git fetch upstream
|
|
||||||
$ git pull upstream v0.5
|
|
||||||
|
|
||||||
# create a signed tag
|
|
||||||
$ git tag -s -m "Release v0.5.0" v0.5.0
|
|
||||||
|
|
||||||
# push it to "upstream"
|
|
||||||
$ git push --tags upstream
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verify a signed tag
|
|
||||||
[`v0.5.0`](https://github.com/shaarli/Shaarli/releases/tag/v0.5.0) is the first GPG-signed tag pushed on the Community Shaarli.
|
|
||||||
|
|
||||||
Let's have a look at its signature!
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd /path/to/shaarli
|
|
||||||
$ git fetch upstream
|
|
||||||
|
|
||||||
# get the SHA1 reference of the tag
|
|
||||||
$ git show-ref tags/v0.5.0
|
|
||||||
f7762cf803f03f5caf4b8078359a63783d0090c1 refs/tags/v0.5.0
|
|
||||||
|
|
||||||
# verify the tag signature information
|
|
||||||
$ git verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1
|
|
||||||
gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
|
|
||||||
gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Publish the GitHub release
|
|
||||||
### Update release badges
|
|
||||||
Update `README.md` so version badges display and point to the newly released Shaarli version(s), in the `master` branch.
|
|
||||||
|
|
||||||
### Create a GitHub release from a Git tag
|
|
||||||
From the previously drafted release:
|
|
||||||
|
|
||||||
- edit the release notes (if needed)
|
|
||||||
- specify the appropriate Git tag
|
|
||||||
- publish the release
|
|
||||||
- profit!
|
|
||||||
|
|
||||||
### Generate and upload all-in-one release archives
|
|
||||||
Users with a shared hosting may have:
|
|
||||||
|
|
||||||
- no SSH access
|
|
||||||
- no possibility to install PHP packages or server extensions
|
|
||||||
- no possibility to run scripts
|
|
||||||
|
|
||||||
To ease Shaarli installations, it is possible to generate and upload additional release archives,
|
|
||||||
that will contain Shaarli code plus all required third-party libraries.
|
|
||||||
|
|
||||||
**From the `v0.5` branch:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ make release_archive
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create the following archives:
|
|
||||||
|
|
||||||
- `shaarli-vX.Y.Z-full.tar`
|
|
||||||
- `shaarli-vX.Y.Z-full.zip`
|
|
||||||
|
|
||||||
The archives need to be manually uploaded on the previously created GitHub release.
|
|
||||||
|
|
||||||
### Update `stable` and `latest` branches
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git checkout latest
|
|
||||||
# latest release
|
|
||||||
$ git merge v0.5.0
|
|
||||||
# fix eventual conflicts
|
|
||||||
$ make test
|
|
||||||
$ git push upstream latest
|
|
||||||
$ git checkout stable
|
|
||||||
# latest previous major
|
|
||||||
$ git merge v0.4.5
|
|
||||||
# fix eventual conflicts
|
|
||||||
$ make test
|
|
||||||
$ git push upstream stable
|
|
||||||
```
|
|
142
doc/md/Reverse-proxy.md
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
# Reverse proxy
|
||||||
|
|
||||||
|
If Shaarli is hosted on a server behind a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) (i.e. there is a proxy server between clients and the web server hosting Shaarli), configure it accordingly. See [Reverse proxy](Reverse-proxy.md) configuration. In this example:
|
||||||
|
|
||||||
|
- The Shaarli application server exposes port `10080` to the proxy (for example docker container started with `--publish 127.0.0.1:10080:80`).
|
||||||
|
- The Shaarli application server runs at `127.0.0.1` (container). Replace with the server's IP address if running on a different machine.
|
||||||
|
- Shaarli's Fully Qualified Domain Name (FQDN) is `shaarli.mydomain.org`.
|
||||||
|
- No HTTPS is setup on the application server, SSL termination is done at the reverse proxy.
|
||||||
|
|
||||||
|
In your [Shaarli configuration](Shaarli-configuration.md) `data/config.json.php`, add the public IP of your proxy under `security.trusted_proxies`.
|
||||||
|
|
||||||
|
See also [proxy-related](https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&q=label%3Aproxy+) issues.
|
||||||
|
|
||||||
|
|
||||||
|
## Apache
|
||||||
|
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName shaarli.mydomain.org
|
||||||
|
|
||||||
|
# For SSL/TLS certificates acquired with certbot or self-signed certificates
|
||||||
|
# Redirect HTTP requests to HTTPS, except Let's Encrypt ACME challenge requests
|
||||||
|
RewriteEngine on
|
||||||
|
RewriteRule ^.well-known/acme-challenge/ - [L]
|
||||||
|
RewriteCond %{HTTP_HOST} =shaarli.mydomain.org
|
||||||
|
RewriteRule ^ https://shaarli.mydomain.org%{REQUEST_URI} [END,NE,R=permanent]
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
# SSL/TLS configuration for Let's Encrypt certificates managed with mod_md
|
||||||
|
#MDomain shaarli.mydomain.org
|
||||||
|
#MDCertificateAgreement accepted
|
||||||
|
#MDContactEmail admin@shaarli.mydomain.org
|
||||||
|
#MDPrivateKeys RSA 4096
|
||||||
|
|
||||||
|
<VirtualHost *:443>
|
||||||
|
ServerName shaarli.mydomain.org
|
||||||
|
|
||||||
|
# SSL/TLS configuration for Let's Encrypt certificates acquired with certbot standalone
|
||||||
|
SSLEngine on
|
||||||
|
SSLCertificateFile /etc/letsencrypt/live/shaarli.mydomain.org/fullchain.pem
|
||||||
|
SSLCertificateKeyFile /etc/letsencrypt/live/shaarli.mydomain.org/privkey.pem
|
||||||
|
# Let's Encrypt settings from https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/tls_configs/current-options-ssl-apache.conf
|
||||||
|
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
|
||||||
|
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||||
|
SSLHonorCipherOrder off
|
||||||
|
SSLSessionTickets off
|
||||||
|
SSLOptions +StrictRequire
|
||||||
|
|
||||||
|
# SSL/TLS configuration for self-signed certificates
|
||||||
|
#SSLEngine on
|
||||||
|
#SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||||
|
#SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
||||||
|
|
||||||
|
# let the proxied shaarli server/container know HTTPS URLs should be served
|
||||||
|
RequestHeader set X-Forwarded-Proto "https"
|
||||||
|
|
||||||
|
# send the original SERVER_NAME to the proxied host
|
||||||
|
ProxyPreserveHost On
|
||||||
|
|
||||||
|
# pass requests to the proxied host
|
||||||
|
# sets X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Server headers
|
||||||
|
ProxyPass / http://127.0.0.1:10080/
|
||||||
|
ProxyPassReverse / http://127.0.0.1:10080/
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## HAProxy
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
global
|
||||||
|
[...]
|
||||||
|
|
||||||
|
defaults
|
||||||
|
[...]
|
||||||
|
|
||||||
|
frontend http-in
|
||||||
|
bind :80
|
||||||
|
redirect scheme https code 301 if !{ ssl_fc }
|
||||||
|
bind :443 ssl crt /path/to/cert.pem
|
||||||
|
default_backend shaarli
|
||||||
|
|
||||||
|
backend shaarli
|
||||||
|
mode http
|
||||||
|
option http-server-close
|
||||||
|
option forwardfor
|
||||||
|
reqadd X-Forwarded-Proto: https
|
||||||
|
server shaarli1 127.0.0.1:10080
|
||||||
|
```
|
||||||
|
|
||||||
|
- [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/)
|
||||||
|
|
||||||
|
## Nginx
|
||||||
|
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
http {
|
||||||
|
[...]
|
||||||
|
|
||||||
|
index index.html index.php;
|
||||||
|
|
||||||
|
root /home/john/web;
|
||||||
|
access_log /var/log/nginx/access.log combined;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name shaarli.mydomain.org;
|
||||||
|
# redirect HTTP to HTTPS
|
||||||
|
return 301 https://shaarli.mydomain.org$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name shaarli.mydomain.org;
|
||||||
|
|
||||||
|
ssl_certificate /path/to/certificate
|
||||||
|
ssl_certificate_key /path/to/private/key
|
||||||
|
|
||||||
|
# if shaarli is installed in a subdirectory of the main domain, edit the location accordingly
|
||||||
|
location / {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
|
||||||
|
# pass requests to the proxied host
|
||||||
|
proxy_pass http://localhost:10080/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_connect_timeout 30s;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto)
|
||||||
|
- [`X-Forwarded-Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host)
|
||||||
|
- [`X-Forwarded-For`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)
|
|
@ -1,25 +0,0 @@
|
||||||
## Client browser
|
|
||||||
- Shaarli relies on `HTTP_REFERER` for some functions (like redirects and clicking on tags). If you have disabled or masqueraded `HTTP_REFERER` in your browser, some features of Shaarli may not work
|
|
||||||
|
|
||||||
## Server and sessions
|
|
||||||
- Directories are protected using `.htaccess` files
|
|
||||||
- Forms are protected against XSRF (Cross-site requests forgery):
|
|
||||||
- Forms which act on data (save,delete…) contain a token generated by the server.
|
|
||||||
- Any posted form which does not contain a valid token is rejected.
|
|
||||||
- Any token can only be used once.
|
|
||||||
- Tokens are attached to the session and cannot be reused in another session.
|
|
||||||
- Sessions automatically expire after 60 minutes.
|
|
||||||
- Sessions are protected against hijacking: the session ID cannot be used from a different IP address.
|
|
||||||
|
|
||||||
## Shaarli datastore and configuration
|
|
||||||
- The password is salted, hashed and stored in the data subdirectory, in a PHP file, and protected by htaccess. Even if the webserver does not support htaccess, the hash is not readable by URL. Even if the .php file is stolen, the password cannot deduced from the hash. The salt prevents rainbow-tables attacks.
|
|
||||||
- Links are stored as an associative array which is serialized, compressed (with deflate), base64-encoded and saved as a comment in a `.php` file.
|
|
||||||
- Even if the server does not support `.htaccess` files, the data file will still not be readable by URL.
|
|
||||||
- The database looks like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php /* zP1ZjxxJtiYIvvevEPJ2lDOaLrZv7o...
|
|
||||||
...ka7gaco/Z+TFXM2i7BlfMf8qxpaSSYfKlvqv/x8= */ ?>
|
|
||||||
```
|
|
||||||
|
|
||||||
- Small hashes are used to make a link to an entry in Shaarli. They are unique. In fact, the date of the items (eg. `20110923_150523`) is hashed with CRC32, then converted to base64 and some characters are replaced. They are always 6 characters longs and use only `A-Z a-z 0-9 - _` and `@`.
|
|
|
@ -1,96 +1,183 @@
|
||||||
|
# Server configuration
|
||||||
|
|
||||||
- [Prerequisites](#prerequisistes)
|
## Requirements
|
||||||
- [Apache](#apache)
|
|
||||||
- [Nginx](#nginx)
|
|
||||||
- [Proxies](#proxies)
|
|
||||||
- [See also](#see-also)
|
|
||||||
|
|
||||||
## Prerequisites
|
### Operating system and web server
|
||||||
### Shaarli
|
|
||||||
|
|
||||||
- A web server and PHP interpreter module/service have been installed.
|
Shaarli can be hosted on dedicated/virtual servers, or shared hosting.
|
||||||
- You have write access to the Shaarli installation directory.
|
|
||||||
- The correct read/write permissions have been granted to the web server user and group.
|
You need write access to the Shaarli installation directory - you should have received instructions from your hosting provider on how to connect to the server using SSH (or FTP for shared hosts).
|
||||||
- Your PHP interpreter is compatible with supported PHP versions:
|
|
||||||
|
Examples in this documentation are given for [Debian](https://www.debian.org/), a GNU/Linux distribution widely used in server environments. Please adapt them to your specific Linux distribution.
|
||||||
|
|
||||||
|
A $5/month VPS (1 CPU, 1 GiB RAM and 25 GiB SSD) will run any Shaarli installation without problems. Some hosting providers: [DigitalOcean](https://www.digitalocean.com/) ([1](https://docs.digitalocean.com/products/droplets/), [2](https://www.digitalocean.com/pricing/), [3](https://docs.digitalocean.com/products/droplets/how-to/create/), [4](https://docs.digitalocean.com/products/droplets/how-to/add-ssh-keys/), [5](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-debian-8), [6](https://www.digitalocean.com/community/tutorials/an-introduction-to-securing-your-linux-vps)), [Gandi](https://www.gandi.net/en), [OVH](https://www.ovhcloud.com/en-gb/), [RackSpace](https://www.rackspace.com/), etc.
|
||||||
|
|
||||||
|
|
||||||
|
### Network and domain name
|
||||||
|
|
||||||
|
Try to host the server in a region that is geographically close to your users.
|
||||||
|
|
||||||
|
A **domain name** ([DNS record](https://opensource.com/article/17/4/introduction-domain-name-system-dns)) pointing to the server's public IP address is required to obtain a SSL/TLS certificate and setup HTTPS to secure client traffic to your Shaarli instance.
|
||||||
|
|
||||||
|
You can obtain a domain name from a [registrar](https://en.wikipedia.org/wiki/Domain_name_registrar) ([1](https://www.ovhcloud.com/en-gb/domains), [2](https://www.gandi.net/en/domain)), or from free subdomain providers ([1](https://freedns.afraid.org/)). If you don't have a domain name, please set up a private domain name ([FQDN](ttps://en.wikipedia.org/wiki/Fully_qualified_domain_name)) in your clients' [hosts files](https://en.wikipedia.org/wiki/Hosts_(file)) to access the server (direct access by IP address can result in unexpected behavior).
|
||||||
|
|
||||||
|
Setup a **firewall** (using `iptables`, [ufw](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-debian-10), [firewalld](https://firewalld.org/) or any frontend of your choice) to deny all incoming traffic except `tcp/80` and `tcp/443`, which are needed to access the web server (and any other posrts you might need, like SSH). If the server is in a private network behind a NAT, ensure these **ports are forwarded** to the server.
|
||||||
|
|
||||||
|
Shaarli makes outbound HTTP/HTTPS connections to websites you bookmark to fetch page information (title, thumbnails), the server must then have access to the Internet as well, and a working DNS resolver.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
### PHP
|
||||||
|
|
||||||
|
Supported PHP versions:
|
||||||
|
|
||||||
Version | Status | Shaarli compatibility
|
Version | Status | Shaarli compatibility
|
||||||
:---:|:---:|:---:
|
:---:|:---:|:---:
|
||||||
7.2 | Supported | Yes
|
8.2 | Supported | Yes
|
||||||
7.1 | Supported | Yes
|
8.1 | Supported | Yes
|
||||||
|
8.0 | EOL: 2023-11-26| Yes
|
||||||
|
7.4 | EOL: 2022-11-28 | Yes
|
||||||
|
7.3 | EOL: 2021-12-06 | Yes (up to Shaarli 0.12.2)
|
||||||
|
7.2 | EOL: 2020-11-30 | Yes (up to Shaarli 0.12.2)
|
||||||
|
7.1 | EOL: 2019-12-01 | Yes (up to Shaarli 0.12.2)
|
||||||
7.0 | EOL: 2018-12-03 | Yes (up to Shaarli 0.10.x)
|
7.0 | EOL: 2018-12-03 | Yes (up to Shaarli 0.10.x)
|
||||||
5.6 | EOL: 2018-12-31 | Yes (up to Shaarli 0.10.x)
|
5.6 | EOL: 2018-12-31 | Yes (up to Shaarli 0.10.x)
|
||||||
5.5 | EOL: 2016-07-10 | Yes
|
5.5 | EOL: 2016-07-10 | Yes
|
||||||
5.4 | EOL: 2015-09-14 | Yes (up to Shaarli 0.8.x)
|
5.4 | EOL: 2015-09-14 | Yes (up to Shaarli 0.8.x)
|
||||||
5.3 | EOL: 2014-08-14 | Yes (up to Shaarli 0.8.x)
|
5.3 | EOL: 2014-08-14 | Yes (up to Shaarli 0.8.x)
|
||||||
|
|
||||||
- The following PHP extensions are installed on the server:
|
Required PHP extensions:
|
||||||
|
|
||||||
Extension | Required? | Usage
|
Extension | Required? | Usage
|
||||||
---|:---:|---
|
---|:---:|---
|
||||||
[`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS
|
[`openssl`](https://www.php.net/manual/en/book.openssl.php) | required | OpenSSL, HTTPS
|
||||||
[`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows, some hosting providers | multibyte (Unicode) string support
|
[`php-json`](https://www.php.net/manual/en/book.json.php) | required | configuration parsing
|
||||||
[`php-gd`](http://php.net/manual/en/book.image.php) | optional | required to use thumbnails
|
[`php-simplexml`](https://www.php.net/manual/en/book.simplexml.php) | required | REST API (Slim framework)
|
||||||
[`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`)
|
[`php-mbstring`](https://www.php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows, some hosting providers | multibyte (Unicode) string support
|
||||||
[`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way
|
[`php-ctype`](https://www.php.net/manual/en/book.ctype.php) | required (bundled with most PHP installation) | Type checking
|
||||||
[`php-gettext`](http://php.net/manual/en/book.gettext.php) | optional | Use the translation system in gettext mode (faster)
|
[`php-iconv`](https://www.php.net/manual/en/book.iconv.php) | required (bundled with most PHP installation) | Character encoding used in translations
|
||||||
|
[`php-session`](https://www.php.net/manual/en/book.session.php) | required (bundled with most PHP installation) | User session
|
||||||
|
[`php-zlib`](https://www.php.net/manual/en/book.zlib.php) | required (bundled with most PHP installation) | Datastore I/O compression
|
||||||
|
[`php-gd`](https://www.php.net/manual/en/book.image.php) | optional | required to use thumbnails
|
||||||
|
[`php-intl`](https://www.php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`)
|
||||||
|
[`php-curl`](https://www.php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way
|
||||||
|
[`php-gettext`](https://www.php.net/manual/en/book.gettext.php) | optional | Use the translation system in gettext mode (faster)
|
||||||
|
[`php-ldap`](https://www.php.net/manual/en/book.ldap.php) | optional | LDAP login support
|
||||||
|
|
||||||
|
Some [plugins](Plugins.md) may require additional configuration.
|
||||||
|
|
||||||
|
- [PHP: Supported versions](https://www.php.net/supported-versions.php)
|
||||||
|
- [PHP: Unsupported versions (EOL/End-of-life)](https://www.php.net/eol.php)
|
||||||
|
- [PHP 7 Changelog](https://www.php.net/ChangeLog-7.php)
|
||||||
|
- [PHP 5 Changelog](https://www.php.net/ChangeLog-5.php)
|
||||||
|
- [PHP: Bugs](https://bugs.php.net/)
|
||||||
|
|
||||||
|
|
||||||
|
## SSL/TLS (HTTPS)
|
||||||
|
|
||||||
|
We recommend setting up [HTTPS](https://en.wikipedia.org/wiki/HTTPS) (SSL/[TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security)) on your webserver for secure communication between clients and the server.
|
||||||
|
|
||||||
|
### Let's Encrypt
|
||||||
|
|
||||||
|
For public-facing web servers this can be done using free SSL/TLS certificates from [Let's Encrypt](https://en.wikipedia.org/wiki/Let's_Encrypt), a non-profit certificate authority provididing free certificates.
|
||||||
|
|
||||||
|
- [How to secure Apache with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-debian-10)
|
||||||
|
- [How to secure Nginx with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-debian-10)
|
||||||
|
- [How To Use Certbot Standalone Mode to Retrieve Let's Encrypt SSL Certificates](https://www.digitalocean.com/community/tutorials/how-to-use-certbot-standalone-mode-to-retrieve-let-s-encrypt-ssl-certificates-on-debian-10).
|
||||||
|
|
||||||
|
In short:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# install certbot
|
||||||
|
sudo apt install certbot
|
||||||
|
|
||||||
|
# stop your webserver if you already have one running
|
||||||
|
# certbot in standalone mode needs to bind to port 80 (only needed on initial generation)
|
||||||
|
sudo systemctl stop apache2
|
||||||
|
sudo systemctl stop nginx
|
||||||
|
|
||||||
|
# generate initial certificates
|
||||||
|
# Let's Encrypt ACME servers must be able to access your server! port forwarding and firewall must be properly configured
|
||||||
|
sudo certbot certonly --standalone --noninteractive --agree-tos --email "admin@shaarli.mydomain.org" -d shaarli.mydomain.org
|
||||||
|
# this will generate a private key and certificate at /etc/letsencrypt/live/shaarli.mydomain.org/{privkey,fullchain}.pem
|
||||||
|
|
||||||
|
# restart the web server
|
||||||
|
sudo systemctl start apache2
|
||||||
|
sudo systemctl start nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
On apache `2.4.43+`, you can also delegate LE certificate management to [mod_md](https://httpd.apache.org/docs/2.4/mod/mod_md.html) [[1](https://www.cyberciti.biz/faq/how-to-secure-apache-with-mod_md-lets-encrypt-on-ubuntu-20-04-lts/)] in which case you don't need certbot and manual SSL configuration in virtualhosts.
|
||||||
|
|
||||||
|
### Self-signed
|
||||||
|
|
||||||
|
If you don't want to rely on a certificate authority, or the server can only be accessed from your own network, you can also generate self-signed certificates. Not that this will generate security warnings in web browsers/clients trying to access Shaarli:
|
||||||
|
|
||||||
|
- [How To Create a Self-Signed SSL Certificate for Apache](https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-apache-in-debian-10)
|
||||||
|
- [How To Create a Self-Signed SSL Certificate for Nginx](https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-nginx-on-debian-10)
|
||||||
|
- [How do I create my own Certificate Authority?](https://workaround.org/certificate-authority)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
### SSL/TLS configuration
|
## Examples
|
||||||
|
|
||||||
To setup HTTPS / SSL on your webserver (recommended), you must generate a public/private **key pair** and a **certificate**, and install, configure and activate the appropriate **webserver SSL extension**.
|
The following examples assume a Debian-based operating system is installed. On other distributions you may have to adapt details such as package installation procedures, configuration file locations, and webserver username/group (`www-data` or `httpd` are common values). In these examples we assume that the web server and the `php-fpm` PHP interpreter are running as the same user, and the document root for your web server/virtualhost is at `/var/www/shaarli.mydomain.org/`,:
|
||||||
|
|
||||||
#### Let's Encrypt
|
```bash
|
||||||
|
# create the document root (replace with your own domain name)
|
||||||
|
sudo mkdir -p /var/www/shaarli.mydomain.org/
|
||||||
|
```
|
||||||
|
|
||||||
[Let's Encrypt](https://en.wikipedia.org/wiki/Let%27s_Encrypt) is a certificate authority that provides free TLS/X.509 certificates via an automated process.
|
You can install Shaarli at the root of your virtualhost, or in a subdirectory as well. See [Directory structure](dev/Development.md#directory-structure)
|
||||||
|
|
||||||
* Install `certbot` using the appropriate method described on https://certbot.eff.org/.
|
|
||||||
|
|
||||||
Location of the `certbot` program and template configuration files may vary depending on which installation method was used. Change the file paths below accordingly. Here is an easy way to create a signed certificate using `certbot`, it assumes `certbot` was installed through APT on a Debian-based distribution:
|
### Apache
|
||||||
|
|
||||||
* Stop the apache2/nginx service.
|
```bash
|
||||||
* Run `certbot --agree-tos --standalone --preferred-challenges tls-sni --email "youremail@example.com" --domain yourdomain.example.com`
|
# Install apache + php-fpm
|
||||||
* For the Apache webserver, copy `/usr/lib/python2.7/dist-packages/certbot_apache/options-ssl-apache.conf` to `/etc/letsencrypt/options-ssl-apache.conf` (paths may vary depending on installation method)
|
sudo apt update
|
||||||
* For Nginx: TODO
|
sudo apt install apache2 libapache2-mod-md libapache2-mod-fcgid php8.2-fpm php8.2-mbstring php8.2-gd php8.2-intl php8.2-curl php8.2-gettext php8.2-ldap
|
||||||
* Setup your webserver as described below
|
|
||||||
* Restart the apache2/nginx service.
|
|
||||||
|
|
||||||
#### Self-signed certificates
|
# Enable required modules
|
||||||
|
sudo a2enmod ssl # SSL/TLS certificates https://httpd.apache.org/docs/current/mod/mod_ssl.html
|
||||||
|
sudo a2enmod rewrite # REST API support https://httpd.apache.org/docs/current/mod/mod_rewrite.html
|
||||||
|
sudo a2enmod headers # custom HTTP headers
|
||||||
|
|
||||||
If you don't want to request a certificate from Let's Encrypt, or are unable to (for example, webserver on a LAN, or domain name not registered in the public DNS system), you can generate a self-signed certificate. This certificate will trigger security warnings in web browsers, unless you add it to the browser's SSL store manually.
|
# Edit the virtualhost configuration file with your favorite editor (replace the example domain name)
|
||||||
|
sudo nano /etc/apache2/sites-available/shaarli.mydomain.org.conf
|
||||||
* Apache: run `make-ssl-cert generate-default-snakeoil --force-overwrite`
|
```
|
||||||
* Nginx: TODO
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Apache
|
|
||||||
|
|
||||||
Here is a basic configuration example for the Apache web server with `mod_php`.
|
|
||||||
|
|
||||||
In `/etc/apache2/sites-available/shaarli.conf`:
|
|
||||||
|
|
||||||
```apache
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName shaarli.mydomain.org
|
||||||
|
DocumentRoot /var/www/shaarli.mydomain.org/
|
||||||
|
|
||||||
|
# If using certbot or self-signed certificates:
|
||||||
|
# Redirect HTTP requests to HTTPS, except Let's Encrypt ACME challenge requests
|
||||||
|
RewriteEngine on
|
||||||
|
RewriteRule ^.well-known/acme-challenge/ - [L]
|
||||||
|
RewriteCond %{HTTP_HOST} =shaarli.mydomain.org
|
||||||
|
RewriteRule ^ https://shaarli.mydomain.org%{REQUEST_URI} [END,NE,R=permanent]
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
# If using mod_md:
|
||||||
|
MDomain shaarli.mydomain.org
|
||||||
|
MDCertificateAgreement accepted
|
||||||
|
MDContactEmail admin@shaarli.mydomain.org
|
||||||
|
MDPrivateKeys RSA 4096
|
||||||
|
|
||||||
<VirtualHost *:443>
|
<VirtualHost *:443>
|
||||||
ServerName shaarli.my-domain.org
|
ServerName shaarli.mydomain.org
|
||||||
DocumentRoot /absolute/path/to/shaarli/
|
DocumentRoot /var/www/shaarli.mydomain.org/
|
||||||
|
|
||||||
# Logging
|
|
||||||
# Possible values include: debug, info, notice, warn, error, crit, alert, emerg.
|
|
||||||
LogLevel warn
|
|
||||||
ErrorLog /var/log/apache2/shaarli-error.log
|
|
||||||
CustomLog /var/log/apache2/shaarli-access.log combined
|
|
||||||
|
|
||||||
# Let's Encrypt SSL configuration (recommended)
|
|
||||||
SSLEngine on
|
SSLEngine on
|
||||||
SSLCertificateFile /etc/letsencrypt/live/yourdomain.example.com/fullchain.pem
|
|
||||||
SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.example.com/privkey.pem
|
|
||||||
Include /etc/letsencrypt/options-ssl-apache.conf
|
|
||||||
|
|
||||||
# Self-signed SSL cert configuration
|
# If using certbot:
|
||||||
#SSLEngine on
|
SSLCertificateFile /etc/letsencrypt/live/shaarli.mydomain.org/fullchain.pem
|
||||||
#SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
SSLCertificateKeyFile /etc/letsencrypt/live/shaarli.mydomain.org/privkey.pem
|
||||||
#SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
|
||||||
|
# If using self-signed certificates:
|
||||||
|
SSLEngine on
|
||||||
|
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||||
|
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
||||||
|
|
||||||
# Optional, log PHP errors, useful for debugging
|
# Optional, log PHP errors, useful for debugging
|
||||||
#php_flag log_errors on
|
#php_flag log_errors on
|
||||||
|
@ -98,345 +185,261 @@ In `/etc/apache2/sites-available/shaarli.conf`:
|
||||||
#php_value error_reporting 2147483647
|
#php_value error_reporting 2147483647
|
||||||
#php_value error_log /var/log/apache2/shaarli-php-error.log
|
#php_value error_log /var/log/apache2/shaarli-php-error.log
|
||||||
|
|
||||||
<Directory /absolute/path/to/shaarli/>
|
<FilesMatch \.php$>
|
||||||
#Required for .htaccess support
|
SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
<Directory /var/www/shaarli.mydomain.org/>
|
||||||
|
# Required for .htaccess support
|
||||||
AllowOverride All
|
AllowOverride All
|
||||||
Order allow,deny
|
Require all granted
|
||||||
Allow from all
|
|
||||||
|
|
||||||
Options Indexes FollowSymLinks MultiViews #TODO is Indexes/Multiviews required?
|
|
||||||
|
|
||||||
# Optional - required for playvideos plugin
|
|
||||||
#Header set Content-Security-Policy "script-src 'self' 'unsafe-inline' https://www.youtube.com https://s.ytimg.com 'unsafe-eval'"
|
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
|
<Directory /var/www/shaarli.mydomain.org/doc/html/>
|
||||||
|
DirectoryIndex index.html
|
||||||
|
<FilesMatch ".*\.html">
|
||||||
|
Require all granted
|
||||||
|
</FilesMatch>
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<FilesMatch ".*\.(?!(ico|css|js|gif|jpe?g|png|ttf|oet|woff2?)$)[^\.]*$">
|
||||||
|
Require all denied
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
DirectoryIndex index.php
|
||||||
|
|
||||||
|
<Files "index.php">
|
||||||
|
Require all granted
|
||||||
|
</Files>
|
||||||
|
|
||||||
|
<FilesMatch "\.(?:ico|css|js|gif|jpe?g|png|ttf|oet|woff2)$">
|
||||||
|
# allow client-side caching of static files
|
||||||
|
Header set Cache-Control "max-age=2628000, public, must-revalidate, proxy-revalidate"
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
<FilesMatch "robots\.txt">
|
||||||
|
Require all granted
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# serve the Shaarli favicon from its custom location
|
||||||
|
Alias favicon.ico /var/www/shaarli.mydomain.org/images/favicon.ico
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
```
|
```
|
||||||
|
|
||||||
Enable this configuration with `sudo a2ensite shaarli`
|
```bash
|
||||||
|
# Enable the virtualhost
|
||||||
|
sudo a2ensite shaarli.mydomain.org
|
||||||
|
|
||||||
_Note: If you use Apache 2.2 or lower, you need [mod_version](https://httpd.apache.org/docs/current/mod/mod_version.html) to be installed and enabled._
|
# Restart the apache service
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
```
|
||||||
|
|
||||||
_Note: Apache module `mod_rewrite` must be enabled to use the REST API._
|
- [How to install the Apache web server](https://www.digitalocean.com/community/tutorials/how-to-install-the-apache-web-server-on-debian-10)
|
||||||
|
- [Apache/PHP - error log per VirtualHost - StackOverflow](https://stackoverflow.com/questions/176/error-log-per-virtual-host)
|
||||||
|
- [Apache - PHP: php_value vs php_admin_value and the use of php_flag explained](https://ma.ttias.be/php-php_value-vs-php_admin_value-and-the-use-of-php_flag-explained/)
|
||||||
|
- [Server-side TLS (Apache) - Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS)
|
||||||
|
- [Apache 2.4 documentation](https://httpd.apache.org/docs/2.4/)
|
||||||
|
- [Apache mod_proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html)
|
||||||
|
- [Apache Reverse Proxy Request Headers](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers)
|
||||||
|
|
||||||
|
|
||||||
## Nginx
|
### Nginx
|
||||||
|
|
||||||
Here is a basic configuration example for the Nginx web server, using the [php-fpm](http://php-fpm.org) PHP FastCGI Process Manager, and Nginx's [FastCGI](https://en.wikipedia.org/wiki/FastCGI) module.
|
```bash
|
||||||
|
# Install nginx and php-fpm
|
||||||
|
sudo apt install nginx php-fpm
|
||||||
|
|
||||||
<!--- TODO refactor everything below this point --->
|
# Edit the virtualhost configuration file with your favorite editor
|
||||||
|
sudo nano /etc/nginx/sites-available/shaarli.mydomain.org
|
||||||
### Common setup
|
|
||||||
Once Nginx and PHP-FPM are installed, we need to ensure:
|
|
||||||
|
|
||||||
- Nginx and PHP-FPM are running using the _same user and group_
|
|
||||||
- both these user and group have
|
|
||||||
- `read` permissions for Shaarli resources
|
|
||||||
- `execute` permissions for Shaarli directories _AND_ their parent directories
|
|
||||||
|
|
||||||
On a production server:
|
|
||||||
|
|
||||||
- `user:group` will likely be `http:http`, `www:www` or `www-data:www-data`
|
|
||||||
- files will be located under `/var/www`, `/var/http` or `/usr/share/nginx`
|
|
||||||
|
|
||||||
On a development server:
|
|
||||||
|
|
||||||
- files may be located in a user's home directory
|
|
||||||
- in this case, make sure both Nginx and PHP-FPM are running as the local user/group!
|
|
||||||
|
|
||||||
For all following configuration examples, this user/group pair will be used:
|
|
||||||
|
|
||||||
- `user:group = john:users`,
|
|
||||||
|
|
||||||
which corresponds to the following service configuration:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
; /etc/php/php-fpm.conf
|
|
||||||
user = john
|
|
||||||
group = users
|
|
||||||
|
|
||||||
[...]
|
|
||||||
listen.owner = john
|
|
||||||
listen.group = users
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
# /etc/nginx/nginx.conf
|
server {
|
||||||
user john users;
|
listen 80;
|
||||||
|
server_name shaarli.mydomain.org;
|
||||||
|
|
||||||
http {
|
# redirect all plain HTTP requests to HTTPS
|
||||||
[...]
|
return 301 https://shaarli.mydomain.org$request_uri;
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### (Optional) Increase the maximum file upload size
|
|
||||||
Some bookmark dumps generated by web browsers can be _huge_ due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.
|
|
||||||
|
|
||||||
To increase upload size, you will need to modify both nginx and PHP configuration:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# /etc/nginx/nginx.conf
|
|
||||||
|
|
||||||
http {
|
|
||||||
[...]
|
|
||||||
|
|
||||||
client_max_body_size 10m;
|
|
||||||
|
|
||||||
[...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ini
|
|
||||||
# /etc/php5/fpm/php.ini
|
|
||||||
|
|
||||||
[...]
|
|
||||||
post_max_size = 10M
|
|
||||||
[...]
|
|
||||||
upload_max_filesize = 10M
|
|
||||||
```
|
|
||||||
|
|
||||||
### Minimal
|
|
||||||
_WARNING: Use for development only!_
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
user john users;
|
|
||||||
worker_processes 1;
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
server {
|
||||||
include mime.types;
|
# ipv4 listening port/protocol
|
||||||
default_type application/octet-stream;
|
listen 443 ssl http2;
|
||||||
keepalive_timeout 20;
|
# ipv6 listening port/protocol
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name shaarli.mydomain.org;
|
||||||
|
root /var/www/shaarli.mydomain.org;
|
||||||
|
|
||||||
index index.html index.php;
|
# log file locations
|
||||||
|
# combined log format prepends the virtualhost/domain name to log entries
|
||||||
server {
|
access_log /var/log/nginx/access.log combined;
|
||||||
listen 80;
|
|
||||||
server_name localhost;
|
|
||||||
root /home/john/web;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log;
|
|
||||||
error_log /var/log/nginx/error.log;
|
|
||||||
|
|
||||||
location /shaarli/ {
|
|
||||||
try_files $uri /shaarli/index.php$is_args$args;
|
|
||||||
access_log /var/log/nginx/shaarli.access.log;
|
|
||||||
error_log /var/log/nginx/shaarli.error.log;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ (index)\.php$ {
|
|
||||||
try_files $uri =404;
|
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
||||||
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
include fastcgi.conf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modular
|
|
||||||
The previous setup is sufficient for development purposes, but has several major caveats:
|
|
||||||
|
|
||||||
- every content that does not match the PHP rule will be sent to client browsers:
|
|
||||||
- dotfiles - in our case, `.htaccess`
|
|
||||||
- temporary files, e.g. Vim or Emacs files: `index.php~`
|
|
||||||
- asset / static resource caching is not optimized
|
|
||||||
- if serving several PHP sites, there will be a lot of duplication: `location /shaarli/`, `location /mysite/`, etc.
|
|
||||||
|
|
||||||
To solve this, we will split Nginx configuration in several parts, that will be included when needed:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# /etc/nginx/deny.conf
|
|
||||||
location ~ /\. {
|
|
||||||
# deny access to dotfiles
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ~$ {
|
|
||||||
# deny access to temp editor files, e.g. "script.php~"
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# /etc/nginx/php.conf
|
|
||||||
location ~ (index)\.php$ {
|
|
||||||
# Slim - split URL path into (script_filename, path_info)
|
|
||||||
try_files $uri =404;
|
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
||||||
|
|
||||||
# filter and proxy PHP requests to PHP-FPM
|
|
||||||
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
include fastcgi.conf;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ \.php$ {
|
|
||||||
# deny access to all other PHP scripts
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# /etc/nginx/static_assets.conf
|
|
||||||
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
|
|
||||||
expires max;
|
|
||||||
add_header Pragma public;
|
|
||||||
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# /etc/nginx/nginx.conf
|
|
||||||
[...]
|
|
||||||
|
|
||||||
http {
|
|
||||||
[...]
|
|
||||||
|
|
||||||
root /home/john/web;
|
|
||||||
access_log /var/log/nginx/access.log;
|
|
||||||
error_log /var/log/nginx/error.log;
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
server {
|
# paths to private key and certificates for SSL/TLS
|
||||||
# virtual host for a first domain
|
ssl_certificate /etc/ssl/shaarli.mydomain.org.crt;
|
||||||
listen 80;
|
ssl_certificate_key /etc/ssl/private/shaarli.mydomain.org.key;
|
||||||
server_name my.first.domain.org;
|
|
||||||
|
|
||||||
location /shaarli/ {
|
# Let's Encrypt SSL settings from https://github.com/certbot/certbot/blob/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf
|
||||||
# Slim - rewrite URLs
|
ssl_session_cache shared:le_nginx_SSL:10m;
|
||||||
try_files $uri /shaarli/index.php$is_args$args;
|
ssl_session_timeout 1440m;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_prefer_server_ciphers off;
|
||||||
|
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
|
||||||
|
|
||||||
access_log /var/log/nginx/shaarli.access.log;
|
# increase the maximum file upload size if needed: by default nginx limits file upload to 1MB (413 Entity Too Large error)
|
||||||
error_log /var/log/nginx/shaarli.error.log;
|
client_max_body_size 100m;
|
||||||
}
|
|
||||||
|
|
||||||
location = /shaarli/favicon.ico {
|
# relative path to shaarli from the root of the webserver
|
||||||
# serve the Shaarli favicon from its custom location
|
# if shaarli is installed in a subdirectory of the main domain, edit the location accordingly
|
||||||
alias /var/www/shaarli/images/favicon.ico;
|
location / {
|
||||||
}
|
# default index file when no file URI is requested
|
||||||
|
index index.php;
|
||||||
include deny.conf;
|
try_files _ /index.php$is_args$args;
|
||||||
include static_assets.conf;
|
|
||||||
include php.conf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
location ~ (index)\.php$ {
|
||||||
# virtual host for a second domain
|
try_files $uri =404;
|
||||||
listen 80;
|
# slim API - split URL path into (script_filename, path_info)
|
||||||
server_name second.domain.com;
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
# pass PHP requests to PHP-FPM
|
||||||
|
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
include fastcgi.conf;
|
||||||
|
}
|
||||||
|
|
||||||
location /minigal/ {
|
location ~ /doc/html/ {
|
||||||
access_log /var/log/nginx/minigal.access.log;
|
default_type "text/html";
|
||||||
error_log /var/log/nginx/minigal.error.log;
|
try_files $uri $uri/ $uri.html =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
include deny.conf;
|
location = /favicon.ico {
|
||||||
include static_assets.conf;
|
# serve the Shaarli favicon from its custom location
|
||||||
include php.conf;
|
alias /var/www/shaarli/images/favicon.ico;
|
||||||
|
}
|
||||||
|
|
||||||
|
# allow client-side caching of static files
|
||||||
|
location ~* \.(?:ico|css|js|gif|jpe?g|png|ttf|oet|woff2?)$ {
|
||||||
|
expires max;
|
||||||
|
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
|
||||||
|
# HTTP 1.0 compatibility
|
||||||
|
add_header Pragma public;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Redirect HTTP to HTTPS
|
```bash
|
||||||
Assuming you have generated a (self-signed) key and certificate, and they are
|
# enable the configuration/virtualhost
|
||||||
located under `/home/john/ssl/localhost.{key,crt}`, it is pretty straightforward
|
sudo ln -s /etc/nginx/sites-available/shaarli.mydomain.org /etc/nginx/sites-enabled/shaarli.mydomain.org
|
||||||
to set an HTTP (:80) to HTTPS (:443) redirection to force SSL/TLS usage.
|
# reload nginx configuration
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
```nginx
|
- [How to install the Nginx web server](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-debian-10)
|
||||||
# /etc/nginx/nginx.conf
|
- [Nginx Beginner's guide](https://nginx.org/en/docs/beginners_guide.html)
|
||||||
|
- [Nginx documentation](https://nginx.org/en/docs/)
|
||||||
|
- [Nginx ngx_http_fastcgi_module](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html)
|
||||||
|
- [Nginx Pitfalls](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/)
|
||||||
|
- [Server-side TLS (Nginx) - Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS)
|
||||||
|
|
||||||
|
|
||||||
|
## Reverse proxies
|
||||||
|
|
||||||
|
If Shaarli is hosted on a server behind a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) (i.e. there is a proxy server between clients and the web server hosting Shaarli), configure it accordingly. See [Reverse proxy](Reverse-proxy.md) configuration.
|
||||||
|
|
||||||
|
|
||||||
|
## Using Shaarli without URL rewriting
|
||||||
|
|
||||||
|
By default, Shaarli uses Slim framework's URL, which requires URL rewriting.
|
||||||
|
|
||||||
|
If you can't use URL rewriting for any reason (not supported by your web server, shared hosting, etc.), you *can* use Shaarli without URL rewriting.
|
||||||
|
|
||||||
|
You just need to prefix your URL by `/index.php/`. Example: instead of accessing `https://shaarli.mydomain.org/`, use `https://shaarli.mydomain.org/index.php/`.
|
||||||
|
|
||||||
|
**Recommended:**
|
||||||
|
* after installation, in the configuration page, set your header link to `/index.php/`.
|
||||||
|
* in your configuration file `config.json.php` set `general.root_url` to `https://shaarli.mydomain.org/index.php/`.
|
||||||
|
|
||||||
|
|
||||||
|
## Allow import of large browser bookmarks export
|
||||||
|
|
||||||
|
Web browser bookmark exports can be large due to the presence of base64-encoded images and favicons/long subfolder names. Edit the PHP configuration file.
|
||||||
|
|
||||||
|
- Apache: `/etc/php/<PHP_VERSION>/apache2/php.ini`
|
||||||
|
- Nginx + PHP-FPM: `/etc/php/<PHP_VERSION>/fpm/php.ini` (in addition to `client_max_body_size` in the [Nginx configuration](#nginx))
|
||||||
|
|
||||||
|
```ini
|
||||||
[...]
|
[...]
|
||||||
|
# (optional) increase the maximum file upload size:
|
||||||
http {
|
post_max_size = 100M
|
||||||
[...]
|
[...]
|
||||||
|
# (optional) increase the maximum file upload size:
|
||||||
index index.html index.php;
|
upload_max_filesize = 100M
|
||||||
|
|
||||||
root /home/john/web;
|
|
||||||
access_log /var/log/nginx/access.log;
|
|
||||||
error_log /var/log/nginx/error.log;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
return 301 https://localhost$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
ssl_certificate /home/john/ssl/localhost.crt;
|
|
||||||
ssl_certificate_key /home/john/ssl/localhost.key;
|
|
||||||
|
|
||||||
location /shaarli/ {
|
|
||||||
# Slim - rewrite URLs
|
|
||||||
try_files $uri /index.php$is_args$args;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/shaarli.access.log;
|
|
||||||
error_log /var/log/nginx/shaarli.error.log;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /shaarli/favicon.ico {
|
|
||||||
# serve the Shaarli favicon from its custom location
|
|
||||||
alias /var/www/shaarli/images/favicon.ico;
|
|
||||||
}
|
|
||||||
|
|
||||||
include deny.conf;
|
|
||||||
include static_assets.conf;
|
|
||||||
include php.conf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Proxies
|
To verify PHP settings currently set on the server, create a `phpinfo.php` in your webserver's document root
|
||||||
|
|
||||||
If Shaarli is served behind a proxy (i.e. there is a proxy server between clients and the web server hosting Shaarli), please refer to the proxy server documentation for proper configuration. In particular, you have to ensure that the following server variables are properly set:
|
```bash
|
||||||
|
# example
|
||||||
|
echo '<?php phpinfo(); ?>' | sudo tee /var/www/shaarli.mydomain.org/phpinfo.php
|
||||||
|
#give read-only access to this file to the webserver user
|
||||||
|
sudo chown www-data:root /var/www/shaarli.mydomain.org/phpinfo.php
|
||||||
|
sudo chmod 0400 /var/www/shaarli.mydomain.org/phpinfo.php
|
||||||
|
```
|
||||||
|
|
||||||
- `X-Forwarded-Proto`
|
Access the file from a web browser (eg. <https://shaarli.mydomain.org/phpinfo.php> and look at the _Loaded Configuration File_ and _Scan this dir for additional .ini files_ entries
|
||||||
- `X-Forwarded-Host`
|
|
||||||
- `X-Forwarded-For`
|
|
||||||
|
|
||||||
In you [Shaarli configuration](Shaarli-configuration) `data/config.json.php`, add the public IP of your proxy under `security.trusted_proxies`.
|
It is recommended to remove the `phpinfo.php` when no longer needed as it publicly discloses details about your webserver configuration.
|
||||||
|
|
||||||
See also [proxy-related](https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&q=label%3Aproxy+) issues.
|
|
||||||
|
|
||||||
## Robots and crawlers
|
## Robots and crawlers
|
||||||
|
|
||||||
Shaarli disallows indexing and crawling of your local documentation pages by search engines, using `<meta name="robots">` HTML tags.
|
To opt-out of indexing your Shaarli instance by search engines, create a `robots.txt` file at the root of your virtualhost:
|
||||||
Your Shaarli instance and other pages you host may still be indexed by various robots on the public Internet.
|
|
||||||
You may want to setup a robots.txt file or other crawler control mechanism on your server.
|
|
||||||
See [[1]](https://en.wikipedia.org/wiki/Robots_exclusion_standard), [[2]](https://support.google.com/webmasters/answer/6062608?hl=en) and [[3]](https://developers.google.com/search/reference/robots_meta_tag)
|
|
||||||
|
|
||||||
## See also
|
```
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
```
|
||||||
|
|
||||||
* [Server security](Server-security.md)
|
By default Shaarli already disallows indexing of your local copy of the documentation by default, using `<meta name="robots">` HTML tags. Your Shaarli instance may still be indexed by various robots on the public Internet, that do not respect this header or the robots standard.
|
||||||
|
|
||||||
#### Webservers
|
- [Robots exclusion standard](https://en.wikipedia.org/wiki/Robots_exclusion_standard)
|
||||||
|
- [Introduction to robots.txt](https://developers.google.com/search/docs/crawling-indexing/robots/intro?hl=en)
|
||||||
|
- [Robots meta tag, data-nosnippet, and X-Robots-Tag specifications](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag)
|
||||||
|
- [About robots.txt](https://www.robotstxt.org)
|
||||||
|
- [About the robots META tag](https://www.robotstxt.org/meta.html)
|
||||||
|
|
||||||
- [Apache/PHP - error log per VirtualHost](http://stackoverflow.com/q/176) (StackOverflow)
|
|
||||||
- [Apache - PHP: php_value vs php_admin_value and the use of php_flag explained](https://ma.ttias.be/php-php_value-vs-php_admin_value-and-the-use-of-php_flag-explained/)
|
|
||||||
- [Server-side TLS (Apache)](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache) (Mozilla)
|
|
||||||
- [Nginx Beginner's guide](http://nginx.org/en/docs/beginners_guide.html)
|
|
||||||
- [Nginx ngx_http_fastcgi_module](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html)
|
|
||||||
- [Nginx Pitfalls](http://wiki.nginx.org/Pitfalls)
|
|
||||||
- [Nginx PHP configuration examples](http://kbeezie.com/nginx-configuration-examples/) (Karl Blessing)
|
|
||||||
- [Server-side TLS (Nginx)](https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx) (Mozilla)
|
|
||||||
- [How to Create Self-Signed SSL Certificates with OpenSSL](http://www.xenocafe.com/tutorials/linux/centos/openssl/self_signed_certificates/index.php)
|
|
||||||
- [How do I create my own Certificate Authority?](https://workaround.org/certificate-authority)
|
|
||||||
|
|
||||||
#### PHP
|
## Fail2ban
|
||||||
|
|
||||||
- [Travis configuration](https://github.com/shaarli/Shaarli/blob/master/.travis.yml)
|
[fail2ban](https://github.com/fail2ban/fail2ban) is an intrusion prevention framework that reads server (Apache, SSH, etc.) and uses `iptables` profiles to block brute-force attempts. You need to create a filter to detect shaarli login failures in logs, and a jail configuation to configure the behavior when failed login attempts are detected:
|
||||||
- [PHP: Supported versions](http://php.net/supported-versions.php)
|
|
||||||
- [PHP: Unsupported versions](http://php.net/eol.php) _(EOL - End Of Life)_
|
```ini
|
||||||
- [PHP 7 Changelog](http://php.net/ChangeLog-7.php)
|
# /etc/fail2ban/filter.d/shaarli-auth.conf
|
||||||
- [PHP 5 Changelog](http://php.net/ChangeLog-5.php)
|
[INCLUDES]
|
||||||
- [PHP: Bugs](https://bugs.php.net/)
|
before = common.conf
|
||||||
|
[Definition]
|
||||||
|
failregex = \s-\s<HOST>\s-\sLogin failed for user.*$
|
||||||
|
ignoreregex =
|
||||||
|
```
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/fail2ban/jail.local
|
||||||
|
[shaarli-auth]
|
||||||
|
enabled = true
|
||||||
|
port = https,http
|
||||||
|
filter = shaarli-auth
|
||||||
|
logpath = /var/www/shaarli.mydomain.org/data/log.txt
|
||||||
|
# allow 3 login attempts per IP address
|
||||||
|
# (over a period specified by findtime = in /etc/fail2ban/jail.conf)
|
||||||
|
maxretry = 3
|
||||||
|
# permanently ban the IP address after reaching the limit
|
||||||
|
bantime = -1
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart the service: `sudo systemctl restart fail2ban`
|
||||||
|
|
||||||
|
|
||||||
|
## What next?
|
||||||
|
|
||||||
|
[Shaarli installation](Installation.md)
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
## php.ini
|
|
||||||
PHP settings are defined in:
|
|
||||||
|
|
||||||
- a main configuration file, usually found under `/etc/php5/php.ini`; some distributions provide different configuration environments, e.g.
|
|
||||||
- `/etc/php5/php.ini` - used when running console scripts
|
|
||||||
- `/etc/php5/apache2/php.ini` - used when a client requests PHP resources from Apache
|
|
||||||
- `/etc/php5/php-fpm.conf` - used when PHP requests are proxied to PHP-FPM
|
|
||||||
- additional configuration files/entries, depending on the installed/enabled extensions:
|
|
||||||
- `/etc/php/conf.d/xdebug.ini`
|
|
||||||
|
|
||||||
### Locate .ini files
|
|
||||||
#### Console environment
|
|
||||||
```bash
|
|
||||||
$ php --ini
|
|
||||||
Configuration File (php.ini) Path: /etc/php
|
|
||||||
Loaded Configuration File: /etc/php/php.ini
|
|
||||||
Scan for additional .ini files in: /etc/php/conf.d
|
|
||||||
Additional .ini files parsed: /etc/php/conf.d/xdebug.ini
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Server environment
|
|
||||||
- create a `phpinfo.php` script located in a path supported by the web server, e.g.
|
|
||||||
- Apache (with user dirs enabled): `/home/myself/public_html/phpinfo.php`
|
|
||||||
- `/var/www/test/phpinfo.php`
|
|
||||||
- make sure the script is readable by the web server user/group (usually, `www`, `www-data` or `httpd`)
|
|
||||||
- access the script from a web browser
|
|
||||||
- look at the _Loaded Configuration File_ and _Scan this dir for additional .ini files_ entries
|
|
||||||
```php
|
|
||||||
<?php phpinfo(); ?>
|
|
||||||
```
|
|
||||||
|
|
||||||
## fail2ban
|
|
||||||
`fail2ban` is an intrusion prevention framework that reads server (Apache, SSH, etc.) and uses `iptables` profiles to block brute-force attempts:
|
|
||||||
|
|
||||||
- [Official website](http://www.fail2ban.org/wiki/index.php/Main_Page)
|
|
||||||
- [Source code](https://github.com/fail2ban/fail2ban)
|
|
||||||
|
|
||||||
### Read Shaarli logs to ban IPs
|
|
||||||
Example configuration:
|
|
||||||
- allow 3 login attempts per IP address
|
|
||||||
- after 3 failures, permanently ban the corresponding IP adddress
|
|
||||||
|
|
||||||
`/etc/fail2ban/jail.local`
|
|
||||||
```ini
|
|
||||||
[shaarli-auth]
|
|
||||||
enabled = true
|
|
||||||
port = https,http
|
|
||||||
filter = shaarli-auth
|
|
||||||
logpath = /var/www/path/to/shaarli/data/log.txt
|
|
||||||
maxretry = 3
|
|
||||||
bantime = -1
|
|
||||||
```
|
|
||||||
|
|
||||||
`/etc/fail2ban/filter.d/shaarli-auth.conf`
|
|
||||||
```ini
|
|
||||||
[INCLUDES]
|
|
||||||
before = common.conf
|
|
||||||
[Definition]
|
|
||||||
failregex = \s-\s<HOST>\s-\sLogin failed for user.*$
|
|
||||||
ignoreregex =
|
|
||||||
```
|
|
||||||
|
|
||||||
## Robots - Restricting search engines and web crawler traffic
|
|
||||||
|
|
||||||
Creating a `robots.txt` with the following contents at the root of your Shaarli installation will prevent _honest_ web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic.
|
|
||||||
|
|
||||||
```
|
|
||||||
User-agent: *
|
|
||||||
Disallow: /
|
|
||||||
```
|
|
||||||
|
|
||||||
See:
|
|
||||||
|
|
||||||
- http://www.robotstxt.org
|
|
||||||
- http://www.robotstxt.org/robotstxt.html
|
|
||||||
- http://www.robotstxt.org/meta.html
|
|
|
@ -1,130 +1,28 @@
|
||||||
## Foreword
|
# Shaarli configuration
|
||||||
|
|
||||||
|
Once your Shaarli instance is installed, the file `data/config.json.php` is generated:
|
||||||
|
|
||||||
|
- it contains all settings in JSON format, and can be edited to customize values
|
||||||
|
- it defines which [plugins](Plugins.md) are enabled
|
||||||
|
- its values override those defined in `index.php`
|
||||||
|
- it is wrapped in a PHP comment so that its contents are never served by the web server, regardless of configuration
|
||||||
|
|
||||||
**Do not edit configuration options in index.php! Your changes would be lost.**
|
**Do not edit configuration options in index.php! Your changes would be lost.**
|
||||||
|
|
||||||
Once your Shaarli instance is installed, the file `data/config.json.php` is generated:
|
## Tools menu
|
||||||
* it contains all settings in JSON format, and can be edited to customize values
|
|
||||||
* it defines which [plugins](Plugin-System) are enabled
|
|
||||||
* its values override those defined in `index.php`
|
|
||||||
* it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration
|
|
||||||
|
|
||||||
## File and directory permissions
|
Some settings can be configured directly from a web browser by accesing the `Tools` menu. Values are read/written to/from the configuration file.
|
||||||
|
|
||||||
The server process running Shaarli must have:
|
![](https://i.imgur.com/boaaibC.png)
|
||||||
|
|
||||||
- `read` access to the following resources:
|
### LDAP
|
||||||
- PHP scripts: `index.php`, `application/*.php`, `plugins/*.php`
|
|
||||||
- 3rd party PHP and Javascript libraries: `inc/*.php`, `inc/*.js`
|
|
||||||
- static assets:
|
|
||||||
- CSS stylesheets: `inc/*.css`
|
|
||||||
- `images/*`
|
|
||||||
- RainTPL templates: `tpl/*.html`
|
|
||||||
- `read`, `write` and `execution` access to the following directories:
|
|
||||||
- `cache` - thumbnail cache
|
|
||||||
- `data` - link data store, configuration options
|
|
||||||
- `pagecache` - Atom/RSS feed cache
|
|
||||||
- `tmp` - RainTPL page cache
|
|
||||||
|
|
||||||
On a Linux distribution:
|
- **host**: LDAP host used for user authentication
|
||||||
|
- **dn**: user DN template (`sprintf` format, `%s` being replaced by user login)
|
||||||
- the web server user will likely be `www` or `http` (for Apache2)
|
|
||||||
- it will be a member of a group of the same name: `www:www`, `http:http`
|
|
||||||
- to give it access to Shaarli, either:
|
|
||||||
- unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner
|
|
||||||
- put users in the same group as the web server, and set the appropriate access rights
|
|
||||||
- if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
In `data/config.json.php`.
|
|
||||||
|
|
||||||
See also [Plugin System](Plugin-System).
|
|
||||||
|
|
||||||
### Credentials
|
|
||||||
|
|
||||||
_These settings should not be edited_
|
|
||||||
|
|
||||||
- **login**: Login username.
|
|
||||||
- **hash**: Generated password hash.
|
|
||||||
- **salt**: Password salt.
|
|
||||||
|
|
||||||
### General
|
|
||||||
|
|
||||||
- **title**: Shaarli's instance title.
|
|
||||||
- **header_link**: Link to the homepage.
|
|
||||||
- **links_per_page**: Number of shaares displayed per page.
|
|
||||||
- **timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php).
|
|
||||||
- **enabled_plugins**: List of enabled plugins.
|
|
||||||
- **default_note_title**: Default title of a new note.
|
|
||||||
- **retrieve_description** (boolean): If set to true, for every new links Shaarli will try
|
|
||||||
to retrieve the description and keywords from the HTML meta tags.
|
|
||||||
|
|
||||||
### Security
|
|
||||||
|
|
||||||
- **session_protection_disabled**: Disable session cookie hijacking protection (not recommended).
|
|
||||||
It might be useful if your IP adress often changes.
|
|
||||||
- **ban_after**: Failed login attempts before being IP banned.
|
|
||||||
- **ban_duration**: IP ban duration in seconds.
|
|
||||||
- **open_shaarli**: Anyone can add a new link while logged out if enabled.
|
|
||||||
- **trusted_proxies**: List of trusted IP which won't be banned after failed login attemps. Useful if Shaarli is behind a reverse proxy.
|
|
||||||
- **allowed_protocols**: List of allowed protocols in shaare URLs or markdown-rendered descriptions. Useful if you want to store `javascript:` links (bookmarklets) in Shaarli (default: `["ftp", "ftps", "magnet"]`).
|
|
||||||
|
|
||||||
### Resources
|
|
||||||
|
|
||||||
- **data_dir**: Data directory.
|
|
||||||
- **datastore**: Shaarli's links database file path.
|
|
||||||
- **history**: Shaarli's operation history file path.
|
|
||||||
- **updates**: File path for the ran updates file.
|
|
||||||
- **log**: Log file path.
|
|
||||||
- **update_check**: Last update check file path.
|
|
||||||
- **raintpl_tpl**: Templates directory.
|
|
||||||
- **raintpl_tmp**: Template engine cache directory.
|
|
||||||
- **thumbnails_cache**: Thumbnails cache directory.
|
|
||||||
- **page_cache**: Shaarli's internal cache directory.
|
|
||||||
- **ban_file**: Banned IP file path.
|
|
||||||
|
|
||||||
### Translation
|
|
||||||
|
|
||||||
- **language**: translation language (also see [Translations](Translations))
|
|
||||||
- **auto** (default): The translation language is chosen from the browser locale.
|
|
||||||
It means that the language can be different for 2 different visitors depending on their locale.
|
|
||||||
- **en**: Use the English translation.
|
|
||||||
- **fr**: Use the French translation.
|
|
||||||
- **mode**:
|
|
||||||
- **auto** or **php** (default): Use the PHP implementation of gettext (slower)
|
|
||||||
- **gettext**: Use PHP builtin gettext extension
|
|
||||||
(faster, but requires `php-gettext` to be installed and to reload the web server on update)
|
|
||||||
- **extension**: Translation extensions for custom themes or plugins.
|
|
||||||
Must be an associative array: `translation domain => translation path`.
|
|
||||||
|
|
||||||
### Updates
|
|
||||||
|
|
||||||
- **check_updates**: Enable or disable update check to the git repository.
|
|
||||||
- **check_updates_branch**: Git branch used to check updates (e.g. `stable` or `master`).
|
|
||||||
- **check_updates_interval**: Look for new version every N seconds (default: every day).
|
|
||||||
|
|
||||||
### Privacy
|
|
||||||
|
|
||||||
- **default_private_links**: Check the private checkbox by default for every new link.
|
|
||||||
- **hide_public_links**: All links are hidden while logged out.
|
|
||||||
- **force_login**: if **hide_public_links** and this are set to `true`, all anonymous users are redirected to the login page.
|
|
||||||
- **hide_timestamps**: Timestamps are hidden.
|
|
||||||
- **remember_user_default**: Default state of the login page's *remember me* checkbox
|
|
||||||
- `true`: checked by default, `false`: unchecked by default
|
|
||||||
|
|
||||||
### Feed
|
|
||||||
|
|
||||||
- **rss_permalinks**: Enable this to redirect RSS links to Shaarli's permalinks instead of shaared URL.
|
|
||||||
- **show_atom**: Display ATOM feed button.
|
|
||||||
|
|
||||||
### Thumbnail
|
|
||||||
|
|
||||||
- **enable_thumbnails**: Enable or disable thumbnail display.
|
|
||||||
- **enable_localcache**: Enable or disable local cache.
|
|
||||||
|
|
||||||
## Configuration file example
|
## Configuration file example
|
||||||
|
|
||||||
```json
|
```
|
||||||
<?php /*
|
<?php /*
|
||||||
{
|
{
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
@ -164,10 +62,10 @@ Must be an associative array: `translation domain => translation path`.
|
||||||
"rss_permalinks": true,
|
"rss_permalinks": true,
|
||||||
"links_per_page": 20,
|
"links_per_page": 20,
|
||||||
"default_private_links": true,
|
"default_private_links": true,
|
||||||
"enable_thumbnails": true,
|
|
||||||
"enable_localcache": true,
|
|
||||||
"check_updates_branch": "stable",
|
"check_updates_branch": "stable",
|
||||||
"check_updates_interval": 86400,
|
"check_updates_interval": 86400,
|
||||||
|
"download_max_size": 4194304,
|
||||||
|
"download_timeout": 30,
|
||||||
"enabled_plugins": [
|
"enabled_plugins": [
|
||||||
"markdown",
|
"markdown",
|
||||||
"wallabag",
|
"wallabag",
|
||||||
|
@ -176,23 +74,18 @@ Must be an associative array: `translation domain => translation path`.
|
||||||
"timezone": "Europe\/Paris",
|
"timezone": "Europe\/Paris",
|
||||||
"title": "My Shaarli",
|
"title": "My Shaarli",
|
||||||
"header_link": "?"
|
"header_link": "?"
|
||||||
|
"tags_separator": " "
|
||||||
},
|
},
|
||||||
|
"dev": {
|
||||||
|
"debug": false,
|
||||||
|
}
|
||||||
"extras": {
|
"extras": {
|
||||||
"show_atom": false,
|
"show_atom": false,
|
||||||
"hide_public_links": false,
|
"hide_public_links": false,
|
||||||
"hide_timestamps": false,
|
"hide_timestamps": false,
|
||||||
"open_shaarli": false,
|
"open_shaarli": false,
|
||||||
},
|
},
|
||||||
"general": {
|
"formatter": "markdown",
|
||||||
"header_link": "?",
|
|
||||||
"links_per_page": 20,
|
|
||||||
"enabled_plugins": [
|
|
||||||
"markdown",
|
|
||||||
"wallabag"
|
|
||||||
],
|
|
||||||
"timezone": "Europe\/Paris",
|
|
||||||
"title": "My Shaarli"
|
|
||||||
},
|
|
||||||
"updates": {
|
"updates": {
|
||||||
"check_updates": true,
|
"check_updates": true,
|
||||||
"check_updates_branch": "stable",
|
"check_updates_branch": "stable",
|
||||||
|
@ -209,12 +102,13 @@ Must be an associative array: `translation domain => translation path`.
|
||||||
"hide_timestamps": false,
|
"hide_timestamps": false,
|
||||||
"remember_user_default": true
|
"remember_user_default": true
|
||||||
},
|
},
|
||||||
"thumbnail": {
|
"thumbnails": {
|
||||||
"enable_thumbnails": true,
|
"width": 125,
|
||||||
"enable_localcache": true
|
"height": 90,
|
||||||
|
"mode": "common"
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"WALLABAG_URL": "http://demo.wallabag.org",
|
"WALLABAG_URL": "https://demo.wallabag.org",
|
||||||
"WALLABAG_VERSION": "1"
|
"WALLABAG_VERSION": "1"
|
||||||
},
|
},
|
||||||
"translation": {
|
"translation": {
|
||||||
|
@ -223,13 +117,119 @@ Must be an associative array: `translation domain => translation path`.
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"demo": "plugins/demo_plugin/languages/"
|
"demo": "plugins/demo_plugin/languages/"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"host": "ldap://localhost",
|
||||||
|
"dn": "uid=%s,ou=people,dc=example,dc=org"
|
||||||
}
|
}
|
||||||
} ?>
|
} ?>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Additional configuration
|
## Settings
|
||||||
|
|
||||||
The `playvideos` plugin may require that you adapt your server's
|
### Credentials
|
||||||
[Content Security Policy](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md#troubleshooting)
|
|
||||||
configuration to work properly.
|
|
||||||
|
|
||||||
|
_These settings should not be edited_
|
||||||
|
|
||||||
|
- **login**: Login username.
|
||||||
|
- **hash**: Generated password hash.
|
||||||
|
- **salt**: Password salt.
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
|
- **title**: Shaarli's instance title.
|
||||||
|
- **header_link**: Link to the homepage.
|
||||||
|
- **links_per_page**: Number of Shaares displayed per page.
|
||||||
|
- **timezone**: See [the list of supported timezones](https://www.php.net/manual/en/timezones.php).
|
||||||
|
- **enabled_plugins**: List of enabled plugins.
|
||||||
|
- **default_note_title**: Default title of a new note.
|
||||||
|
- **download_max_size:**: Maximum number of bytes to download when retrieveing page content/metadata.
|
||||||
|
- **download_timeout:**: Network timeout (in seconds) when retrieveing page content/metadata.
|
||||||
|
- **enable_async_metadata** (boolean): Retrieve external bookmark metadata asynchronously to prevent bookmark creation slowdown.
|
||||||
|
- **retrieve_description** (boolean): If set to true, for every new Shaare Shaarli will try to retrieve the description and keywords from the HTML meta tags.
|
||||||
|
- **root_url**: Overrides automatic discovery of Shaarli instance's URL (e.g.) `https://sub.domain.tld/shaarli-folder/`.
|
||||||
|
- **tags_separator**: Defines your tags separator (default: whitespace).
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- **session_protection_disabled**: Disable session cookie hijacking protection (not recommended).
|
||||||
|
It might be useful if your IP adress often changes.
|
||||||
|
- **ban_after**: Failed login attempts before being IP banned.
|
||||||
|
- **ban_duration**: IP ban duration in seconds.
|
||||||
|
- **open_shaarli**: Anyone can add a new Shaare while logged out if enabled.
|
||||||
|
- **trusted_proxies**: List of trusted IP which won't be banned after failed login attemps. Useful if Shaarli is behind a reverse proxy.
|
||||||
|
- **allowed_protocols**: List of allowed protocols in shaare URLs or markdown-rendered descriptions. Useful if you want to store `javascript:` links (bookmarklets) or `file:///` URIs in Shaarli (default: `["ftp", "ftps", "magnet"]`).
|
||||||
|
|
||||||
|
### Formatter
|
||||||
|
|
||||||
|
Single string value. Default available:
|
||||||
|
|
||||||
|
- `default`: supports line breaks, URL and hashtag auto-links.
|
||||||
|
- `markdown`: supports [Markdown](https://daringfireball.net/projects/markdown/syntax).
|
||||||
|
- `markdownExtra`: adds [extra](https://michelf.ca/projects/php-markdown/extra/) flavor to Markdown.
|
||||||
|
|
||||||
|
### Formatter Settings
|
||||||
|
|
||||||
|
Additional settings applied to formatters.
|
||||||
|
|
||||||
|
#### default
|
||||||
|
|
||||||
|
- **autolink**: boolean to enable or disable automatic linkification of URL and hashtags.
|
||||||
|
|
||||||
|
### Resources
|
||||||
|
|
||||||
|
- **data_dir**: Data directory.
|
||||||
|
- **datastore**: Shaarli's Shaares database file path.
|
||||||
|
- **history**: Shaarli's operation history file path.
|
||||||
|
- **updates**: File path for the ran updates file.
|
||||||
|
- **log**: Log file path.
|
||||||
|
- **update_check**: Last update check file path.
|
||||||
|
- **raintpl_tpl**: Templates directory.
|
||||||
|
- **raintpl_tmp**: Template engine cache directory.
|
||||||
|
- **thumbnails_cache**: Thumbnails cache directory.
|
||||||
|
- **page_cache**: Shaarli's internal cache directory.
|
||||||
|
- **ban_file**: Banned IP file path.
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
|
||||||
|
- **language**: translation language (also see [Translations](dev/Development.md#translations))
|
||||||
|
- **auto** (default): The translation language is chosen from the browser locale.
|
||||||
|
It means that the language can be different for 2 different visitors depending on their locale.
|
||||||
|
- **en**: Use the English translation.
|
||||||
|
- **fr**: Use the French translation.
|
||||||
|
- **mode**:
|
||||||
|
- **auto** or **php** (default): Use the PHP implementation of gettext (slower)
|
||||||
|
- **gettext**: Use PHP builtin gettext extension
|
||||||
|
(faster, but requires `php-gettext` to be installed and to reload the web server on update)
|
||||||
|
- **extension**: Translation extensions for custom themes or plugins.
|
||||||
|
Must be an associative array: `translation domain => translation path`.
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
|
||||||
|
- **check_updates**: Enable or disable update check to the git repository.
|
||||||
|
- **check_updates_branch**: Git branch used to check updates (e.g. `stable` or `master`).
|
||||||
|
- **check_updates_interval**: Look for new version every N seconds (default: every day).
|
||||||
|
|
||||||
|
### Privacy
|
||||||
|
|
||||||
|
- **default_private_links**: Check the private checkbox by default for every new Shaare.
|
||||||
|
- **hide_public_links**: All Shaares are hidden while logged out.
|
||||||
|
- **force_login**: if **hide_public_links** and this are set to `true`, all anonymous users are redirected to the login page.
|
||||||
|
- **hide_timestamps**: Timestamps are hidden.
|
||||||
|
- **remember_user_default**: Default state of the login page's *remember me* checkbox
|
||||||
|
- `true`: checked by default, `false`: unchecked by default
|
||||||
|
|
||||||
|
### Feed
|
||||||
|
|
||||||
|
- **rss_permalinks**: Enable this to redirect RSS links to Shaarli's permalinks instead of shaared URL.
|
||||||
|
- **show_atom**: Display ATOM feed button.
|
||||||
|
|
||||||
|
### Thumbnails
|
||||||
|
|
||||||
|
- **width:** width of generated thumbnails, in pixels
|
||||||
|
- **height:** height of generated thumbnails, in pixels
|
||||||
|
- **mode:** enable thumbnails for `all` shaares, or `common` media hosts, or `none`.
|
||||||
|
|
||||||
|
## Plugins configuration
|
||||||
|
|
||||||
|
See [Plugins](Plugins.md)
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
Content posted to Shaarli is separated in items called _Shaares_. For each Shaare,
|
|
||||||
you can customize the following aspects:
|
|
||||||
|
|
||||||
* URL to link to
|
|
||||||
* Title
|
|
||||||
* Free-text description
|
|
||||||
* Tags
|
|
||||||
* Public/private status
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Adding new Shaares
|
|
||||||
|
|
||||||
While logged in to your Shaarli, you can add new Shaares in several ways:
|
|
||||||
|
|
||||||
* [+Shaare button](#shaare-button)
|
|
||||||
* [Bookmarklet](#bookmarklet)
|
|
||||||
* Third-party [apps and browser addons](Community-&-Related-software.md#mobile-apps)
|
|
||||||
* [REST API](https://shaarli.github.io/api-documentation/)
|
|
||||||
|
|
||||||
### +Shaare button
|
|
||||||
|
|
||||||
* While logged in to your Shaarli, click the **`+Shaare`** button located in the toolbar.
|
|
||||||
* Enter the URL of a link you want to share.
|
|
||||||
* Click `Add link`
|
|
||||||
* The `New Shaare` dialog appears, allowing you to fill in the details of your Shaare.
|
|
||||||
* The Description, Title, and Tags will help you find your Shaare later using tags or full-text search.
|
|
||||||
* You can also check the “Private” box so that the link is saved but only visible to you (the logged-in user).
|
|
||||||
* Click `Save`.
|
|
||||||
|
|
||||||
<!-- TODO Add screenshot of add/edit link dialog -->
|
|
||||||
|
|
||||||
### Bookmarklet
|
|
||||||
|
|
||||||
The _Bookmarklet_ \[[1](https://en.wikipedia.org/wiki/Bookmarklet)\] is a special
|
|
||||||
browser bookmark you can use to add new content to your Shaarli. This bookmarklet is
|
|
||||||
compatible with Firefox, Opera, Chrome and Safari. To set it up:
|
|
||||||
|
|
||||||
* Access the `Tools` page from the button in the toolbar.
|
|
||||||
* Drag the **`✚Shaare link` button** to your browser's bookmarks bar.
|
|
||||||
|
|
||||||
Once this is done, you can shaare any URL you are visiting simply by clicking the
|
|
||||||
bookmarklet in your browser! The same `New Shaare` dialog as above is displayed.
|
|
||||||
|
|
||||||
| Note | Websites which enforce Content Security Policy (CSP), such as github.com, disallow usage of bookmarklets. Unfortunately, there is nothing Shaarli can do about it. \[[1](https://github.com/shaarli/Shaarli/issues/196)]\ \[[2](https://bugzilla.mozilla.org/show_bug.cgi?id=866522)]\ \[[3](https://code.google.com/p/chromium/issues/detail?id=233903)]\ |
|
|
||||||
|---------|---------|
|
|
||||||
|
|
||||||
| Note | Under Opera, you can't drag'n drop the button: You have to right-click on it and add a bookmark to your personal toolbar. |
|
|
||||||
|---------|---------|
|
|
||||||
|
|
||||||
![](images/bookmarklet.png)
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Editing Shaares
|
|
||||||
|
|
||||||
Any Shaare can edited by clicking its ![](images/edit_icon.png) `Edit` button.
|
|
||||||
|
|
||||||
Editing a Shaare will not change it's permalink, each permalink always points to the
|
|
||||||
latest revision of a Shaare.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Using shaarli as a blog, notepad, pastebin...
|
|
||||||
|
|
||||||
While adding or editing a link, leave the URL field blank to create a text-only
|
|
||||||
("note") post. This allows you to post any kind of text content, such as blog
|
|
||||||
articles, private or public notes, snippets... There is no character limit! You can
|
|
||||||
access your Shaare from its permalink.
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
## WIP
|
|
||||||
This topic is currently being discussed here:
|
|
||||||
|
|
||||||
- [Fix coding style (static analysis)](https://github.com/shaarli/Shaarli/issues/95) (#95)
|
|
||||||
- [Continuous Integration tools & features](https://github.com/shaarli/Shaarli/issues/130) (#130)
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
Static analysis tools can be installed with Composer, and used through Shaarli's [Makefile](https://github.com/shaarli/Shaarli/blob/master/Makefile).
|
|
||||||
|
|
||||||
For an overview of the available features, see:
|
|
||||||
|
|
||||||
- [Code quality: Makefile to run static code checkers](https://github.com/shaarli/Shaarli/pull/124) (#124)
|
|
||||||
- [Run PHPCS against different coding standards](https://github.com/shaarli/Shaarli/pull/276) (#276)
|
|
|
@ -1,85 +0,0 @@
|
||||||
## Foreword
|
|
||||||
|
|
||||||
There are two ways of customizing how Shaarli looks:
|
|
||||||
|
|
||||||
1. by using a custom CSS to override Shaarli's CSS
|
|
||||||
2. by using a full theme that provides its own RainTPL templates, CSS and Javascript resources
|
|
||||||
|
|
||||||
## Custom CSS
|
|
||||||
|
|
||||||
Shaarli's appearance can be modified by adding CSS rules to:
|
|
||||||
|
|
||||||
- Shaarli < `v0.9.0`: `inc/user.css`
|
|
||||||
- Shaarli >= `v0.9.0`: `data/user.css`
|
|
||||||
|
|
||||||
This file allows overriding rules defined in the template CSS files (only add changed rules), or define a whole new theme.
|
|
||||||
|
|
||||||
**Note**: Do not edit `tpl/default/css/shaarli.css`! Your changes would be overridden when updating Shaarli.
|
|
||||||
|
|
||||||
See also [Download CSS styles from an OPML list](Download CSS styles from an OPML list)
|
|
||||||
|
|
||||||
## Themes
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
|
|
||||||
- find a theme you'd like to install
|
|
||||||
- copy or clone the theme folder under `tpl/<a_sweet_theme>`
|
|
||||||
- enable the theme:
|
|
||||||
- Shaarli < `v0.9.0`: edit `data/config.json.php` and set the value of `raintpl_tpl` to the new theme name:
|
|
||||||
`"raintpl_tpl": "tpl\/my-template\/"`
|
|
||||||
- Shaarli >= `v0.9.0`: select the theme through the _Tools_ page
|
|
||||||
|
|
||||||
## Community CSS & themes
|
|
||||||
|
|
||||||
### Custom CSS
|
|
||||||
|
|
||||||
- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for Shaarli
|
|
||||||
- [shaarli/shaarli-themes](https://github.com/shaarli/shaarli-themes)
|
|
||||||
|
|
||||||
### Themes
|
|
||||||
|
|
||||||
- [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli
|
|
||||||
- [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli
|
|
||||||
- [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme
|
|
||||||
- [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli
|
|
||||||
- [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli
|
|
||||||
- [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone
|
|
||||||
- [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site
|
|
||||||
|
|
||||||
### Shaarli forks
|
|
||||||
|
|
||||||
- [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template
|
|
||||||
- [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance
|
|
||||||
|
|
||||||
## Example installation: AlbinoMouse theme
|
|
||||||
|
|
||||||
With the following configuration:
|
|
||||||
|
|
||||||
- Apache 2 / PHP 5.6
|
|
||||||
- user sites are enabled, e.g. `/home/user/public_html/somedir` is served as `http://localhost/~user/somedir`
|
|
||||||
- `http` is the name of the Apache user
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd ~/public_html
|
|
||||||
|
|
||||||
# clone repositories
|
|
||||||
$ git clone https://github.com/shaarli/Shaarli.git shaarli
|
|
||||||
$ pushd shaarli/tpl
|
|
||||||
$ git clone https://github.com/alexisju/albinomouse-template.git
|
|
||||||
$ popd
|
|
||||||
|
|
||||||
# set access rights for Apache
|
|
||||||
$ chgrp -R http shaarli
|
|
||||||
$ chmod g+rwx shaarli shaarli/cache shaarli/data shaarli/pagecache shaarli/tmp
|
|
||||||
```
|
|
||||||
|
|
||||||
Get config written:
|
|
||||||
- go to the freshly installed site
|
|
||||||
- fill the install form
|
|
||||||
- log in to Shaarli
|
|
||||||
|
|
||||||
Edit Shaarli's [configuration](Shaarli-configuration):
|
|
||||||
```bash
|
|
||||||
# the file should be owned by Apache, thus not writeable => sudo
|
|
||||||
$ sudo sed -i s=tpl=tpl/albinomouse-template=g shaarli/data/config.php
|
|
||||||
```
|
|
|
@ -1,164 +0,0 @@
|
||||||
## Translations
|
|
||||||
|
|
||||||
Shaarli supports [gettext](https://www.gnu.org/software/gettext/manual/gettext.html) translations
|
|
||||||
since `>= v0.9.2`.
|
|
||||||
|
|
||||||
Note that only the `default` theme supports translations.
|
|
||||||
|
|
||||||
### Contributing
|
|
||||||
|
|
||||||
We encourage the community to contribute to Shaarli's translation either by improving existing
|
|
||||||
translations or submitting a new language.
|
|
||||||
|
|
||||||
Contributing to the translation does not require development skill.
|
|
||||||
|
|
||||||
Please submit a pull request with the `.po` file updated/created. Note that the compiled file (`.mo`)
|
|
||||||
is not stored on the repository, and is generated during the release process.
|
|
||||||
|
|
||||||
### How to
|
|
||||||
|
|
||||||
First, install [Poedit](https://poedit.net/) tool.
|
|
||||||
|
|
||||||
Poedit will extract strings to translate from the PHP source code.
|
|
||||||
|
|
||||||
**Important**: due to the usage of a template engine, it's important to generate PHP cache files to extract
|
|
||||||
every translatable string.
|
|
||||||
|
|
||||||
You can either use [this script](https://gist.github.com/ArthurHoaro/5d0323f758ab2401ef444a53f54e9a07) (recommended)
|
|
||||||
or visit every template page in your browser to generate cache files, while logged in.
|
|
||||||
|
|
||||||
Here is a list :
|
|
||||||
|
|
||||||
```
|
|
||||||
http://<replace_domain>/
|
|
||||||
http://<replace_domain>/?nonope
|
|
||||||
http://<replace_domain>/?do=addlink
|
|
||||||
http://<replace_domain>/?do=changepasswd
|
|
||||||
http://<replace_domain>/?do=changetag
|
|
||||||
http://<replace_domain>/?do=configure
|
|
||||||
http://<replace_domain>/?do=tools
|
|
||||||
http://<replace_domain>/?do=daily
|
|
||||||
http://<replace_domain>/?post
|
|
||||||
http://<replace_domain>/?do=export
|
|
||||||
http://<replace_domain>/?do=import
|
|
||||||
http://<replace_domain>/?do=login
|
|
||||||
http://<replace_domain>/?do=picwall
|
|
||||||
http://<replace_domain>/?do=pluginadmin
|
|
||||||
http://<replace_domain>/?do=tagcloud
|
|
||||||
http://<replace_domain>/?do=taglist
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Improve existing translation
|
|
||||||
|
|
||||||
In Poedit, click on "Edit a Translation", and from Shaarli's directory open
|
|
||||||
`inc/languages/<lang>/LC_MESSAGES/shaarli.po`.
|
|
||||||
|
|
||||||
The existing list of translatable strings should have been loaded, then click on the "Update" button.
|
|
||||||
|
|
||||||
You can start editing the translation.
|
|
||||||
|
|
||||||
![poedit-screenshot](images/poedit-1.jpg)
|
|
||||||
|
|
||||||
Save when you're done, then you can submit a pull request containing the updated `shaarli.po`.
|
|
||||||
|
|
||||||
#### Add a new language
|
|
||||||
|
|
||||||
Open Poedit and select "Create New Translation", then from Shaarli's directory open
|
|
||||||
`inc/languages/<lang>/LC_MESSAGES/shaarli.po`.
|
|
||||||
|
|
||||||
Then select the language you want to create.
|
|
||||||
|
|
||||||
Click on `File > Save as...`, and save your file in `<shaarli directory>/inc/language/<new language>/LC_MESSAGES/shaarli.po`.
|
|
||||||
`<new language>` here should be the language code respecting the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-2)
|
|
||||||
format in lowercase (e.g. `de` for German).
|
|
||||||
|
|
||||||
Then click on the "Update" button, and you can start to translate every available string.
|
|
||||||
|
|
||||||
Save when you're done, then you can submit a pull request containing the new `shaarli.po`.
|
|
||||||
|
|
||||||
### Theme translations
|
|
||||||
|
|
||||||
Theme translation extensions are loaded automatically if they're present.
|
|
||||||
|
|
||||||
As a theme developer, all you have to do is to add the `.po` and `.mo` compiled file like this:
|
|
||||||
|
|
||||||
tpl/<theme name>/language/<lang>/LC_MESSAGES/<theme name>.po
|
|
||||||
tpl/<theme name>/language/<lang>/LC_MESSAGES/<theme name>.mo
|
|
||||||
|
|
||||||
Where `<lang>` is the ISO 3166-1 alpha-2 language code.
|
|
||||||
Read the following section "Extend Shaarli's translation" to learn how to generate those files.
|
|
||||||
|
|
||||||
### Extend Shaarli's translation
|
|
||||||
|
|
||||||
If you're writing a custom theme, or a non official plugin, you might want to use the translation system,
|
|
||||||
but you won't be able to able to override Shaarli's translation.
|
|
||||||
|
|
||||||
However, you can add your own translation domain which extends the main translation list.
|
|
||||||
|
|
||||||
> Note that you can find a live example of translation extension in the `demo_plugin`.
|
|
||||||
|
|
||||||
First, create your translation files tree directory:
|
|
||||||
|
|
||||||
```
|
|
||||||
<your_module>/languages/<ISO 3166-1 alpha-2 language code>/LC_MESSAGES/
|
|
||||||
```
|
|
||||||
|
|
||||||
Your `.po` files must be named like your domain. E.g. if your translation domain is `my_theme`, then your file will be
|
|
||||||
`my_theme.po`.
|
|
||||||
|
|
||||||
Users have to register your extension in their configuration with the parameter
|
|
||||||
`translation.extensions.<domain>: <translation files path>`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```php
|
|
||||||
if (! $conf->exists('translation.extensions.my_theme')) {
|
|
||||||
$conf->set('translation.extensions.my_theme', '<your_module>/languages/');
|
|
||||||
$conf->write(true);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note that the page needs to be reloaded after the registration.
|
|
||||||
|
|
||||||
It is then recommended to create a custom translation function which will call the `t()` function with your domain.
|
|
||||||
For example :
|
|
||||||
|
|
||||||
```php
|
|
||||||
function my_theme_t($text, $nText = '', $nb = 1)
|
|
||||||
{
|
|
||||||
return t($text, $nText, $nb, 'my_theme'); // the last parameter is your translation domain.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
All strings which can be translated should be processed through your function:
|
|
||||||
|
|
||||||
```php
|
|
||||||
my_theme_t('Comment');
|
|
||||||
my_theme_t('Comment', 'Comments', 2);
|
|
||||||
```
|
|
||||||
|
|
||||||
Or in templates:
|
|
||||||
|
|
||||||
```php
|
|
||||||
{'Comment'|my_theme_t}
|
|
||||||
{function="my_theme_t('Comment', 'Comments', 2)"}
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note than in template, you need to visit your page at least once to generate a cache file.
|
|
||||||
|
|
||||||
When you're done, open Poedit and load translation strings from sources:
|
|
||||||
|
|
||||||
1. `File > New`
|
|
||||||
2. Choose your language
|
|
||||||
3. Save your `PO` file in `<your_module>/languages/<language code>/LC_MESSAGES/my_theme.po`.
|
|
||||||
4. Go to `Catalog > Properties...`
|
|
||||||
5. Fill the `Translation Properties` tab
|
|
||||||
6. Add your source path in the `Sources Paths` tab
|
|
||||||
7. In the `Sources Keywords` tab uncheck "Also use default keywords" and add the following lines:
|
|
||||||
|
|
||||||
```
|
|
||||||
my_theme_t
|
|
||||||
my_theme_t:1,2
|
|
||||||
```
|
|
||||||
|
|
||||||
Click on the "Update" button and you're free to start your translations!
|
|
|
@ -1,63 +1,7 @@
|
||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
|
|
||||||
## Browser
|
First of all, ensure that both the [web server](Server-configuration.md) and [Shaarli](Shaarli-configuration.md) are correctly configured.
|
||||||
|
|
||||||
### Redirection issues (HTTP Referer)
|
|
||||||
|
|
||||||
Depending on its configuration and installed plugins, the browser may remove or alter (spoof) HTTP referers, thus preventing Shaarli from properly redirecting between pages.
|
|
||||||
|
|
||||||
See:
|
|
||||||
|
|
||||||
- [HTTP referer](https://en.wikipedia.org/wiki/HTTP_referer) (Wikipedia)
|
|
||||||
- [Improve online privacy by controlling referrer information](http://www.ghacks.net/2015/01/22/improve-online-privacy-by-controlling-referrer-information/)
|
|
||||||
- [Better security, privacy and anonymity in Firefox](http://b.agilob.net/better-security-privacy-and-anonymity-in-firefox/)
|
|
||||||
|
|
||||||
### Firefox HTTP Referer options
|
|
||||||
|
|
||||||
HTTP settings are available by browsing `about:config`, here are the available settings and their values.
|
|
||||||
|
|
||||||
`network.http.sendRefererHeader` - determines when to send the Referer HTTP header
|
|
||||||
|
|
||||||
- `0`: Never send the referring URL
|
|
||||||
- not recommended, may break some sites
|
|
||||||
- `1`: Send only on clicked links
|
|
||||||
- `2` (default): Send for links and images
|
|
||||||
|
|
||||||
`network.http.referer.XOriginPolicy` - Cross-domain origin policy
|
|
||||||
|
|
||||||
- `0` (default): Always send
|
|
||||||
- `1`: Send if base domains match
|
|
||||||
- `2`: Send if hosts match
|
|
||||||
|
|
||||||
`network.http.referer.spoofSource` - Referer spoofing (~faking)
|
|
||||||
|
|
||||||
- `false` (default): real referer
|
|
||||||
- `true`: spoof referer (use target URI as referer)
|
|
||||||
- known to break some functionality in Shaarli
|
|
||||||
|
|
||||||
`network.http.referer.trimmingPolicy` - trim the URI not to send a full Referer
|
|
||||||
|
|
||||||
- `0`: (default): send full URI
|
|
||||||
- `1`: scheme+host+port+path
|
|
||||||
- `2`: scheme+host+port
|
|
||||||
|
|
||||||
### Firefox, localhost and redirections
|
|
||||||
|
|
||||||
`localhost` is not a proper Fully Qualified Domain Name (FQDN); if Firefox has
|
|
||||||
been set up to spoof referers, or only accept requests from the same base domain/host,
|
|
||||||
Shaarli redirections will not work properly.
|
|
||||||
|
|
||||||
To solve this, assign a local domain to your host, e.g.
|
|
||||||
```
|
|
||||||
127.0.0.1 localhost desktop localhost.lan
|
|
||||||
::1 localhost desktop localhost.lan
|
|
||||||
```
|
|
||||||
|
|
||||||
and browse Shaarli at http://localhost.lan/.
|
|
||||||
|
|
||||||
Related threads:
|
|
||||||
- [What is localhost.localdomain for?](https://bbs.archlinux.org/viewtopic.php?id=156064)
|
|
||||||
- [Stop returning to the first page after editing a bookmark from another page](https://github.com/shaarli/Shaarli/issues/311)
|
|
||||||
|
|
||||||
## Login
|
## Login
|
||||||
|
|
||||||
|
@ -67,31 +11,48 @@ Delete the file `data/config.json.php` and display the page again. You will be a
|
||||||
|
|
||||||
### I'm locked out - Login bruteforce protection
|
### I'm locked out - Login bruteforce protection
|
||||||
|
|
||||||
Login form is protected against brute force attacks: 4 failed logins will ban the IP address from login for 30 minutes. Banned IPs can still browse links.
|
Login form is protected against brute force attacks: 4 failed logins will ban the IP address from login for 30 minutes. Banned IPs can still browse Shaares.
|
||||||
|
|
||||||
To remove the current IP bans, delete the file `data/ipbans.php`
|
- To remove the current IP bans, delete the file `data/ipbans.php`
|
||||||
|
- To list all login attempts, see `data/log.txt` (succesful/failed logins, bans/lifted bans)
|
||||||
|
|
||||||
### List of all login attempts
|
--------------------------------------
|
||||||
|
|
||||||
The file `data/log.txt` shows all logins (successful or failed) and bans/lifted bans.
|
## Browser issues
|
||||||
Search for `failed` in this file to look for unauthorized login attempts.
|
|
||||||
|
### Redirection issues (HTTP Referer)
|
||||||
|
|
||||||
|
Shaarli relies on `HTTP_REFERER` for some functions (like redirects and clicking on tags). If you have disabled or altered/spoofed [HTTP referers](https://en.wikipedia.org/wiki/HTTP_referer) in your browser, some features of Shaarli may not work as expected (depending on configuration and installed plugins), notably redirections between pages.
|
||||||
|
|
||||||
|
Firefox Referer settings are available by browsing `about:config` and are documented [here](https://wiki.mozilla.org/Security/Referrer). `network.http.referer.spoofSource = true` in particular is known to break some functionality in Shaarli.
|
||||||
|
|
||||||
|
|
||||||
|
### Firefox, localhost and redirections
|
||||||
|
|
||||||
|
`localhost` is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or only accept requests from the same base domain/host,
|
||||||
|
Shaarli redirections will not work properly. To solve this, assign a local domain to your host, e.g. `localhost.lan` in your [hosts file](https://en.wikipedia.org/wiki/Hosts_(file)) and browse Shaarli at http://localhost.lan/.
|
||||||
|
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
## Hosting problems
|
## Hosting problems
|
||||||
|
|
||||||
### Old PHP versions
|
### Old PHP versions
|
||||||
|
|
||||||
On **free.fr**: free.fr now supports php 5.6.x([link](http://les.pages.perso.chez.free.fr/migrations/php5v6.io))
|
- On hosts (such as **free.fr**) which only support PHP 5.6, Shaarli [v0.10.4](https://github.com/shaarli/Shaarli/releases/tag/v0.10.4) is the maximum supported version. At the root of your webspace create a `sessions` directory and a `.htaccess` file containing:
|
||||||
and so support now the tag autocompletion but you have to do the following.
|
|
||||||
|
|
||||||
At the root of your webspace create a `sessions` directory and a `.htaccess` file containing:
|
```apacheconf
|
||||||
|
|
||||||
```xml
|
|
||||||
<IfDefine Free>
|
<IfDefine Free>
|
||||||
php56 1
|
php56 1
|
||||||
</IfDefine>
|
</IfDefine>
|
||||||
|
<Files ".ht*">
|
||||||
|
Order allow,deny
|
||||||
|
Deny from all
|
||||||
|
Satisfy all
|
||||||
|
</Files>
|
||||||
|
Options -Indexes
|
||||||
```
|
```
|
||||||
|
|
||||||
- If you have an error such as: `Parse error: syntax error, unexpected '=', expecting '(' in /links/index.php on line xxx`, it means that your host is using php4, not php5. Shaarli requires php 5.1. Try changing the file extension to `.php5`
|
- If you have an error such as: `Parse error: syntax error, unexpected '=', expecting '(' in /links/index.php on line xxx`, it means that your host is using PHP 4, not PHP 5. Shaarli requires PHP 5.1. Try changing the file extension to `.php5`
|
||||||
- On **1and1** : If you add the link from the page (and not from the bookmarklet), Shaarli will no be able to get the title of the page. You will have to enter it manually. (Because they have disabled the ability to download a file through HTTP).
|
- On **1and1** : If you add the link from the page (and not from the bookmarklet), Shaarli will no be able to get the title of the page. You will have to enter it manually. (Because they have disabled the ability to download a file through HTTP).
|
||||||
- If you have the error `Warning: file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration in /…/index.php on line xxx`, it means that your host has disabled the ability to fetch a file by HTTP in the php config (Typically in 1and1 hosting). Bad host. Change host. Or comment the following lines:
|
- If you have the error `Warning: file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration in /…/index.php on line xxx`, it means that your host has disabled the ability to fetch a file by HTTP in the php config (Typically in 1and1 hosting). Bad host. Change host. Or comment the following lines:
|
||||||
|
|
||||||
|
@ -101,9 +62,11 @@ php56 1
|
||||||
//if (strpos($status,'200 OK')) $title=html_extract_title($data);
|
//if (strpos($status,'200 OK')) $title=html_extract_title($data);
|
||||||
```
|
```
|
||||||
|
|
||||||
- On hosts which forbid outgoing HTTP requests (such as free.fr), some thumbnails will not work.
|
- On hosts (such as **free.fr**) which forbid outgoing HTTP requests, some thumbnails will not work.
|
||||||
|
- On hosts (such as **free.fr**) which limit the number of FTP connections, setup your FTP client accordingly (else some files may be missing after upload).
|
||||||
- On **lost-oasis**, RSS doesn't work correctly, because of this message at the begining of the RSS/ATOM feed : `<? // tout ce qui est charge ici (generalement des includes et require) est charge en permanence. ?>`. To fix this, remove this message from `php-include/prepend.php`
|
- On **lost-oasis**, RSS doesn't work correctly, because of this message at the begining of the RSS/ATOM feed : `<? // tout ce qui est charge ici (generalement des includes et require) est charge en permanence. ?>`. To fix this, remove this message from `php-include/prepend.php`
|
||||||
|
|
||||||
|
|
||||||
### Dates are not properly formatted
|
### Dates are not properly formatted
|
||||||
|
|
||||||
Shaarli tries to sniff the language of the browser (using `HTTP_ACCEPT_LANGUAGE` headers)
|
Shaarli tries to sniff the language of the browser (using `HTTP_ACCEPT_LANGUAGE` headers)
|
||||||
|
@ -123,6 +86,135 @@ This can be caused by several things:
|
||||||
- You may be using OperaTurbo or OperaMini, which use their own proxies which may change from time to time.
|
- You may be using OperaTurbo or OperaMini, which use their own proxies which may change from time to time.
|
||||||
- If you have another application on the same webserver where Shaarli is installed, these application may forcefully expire php sessions.
|
- If you have another application on the same webserver where Shaarli is installed, these application may forcefully expire php sessions.
|
||||||
|
|
||||||
## Sessions do not seem to work correctly on your server
|
|
||||||
|
|
||||||
Follow the instructions in the error message. Make sure you are accessing shaarli via a direct IP address or a proper hostname. If you have **no dots** in the hostname (e.g. `localhost` or `http://my-webserver/shaarli/`), some browsers will not store cookies at all (this respects the [HTTP cookie specification](http://curl.haxx.se/rfc/cookie_spec.html)).
|
### Old apache versions, Internal Server Error
|
||||||
|
|
||||||
|
If you hosting provider only provides apache 2.2 and no support for `mod_version`, `.htaccess` files may cause 500 errors (Internal Server Error). See [this workaround](https://github.com/shaarli/Shaarli/issues/1196#issuecomment-412271085).
|
||||||
|
|
||||||
|
|
||||||
|
### Sessions do not seem to work correctly on your server
|
||||||
|
|
||||||
|
Follow the instructions in the error message. Make sure you are accessing shaarli via a direct IP address or a proper hostname. If you have **no dots** in the hostname (e.g. `localhost` or `http://my-webserver/shaarli/`), some browsers will not store cookies at all (this respects the [HTTP cookie specification](https://curl.se/rfc/cookie_spec.html)).
|
||||||
|
|
||||||
|
|
||||||
|
### Error 406 "Not acceptable"
|
||||||
|
|
||||||
|
If attempting to save a link results in a `Not acceptable` error (HTTP status code of `406`), it is likely due to strict settings for `mod_security` (a module used with Apache). This cannot be mitigated by reconfiguring Shaarli itself, and must be dealt with at the level of the underlying web server instead.
|
||||||
|
|
||||||
|
On some shared hosting services (such as **Bluehost**), `mod_security` is enabled by default, so the recommended course of action is to get in touch with the helpdesk and ask them to disable `mod_security` for your domain, sub-domain, or the subdirectory where Shaarli is installed. Ideally, you want to narrow it down to a very specific location, so as to continue reaping the benefits of `mod_security` elsewhere on your domain. If asked for specific, you can refer support staff to this [issue](https://github.com/shaarli/Shaarli/issues/1736), where more technical details are available.
|
||||||
|
|
||||||
|
|
||||||
|
### Timeout during long-lasting operations
|
||||||
|
|
||||||
|
You may need to adjust timeouts to larger values in your [reverse proxy configuration](Reverse-proxy.md) if you're getting `504 Gateway Timeout` errors during long-lasting operations (like importing many bookmarks from HTML, or batch deleting tags) on slow hardware. The PHP setting `max_execution_time` may also need to be adjusted for your specific setup. See issues [#1854](https://github.com/shaarli/Shaarli/issues/1854) and [#1910](https://github.com/shaarli/Shaarli/issues/1910).
|
||||||
|
|
||||||
|
|
||||||
|
### Automatic title retrieval fails
|
||||||
|
|
||||||
|
When bookmarking a page using the `+ Shaare > Add Link` dialog, Shaarli cannot retrieve the page `<title>` HTML attribute if it is set by javascript at page load (e.g. Youtube videos). You can work around this limitation by using a [Browser extension](Community-and-related-software.md) or the [Bookmarklet](#Usage).
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
## Upgrades
|
||||||
|
|
||||||
|
### You must specify an integer as a key
|
||||||
|
|
||||||
|
In `v0.8.1` we changed how Shaare keys are handled (from timestamps to incremental integers). Take a look at `data/updates.txt` content.
|
||||||
|
|
||||||
|
|
||||||
|
### `updates.txt` contains `updateMethodDatastoreIds`
|
||||||
|
|
||||||
|
Try to delete it and refresh your page while being logged in.
|
||||||
|
|
||||||
|
### `updates.txt` doesn't exist or doesn't contain `updateMethodDatastoreIds`
|
||||||
|
|
||||||
|
1. Create `data/updates.txt` if it doesn't exist
|
||||||
|
2. Paste this string in the update file `;updateMethodRenameDashTags;`
|
||||||
|
3. Login to Shaarli
|
||||||
|
4. Delete the update file
|
||||||
|
5. Refresh
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
## Import/export
|
||||||
|
|
||||||
|
### Importing shaarli data to Firefox
|
||||||
|
|
||||||
|
- In Firefox, open the bookmark manager (`Bookmarks menu > Show all bookmarks` or `Ctrl+Shift+B`), select `Import and Backup > Import bookmarks in HTML format`
|
||||||
|
- Make sure the `Prepend note permalinks with this Shaarli instance's URL` box is checked when exporting, so that text-only/notes Shaares still point to the Shaarli instance you exported them from.
|
||||||
|
- Depending on the number of bookmarks, the import can take some time.
|
||||||
|
|
||||||
|
You may be interested in these Firefox addons to manage bookmarks imported from Shaarli
|
||||||
|
|
||||||
|
- [Bookmark Dupes](https://addons.mozilla.org/en-US/firefox/addon/bookmark-dupes/) - provides an easy way to deduplicate your bookmarks
|
||||||
|
|
||||||
|
### Diigo
|
||||||
|
|
||||||
|
If you export your bookmark from Diigo, make sure you use the Delicious export, not the Netscape export. (Their Netscape export is broken, and they don't seem to be interested in fixing it.)
|
||||||
|
|
||||||
|
### Mister Wong
|
||||||
|
|
||||||
|
See [this issue](https://github.com/sebsauvage/Shaarli/issues/146) for import tweaks.
|
||||||
|
|
||||||
|
### SemanticScuttle
|
||||||
|
|
||||||
|
To correctly import the tags from a [SemanticScuttle](https://semanticscuttle.sourceforge.net/) HTML export, edit the HTML file before importing and replace all occurences of `tags=` (lowercase) to `TAGS=` (uppercase).
|
||||||
|
|
||||||
|
### Scuttle
|
||||||
|
|
||||||
|
Shaarli cannot import data directly from [Scuttle](https://github.com/scronide/scuttle).
|
||||||
|
|
||||||
|
However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli)
|
||||||
|
tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer.
|
||||||
|
|
||||||
|
### Refind.com
|
||||||
|
|
||||||
|
You can use the third-party tool [Derefind](https://github.com/ShawnPConroy/Derefind) to convert refind.com bookmark exports to a format that can be imported into Shaarli.
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
### The bookmarklet doesn't work
|
||||||
|
|
||||||
|
Some websites may disallow usage of bookmarklets through [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). Unfortunately, there is nothing Shaarli can do about it ([1](https://github.com/shaarli/Shaarli/issues/196), [2](https://bugzilla.mozilla.org/show_bug.cgi?id=866522), [3](https://bugs.chromium.org/p/chromium/issues/detail?id=233903).
|
||||||
|
|
||||||
|
Under Opera, you can't drag'n drop the button: You have to right-click on it and add a bookmark to your personal toolbar.
|
||||||
|
|
||||||
|
|
||||||
|
### Changing the timestamp for a shaare
|
||||||
|
|
||||||
|
- Look for `<input type="hidden" name="lf_linkdate" value="{$link.linkdate}">` in `tpl/editlink.tpl` (line 14)
|
||||||
|
- Replace `type="hidden"` with `type="text"` from this line
|
||||||
|
- A new date/time field becomes available in the edit/new Shaare dialog.
|
||||||
|
- You can set the timestamp manually by entering it in the format `YYYMMDD_HHMMS`.
|
||||||
|
|
||||||
|
### Clearing Shaarli caches
|
||||||
|
|
||||||
|
For debugging purposes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# clear raintpl cache and temporary files
|
||||||
|
find /var/www/links/cache/ /var/www/links/pagecache/ /var/www/links/tmp/ -type f -exec rm -v '{}' \;
|
||||||
|
# if you have a php accelerator such as php-apcu, restart the webserver
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If the solutions above did not help, please:
|
||||||
|
|
||||||
|
- Come and ask question on the [Gitter chat](https://app.gitter.im/#/room/#shaarli_Shaarli:gitter.im)
|
||||||
|
- Search for [issues](https://github.com/shaarli/Shaarli/issues) and [Pull Requests](https://github.com/shaarli/Shaarli/pulls)
|
||||||
|
- if you find one that is related to the issue, feel free to comment and provide additional details (host/Shaarli setup...)
|
||||||
|
- check issues labeled [`feature`](https://github.com/shaarli/Shaarli/labels/feature), [`enhancement`](https://github.com/shaarli/Shaarli/labels/enhancement), and [`plugin`](https://github.com/shaarli/Shaarli/labels/plugin) if you would like a feature added to Shaarli.
|
||||||
|
- else, [open a new issue](https://github.com/shaarli/Shaarli/issues/new), and provide information about the problem:
|
||||||
|
- _what happens?_ - display glitches, invalid data, security flaws...
|
||||||
|
- _what is your configuration?_ - OS, server version, activated extensions, web browser...
|
||||||
|
- _is it reproducible?_
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
## Running tests inside Docker containers
|
|
||||||
|
|
||||||
Read first:
|
|
||||||
|
|
||||||
- [Docker 101](docker/docker-101.md)
|
|
||||||
- [Docker resources](docker/resources.md)
|
|
||||||
- [Unit tests](Unit-tests.md)
|
|
||||||
|
|
||||||
### Docker test images
|
|
||||||
|
|
||||||
Test Dockerfiles are located under `tests/docker/<distribution>/Dockerfile`,
|
|
||||||
and can be used to build Docker images to run Shaarli test suites under common
|
|
||||||
Linux environments.
|
|
||||||
|
|
||||||
Dockerfiles are provided for the following environments:
|
|
||||||
|
|
||||||
- `alpine36` - [Alpine 3.6](https://www.alpinelinux.org/downloads/)
|
|
||||||
- `debian8` - [Debian 8 Jessie](https://www.debian.org/DebianJessie) (oldstable)
|
|
||||||
- `debian9` - [Debian 9 Stretch](https://wiki.debian.org/DebianStretch) (stable)
|
|
||||||
- `ubuntu16` - [Ubuntu 16.04 Xenial Xerus](http://releases.ubuntu.com/16.04/) (LTS)
|
|
||||||
|
|
||||||
What's behind the curtains:
|
|
||||||
|
|
||||||
- each image provides:
|
|
||||||
- a base Linux OS
|
|
||||||
- Shaarli PHP dependencies (OS packages)
|
|
||||||
- test PHP dependencies (OS packages)
|
|
||||||
- Composer
|
|
||||||
- the local workspace is mapped to the container's `/shaarli/` directory,
|
|
||||||
- the files are rsync'd so tests are run using a standard Linux user account
|
|
||||||
(running tests as `root` would bypass permission checks and may hide issues)
|
|
||||||
- the tests are run inside the container.
|
|
||||||
|
|
||||||
### Building test images
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# build the Debian 9 Docker image
|
|
||||||
$ cd /path/to/shaarli
|
|
||||||
$ cd tests/docker/debian9
|
|
||||||
$ docker build -t shaarli-test:debian9 .
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd /path/to/shaarli
|
|
||||||
|
|
||||||
# install/update 3rd-party test dependencies
|
|
||||||
$ composer install --prefer-dist
|
|
||||||
|
|
||||||
# run tests using the freshly built image
|
|
||||||
$ docker run -v $PWD:/shaarli shaarli-test:debian9 docker_test
|
|
||||||
|
|
||||||
# run the full test campaign
|
|
||||||
$ docker run -v $PWD:/shaarli shaarli-test:debian9 docker_all_tests
|
|
||||||
```
|
|
|
@ -1,157 +0,0 @@
|
||||||
### Setup your environment for tests
|
|
||||||
|
|
||||||
The framework used is [PHPUnit](https://phpunit.de/); it can be installed with [Composer](https://getcomposer.org/), which is a dependency management tool.
|
|
||||||
|
|
||||||
### Install composer
|
|
||||||
|
|
||||||
You can either use:
|
|
||||||
|
|
||||||
- a system-wide version, e.g. installed through your distro's package manager
|
|
||||||
- a local version, downloadable [here](https://getcomposer.org/download/).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# system-wide version
|
|
||||||
$ composer install
|
|
||||||
$ composer update
|
|
||||||
|
|
||||||
# local version
|
|
||||||
$ php composer.phar self-update
|
|
||||||
$ php composer.phar install
|
|
||||||
$ php composer.phar update
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Install Shaarli dev dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd /path/to/shaarli
|
|
||||||
$ composer update
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Install and enable Xdebug to generate PHPUnit coverage reports
|
|
||||||
|
|
||||||
See http://xdebug.org/docs/install
|
|
||||||
|
|
||||||
For Debian-based distros:
|
|
||||||
```bash
|
|
||||||
$ aptitude install php5-xdebug
|
|
||||||
```
|
|
||||||
For ArchLinux:
|
|
||||||
```bash
|
|
||||||
$ pacman -S xdebug
|
|
||||||
```
|
|
||||||
|
|
||||||
Then add the following line to `/etc/php/php.ini`:
|
|
||||||
```ini
|
|
||||||
zend_extension=xdebug.so
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Run unit tests
|
|
||||||
|
|
||||||
Successful test suite:
|
|
||||||
```bash
|
|
||||||
$ make test
|
|
||||||
|
|
||||||
-------
|
|
||||||
PHPUNIT
|
|
||||||
-------
|
|
||||||
PHPUnit 4.6.9 by Sebastian Bergmann and contributors.
|
|
||||||
|
|
||||||
Configuration read from /home/virtualtam/public_html/shaarli/phpunit.xml
|
|
||||||
|
|
||||||
....................................
|
|
||||||
|
|
||||||
Time: 759 ms, Memory: 8.25Mb
|
|
||||||
|
|
||||||
OK (36 tests, 65 assertions)
|
|
||||||
```
|
|
||||||
|
|
||||||
Test suite with failures and errors:
|
|
||||||
```bash
|
|
||||||
$ make test
|
|
||||||
-------
|
|
||||||
PHPUNIT
|
|
||||||
-------
|
|
||||||
PHPUnit 4.6.9 by Sebastian Bergmann and contributors.
|
|
||||||
|
|
||||||
Configuration read from /home/virtualtam/public_html/shaarli/phpunit.xml
|
|
||||||
|
|
||||||
E..FF...............................
|
|
||||||
|
|
||||||
Time: 802 ms, Memory: 8.25Mb
|
|
||||||
|
|
||||||
There was 1 error:
|
|
||||||
|
|
||||||
1) LinkDBTest::testConstructLoggedIn
|
|
||||||
Missing argument 2 for LinkDB::__construct(), called in /home/virtualtam/public_html/shaarli/tests/Link\
|
|
||||||
DBTest.php on line 79 and defined
|
|
||||||
|
|
||||||
/home/virtualtam/public_html/shaarli/application/LinkDB.php:58
|
|
||||||
/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:79
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
There were 2 failures:
|
|
||||||
|
|
||||||
1) LinkDBTest::testCheckDBNew
|
|
||||||
Failed asserting that two strings are equal.
|
|
||||||
--- Expected
|
|
||||||
+++ Actual
|
|
||||||
@@ @@
|
|
||||||
-'e3edea8ea7bb50be4bcb404df53fbb4546a7156e'
|
|
||||||
+'85eab0c610d4f68025f6ed6e6b6b5fabd4b55834'
|
|
||||||
|
|
||||||
/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:121
|
|
||||||
|
|
||||||
2) LinkDBTest::testCheckDBLoad
|
|
||||||
Failed asserting that two strings are equal.
|
|
||||||
--- Expected
|
|
||||||
+++ Actual
|
|
||||||
@@ @@
|
|
||||||
-'e3edea8ea7bb50be4bcb404df53fbb4546a7156e'
|
|
||||||
+'85eab0c610d4f68025f6ed6e6b6b5fabd4b55834'
|
|
||||||
|
|
||||||
/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:133
|
|
||||||
|
|
||||||
FAILURES!
|
|
||||||
Tests: 36, Assertions: 63, Errors: 1, Failures: 2.
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Test results and coverage
|
|
||||||
|
|
||||||
By default, PHPUnit will run all suitable tests found under the `tests` directory.
|
|
||||||
|
|
||||||
Each test has 3 possible outcomes:
|
|
||||||
|
|
||||||
- `.` - success
|
|
||||||
- `F` - failure: the test was run but its results are invalid
|
|
||||||
- the code does not behave as expected
|
|
||||||
- dependencies to external elements: globals, session, cache...
|
|
||||||
- `E` - error: something went wrong and the tested code has crashed
|
|
||||||
- typos in the code, or in the test code
|
|
||||||
- dependencies to missing external elements
|
|
||||||
|
|
||||||
If Xdebug has been installed and activated, two coverage reports will be generated:
|
|
||||||
|
|
||||||
- a summary in the console
|
|
||||||
- a detailed HTML report with metrics for tested code
|
|
||||||
- to open it in a web browser: `firefox coverage/index.html &`
|
|
||||||
|
|
||||||
### Executing specific tests
|
|
||||||
|
|
||||||
Add a [`@group`](https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group) annotation in a test class or method comment:
|
|
||||||
|
|
||||||
```php
|
|
||||||
/**
|
|
||||||
* Netscape bookmark import
|
|
||||||
* @group WIP
|
|
||||||
*/
|
|
||||||
class BookmarkImportTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
[...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To run all tests annotated with `@group WIP`:
|
|
||||||
```bash
|
|
||||||
$ vendor/bin/phpunit --group WIP tests/
|
|
||||||
```
|
|
|
@ -1,96 +1,83 @@
|
||||||
## Preparation
|
# Upgrade and migration
|
||||||
|
|
||||||
### Note your current version
|
## Note your current version
|
||||||
|
|
||||||
If anything goes wrong, it's important for us to know which version you're upgrading from.
|
If anything goes wrong, it's important for us to know which version you're upgrading from.
|
||||||
The current version is present in the `shaarli_version.php` file.
|
The current version is present in the `shaarli_version.php` file.
|
||||||
|
|
||||||
### Backup your data
|
|
||||||
|
|
||||||
Shaarli stores all user data under the `data` directory:
|
## Backup your data
|
||||||
|
|
||||||
- `data/config.json.php` (or `data/config.php` for older Shaarli versions) - main configuration file
|
Shaarli stores all user data and [configuration](Shaarli-configuration.md) under the `data` directory. [Backup](Backup-and-restore.md) this repository _before_ upgrading Shaarli. You will need to restore it after the following upgrade steps.
|
||||||
- `data/datastore.php` - bookmarked links
|
|
||||||
- `data/ipbans.php` - banned IP addresses
|
|
||||||
- `data/updates.txt` - contains all automatic update to the configuration and datastore files already run
|
|
||||||
|
|
||||||
See [Shaarli configuration](Shaarli-configuration) for more information about Shaarli resources.
|
|
||||||
|
|
||||||
It is recommended to backup this repository _before_ starting updating/upgrading Shaarli:
|
|
||||||
|
|
||||||
- users with SSH access: copy or archive the directory to a temporary location
|
|
||||||
- users with FTP access: download a local copy of your Shaarli installation using your favourite client
|
|
||||||
|
|
||||||
### Migrating data from a previous installation
|
|
||||||
|
|
||||||
As all user data is kept under `data`, this is the only directory you need to worry about when migrating to a new installation, which corresponds to the following steps:
|
|
||||||
|
|
||||||
- backup the `data` directory
|
|
||||||
- install or update Shaarli:
|
|
||||||
- fresh installation - see [Download and Installation](Download-and-Installation)
|
|
||||||
- update - see the following sections
|
|
||||||
- check or restore the `data` directory
|
|
||||||
|
|
||||||
## Recommended : Upgrading from release archives
|
|
||||||
|
|
||||||
All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.
|
|
||||||
|
|
||||||
We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and Installation](Download-and-Installation) for `git` complete instructions.
|
|
||||||
|
|
||||||
Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the `data` directory!
|
|
||||||
|
|
||||||
If you use translations in gettext mode - meaning you manually changed the default mode -,
|
|
||||||
reload your web server.
|
|
||||||
|
|
||||||
After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli configuration) for more details).
|
|
||||||
|
|
||||||
## Upgrading with Git
|
|
||||||
|
|
||||||
### Updating a community Shaarli
|
|
||||||
|
|
||||||
If you have installed Shaarli from the [community Git repository](Download#clone-with-git-recommended), simply [pull new changes](https://www.git-scm.com/docs/git-pull) from your local clone:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd /path/to/shaarli
|
sudo cp -r /var/www/shaarli.mydomain.org/data ~/shaarli-data-backup
|
||||||
$ git pull
|
|
||||||
|
|
||||||
From github.com:shaarli/Shaarli
|
|
||||||
* branch master -> FETCH_HEAD
|
|
||||||
Updating ebd67c6..521f0e6
|
|
||||||
Fast-forward
|
|
||||||
application/Url.php | 1 +
|
|
||||||
shaarli_version.php | 2 +-
|
|
||||||
tests/Url/UrlTest.php | 1 +
|
|
||||||
3 files changed, 3 insertions(+), 1 deletion(-)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):
|
## Upgrading from ZIP archives
|
||||||
|
|
||||||
|
If you installed Shaarli from a [release ZIP archive](Installation.md#from-release-zip):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ composer install --no-dev
|
# Download the archive to the server, and extract it
|
||||||
|
cd ~
|
||||||
|
wget https://github.com/shaarli/Shaarli/releases/download/v0.X.Y/shaarli-v0.X.Y-full.zip
|
||||||
|
unzip shaarli-v0.X.Y-full.zip
|
||||||
|
|
||||||
Loading composer repositories with package information
|
# overwrite your Shaarli installation with the new release **All data will be lost, see _Backup your data_ above.**
|
||||||
Updating dependencies
|
sudo rsync -avP --delete Shaarli/ /var/www/shaarli.mydomain.org/
|
||||||
- Installing shaarli/netscape-bookmark-parser (v1.0.1)
|
|
||||||
Downloading: 100%
|
# restore file permissions as described on the installation page
|
||||||
|
sudo chown -R root:www-data /var/www/shaarli.mydomain.org
|
||||||
|
sudo chmod -R g+rX /var/www/shaarli.mydomain.org
|
||||||
|
sudo chmod -R g+rwX /var/www/shaarli.mydomain.org/{cache/,data/,pagecache/,tmp/}
|
||||||
|
|
||||||
|
# restore backups of the data directory
|
||||||
|
sudo cp -r ~/shaarli-data-backup/* /var/www/shaarli.mydomain.org/data/
|
||||||
|
|
||||||
|
# If you use gettext mode for translations (not the default), reload your web server.
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
sudo systemctl restart nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
Shaarli >= `v0.9.2` supports translations:
|
If you don't have shell access (eg. on shared hosting), backup the shaarli data directory, download the ZIP archive locally, extract it, upload it to the server using file transfer, and restore the data directory backup.
|
||||||
|
|
||||||
|
Access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli-configuration.md) for more details).
|
||||||
|
|
||||||
|
|
||||||
|
## Upgrading from Git
|
||||||
|
|
||||||
|
If you have installed Shaarli [from sources](Installation.md#from-sources):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ make translate
|
# pull new changes from your local clone
|
||||||
|
cd /var/www/shaarli.mydomain.org/
|
||||||
|
sudo git pull
|
||||||
|
|
||||||
|
# update PHP dependencies (Shaarli >= v0.8)
|
||||||
|
sudo composer install --no-dev
|
||||||
|
|
||||||
|
# update translations (Shaarli >= v0.9.2)
|
||||||
|
sudo make translate
|
||||||
|
|
||||||
|
# If you use translations in gettext mode (not the default), reload your web server.
|
||||||
|
sudo systemctl reload apache
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
|
||||||
|
# update front-end dependencies (Shaarli >= v0.10.0)
|
||||||
|
sudo make build_frontend
|
||||||
|
|
||||||
|
# restore file permissions as described on the installation page
|
||||||
|
sudo chown -R root:www-data /var/www/shaarli.mydomain.org
|
||||||
|
sudo chmod -R g+rX /var/www/shaarli.mydomain.org
|
||||||
|
sudo chmod -R g+rwX /var/www/shaarli.mydomain.org/{cache/,data/,pagecache/,tmp/}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use translations in gettext mode, reload your web server.
|
Access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli-configuration.md) for more details).
|
||||||
|
|
||||||
Shaarli >= `v0.10.0` manages its front-end dependencies with nodejs. You need to install
|
---------------------------------------------------------------
|
||||||
[yarn](https://yarnpkg.com/lang/en/docs/install/):
|
|
||||||
|
|
||||||
```bash
|
## Migrating and upgrading from Sebsauvage's repository
|
||||||
$ make build_frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrating and upgrading from Sebsauvage's repository
|
|
||||||
|
|
||||||
If you have installed Shaarli from [Sebsauvage's original Git repository](https://github.com/sebsauvage/Shaarli), you can use [Git remotes](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) to update your working copy.
|
If you have installed Shaarli from [Sebsauvage's original Git repository](https://github.com/sebsauvage/Shaarli), you can use [Git remotes](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) to update your working copy.
|
||||||
|
|
||||||
|
@ -104,7 +91,7 @@ The following guide assumes that:
|
||||||
- no versioned file has been locally modified
|
- no versioned file has been locally modified
|
||||||
- no untracked files are present
|
- no untracked files are present
|
||||||
|
|
||||||
#### Step 0: show repository information
|
### Step 0: show repository information
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd /path/to/shaarli
|
$ cd /path/to/shaarli
|
||||||
|
@ -122,7 +109,7 @@ Your branch is up-to-date with 'origin/master'.
|
||||||
nothing to commit, working directory clean
|
nothing to commit, working directory clean
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Step 1: update Git remotes
|
### Step 1: update Git remotes
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git remote rename origin sebsauvage
|
$ git remote rename origin sebsauvage
|
||||||
|
@ -146,7 +133,7 @@ From https://github.com/shaarli/Shaarli
|
||||||
* [new tag] v0.7.0 -> v0.7.0
|
* [new tag] v0.7.0 -> v0.7.0
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Step 2: use the stable community branch
|
### Step 2: use the stable community branch
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git checkout origin/stable -b stable
|
$ git checkout origin/stable -b stable
|
||||||
|
@ -177,8 +164,7 @@ $ make translate
|
||||||
|
|
||||||
If you use translations in gettext mode, reload your web server.
|
If you use translations in gettext mode, reload your web server.
|
||||||
|
|
||||||
Shaarli >= `v0.10.0` manages its front-end dependencies with nodejs. You need to install
|
Shaarli >= `v0.10.0` manages its front-end dependencies with nodejs. You need to install [yarn](https://classic.yarnpkg.com/en/docs/install/):
|
||||||
[yarn](https://yarnpkg.com/lang/en/docs/install/):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ make build_frontend
|
$ make build_frontend
|
||||||
|
@ -204,30 +190,14 @@ Writing objects: 100% (3317/3317), done.
|
||||||
Total 3317 (delta 2050), reused 3301 (delta 2034)to
|
Total 3317 (delta 2050), reused 3301 (delta 2034)to
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Step 3: configuration
|
### Step 3: configuration
|
||||||
|
|
||||||
After migrating, access your fresh Shaarli installation from a web browser; the
|
After migrating, access your fresh Shaarli installation from a web browser; the
|
||||||
configuration will then be automatically updated, and new settings added to
|
configuration will then be automatically updated, and new settings added to
|
||||||
`data/config.json.php` (see [Shaarli configuration](Shaarli-configuration) for more
|
`data/config.json.php` (see [Shaarli configuration](Shaarli-configuration.md) for more
|
||||||
details).
|
details).
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
If the solutions provided here don't work, please open an issue specifying which version you're upgrading from and to.
|
If the solutions provided here don't work, see [Troubleshooting](Troubleshooting.md) and/or open an issue specifying which version you're upgrading from and to.
|
||||||
|
|
||||||
### You must specify an integer as a key
|
|
||||||
|
|
||||||
In `v0.8.1` we changed how link keys are handled (from timestamps to incremental integers).
|
|
||||||
Take a look at `data/updates.txt` content.
|
|
||||||
|
|
||||||
#### `updates.txt` contains `updateMethodDatastoreIds`
|
|
||||||
|
|
||||||
Try to delete it and refresh your page while being logged in.
|
|
||||||
|
|
||||||
#### `updates.txt` doesn't exist or doesn't contain `updateMethodDatastoreIds`
|
|
||||||
|
|
||||||
1. Create `data/updates.txt` if it doesn't exist
|
|
||||||
2. Paste this string in the update file `;updateMethodRenameDashTags;`
|
|
||||||
3. Login to Shaarli
|
|
||||||
4. Delete the update file
|
|
||||||
5. Refresh
|
|
||||||
|
|
116
doc/md/Usage.md
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
For any item posted to Shaarli (called a _Shaare_), you can customize the following aspects:
|
||||||
|
|
||||||
|
- URL to link to
|
||||||
|
- Title
|
||||||
|
- Free-text description
|
||||||
|
- Tags
|
||||||
|
- Public/private status
|
||||||
|
|
||||||
|
|
||||||
|
### Adding/editing Shaares
|
||||||
|
|
||||||
|
While logged in to your Shaarli, you can add, edit or delete Shaares:
|
||||||
|
|
||||||
|
- Using the **+Shaare** button: enter the URL you want to share, click `Add link`, fill in the details of your Shaare, and `Save`
|
||||||
|
- Using the [Bookmarklet](https://en.wikipedia.org/wiki/Bookmarklet): drag the `✚Shaare link` button from the `Tools` page to your browser's bookmarks bar, click it to share the current page.
|
||||||
|
- Using [apps and browser addons](Community-and-related-software.md#mobile-apps)
|
||||||
|
- Using the [REST API](https://shaarli.github.io/api-documentation/)
|
||||||
|
- Any Shaare can edited by clicking its ![](images/edit_icon.png) `Edit` button.
|
||||||
|
|
||||||
|
The `Shaare a new link` dialog also allows you to create multiple bookmarks at once (unfold the `bulk creation` section and add one URL per line).
|
||||||
|
|
||||||
|
### Tags
|
||||||
|
|
||||||
|
Tags can be be used to organize and categorize your Shaares:
|
||||||
|
|
||||||
|
- You can rename, merge and delete tags from the _Tools_ menu or the [tag cloud/list](#tag-cloud)
|
||||||
|
- Tags are auto-completed (from the list of existing tags) in all dialogs
|
||||||
|
- Tags can be combined with text in [search](#search) queries
|
||||||
|
|
||||||
|
|
||||||
|
### Public/private Shaares
|
||||||
|
|
||||||
|
Additional filter buttons can be found at the top left of the Shaare list **only when logged in**:
|
||||||
|
|
||||||
|
- **Only show private Shaares:** Private shares can be searched by clicking the `only show private links` toggle button top left of the Shaares list (only when logged in)
|
||||||
|
|
||||||
|
|
||||||
|
### Permalinks
|
||||||
|
|
||||||
|
Permalinks are fixed, short links attached to each Shaare. Editing a Shaare will not change it's permalink, each permalink always points to the latest revision of a Shaare.
|
||||||
|
|
||||||
|
|
||||||
|
### Text-only (note) Shaares
|
||||||
|
|
||||||
|
Shaarli can be used as a minimal blog, notepad, pastebin...: While adding or editing a Shaare, leave the URL field blank to create a text-only ("note") post. This allows you to post any kind of text content, such as blog articles, private or public notes, snippets... There is no character limit! You can access your post from its permalink.
|
||||||
|
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
- **Plain text search:** Use `Search text` to search in all fields of all Shaares (Title, URL, Description...). Use double-quotes (example `"exact search"`) to search for the exact expression.
|
||||||
|
- **Tags search:** `Filter by tags` allow only displaying Shaares tagged with one or multiple tags (use space to separate tags). A plus sign `+` is optional and will restrict suggested tags to only those starting with the string (example: `pr` will hint `apron` and `printer` but `+pr` will only hint printer).
|
||||||
|
- **Hidden tags:** tags starting with a dot `.` (example `.secret`) are private. They can only be seen and searched when logged in.
|
||||||
|
- **Exclude text/tags:** Use the `-` operator before a word or tag to exclude Shaares matching this word from search results (`NOT` operator).
|
||||||
|
- **Optional tags:** Use the `~` operator before multiple tags to search for any one of them (`OR` operator). Note that the OR operator only works if there are multiple tags with a tilde. A search for `webdesign ~ai ~youtube` search would match `webdesign AND (ai OR youtube)`. A search for `webdesign ~youtube` is equivalent to `+webdesign +youtube`.
|
||||||
|
- **Wildcard tag search:** An asterisk (`*`) can be used as a wildcard and will match any number of characters. Wildcards can appear in the middle of a search term or at the end (example: pro\*in\* will match programming and protein).
|
||||||
|
- **Untagged links:** Shaares without tags can be searched by clicking the `untagged` toggle button top left of the Shaares list (only when logged in).
|
||||||
|
|
||||||
|
Both exclude patterns and exact searches can be combined with normal searches (example `"exact search" term otherterm -notthis "very exact" stuff -notagain`). Only AND (and NOT) text search is currently supported.
|
||||||
|
|
||||||
|
Active search terms are displayed on top of the link list. To remove terms/tags from the current search, click the `x` next to any of them, or simply clear text/tag search fields.
|
||||||
|
|
||||||
|
|
||||||
|
### Tag cloud
|
||||||
|
|
||||||
|
The `Tag cloud` page displays a "cloud" or list view of all tags in your Shaarli (most frequently used tags are displayed with a bigger font size)
|
||||||
|
|
||||||
|
|
||||||
|
- **Tags list:** click on `Most used` or `Alphabetical` to display tags as a list. You can also edit/delete tags for this page.
|
||||||
|
- Click on any tag to search all Shaares matching this tag.
|
||||||
|
- **Filtering the tag cloud/list:** Click on the counter next to a tag to show other tags of Shaares with this tag. Repeat this any number of times to further filter the tag cloud. Click `List all links with those tags` to display Shaares matching your current tag filter set.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### RSS feeds
|
||||||
|
|
||||||
|
RSS/ATOM feeds feeds are available (in ATOM with `/feed/atom` and RSS with `/feed/rss`)
|
||||||
|
|
||||||
|
- **Filtering RSS feeds:** RSS feeds and picture wall can also be restricted to only return items matching a text/tag search. For example, search for `photography` (text or tags) in Shaarli, then click the `RSS Feed` button. A feed with only matching results is displayed.
|
||||||
|
- Add the `&nb` parameter in feed URLs to specify the number of Shaares you want in a feed (default if not specified: `50`). The keyword `all` is available if you want everything.
|
||||||
|
- Add the `&permalinks` parameter in feed URLs to point permalinks to the corresponding Shaarli entry/link instead of the direct, Shaare URL attribute
|
||||||
|
|
||||||
|
![](images/rss-filter-1.png) ![](images/rss-filter-2.png)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# examples
|
||||||
|
https://shaarli.mydomain.org/feed/atom?permalinks
|
||||||
|
https://shaarli.mydomain.org/feed/atom?permalinks&nb=42
|
||||||
|
https://shaarli.mydomain.org/feed/atom?permalinks&nb=all
|
||||||
|
https://shaarli.mydomain.org/feed/rss?searchtags=nature
|
||||||
|
https://shaarli.mydomain.org/links/picture-wall?searchterm=poney
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Picture wall
|
||||||
|
|
||||||
|
- The picture wall can be filtered by text or tags search in the same way as [RSS feeds](#rss-feeds)
|
||||||
|
|
||||||
|
|
||||||
|
### Import/export
|
||||||
|
|
||||||
|
To **export Shaares as a HTML file**, under _Tools > Export_, choose:
|
||||||
|
|
||||||
|
- `Export all` to export both public and private Shaares
|
||||||
|
- `Export public` to export public Shaares only
|
||||||
|
- `Export private` to export private Shaares only
|
||||||
|
|
||||||
|
Restore by using the `Import` feature.
|
||||||
|
|
||||||
|
- These exports contain the full data (URL, title, tags, date, description, public/private status of your Shaares)
|
||||||
|
- They can also be imported to your web browser bookmarks.
|
||||||
|
|
||||||
|
To **import a HTML bookmarks file** exported from your browser, just use the `Import` feature. For each "folder" in the bookmarks you imported, a new tag will be created (for example a bookmark in `Movies > Sci-fi` folder will be tagged `Movies` `Sci-fi`).
|
|
@ -1,75 +0,0 @@
|
||||||
**WORK IN PROGRESS**
|
|
||||||
|
|
||||||
It's important to understand how Shaarli branches work, especially if you're maintaining a 3rd party tools for Shaarli (theme, plugin, etc.), to be sure stay compatible.
|
|
||||||
|
|
||||||
## `master` branch
|
|
||||||
|
|
||||||
The `master` branch is the development branch. Any new change MUST go through this branch using Pull Requests.
|
|
||||||
|
|
||||||
Remarks:
|
|
||||||
|
|
||||||
- This branch shouldn't be used for production as it isn't necessary stable.
|
|
||||||
- 3rd party aren't required to be compatible with the latest changes.
|
|
||||||
- Official plugins, themes and libraries (contained within Shaarli organization repos) must be compatible with the master branch.
|
|
||||||
- The version in this branch is always `dev`.
|
|
||||||
|
|
||||||
## `v0.x` branch
|
|
||||||
|
|
||||||
This `v0.x` branch, points to the latest `v0.x.y` release.
|
|
||||||
|
|
||||||
Explanation:
|
|
||||||
|
|
||||||
When a new version is released, it might contains a major bug which isn't detected right away. For example, a new PHP version is released, containing backward compatibility issue which doesn't work with Shaarli.
|
|
||||||
|
|
||||||
In this case, the issue is fixed in the `master` branch, and the fix is backported the to the `v0.x` branch. Then a new release is made from the `v0.x` branch.
|
|
||||||
|
|
||||||
This workflow allow us to fix any major bug detected, without having to release bleeding edge feature too soon.
|
|
||||||
|
|
||||||
## `latest` branch
|
|
||||||
|
|
||||||
This branch point the latest release. It recommended to use it to get the latest tested changes.
|
|
||||||
|
|
||||||
## `stable` branch
|
|
||||||
|
|
||||||
The `stable` branch doesn't contain any major bug, and is one major digit version behind the latest release.
|
|
||||||
|
|
||||||
For example, the current latest release is `v0.8.3`, the stable branch is an alias to the latest `v0.7.x` release. When the `v0.9.0` version will be released, the stable will move to the latest `v0.8.x` release.
|
|
||||||
|
|
||||||
Remarks:
|
|
||||||
|
|
||||||
- Shaarli release pace isn't fast, and the stable branch might be a few months behind the latest release.
|
|
||||||
|
|
||||||
## Releases
|
|
||||||
|
|
||||||
Releases are always made from the latest `v0.x` branch.
|
|
||||||
|
|
||||||
Note that for every release, we manually generate a tarball which contains all Shaarli dependencies, making Shaarli's installation only one step.
|
|
||||||
|
|
||||||
## Advices on 3rd party git repos workflow
|
|
||||||
|
|
||||||
### Versioning
|
|
||||||
|
|
||||||
Any time a new Shaarli release is published, you should publish a new release of your repo if the changes affected you since the latest release (take a look at the [changelog](https://github.com/shaarli/Shaarli/releases) (*Draft* means not released yet) and the commit log (like [`tpl` folder](https://github.com/shaarli/Shaarli/commits/master/tpl/default) for themes)). You can either:
|
|
||||||
|
|
||||||
- use the Shaarli version number, with your repo version. For example, if Shaarli `v0.8.3` is released, publish a `v0.8.3-1` release, where `v0.8.3` states Shaarli compatibility and `-1` is your own version digit for the current Shaarli version.
|
|
||||||
- use your own versioning scheme, and state Shaarli compatibility in the release description.
|
|
||||||
|
|
||||||
Using this, any user will be able to pick the release matching his own Shaarli version.
|
|
||||||
|
|
||||||
### Major bugfix backport releases
|
|
||||||
|
|
||||||
To be able to support backported fixes, it recommended to use our workflow:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# In master, fix the major bug
|
|
||||||
git commit -m "Katastrophe"
|
|
||||||
git push origin master
|
|
||||||
# Get your commit hash
|
|
||||||
git log --format="%H" -n 1
|
|
||||||
# Create a new branch from your latest release, let's say v0.8.2-1 (the tag name)
|
|
||||||
git checkout -b katastrophe v0.8.2-1
|
|
||||||
# Backport the fix commit to your brand new branch
|
|
||||||
git cherry-pick <fix commit hash>
|
|
||||||
git push origin katastrophe
|
|
||||||
# Then you just have to make a new release from the `katastrophe` branch tagged `v0.8.3-1`
|
|
||||||
```
|
|
1449
doc/md/dev/Development.md
Normal file
|
@ -1,140 +0,0 @@
|
||||||
## Basics
|
|
||||||
Install [Docker](https://www.docker.com/), by following the instructions relevant
|
|
||||||
to your OS / distribution, and start the service.
|
|
||||||
|
|
||||||
### Search an image on [DockerHub](https://hub.docker.com/)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ docker search debian
|
|
||||||
|
|
||||||
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
|
||||||
ubuntu Ubuntu is a Debian-based Linux operating s... 2065 [OK]
|
|
||||||
debian Debian is a Linux distribution that's comp... 603 [OK]
|
|
||||||
google/debian 47 [OK]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Show available tags for a repository
|
|
||||||
```bash
|
|
||||||
$ curl https://index.docker.io/v1/repositories/debian/tags | python -m json.tool
|
|
||||||
|
|
||||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
|
||||||
Dload Upload Total Spent Left Speed
|
|
||||||
100 1283 0 1283 0 0 433 0 --:--:-- 0:00:02 --:--:-- 433
|
|
||||||
```
|
|
||||||
|
|
||||||
Sample output:
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"layer": "85a02782",
|
|
||||||
"name": "stretch"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"layer": "59abecbc",
|
|
||||||
"name": "testing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"layer": "bf0fd686",
|
|
||||||
"name": "unstable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"layer": "60c52dbe",
|
|
||||||
"name": "wheezy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"layer": "c5b806fe",
|
|
||||||
"name": "wheezy-backports"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pull an image from DockerHub
|
|
||||||
```bash
|
|
||||||
$ docker pull repository[:tag]
|
|
||||||
|
|
||||||
$ docker pull debian:wheezy
|
|
||||||
wheezy: Pulling from debian
|
|
||||||
4c8cbfd2973e: Pull complete
|
|
||||||
60c52dbe9d91: Pull complete
|
|
||||||
Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe
|
|
||||||
Status: Downloaded newer image for debian:wheezy
|
|
||||||
```
|
|
||||||
|
|
||||||
Docker re-uses layers already downloaded. In other words if you have images based on Alpine or some Ubuntu version for example, those can share disk space.
|
|
||||||
|
|
||||||
### Start a container
|
|
||||||
A container is an instance created from an image, that can be run and that keeps running until its main process exits. Or until the user stops the container.
|
|
||||||
|
|
||||||
The simplest way to start a container from image is ``docker run``. It also pulls the image for you if it is not locally available. For more advanced use, refer to ``docker create``.
|
|
||||||
|
|
||||||
Stopped containers are not destroyed, unless you specify ``--rm``. To view all created, running and stopped containers, enter:
|
|
||||||
```bash
|
|
||||||
$ docker ps -a
|
|
||||||
```
|
|
||||||
|
|
||||||
Some containers may be designed or configured to be restarted, others are not. Also remember both network ports and volumes of a container are created on start, and not editable later.
|
|
||||||
|
|
||||||
### Access a running container
|
|
||||||
A running container is accessible using ``docker exec``, or ``docker copy``. You can use ``exec`` to start a root shell in the Shaarli container:
|
|
||||||
```bash
|
|
||||||
$ docker exec -ti <container-name-or-id> bash
|
|
||||||
```
|
|
||||||
Note the names and ID's of containers are listed in ``docker ps``. You can even type only one or two letters of the ID, given they are unique.
|
|
||||||
|
|
||||||
Access can also be through one or more network ports, or disk volumes. Both are specified on and fixed on ``docker create`` or ``run``.
|
|
||||||
|
|
||||||
You can view the console output of the main container process too:
|
|
||||||
```bash
|
|
||||||
$ docker logs -f <container-name-or-id>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker disk use
|
|
||||||
Trying out different images can fill some gigabytes of disk quickly. Besides images, the docker volumes usually take up most disk space.
|
|
||||||
|
|
||||||
If you care only about trying out docker and not about what is running or saved, the following commands should help you out quickly if you run low on disk space:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ docker rmi -f $(docker images -aq) # remove or mark all images for disposal
|
|
||||||
$ docker volume rm $(docker volume ls -q) # remove all volumes
|
|
||||||
```
|
|
||||||
|
|
||||||
### Systemd config
|
|
||||||
Systemd is the process manager of choice on Debian-based distributions. Once you have a ``docker`` service installed, you can use the following steps to set up Shaarli to run on system start.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl enable /etc/systemd/system/docker.shaarli.service
|
|
||||||
systemctl start docker.shaarli
|
|
||||||
systemctl status docker.*
|
|
||||||
journalctl -f # inspect system log if needed
|
|
||||||
```
|
|
||||||
|
|
||||||
You will need sudo or a root terminal to perform some or all of the steps above. Here are the contents for the service file:
|
|
||||||
```
|
|
||||||
[Unit]
|
|
||||||
Description=Shaarli Bookmark Manager Container
|
|
||||||
After=docker.service
|
|
||||||
Requires=docker.service
|
|
||||||
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
# Put any environment you want in an included file, like $host- or $domainname in this example
|
|
||||||
EnvironmentFile=/etc/sysconfig/box-environment
|
|
||||||
|
|
||||||
# It's just an example..
|
|
||||||
ExecStart=/usr/bin/docker run \
|
|
||||||
-p 28010:80 \
|
|
||||||
--name ${hostname}-shaarli \
|
|
||||||
--hostname shaarli.${domainname} \
|
|
||||||
-v /srv/docker-volumes-local/shaarli-data:/var/www/shaarli/data:rw \
|
|
||||||
-v /etc/localtime:/etc/localtime:ro \
|
|
||||||
shaarli/shaarli:latest
|
|
||||||
|
|
||||||
ExecStop=/usr/bin/docker rm -f ${hostname}-shaarli
|
|
||||||
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
|
@ -1,19 +0,0 @@
|
||||||
### Docker
|
|
||||||
|
|
||||||
- [Interactive Docker training portal](https://www.katacoda.com/courses/docker/) on [Katakoda](https://www.katacoda.com/)
|
|
||||||
- [Where are Docker images stored?](http://blog.thoward37.me/articles/where-are-docker-images-stored/)
|
|
||||||
- [Dockerfile reference](https://docs.docker.com/reference/builder/)
|
|
||||||
- [Dockerfile best practices](https://docs.docker.com/articles/dockerfile_best-practices/)
|
|
||||||
- [Volumes](https://docs.docker.com/userguide/dockervolumes/)
|
|
||||||
|
|
||||||
### DockerHub
|
|
||||||
|
|
||||||
- [Repositories](https://docs.docker.com/userguide/dockerrepos/)
|
|
||||||
- [Teams and organizations](https://docs.docker.com/docker-hub/orgs/)
|
|
||||||
- [GitHub automated build](https://docs.docker.com/docker-hub/github/)
|
|
||||||
|
|
||||||
### Service management
|
|
||||||
|
|
||||||
- [Using supervisord](https://docs.docker.com/articles/using_supervisord/)
|
|
||||||
- [Nginx in the foreground](http://nginx.org/en/docs/ngx_core_module.html#daemon)
|
|
||||||
- [supervisord](http://supervisord.org/)
|
|
|
@ -1,123 +0,0 @@
|
||||||
## Foreword
|
|
||||||
|
|
||||||
This guide assumes that:
|
|
||||||
|
|
||||||
- Shaarli runs in a Docker container
|
|
||||||
- The host's `10080` port is mapped to the container's `80` port
|
|
||||||
- Shaarli's Fully Qualified Domain Name (FQDN) is `shaarli.domain.tld`
|
|
||||||
- HTTP traffic is redirected to HTTPS
|
|
||||||
|
|
||||||
## Apache
|
|
||||||
|
|
||||||
- [Apache 2.4 documentation](https://httpd.apache.org/docs/2.4/)
|
|
||||||
- [mod_proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html)
|
|
||||||
- [Reverse Proxy Request Headers](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers)
|
|
||||||
|
|
||||||
The following HTTP headers are set when the `ProxyPass` directive is set:
|
|
||||||
|
|
||||||
- `X-Forwarded-For`
|
|
||||||
- `X-Forwarded-Host`
|
|
||||||
- `X-Forwarded-Server`
|
|
||||||
|
|
||||||
The original `SERVER_NAME` can be sent to the proxied host by setting the [`ProxyPreserveHost`](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#ProxyPreserveHost) directive to `On`.
|
|
||||||
|
|
||||||
```apache
|
|
||||||
<VirtualHost *:80>
|
|
||||||
ServerName shaarli.domain.tld
|
|
||||||
Redirect permanent / https://shaarli.domain.tld
|
|
||||||
</VirtualHost>
|
|
||||||
|
|
||||||
<VirtualHost *:443>
|
|
||||||
ServerName shaarli.domain.tld
|
|
||||||
|
|
||||||
SSLEngine on
|
|
||||||
SSLCertificateFile /path/to/cert
|
|
||||||
SSLCertificateKeyFile /path/to/certkey
|
|
||||||
|
|
||||||
LogLevel warn
|
|
||||||
ErrorLog /var/log/apache2/shaarli-error.log
|
|
||||||
CustomLog /var/log/apache2/shaarli-access.log combined
|
|
||||||
|
|
||||||
RequestHeader set X-Forwarded-Proto "https"
|
|
||||||
ProxyPreserveHost On
|
|
||||||
|
|
||||||
ProxyPass / http://127.0.0.1:10080/
|
|
||||||
ProxyPassReverse / http://127.0.0.1:10080/
|
|
||||||
</VirtualHost>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## HAProxy
|
|
||||||
|
|
||||||
- [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/)
|
|
||||||
|
|
||||||
```conf
|
|
||||||
global
|
|
||||||
[...]
|
|
||||||
|
|
||||||
defaults
|
|
||||||
[...]
|
|
||||||
|
|
||||||
frontend http-in
|
|
||||||
bind :80
|
|
||||||
redirect scheme https code 301 if !{ ssl_fc }
|
|
||||||
|
|
||||||
bind :443 ssl crt /path/to/cert.pem
|
|
||||||
|
|
||||||
default_backend shaarli
|
|
||||||
|
|
||||||
|
|
||||||
backend shaarli
|
|
||||||
mode http
|
|
||||||
option http-server-close
|
|
||||||
option forwardfor
|
|
||||||
reqadd X-Forwarded-Proto: https
|
|
||||||
|
|
||||||
server shaarli1 127.0.0.1:10080
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Nginx
|
|
||||||
|
|
||||||
- [Nginx documentation](https://nginx.org/en/docs/)
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
http {
|
|
||||||
[...]
|
|
||||||
|
|
||||||
index index.html index.php;
|
|
||||||
|
|
||||||
root /home/john/web;
|
|
||||||
access_log /var/log/nginx/access.log;
|
|
||||||
error_log /var/log/nginx/error.log;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name shaarli.domain.tld;
|
|
||||||
return 301 https://shaarli.domain.tld$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name shaarli.domain.tld;
|
|
||||||
|
|
||||||
ssl_certificate /path/to/cert
|
|
||||||
ssl_certificate_key /path/to/certkey
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
|
|
||||||
proxy_pass http://localhost:10080/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_connect_timeout 30s;
|
|
||||||
proxy_read_timeout 120s;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/shaarli.access.log;
|
|
||||||
error_log /var/log/nginx/shaarli.error.log;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,124 +0,0 @@
|
||||||
A brief guide on getting starting using docker is given in [Docker 101](docker-101.md).
|
|
||||||
To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](../Upgrade-and-migration.md).
|
|
||||||
|
|
||||||
## Get and run a Shaarli image
|
|
||||||
|
|
||||||
### DockerHub repository
|
|
||||||
The images can be found in the [`shaarli/shaarli`](https://hub.docker.com/r/shaarli/shaarli/)
|
|
||||||
repository.
|
|
||||||
|
|
||||||
### Available image tags
|
|
||||||
- `latest`: latest branch
|
|
||||||
- `master`: master branch
|
|
||||||
- `stable`: stable branch
|
|
||||||
|
|
||||||
The `latest` and `master` images rely on:
|
|
||||||
|
|
||||||
- [Alpine Linux](https://www.alpinelinux.org/)
|
|
||||||
- [PHP7-FPM](http://php-fpm.org/)
|
|
||||||
- [Nginx](http://nginx.org/)
|
|
||||||
|
|
||||||
The `stable` image relies on:
|
|
||||||
|
|
||||||
- [Debian 8 Jessie](https://hub.docker.com/_/debian/)
|
|
||||||
- [PHP5-FPM](http://php-fpm.org/)
|
|
||||||
- [Nginx](http://nginx.org/)
|
|
||||||
|
|
||||||
Additional Dockerfiles are provided for the `arm32v7` platform, relying on
|
|
||||||
[Linuxserver.io Alpine armhf
|
|
||||||
images](https://hub.docker.com/r/lsiobase/alpine.armhf/). These images must be
|
|
||||||
built using [`docker
|
|
||||||
build`](https://docs.docker.com/engine/reference/commandline/build/) on an
|
|
||||||
`arm32v7` machine or using an emulator such as
|
|
||||||
[qemu](https://resin.io/blog/building-arm-containers-on-any-x86-machine-even-dockerhub/).
|
|
||||||
|
|
||||||
### Download from Docker Hub
|
|
||||||
```shell
|
|
||||||
$ docker pull shaarli/shaarli
|
|
||||||
|
|
||||||
latest: Pulling from shaarli/shaarli
|
|
||||||
32716d9fcddb: Pull complete
|
|
||||||
84899d045435: Pull complete
|
|
||||||
4b6ad7444763: Pull complete
|
|
||||||
e0345ef7a3e0: Pull complete
|
|
||||||
5c1dd344094f: Pull complete
|
|
||||||
6422305a200b: Pull complete
|
|
||||||
7d63f861dbef: Pull complete
|
|
||||||
3eb97210645c: Pull complete
|
|
||||||
869319d746ff: Already exists
|
|
||||||
869319d746ff: Pulling fs layer
|
|
||||||
902b87aaaec9: Already exists
|
|
||||||
Digest: sha256:f836b4627b958b3f83f59c332f22f02fcd495ace3056f2be2c4912bd8704cc98
|
|
||||||
Status: Downloaded newer image for shaarli/shaarli:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create and start a new container from the image
|
|
||||||
```shell
|
|
||||||
# map the host's :8000 port to the container's :80 port
|
|
||||||
$ docker create -p 8000:80 shaarli/shaarli
|
|
||||||
d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
|
|
||||||
|
|
||||||
# launch the container in the background
|
|
||||||
$ docker start d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
|
|
||||||
d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
|
|
||||||
|
|
||||||
# list active containers
|
|
||||||
$ docker ps
|
|
||||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
|
||||||
d40b7af693d6 shaarli/shaarli /usr/bin/supervisor 15 seconds ago Up 4 seconds 0.0.0.0:8000->80/tcp backstabbing_galileo
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stop and destroy a container
|
|
||||||
```shell
|
|
||||||
$ docker stop backstabbing_galileo # those docker guys are really rude to physicists!
|
|
||||||
backstabbing_galileo
|
|
||||||
|
|
||||||
# check the container is stopped
|
|
||||||
$ docker ps
|
|
||||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
|
||||||
|
|
||||||
# list ALL containers
|
|
||||||
$ docker ps -a
|
|
||||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
|
||||||
d40b7af693d6 shaarli/shaarli /usr/bin/supervisor 5 minutes ago Exited (0) 48 seconds ago backstabbing_galileo
|
|
||||||
|
|
||||||
# destroy the container
|
|
||||||
$ docker rm backstabbing_galileo # let's put an end to these barbarian practices
|
|
||||||
backstabbing_galileo
|
|
||||||
|
|
||||||
$ docker ps -a
|
|
||||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
|
||||||
```
|
|
||||||
|
|
||||||
### Automatic builds
|
|
||||||
Docker users can start a personal instance from an
|
|
||||||
[autobuild image](https://hub.docker.com/r/shaarli/shaarli/).
|
|
||||||
For example to start a temporary Shaarli at ``localhost:8000``, and keep session
|
|
||||||
data (config, storage):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P)
|
|
||||||
docker run -ti --rm \
|
|
||||||
-p 8000:80 \
|
|
||||||
-v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \
|
|
||||||
shaarli/shaarli
|
|
||||||
```
|
|
||||||
|
|
||||||
### Volumes and data persistence
|
|
||||||
Data can be persisted by [using volumes](https://docs.docker.com/storage/volumes/).
|
|
||||||
Volumes allow to keep your data when renewing and/or updating container images:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Create data volumes
|
|
||||||
$ docker volume create shaarli-data
|
|
||||||
$ docker volume create shaarli-cache
|
|
||||||
|
|
||||||
# Create and start a Shaarli container using these volumes to persist data
|
|
||||||
$ docker create \
|
|
||||||
--name shaarli \
|
|
||||||
-v shaarli-cache:/var/www/shaarli/cache \
|
|
||||||
-v shaarli-data:/var/www/shaarli/data \
|
|
||||||
-p 8000:80 \
|
|
||||||
shaarli/shaarli:master
|
|
||||||
$ docker start shaarli
|
|
||||||
```
|
|
|
@ -1,64 +0,0 @@
|
||||||
## Backup and restore the datastore file
|
|
||||||
|
|
||||||
Backup the file `data/datastore.php` (by FTP or SSH). Restore by putting the file back in place.
|
|
||||||
|
|
||||||
Example command:
|
|
||||||
```bash
|
|
||||||
rsync -avzP my.server.com:/var/www/shaarli/data/datastore.php datastore-$(date +%Y-%m-%d_%H%M).php
|
|
||||||
```
|
|
||||||
|
|
||||||
## Export links as...
|
|
||||||
|
|
||||||
To export links as an HTML file, under _Tools > Export_, choose:
|
|
||||||
|
|
||||||
- _Export all_ to export both public and private links
|
|
||||||
- _Export public_ to export public links only
|
|
||||||
- _Export private_ to export private links only
|
|
||||||
|
|
||||||
Restore by using the `Import` feature.
|
|
||||||
|
|
||||||
- This can be done using the [shaarchiver](https://github.com/nodiscc/shaarchiver) tool.
|
|
||||||
|
|
||||||
Example command:
|
|
||||||
```bash
|
|
||||||
./export-bookmarks.py --url=https://my.server.com/shaarli --username=myusername --password=mysupersecretpassword --download-dir=./ --type=all
|
|
||||||
```
|
|
||||||
|
|
||||||
## Import links from...
|
|
||||||
|
|
||||||
### Diigo
|
|
||||||
|
|
||||||
If you export your bookmark from Diigo, make sure you use the Delicious export, not the Netscape export. (Their Netscape export is broken, and they don't seem to be interested in fixing it.)
|
|
||||||
|
|
||||||
### Mister Wong
|
|
||||||
|
|
||||||
See [this issue](https://github.com/sebsauvage/Shaarli/issues/146) for import tweaks.
|
|
||||||
|
|
||||||
### SemanticScuttle
|
|
||||||
|
|
||||||
To correctly import the tags from a [SemanticScuttle](http://semanticscuttle.sourceforge.net/) HTML export, edit the HTML file before importing and replace all occurences of `tags=` (lowercase) to `TAGS=` (uppercase).
|
|
||||||
|
|
||||||
### Scuttle
|
|
||||||
|
|
||||||
Shaarli cannot import data directly from [Scuttle](https://github.com/scronide/scuttle).
|
|
||||||
|
|
||||||
However, you can use the third-party [scuttle-to-shaarli](https://github.com/q2apro/scuttle-to-shaarli)
|
|
||||||
tool to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer.
|
|
||||||
|
|
||||||
### Refind
|
|
||||||
|
|
||||||
You can use the third-party tool [Derefind](https://github.com/ShawnPConroy/Derefind) to convert refind.com bookmark exports to a format that can be imported into Shaarli.
|
|
||||||
|
|
||||||
## Import Shaarli links to Firefox
|
|
||||||
|
|
||||||
- Export your Shaarli links as described above.
|
|
||||||
- For compatibility reasons, check `Prepend note permalinks with this Shaarli instance's URL (useful to import bookmarks in a web browser)`
|
|
||||||
- In Firefox, open the bookmark manager (not the sidebar! `Bookmarks menu > Show all bookmarks` or `Ctrl+Shift+B`)
|
|
||||||
- Select `Import and Backup > Import bookmarks in HTML format`
|
|
||||||
|
|
||||||
Your bookmarks will be imported in Firefox, ready to use, with tags and descriptions retained. "Self" (notes) shaares will still point to the Shaarli instance you exported them from, but the note text can be viewed directly in the bookmark properties inside your browser. Depending on the number of bookmarks, the import can take some time.
|
|
||||||
|
|
||||||
You may be interested in these Firefox addons to manage links imported from Shaarli
|
|
||||||
|
|
||||||
- [Bookmark Deduplicator](https://addons.mozilla.org/en-US/firefox/addon/bookmark-deduplicator/) - provides an easy way to deduplicate your bookmarks
|
|
||||||
- [TagSieve](https://addons.mozilla.org/en-US/firefox/addon/tagsieve/) - browse your bookmarks by their tags
|
|
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 42 KiB |
|
@ -1,257 +0,0 @@
|
||||||
_Last updated on 2018-07-01._
|
|
||||||
|
|
||||||
## Goals
|
|
||||||
- Getting a Virtual Private Server (VPS)
|
|
||||||
- Running Shaarli:
|
|
||||||
- as a Docker container,
|
|
||||||
- using the Træfik reverse proxy,
|
|
||||||
- securized with TLS certificates from Let's Encrypt.
|
|
||||||
|
|
||||||
|
|
||||||
The following components and tools will be used:
|
|
||||||
|
|
||||||
- [Debian](https://www.debian.org/), a GNU/Linux distribution widely used in
|
|
||||||
server environments;
|
|
||||||
- [Docker](https://docs.docker.com/engine/docker-overview/), an open platform
|
|
||||||
for developing, shipping, and running applications;
|
|
||||||
- [Docker Compose](https://docs.docker.com/compose/), a tool for defining and
|
|
||||||
running multi-container Docker applications.
|
|
||||||
|
|
||||||
|
|
||||||
More information can be found in the [Resources](#resources) section at the
|
|
||||||
bottom of the guide.
|
|
||||||
|
|
||||||
## Getting a Virtual Private Server
|
|
||||||
For this guide, I went for the smallest VPS available from DigitalOcean,
|
|
||||||
a Droplet with 1 CPU, 1 GiB RAM and 25 GiB SSD storage, which costs
|
|
||||||
$5/month ($0.007/hour):
|
|
||||||
|
|
||||||
- [Droplets Overview](https://www.digitalocean.com/docs/droplets/overview/)
|
|
||||||
- [Pricing](https://www.digitalocean.com/pricing/)
|
|
||||||
- [How to Create a Droplet from the DigitalOcean Control Panel](https://www.digitalocean.com/docs/droplets/how-to/create/)
|
|
||||||
- [How to Add SSH Keys to Droplets](https://www.digitalocean.com/docs/droplets/how-to/add-ssh-keys/)
|
|
||||||
- [Initial Server Setup with Debian 8](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-debian-8) (also applies to Debian 9)
|
|
||||||
- [An Introduction to Securing your Linux VPS](https://www.digitalocean.com/community/tutorials/an-introduction-to-securing-your-linux-vps)
|
|
||||||
|
|
||||||
### Creating a Droplet
|
|
||||||
Select `Debian 9` as the Droplet distribution:
|
|
||||||
|
|
||||||
<img src="../images/01-create-droplet-distro.jpg"
|
|
||||||
width="500px"
|
|
||||||
alt="Droplet distribution" />
|
|
||||||
|
|
||||||
Choose a region that is geographically close to you:
|
|
||||||
|
|
||||||
<img src="../images/02-create-droplet-region.jpg"
|
|
||||||
width="500px"
|
|
||||||
alt="Droplet region" />
|
|
||||||
|
|
||||||
Choose a Droplet size that corresponds to your usage and budget:
|
|
||||||
|
|
||||||
<img src="../images/03-create-droplet-size.jpg"
|
|
||||||
width="500px"
|
|
||||||
alt="Droplet size" />
|
|
||||||
|
|
||||||
Finalize the Droplet creation:
|
|
||||||
|
|
||||||
<img src="../images/04-finalize.jpg"
|
|
||||||
width="500px"
|
|
||||||
alt="Droplet finalization" />
|
|
||||||
|
|
||||||
Droplet information is displayed on the Control Panel:
|
|
||||||
|
|
||||||
<img src="../images/05-droplet.jpg"
|
|
||||||
width="500px"
|
|
||||||
alt="Droplet summary" />
|
|
||||||
|
|
||||||
Once your VPS has been created, you will receive an e-mail with connection
|
|
||||||
instructions.
|
|
||||||
|
|
||||||
## Obtaining a domain name
|
|
||||||
After creating your VPS, it will be reachable using its IP address; some hosting
|
|
||||||
providers also create a DNS record, e.g. `ns4853142.ip-01-47-127.eu`.
|
|
||||||
|
|
||||||
A domain name (DNS record) is required to obtain a certificate and setup HTTPS
|
|
||||||
(HTTP with TLS encryption).
|
|
||||||
|
|
||||||
Domain names can be obtained from registrars through hosting providers such as
|
|
||||||
[Gandi](https://www.gandi.net/en/domain).
|
|
||||||
|
|
||||||
Once you have your own domain, you need to create a new DNS record that points
|
|
||||||
to your VPS' IP address:
|
|
||||||
|
|
||||||
<img src="../images/06-domain.jpg"
|
|
||||||
width="650px"
|
|
||||||
alt="Domain configuration" />
|
|
||||||
|
|
||||||
## Host setup
|
|
||||||
Now's the time to connect to your freshly created VPS!
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ ssh root@188.166.85.8
|
|
||||||
|
|
||||||
Linux stretch-shaarli-02 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07) x86_64
|
|
||||||
|
|
||||||
The programs included with the Debian GNU/Linux system are free software;
|
|
||||||
the exact distribution terms for each program are described in the
|
|
||||||
individual files in /usr/share/doc/*/copyright.
|
|
||||||
|
|
||||||
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
|
|
||||||
permitted by applicable law.
|
|
||||||
Last login: Sun Jul 1 11:20:18 2018 from <REDACTED>
|
|
||||||
|
|
||||||
root@stretch-shaarli-02:~$
|
|
||||||
```
|
|
||||||
|
|
||||||
### Updating the system
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~$ apt update && apt upgrade -y
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting up Docker
|
|
||||||
_The following instructions are from the
|
|
||||||
[Get Docker CE for Debian](https://docs.docker.com/install/linux/docker-ce/debian/)
|
|
||||||
guide._
|
|
||||||
|
|
||||||
Install package dependencies:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~$ apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
|
|
||||||
```
|
|
||||||
|
|
||||||
Add Docker's package repository GPG key:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
|
|
||||||
```
|
|
||||||
|
|
||||||
Add Docker's package repository:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~$ add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian stretch stable"
|
|
||||||
```
|
|
||||||
|
|
||||||
Update package lists and install Docker:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~$ apt update && apt install -y docker-ce
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify Docker is properly configured by running the `hello-world` image:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~$ docker run hello-world
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting up Docker Compose
|
|
||||||
_The following instructions are from the
|
|
||||||
[Install Docker Compose](https://docs.docker.com/compose/install/)
|
|
||||||
guide._
|
|
||||||
|
|
||||||
Download the current version from the release page:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~$ curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
|
|
||||||
root@stretch-shaarli-02:~$ chmod +x /usr/local/bin/docker-compose
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running Shaarli
|
|
||||||
Shaarli comes with a configuration file for Docker Compose, that will setup:
|
|
||||||
|
|
||||||
- a local Docker network
|
|
||||||
- a Docker [volume](https://docs.docker.com/storage/volumes/) to store Shaarli data
|
|
||||||
- a Docker [volume](https://docs.docker.com/storage/volumes/) to store Træfik TLS configuration and certificates
|
|
||||||
- a [Shaarli](https://hub.docker.com/r/shaarli/shaarli/) instance
|
|
||||||
- a [Træfik](https://hub.docker.com/_/traefik/) instance
|
|
||||||
|
|
||||||
[Træfik](https://docs.traefik.io/) is a modern HTTP reverse proxy, with native
|
|
||||||
support for Docker and [Let's Encrypt](https://letsencrypt.org/).
|
|
||||||
|
|
||||||
### Compose configuration
|
|
||||||
Create a new directory to store the configuration:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~$ mkdir shaarli && cd shaarli
|
|
||||||
root@stretch-shaarli-02:~/shaarli$
|
|
||||||
```
|
|
||||||
|
|
||||||
Download the current version of Shaarli's `docker-compose.yml`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~/shaarli$ curl -L https://raw.githubusercontent.com/shaarli/Shaarli/master/docker-compose.yml -o docker-compose.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Create the `.env` file and fill in your VPS and domain information (replace
|
|
||||||
`<MY_SHAARLI_DOMAIN>` and `<MY_CONTACT_EMAIL>` with your actual information):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~/shaarli$ vim .env
|
|
||||||
```
|
|
||||||
|
|
||||||
```shell
|
|
||||||
SHAARLI_VIRTUAL_HOST=<MY_SHAARLI_DOMAIN>
|
|
||||||
SHAARLI_LETSENCRYPT_EMAIL=<MY_CONTACT_EMAIL>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pull the Docker images
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~/shaarli$ docker-compose pull
|
|
||||||
Pulling shaarli ... done
|
|
||||||
Pulling traefik ... done
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run!
|
|
||||||
```shell
|
|
||||||
root@stretch-shaarli-02:~/shaarli$ docker-compose up -d
|
|
||||||
Creating network "shaarli_http-proxy" with the default driver
|
|
||||||
Creating volume "shaarli_traefik-acme" with default driver
|
|
||||||
Creating volume "shaarli_shaarli-data" with default driver
|
|
||||||
Creating shaarli_shaarli_1 ... done
|
|
||||||
Creating shaarli_traefik_1 ... done
|
|
||||||
```
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
Congratulations! Your Shaarli instance should be up and running, and available
|
|
||||||
at `https://<MY_SHAARLI_DOMAIN>`.
|
|
||||||
|
|
||||||
<img src="../images/07-installation.jpg"
|
|
||||||
width="500px"
|
|
||||||
alt="Shaarli installation page" />
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
### Related Shaarli documentation
|
|
||||||
- [Docker 101](../docker/docker-101.md)
|
|
||||||
- [Shaarli images](../docker/shaarli-images.md)
|
|
||||||
|
|
||||||
### Hosting providers
|
|
||||||
- [DigitalOcean](https://www.digitalocean.com/)
|
|
||||||
- [Gandi](https://www.gandi.net/en)
|
|
||||||
- [OVH](https://www.ovh.co.uk/)
|
|
||||||
- [RackSpace](https://www.rackspace.com/)
|
|
||||||
- etc.
|
|
||||||
|
|
||||||
### Domain Names and Registrars
|
|
||||||
- [Introduction to the Domain Name System (DNS)](https://opensource.com/article/17/4/introduction-domain-name-system-dns)
|
|
||||||
- [ICANN](https://www.icann.org/)
|
|
||||||
- [Domain name registrar](https://en.wikipedia.org/wiki/Domain_name_registrar)
|
|
||||||
- [OVH Domain Registration](https://www.ovh.co.uk/domains/)
|
|
||||||
- [Gandi Domain Registration](https://www.gandi.net/en/domain)
|
|
||||||
|
|
||||||
### HTTPS and Security
|
|
||||||
- [Transport Layer Security](https://en.wikipedia.org/wiki/Transport_Layer_Security)
|
|
||||||
- [Let's Encrypt](https://letsencrypt.org/)
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
- [Docker Overview](https://docs.docker.com/engine/docker-overview/)
|
|
||||||
- [Docker Documentation](https://docs.docker.com/)
|
|
||||||
- [Get Docker CE for Debian](https://docs.docker.com/install/linux/docker-ce/debian/)
|
|
||||||
- [docker logs](https://docs.docker.com/engine/reference/commandline/logs/)
|
|
||||||
- [Volumes](https://docs.docker.com/storage/volumes/)
|
|
||||||
- [Install Docker Compose](https://docs.docker.com/compose/install/)
|
|
||||||
- [docker-compose logs](https://docs.docker.com/compose/reference/logs/)
|
|
||||||
|
|
||||||
### Træfik
|
|
||||||
- [Getting Started](https://docs.traefik.io/)
|
|
||||||
- [Docker backend](https://docs.traefik.io/configuration/backends/docker/)
|
|
||||||
- [Let's Encrypt and Docker](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/)
|
|
||||||
- [traefik](https://hub.docker.com/_/traefik/) Docker image
|
|
|
@ -1,33 +0,0 @@
|
||||||
### Decode datastore content
|
|
||||||
|
|
||||||
To display the array representing the data saved in `data/datastore.php`, use the following snippet:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$data = "tZNdb9MwFIb... <Commented content inside datastore.php>";
|
|
||||||
$out = unserialize(gzinflate(base64_decode($data)));
|
|
||||||
echo "<pre>"; // Pretty printing is love, pretty printing is life
|
|
||||||
print_r($out);
|
|
||||||
echo "</pre>";
|
|
||||||
exit;
|
|
||||||
```
|
|
||||||
This will output the internal representation of the datastore, "unobfuscated" (if this can really be considered obfuscation).
|
|
||||||
|
|
||||||
Alternatively, you can transform to JSON format (and pretty-print if you have `jq` installed):
|
|
||||||
```
|
|
||||||
php -r 'print(json_encode(unserialize(gzinflate(base64_decode(preg_replace("!.*/\* (.+) \*/.*!", "$1", file_get_contents("data/datastore.php")))))));' | jq .
|
|
||||||
```
|
|
||||||
|
|
||||||
### Changing the timestamp for a shaare
|
|
||||||
|
|
||||||
- Look for `<input type="hidden" name="lf_linkdate" value="{$link.linkdate}">` in `tpl/editlink.tpl` (line 14)
|
|
||||||
- Replace `type="hidden"` with `type="text"` from this line
|
|
||||||
- A new date/time field becomes available in the edit/new link dialog.
|
|
||||||
- You can set the timestamp manually by entering it in the format `YYYMMDD_HHMMS`.
|
|
||||||
|
|
||||||
|
|
||||||
### See also
|
|
||||||
|
|
||||||
- [Add a new custom field to shaares (example patch)](https://gist.github.com/nodiscc/8b0194921f059d7b9ad89a581ecd482c)
|
|
||||||
- [Download CSS styles for shaarlis listed in an opml file](https://gist.github.com/nodiscc/dede231c92cab22c3ad2cc24d5035012)
|
|
||||||
- [Copy an existing Shaarli installation over SSH, and serve it locally](https://gist.github.com/nodiscc/ed161c66e5b028b5299b0a3733d01c77)
|
|
||||||
- [Create multiple Shaarli instances, generate an HTML index of them](https://gist.github.com/nodiscc/52e711cda3bc47717c16065231cf6b20)
|
|
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 71 KiB |
149
doc/md/index.md
|
@ -1,121 +1,122 @@
|
||||||
# <img src="images/icon.png" width="20px" height="20px"> Shaarli
|
# Shaarli
|
||||||
|
|
||||||
The personal, minimalist, super-fast, database free, bookmarking service.
|
_The personal, minimalist, super fast, database-free, bookmarking service._
|
||||||
|
|
||||||
Do you want to share the links you discover?
|
Do you want to share the links you discover? Shaarli is a minimalist bookmark manager and link sharing service that you can install on your own server. It is designed to be personal (single-user), fast and handy.
|
||||||
Shaarli is a minimalist bookmark manager and link sharing service that you can install on your own server.
|
|
||||||
It is designed to be personal (single-user), fast and handy.
|
|
||||||
|
|
||||||
<!-- TODO screenshots -->
|
|
||||||
|
|
||||||
Visit the pages in the sidebar to find information on how to setup, use, configure, tweak and troubleshoot Shaarli.
|
Visit the pages in the sidebar to find information on how to setup, use, configure, tweak and troubleshoot Shaarli.
|
||||||
|
|
||||||
|
|
||||||
* [GitHub project page](https://github.com/shaarli/Shaarli)
|
* [GitHub project page](https://github.com/shaarli/Shaarli)
|
||||||
* [Online documentation](https://shaarli.readthedocs.io/)
|
* [Documentation](https://shaarli.readthedocs.io/)
|
||||||
* [Latest releases](https://github.com/shaarli/Shaarli/releases)
|
|
||||||
* [Changelog](https://github.com/shaarli/Shaarli/blob/master/CHANGELOG.md)
|
* [Changelog](https://github.com/shaarli/Shaarli/blob/master/CHANGELOG.md)
|
||||||
|
|
||||||
|
|
||||||
### Demo
|
[![](https://i.imgur.com/8wEBRSG.png)](https://i.imgur.com/WWPfSj0.png) [![](https://i.imgur.com/93PpLLs.png)](https://i.imgur.com/V09kAQt.png) [![](https://i.imgur.com/rrsjWYy.png)](https://i.imgur.com/TZzGHMs.png) [![](https://i.imgur.com/8iRzHfe.png)](https://i.imgur.com/sfJJ6NT.png) [![](https://i.imgur.com/GjZGvIh.png)](https://i.imgur.com/QsedIuJ.png) [![](https://i.imgur.com/TFZ9PEq.png)](https://i.imgur.com/KdtF8Ll.png) [![](https://i.imgur.com/tVvD3gH.png)](https://i.imgur.com/zGF4d6L.jpg)
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
:maxdepth: 1
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
Server-configuration.md
|
||||||
|
Installation.md
|
||||||
|
Reverse-proxy.md
|
||||||
|
Docker.md
|
||||||
|
Shaarli-configuration.md
|
||||||
|
Usage.md
|
||||||
|
Backup-and-restore.md
|
||||||
|
Upgrade-and-migration.md
|
||||||
|
Community-and-related-software.md
|
||||||
|
Plugins.md
|
||||||
|
REST-API.md
|
||||||
|
Troubleshooting.md
|
||||||
|
dev/Development.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
You can use this [public demo instance of Shaarli](https://demo.shaarli.org).
|
You can use this [public demo instance of Shaarli](https://demo.shaarli.org).
|
||||||
It runs the latest development version of Shaarli and is updated/reset daily.
|
It runs the latest development version of Shaarli and is updated/reset daily.
|
||||||
|
|
||||||
Login: `demo`; Password: `demo`
|
Login: `demo`; Password: `demo`
|
||||||
|
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
- [Configure your server](Server-configuration.md)
|
||||||
|
- [Install Shaarli](Installation.md)
|
||||||
|
- Or install Shaarli using [Docker](Docker.md)
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
Shaarli can be used:
|
Shaarli can be used:
|
||||||
|
|
||||||
- to share, comment and save interesting links and news
|
- to share, comment and save interesting links
|
||||||
- to bookmark useful/frequent links and share them between computers
|
- to bookmark useful/frequent links and share them between computers
|
||||||
- as a minimal blog/microblog/writing platform
|
- as a minimal blog/microblog/writing platform
|
||||||
- as a read-it-later list
|
- as a read-it-later/todo list
|
||||||
- to draft and save articles/posts/ideas
|
- as a notepad to draft and save articles/posts/ideas
|
||||||
- to keep notes, documentation and code snippets
|
- as a knowledge base to keep notes, documentation and code snippets
|
||||||
- as a shared clipboard/notepad/pastebin between machines
|
- as a shared clipboard/notepad/pastebin between computers
|
||||||
- as a todo list
|
- as playlist manager for online media
|
||||||
- to store media playlists
|
- to feed other blogs, aggregators, social networks...
|
||||||
- to keep extracts/comments from webpages that may disappear.
|
|
||||||
- to keep track of ongoing discussions
|
|
||||||
- to feed other blogs, aggregators, social networks... using RSS feeds
|
|
||||||
|
|
||||||
### Edit, view and search your links
|
### Edit, view and search your links
|
||||||
|
|
||||||
- Minimalist design
|
- Editable URL, title, description, tags, private/public status for all your [Shaares](Usage.md)
|
||||||
- FAST
|
- [Tags](Usage.md#tags) to organize your Shaares
|
||||||
- Customizable link titles and descriptions
|
- [Search](Usage.md#search) in all fields
|
||||||
- Tags to organize your links (features tag autocompletion, renaming, merging and deletion)
|
- Unique [permalinks](Usage.md#permalinks) for easy reference
|
||||||
- Search by tag or using the full-text search
|
- Paginated Shaares list view (with image and video thumbnails)
|
||||||
- Public and private links (visible only to logged-in users)
|
- [Tag cloud/list](Usage.md#tag-cloud) views
|
||||||
- Unique permalinks for easy reference
|
- [Picture wall](Usage.md#picture-wall)/thumbnails view (with lazy loading)
|
||||||
- Paginated link list (with image and video thumbnails)
|
- [ATOM and RSS feeds](Usage.md#rss-feeds) (can also be filtered using tags or text search)
|
||||||
- Tag cloud and list views
|
- [Daily](Usage.md#daily): newspaper-like daily digest (and daily RSS feed)
|
||||||
- Picture wall: image and video thumbnails view (with lazy loading)
|
- URL cleanup: automatic removal of `?utm_source=...`, `fb=...` tracking parameters
|
||||||
- ATOM and RSS feeds (can also be filtered using tags or text search)
|
- Extensible through [plugins](Plugins.md)
|
||||||
- Daily: newspaper-like daily digest (and daily RSS feed)
|
- Easily extensible by any client using the [REST API](REST-API.md) exposed by Shaarli
|
||||||
- URL cleanup: automatic removal of `?utm_source=...`, `fb=...`
|
- Bookmarklet and [other tools](Community-and-related-software.md) to share links in one click
|
||||||
- Extensible through [plugins](https://shaarli.readthedocs.io/en/master/Plugins/#plugin-usage)
|
- Responsive/support for mobile browsers, degrades gracefully with Javascript disabled
|
||||||
|
- LDAP (single-user) login support
|
||||||
|
|
||||||
|
|
||||||
### Easy setup
|
### Easy setup
|
||||||
|
|
||||||
- Dead-simple installation: drop the files, open the page
|
- Dead-simple [installation](Installation.md): drop the files on your server, open the page
|
||||||
- Links are stored in a file (no database required, easy backup: simply copy the datastore file)
|
- Shaares are stored in a file (no database required, easy [backup](Backup-and-restore.md))
|
||||||
- Import and export links as Netscape bookmarks compatible with most Web browsers
|
- [Configurable](Shaarli-configuration.md) from dialog and configuration file
|
||||||
|
- Extensible through third-party [plugins and themes](Community-and-related-software.md)
|
||||||
|
|
||||||
### Accessibility
|
|
||||||
|
|
||||||
- Bookmarklet and other tools to share links in one click
|
### Fast
|
||||||
- Support for mobile browsers
|
|
||||||
- Degrades gracefully with Javascript disabled
|
|
||||||
- Easy page customization through HTML/CSS/RainTPL
|
|
||||||
|
|
||||||
### Security
|
- Fast! Small datastore file, write-once/read-many, served most of the time from OS disk caches (no disk I/O)
|
||||||
|
- Stays fast with even tens of thousands shaares!
|
||||||
|
|
||||||
- Discreet pop-up notification when a new release is available
|
|
||||||
- Bruteforce protection on the login form
|
|
||||||
- Protected against [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) and session cookie hijacking
|
|
||||||
|
|
||||||
<!-- TODO Limitations -->
|
### Self-hosted
|
||||||
|
|
||||||
### REST API
|
- Shaarli is an alternative to commercial services such as StumbleUpon, Delicio.us, Diigo...
|
||||||
|
- The data is yours, [import and export](Usage.md#import-export) it to HTML bookmarksformat compatible with most web browser, and from a variety of formats
|
||||||
|
- Shaarli does not send any telemetry/metrics/private information to developers
|
||||||
|
- Shaarli is Free and Open-Source software, inspect and change how the program works in the [source code](https://github.com/shaarli/Shaarli)
|
||||||
|
- Built-in [Security](dev/Development.md#security) features to help you protect your Shaarli instance
|
||||||
|
|
||||||
- Easily extensible by any client using the REST API exposed by Shaarli ([API documentation](http://shaarli.github.io/api-documentation/)).
|
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
### Shaarli community fork
|
This [community fork](https://github.com/shaarli/Shaarli) of the original [Shaarli](https://github.com/sebsauvage/Shaarli/) project by [Sébastien Sauvage](https://sebsauvage.net/) (now [unmaintained](https://github.com/sebsauvage/Shaarli/issues/191)) has carried on the work to provide [many patches](https://github.com/sebsauvage/Shaarli/compare/master...shaarli:Shaarli:master) for [bug fixes and enhancements](https://github.com/shaarli/Shaarli/issues?q=is%3Aclosed+) in this repository, and will keep maintaining the project for the foreseeable future, while keeping Shaarli simple and efficient.
|
||||||
|
|
||||||
This friendly fork is maintained by the Shaarli community at <https://github.com/shaarli/Shaarli>
|
The original Shaarli instance is still available [here](https://sebsauvage.net/links/) (+25000 shaares!)
|
||||||
|
|
||||||
This is a community fork of the original [Shaarli](https://github.com/sebsauvage/Shaarli/) project by [Sébastien Sauvage](http://sebsauvage.net/).
|
|
||||||
|
|
||||||
The original project is currently unmaintained, and the developer [has informed us](https://github.com/sebsauvage/Shaarli/issues/191) that he would have no time to work on Shaarli in the near future.
|
|
||||||
|
|
||||||
The Shaarli community has carried on the work to provide [many
|
|
||||||
patches](https://github.com/shaarli/Shaarli/compare/sebsauvage:master...master) for
|
|
||||||
[bug fixes and enhancements](https://github.com/shaarli/Shaarli/issues?q=is%3Aclosed+)
|
|
||||||
in this repository, and will keep maintaining the project for the foreseeable
|
|
||||||
future, while keeping Shaarli simple and efficient.
|
|
||||||
|
|
||||||
|
|
||||||
### Contributing and getting help
|
### Contributing and getting help
|
||||||
|
|
||||||
Feedback is very appreciated!
|
Feedback is very appreciated! Feel free to propose solutions to existing problems, help us improve the documentation and [translations](dev/Development.md#translations), and submit pull requests :-)
|
||||||
|
|
||||||
- If you have any questions or ideas, please join the [chat](https://gitter.im/shaarli/Shaarli) (also reachable via [IRC](https://irc.gitter.im/)), post them in our [general discussion](https://github.com/shaarli/Shaarli/issues/308) or read the current [issues](https://github.com/shaarli/Shaarli/issues).
|
See [Support](Troubleshooting.md#support) to get in touch with the Shaarli community.
|
||||||
- Have a look at the open [issues](https://github.com/shaarli/Shaarli/issues) and [pull requests](https://github.com/shaarli/Shaarli/pulls)
|
|
||||||
- If you would like a feature added to Shaarli, check the issues labeled [`feature`](https://github.com/shaarli/Shaarli/labels/feature), [`enhancement`](https://github.com/shaarli/Shaarli/labels/enhancement), and [`plugin`](https://github.com/shaarli/Shaarli/labels/plugin).
|
|
||||||
- If you've found a bug, please create a [new issue](https://github.com/shaarli/Shaarli/issues/new).
|
|
||||||
- Feel free to propose solutions to existing problems, help us improve the documentation and translations, and submit pull requests :-)
|
|
||||||
|
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
Shaarli is [Free Software](http://en.wikipedia.org/wiki/Free_software). See
|
Shaarli is [Free Software](https://en.wikipedia.org/wiki/Free_software). See [COPYING](https://github.com/shaarli/Shaarli/blob/master/COPYING) for a detail of the contributors and licenses for each individual component. A list of contributors is available [here](https://github.com/shaarli/Shaarli/blob/master/AUTHORS).
|
||||||
[COPYING](https://github.com/shaarli/Shaarli/blob/master/COPYING) for a detail
|
|
||||||
of the contributors and licenses for each individual component. A list of
|
|
||||||
contributors is available
|
|
||||||
[here](https://github.com/shaarli/Shaarli/blob/master/AUTHORS).
|
|
||||||
|
|
||||||
|
|
4
doc/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
sphinx==7.1.0
|
||||||
|
furo==2023.7.26
|
||||||
|
myst-parser
|
||||||
|
sphinx-design
|
|
@ -2,12 +2,13 @@
|
||||||
# Shaarli - Docker Compose example configuration
|
# Shaarli - Docker Compose example configuration
|
||||||
#
|
#
|
||||||
# See:
|
# See:
|
||||||
# - https://shaarli.readthedocs.io/en/master/docker/shaarli-images/
|
# - https://shaarli.readthedocs.io/en/master/Docker/#docker-compose
|
||||||
# - https://shaarli.readthedocs.io/en/master/guides/install-shaarli-with-debian9-and-docker/
|
|
||||||
#
|
#
|
||||||
# Environment variables:
|
# Environment variables:
|
||||||
# - SHAARLI_VIRTUAL_HOST Fully Qualified Domain Name for the Shaarli instance
|
# - SHAARLI_VIRTUAL_HOST Fully Qualified Domain Name for the Shaarli instance
|
||||||
# - SHAARLI_LETSENCRYPT_EMAIL Contact email for certificate renewal
|
# - SHAARLI_LETSENCRYPT_EMAIL Contact email for certificate renewal
|
||||||
|
# - SHAARLI_DOCKER_TAG Shaarli docker tag to use
|
||||||
|
# See: https://github.com/shaarli/Shaarli/pkgs/container/shaarli/versions?filters%5Bversion_type%5D=tagged
|
||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
@ -20,7 +21,7 @@ volumes:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
shaarli:
|
shaarli:
|
||||||
image: shaarli/shaarli:master
|
image: ghcr.io/shaarli/shaarli:${SHAARLI_DOCKER_TAG}
|
||||||
build: ./
|
build: ./
|
||||||
networks:
|
networks:
|
||||||
- http-proxy
|
- http-proxy
|
||||||
|
@ -33,14 +34,14 @@ services:
|
||||||
traefik.frontend.rule: "Host:${SHAARLI_VIRTUAL_HOST}"
|
traefik.frontend.rule: "Host:${SHAARLI_VIRTUAL_HOST}"
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik
|
image: traefik:1.7-alpine
|
||||||
command:
|
command:
|
||||||
- "--defaultentrypoints=http,https"
|
- "--defaultentrypoints=http,https"
|
||||||
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
||||||
- "--entrypoints=Name:https Address::443 TLS"
|
- "--entrypoints=Name:https Address::443 TLS"
|
||||||
- "--retry"
|
- "--retry"
|
||||||
- "--docker"
|
- "--docker"
|
||||||
- "--docker.domain=docker.localhost"
|
- "--docker.domain=${SHAARLI_VIRTUAL_HOST}"
|
||||||
- "--docker.exposedbydefault=true"
|
- "--docker.exposedbydefault=true"
|
||||||
- "--docker.watch=true"
|
- "--docker.watch=true"
|
||||||
- "--acme"
|
- "--acme"
|
||||||
|
|
|
@ -2,17 +2,17 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Shaarli\n"
|
"Project-Id-Version: Shaarli\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-01-23 23:57+0100\n"
|
"POT-Creation-Date: 2023-04-05 08:38+0200\n"
|
||||||
"PO-Revision-Date: 2021-01-24 00:37+0100\n"
|
"PO-Revision-Date: 2023-04-05 08:39+0200\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Shaarli\n"
|
"Language-Team: Shaarli\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Generator: Poedit 2.4.2\n"
|
|
||||||
"X-Poedit-Basepath: ../../../..\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 3.2.2\n"
|
||||||
|
"X-Poedit-Basepath: ../../../..\n"
|
||||||
"X-Poedit-SourceCharset: UTF-8\n"
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
"X-Poedit-KeywordsList: t:1,2;t\n"
|
"X-Poedit-KeywordsList: t:1,2;t\n"
|
||||||
"X-Poedit-SearchPath-0: .\n"
|
"X-Poedit-SearchPath-0: .\n"
|
||||||
|
@ -51,6 +51,10 @@ msgstr "Japanisch"
|
||||||
msgid "Russian"
|
msgid "Russian"
|
||||||
msgstr "Russisch"
|
msgstr "Russisch"
|
||||||
|
|
||||||
|
#: application/Languages.php:190
|
||||||
|
msgid "Chinese (Simplified)"
|
||||||
|
msgstr "Chinesisch (vereinfacht)"
|
||||||
|
|
||||||
#: application/Thumbnailer.php:62
|
#: application/Thumbnailer.php:62
|
||||||
msgid ""
|
msgid ""
|
||||||
"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
|
"php-gd extension must be loaded to use thumbnails. Thumbnails are now "
|
||||||
|
@ -60,41 +64,41 @@ msgstr ""
|
||||||
"(Thumbnails) verwenden zu können. Thumbnails sind jetzt deaktiviert. Bitte "
|
"(Thumbnails) verwenden zu können. Thumbnails sind jetzt deaktiviert. Bitte "
|
||||||
"lade die Seite neu."
|
"lade die Seite neu."
|
||||||
|
|
||||||
#: application/Utils.php:405 tests/UtilsTest.php:327
|
#: application/Utils.php:409 tests/UtilsTest.php:329
|
||||||
msgid "Setting not set"
|
msgid "Setting not set"
|
||||||
msgstr "Einstellung nicht gesetzt"
|
msgstr "Einstellung nicht gesetzt"
|
||||||
|
|
||||||
#: application/Utils.php:412 tests/UtilsTest.php:325 tests/UtilsTest.php:326
|
#: application/Utils.php:416 tests/UtilsTest.php:327 tests/UtilsTest.php:328
|
||||||
msgid "Unlimited"
|
msgid "Unlimited"
|
||||||
msgstr "Unbegrenzt"
|
msgstr "Unbegrenzt"
|
||||||
|
|
||||||
#: application/Utils.php:415 tests/UtilsTest.php:322 tests/UtilsTest.php:323
|
#: application/Utils.php:419 tests/UtilsTest.php:324 tests/UtilsTest.php:325
|
||||||
#: tests/UtilsTest.php:337
|
#: tests/UtilsTest.php:339
|
||||||
msgid "B"
|
msgid "B"
|
||||||
msgstr "B"
|
msgstr "B"
|
||||||
|
|
||||||
#: application/Utils.php:415 tests/UtilsTest.php:316 tests/UtilsTest.php:317
|
#: application/Utils.php:419 tests/UtilsTest.php:318 tests/UtilsTest.php:319
|
||||||
#: tests/UtilsTest.php:324
|
#: tests/UtilsTest.php:326
|
||||||
msgid "kiB"
|
msgid "kiB"
|
||||||
msgstr "kiB"
|
msgstr "kiB"
|
||||||
|
|
||||||
#: application/Utils.php:415 tests/UtilsTest.php:318 tests/UtilsTest.php:319
|
#: application/Utils.php:419 tests/UtilsTest.php:320 tests/UtilsTest.php:321
|
||||||
#: tests/UtilsTest.php:335 tests/UtilsTest.php:336
|
#: tests/UtilsTest.php:337 tests/UtilsTest.php:338
|
||||||
msgid "MiB"
|
msgid "MiB"
|
||||||
msgstr "MiB"
|
msgstr "MiB"
|
||||||
|
|
||||||
#: application/Utils.php:415 tests/UtilsTest.php:320 tests/UtilsTest.php:321
|
#: application/Utils.php:419 tests/UtilsTest.php:322 tests/UtilsTest.php:323
|
||||||
msgid "GiB"
|
msgid "GiB"
|
||||||
msgstr "GiB"
|
msgstr "GiB"
|
||||||
|
|
||||||
#: application/bookmark/BookmarkFileService.php:185
|
#: application/bookmark/BookmarkFileService.php:203
|
||||||
#: application/bookmark/BookmarkFileService.php:207
|
#: application/bookmark/BookmarkFileService.php:225
|
||||||
#: application/bookmark/BookmarkFileService.php:229
|
#: application/bookmark/BookmarkFileService.php:247
|
||||||
#: application/bookmark/BookmarkFileService.php:243
|
#: application/bookmark/BookmarkFileService.php:261
|
||||||
msgid "You're not authorized to alter the datastore"
|
msgid "You're not authorized to alter the datastore"
|
||||||
msgstr "Du bist nicht berechtigt, den Datenspeicher zu ändern"
|
msgstr "Du bist nicht berechtigt, den Datenspeicher zu ändern"
|
||||||
|
|
||||||
#: application/bookmark/BookmarkFileService.php:210
|
#: application/bookmark/BookmarkFileService.php:228
|
||||||
msgid "This bookmarks already exists"
|
msgid "This bookmarks already exists"
|
||||||
msgstr "Diese Lesezeichen sind bereits vorhanden"
|
msgstr "Diese Lesezeichen sind bereits vorhanden"
|
||||||
|
|
||||||
|
@ -194,13 +198,13 @@ msgstr ""
|
||||||
"| Karotte | Gemüse | rot | 14 |\n"
|
"| Karotte | Gemüse | rot | 14 |\n"
|
||||||
|
|
||||||
#: application/bookmark/BookmarkInitializer.php:94
|
#: application/bookmark/BookmarkInitializer.php:94
|
||||||
#: application/legacy/LegacyLinkDB.php:246
|
#: application/legacy/LegacyLinkDB.php:249
|
||||||
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
|
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
|
||||||
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
|
#: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48
|
||||||
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
|
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
|
||||||
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
|
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
|
||||||
msgid ""
|
msgid ""
|
||||||
"The personal, minimalist, super-fast, database free, bookmarking service"
|
"The personal, minimalist, super fast, database-free, bookmarking service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Der persönliche, minimalistische, superschnelle, datenbankfreie "
|
"Der persönliche, minimalistische, superschnelle, datenbankfreie "
|
||||||
"Lesezeichenservice"
|
"Lesezeichenservice"
|
||||||
|
@ -293,11 +297,11 @@ msgstr "Du bist nicht berechtigt, die Konfiguration zu ändern."
|
||||||
msgid "Error accessing"
|
msgid "Error accessing"
|
||||||
msgstr "Fehler beim Zugriff"
|
msgstr "Fehler beim Zugriff"
|
||||||
|
|
||||||
#: application/feed/FeedBuilder.php:180
|
#: application/feed/FeedBuilder.php:174
|
||||||
msgid "Direct link"
|
msgid "Direct link"
|
||||||
msgstr "Direct Link"
|
msgstr "Direct Link"
|
||||||
|
|
||||||
#: application/feed/FeedBuilder.php:182
|
#: application/feed/FeedBuilder.php:176
|
||||||
#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
|
#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103
|
||||||
#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
|
#: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
|
||||||
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
|
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179
|
||||||
|
@ -372,25 +376,25 @@ msgstr "Tags verwalten"
|
||||||
msgid "Invalid tags provided."
|
msgid "Invalid tags provided."
|
||||||
msgstr "Ungültige Tags übergeben."
|
msgstr "Ungültige Tags übergeben."
|
||||||
|
|
||||||
#: application/front/controller/admin/ManageTagController.php:78
|
#: application/front/controller/admin/ManageTagController.php:81
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "The tag was removed from %d bookmark."
|
msgid "The tag was removed from %d bookmark."
|
||||||
msgid_plural "The tag was removed from %d bookmarks."
|
msgid_plural "The tag was removed from %d bookmarks."
|
||||||
msgstr[0] "Der Tag wurde aus dem Lesezeichen %d entfernt."
|
msgstr[0] "Der Tag wurde aus dem Lesezeichen %d entfernt."
|
||||||
msgstr[1] "Der Tag wurde aus den Lesezeichen %d entfernt."
|
msgstr[1] "Der Tag wurde aus den Lesezeichen %d entfernt."
|
||||||
|
|
||||||
#: application/front/controller/admin/ManageTagController.php:83
|
#: application/front/controller/admin/ManageTagController.php:86
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "The tag was renamed in %d bookmark."
|
msgid "The tag was renamed in %d bookmark."
|
||||||
msgid_plural "The tag was renamed in %d bookmarks."
|
msgid_plural "The tag was renamed in %d bookmarks."
|
||||||
msgstr[0] "Der Tag wurde im Lesezeichen %d umbenannt."
|
msgstr[0] "Der Tag wurde im Lesezeichen %d umbenannt."
|
||||||
msgstr[1] "Der Tag wurde in den Lesezeichen %d umbenannt."
|
msgstr[1] "Der Tag wurde in den Lesezeichen %d umbenannt."
|
||||||
|
|
||||||
#: application/front/controller/admin/ManageTagController.php:105
|
#: application/front/controller/admin/ManageTagController.php:108
|
||||||
msgid "Tags separator must be a single character."
|
msgid "Tags separator must be a single character."
|
||||||
msgstr "Tags müssen durch ein einzelnen Zeichen getrennt werden."
|
msgstr "Tags müssen durch ein einzelnen Zeichen getrennt werden."
|
||||||
|
|
||||||
#: application/front/controller/admin/ManageTagController.php:111
|
#: application/front/controller/admin/ManageTagController.php:114
|
||||||
msgid "These characters are reserved and can't be used as tags separator: "
|
msgid "These characters are reserved and can't be used as tags separator: "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Diese Zeichen sind reserviert und können nicht als Tag-Trennzeichen genutzt "
|
"Diese Zeichen sind reserviert und können nicht als Tag-Trennzeichen genutzt "
|
||||||
|
@ -450,22 +454,32 @@ msgid "Shaare a new link"
|
||||||
msgstr "Teile einen neuen Link"
|
msgstr "Teile einen neuen Link"
|
||||||
|
|
||||||
#: application/front/controller/admin/ShaareManageController.php:35
|
#: application/front/controller/admin/ShaareManageController.php:35
|
||||||
#: application/front/controller/admin/ShaareManageController.php:93
|
#: application/front/controller/admin/ShaareManageController.php:97
|
||||||
|
#: application/front/controller/admin/ShaareManageController.php:227
|
||||||
msgid "Invalid bookmark ID provided."
|
msgid "Invalid bookmark ID provided."
|
||||||
msgstr "Ungültige Lesezeichen-ID bereitgestellt."
|
msgstr "Ungültige Lesezeichen-ID bereitgestellt."
|
||||||
|
|
||||||
#: application/front/controller/admin/ShaareManageController.php:47
|
#: application/front/controller/admin/ShaareManageController.php:47
|
||||||
#: application/front/controller/admin/ShaareManageController.php:116
|
#: application/front/controller/admin/ShaareManageController.php:120
|
||||||
#: application/front/controller/admin/ShaareManageController.php:156
|
#: application/front/controller/admin/ShaareManageController.php:160
|
||||||
|
#: application/front/controller/admin/ShaareManageController.php:257
|
||||||
#: application/front/controller/admin/ShaarePublishController.php:82
|
#: application/front/controller/admin/ShaarePublishController.php:82
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Bookmark with identifier %s could not be found."
|
msgid "Bookmark with identifier %s could not be found."
|
||||||
msgstr "Lesezeichen mit der ID %s konnte nicht gefunden werden."
|
msgstr "Lesezeichen mit der ID %s konnte nicht gefunden werden."
|
||||||
|
|
||||||
#: application/front/controller/admin/ShaareManageController.php:101
|
#: application/front/controller/admin/ShaareManageController.php:105
|
||||||
msgid "Invalid visibility provided."
|
msgid "Invalid visibility provided."
|
||||||
msgstr "Ungültige Sichtbarkeit angegeben."
|
msgstr "Ungültige Sichtbarkeit angegeben."
|
||||||
|
|
||||||
|
#: application/front/controller/admin/ShaareManageController.php:235
|
||||||
|
msgid "Invalid action provided."
|
||||||
|
msgstr "Ungültige Aktion übergeben."
|
||||||
|
|
||||||
|
#: application/front/controller/admin/ShaareManageController.php:243
|
||||||
|
msgid "Invalid tag name provided."
|
||||||
|
msgstr "Ungültiger Tag-Name übergeben."
|
||||||
|
|
||||||
#: application/front/controller/admin/ShaarePublishController.php:173
|
#: application/front/controller/admin/ShaarePublishController.php:173
|
||||||
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
|
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
|
@ -492,7 +506,7 @@ msgstr "Thumbnail-Aktualisierung"
|
||||||
msgid "Tools"
|
msgid "Tools"
|
||||||
msgstr "Tools"
|
msgstr "Tools"
|
||||||
|
|
||||||
#: application/front/controller/visitor/BookmarkListController.php:121
|
#: application/front/controller/visitor/BookmarkListController.php:103
|
||||||
msgid "Search: "
|
msgid "Search: "
|
||||||
msgstr "Suche: "
|
msgstr "Suche: "
|
||||||
|
|
||||||
|
@ -566,6 +580,10 @@ msgstr ""
|
||||||
"Speicher fehlschlägt. Wir empfehlen den Zugriff auf deinen Server über die "
|
"Speicher fehlschlägt. Wir empfehlen den Zugriff auf deinen Server über die "
|
||||||
"IP-Adresse oder den Fully Qualified Domain Namen.<br>"
|
"IP-Adresse oder den Fully Qualified Domain Namen.<br>"
|
||||||
|
|
||||||
|
#: application/front/controller/visitor/InstallController.php:134
|
||||||
|
msgid "Shared Bookmarks"
|
||||||
|
msgstr "Geteilte Lesezeichen "
|
||||||
|
|
||||||
#: application/front/controller/visitor/InstallController.php:162
|
#: application/front/controller/visitor/InstallController.php:162
|
||||||
msgid ""
|
msgid ""
|
||||||
"Shaarli is now configured. Please login and start shaaring your bookmarks!"
|
"Shaarli is now configured. Please login and start shaaring your bookmarks!"
|
||||||
|
@ -694,15 +712,15 @@ msgstr "Verwende das Übersetzungssystem im gettext-Modus"
|
||||||
msgid "Login using LDAP server"
|
msgid "Login using LDAP server"
|
||||||
msgstr "Anmeldung mittels LDAP-Server"
|
msgstr "Anmeldung mittels LDAP-Server"
|
||||||
|
|
||||||
#: application/helper/DailyPageHelper.php:179
|
#: application/helper/DailyPageHelper.php:184
|
||||||
msgid "Week"
|
msgid "Week"
|
||||||
msgstr "Woche"
|
msgstr "Woche"
|
||||||
|
|
||||||
#: application/helper/DailyPageHelper.php:183
|
#: application/helper/DailyPageHelper.php:188
|
||||||
msgid "Today"
|
msgid "Today"
|
||||||
msgstr "Heute"
|
msgstr "Heute"
|
||||||
|
|
||||||
#: application/helper/DailyPageHelper.php:185
|
#: application/helper/DailyPageHelper.php:190
|
||||||
msgid "Yesterday"
|
msgid "Yesterday"
|
||||||
msgstr "Gestern"
|
msgstr "Gestern"
|
||||||
|
|
||||||
|
@ -730,7 +748,7 @@ msgstr "Du musst eine Ganzzahl als Schlüssel angeben."
|
||||||
msgid "Array offset and link ID must be equal."
|
msgid "Array offset and link ID must be equal."
|
||||||
msgstr "Array-Offset und Link-ID müssen gleich sein."
|
msgstr "Array-Offset und Link-ID müssen gleich sein."
|
||||||
|
|
||||||
#: application/legacy/LegacyLinkDB.php:249
|
#: application/legacy/LegacyLinkDB.php:252
|
||||||
msgid ""
|
msgid ""
|
||||||
"Welcome to Shaarli! This is your first public bookmark. To edit or delete "
|
"Welcome to Shaarli! This is your first public bookmark. To edit or delete "
|
||||||
"me, you must first login.\n"
|
"me, you must first login.\n"
|
||||||
|
@ -750,11 +768,11 @@ msgstr ""
|
||||||
"Du verwendest die von der Community unterstützte Version des ursprünglichen "
|
"Du verwendest die von der Community unterstützte Version des ursprünglichen "
|
||||||
"Shaarli-Projekts von Sebastien Sauvage."
|
"Shaarli-Projekts von Sebastien Sauvage."
|
||||||
|
|
||||||
#: application/legacy/LegacyLinkDB.php:266
|
#: application/legacy/LegacyLinkDB.php:269
|
||||||
msgid "My secret stuff... - Pastebin.com"
|
msgid "My secret stuff... - Pastebin.com"
|
||||||
msgstr "Meine geheimen Sachen... - Pastebin.com"
|
msgstr "Meine geheimen Sachen... - Pastebin.com"
|
||||||
|
|
||||||
#: application/legacy/LegacyLinkDB.php:268
|
#: application/legacy/LegacyLinkDB.php:271
|
||||||
msgid "Shhhh! I'm a private link only YOU can see. You can delete me too."
|
msgid "Shhhh! I'm a private link only YOU can see. You can delete me too."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Pssst Ich bin ein privater Link, den nur du sehen kannst. Du kannst mich "
|
"Pssst Ich bin ein privater Link, den nur du sehen kannst. Du kannst mich "
|
||||||
|
@ -772,16 +790,16 @@ msgstr "<a href=\"./admin/thumbnails\">"
|
||||||
msgid "Invalid export selection:"
|
msgid "Invalid export selection:"
|
||||||
msgstr "Ungültige Exportauswahl:"
|
msgstr "Ungültige Exportauswahl:"
|
||||||
|
|
||||||
#: application/netscape/NetscapeBookmarkUtils.php:215
|
#: application/netscape/NetscapeBookmarkUtils.php:213
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "File %s (%d bytes) "
|
msgid "File %s (%d bytes) "
|
||||||
msgstr "Datei %s (%d bytes) "
|
msgstr "Datei %s (%d bytes) "
|
||||||
|
|
||||||
#: application/netscape/NetscapeBookmarkUtils.php:217
|
#: application/netscape/NetscapeBookmarkUtils.php:215
|
||||||
msgid "has an unknown file format. Nothing was imported."
|
msgid "has an unknown file format. Nothing was imported."
|
||||||
msgstr "hat ein unbekanntes Dateiformat. Es wurde nichts importiert."
|
msgstr "hat ein unbekanntes Dateiformat. Es wurde nichts importiert."
|
||||||
|
|
||||||
#: application/netscape/NetscapeBookmarkUtils.php:221
|
#: application/netscape/NetscapeBookmarkUtils.php:219
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"was successfully processed in %d seconds: %d bookmarks imported, %d "
|
"was successfully processed in %d seconds: %d bookmarks imported, %d "
|
||||||
|
@ -790,8 +808,8 @@ msgstr ""
|
||||||
"wurde erfolgreich in %d Sekunden verarbeitet: %d Lesezeichen importiert, %d "
|
"wurde erfolgreich in %d Sekunden verarbeitet: %d Lesezeichen importiert, %d "
|
||||||
"Lesezeichen überschrieben, %d Lesezeichen übersprungen."
|
"Lesezeichen überschrieben, %d Lesezeichen übersprungen."
|
||||||
|
|
||||||
#: application/plugin/PluginManager.php:99
|
#: application/plugin/PluginManager.php:103
|
||||||
#: application/plugin/PluginManager.php:137
|
#: application/plugin/PluginManager.php:141
|
||||||
msgid " [plugin incompatibility]: "
|
msgid " [plugin incompatibility]: "
|
||||||
msgstr " [Plugin-Inkompatibiliät]: "
|
msgstr " [Plugin-Inkompatibiliät]: "
|
||||||
|
|
||||||
|
@ -809,7 +827,7 @@ msgstr "Kann nicht löschen, %s ist kein Verzeichnis"
|
||||||
msgid "An error occurred while running the update "
|
msgid "An error occurred while running the update "
|
||||||
msgstr "Beim Ausführen des Updates ist ein Fehler aufgetreten "
|
msgstr "Beim Ausführen des Updates ist ein Fehler aufgetreten "
|
||||||
|
|
||||||
#: index.php:82
|
#: index.php:87
|
||||||
msgid "Shared bookmarks on "
|
msgid "Shared bookmarks on "
|
||||||
msgstr "Geteilte Lesezeichen auf "
|
msgstr "Geteilte Lesezeichen auf "
|
||||||
|
|
||||||
|
@ -858,7 +876,7 @@ msgstr "Hintergrund-Farbe (hellgrau)"
|
||||||
msgid "Dark main color (e.g. visited links)"
|
msgid "Dark main color (e.g. visited links)"
|
||||||
msgstr "Dunkle Haupt-Farbe (z. B. besuchte Links)"
|
msgstr "Dunkle Haupt-Farbe (z. B. besuchte Links)"
|
||||||
|
|
||||||
#: plugins/demo_plugin/demo_plugin.php:495
|
#: plugins/demo_plugin/demo_plugin.php:528
|
||||||
msgid ""
|
msgid ""
|
||||||
"A demo plugin covering all use cases for template designers and plugin "
|
"A demo plugin covering all use cases for template designers and plugin "
|
||||||
"developers."
|
"developers."
|
||||||
|
@ -866,12 +884,12 @@ msgstr ""
|
||||||
"Ein Demo-Plugin, das alle Anwendungsfälle für Template-Designer und Plugin-"
|
"Ein Demo-Plugin, das alle Anwendungsfälle für Template-Designer und Plugin-"
|
||||||
"Entwickler abdeckt."
|
"Entwickler abdeckt."
|
||||||
|
|
||||||
#: plugins/demo_plugin/demo_plugin.php:496
|
#: plugins/demo_plugin/demo_plugin.php:529
|
||||||
msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed."
|
msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Dies ist ein Parameter, der dem Demo-Plugin gewidmet ist. Es wird angehängt."
|
"Dies ist ein Parameter, der dem Demo-Plugin gewidmet ist. Es wird angehängt."
|
||||||
|
|
||||||
#: plugins/demo_plugin/demo_plugin.php:497
|
#: plugins/demo_plugin/demo_plugin.php:530
|
||||||
msgid "Other demo parameter"
|
msgid "Other demo parameter"
|
||||||
msgstr "Andere Demo-Parameter"
|
msgstr "Andere Demo-Parameter"
|
||||||
|
|
||||||
|
@ -885,9 +903,7 @@ msgstr ""
|
||||||
|
|
||||||
#: plugins/isso/isso.php:92
|
#: plugins/isso/isso.php:92
|
||||||
msgid "Let visitor comment your shaares on permalinks with Isso."
|
msgid "Let visitor comment your shaares on permalinks with Isso."
|
||||||
msgstr ""
|
msgstr "Lassen Sie Besucher Ihre Shaares auf Permalinks mit Isso kommentieren."
|
||||||
"Lassen Sie Besucher Ihre Shaares auf Permalinks mit Isso "
|
|
||||||
"kommentieren."
|
|
||||||
|
|
||||||
#: plugins/isso/isso.php:93
|
#: plugins/isso/isso.php:93
|
||||||
msgid "Isso server URL (without 'http://')"
|
msgid "Isso server URL (without 'http://')"
|
||||||
|
@ -932,29 +948,41 @@ msgstr ""
|
||||||
msgid "plugins/playvideos/jquery-1.11.2.min.js"
|
msgid "plugins/playvideos/jquery-1.11.2.min.js"
|
||||||
msgstr "plugins/playvideos/jquery-1.11.2.min.js"
|
msgstr "plugins/playvideos/jquery-1.11.2.min.js"
|
||||||
|
|
||||||
#: plugins/pubsubhubbub/pubsubhubbub.php:72
|
#: plugins/pubsubhubbub/pubsubhubbub.php:80
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Could not publish to PubSubHubbub: %s"
|
msgid "Could not publish to PubSubHubbub: %s"
|
||||||
msgstr "Veröffentlichung auf PubSubHubbub nicht möglich: %s"
|
msgstr "Veröffentlichung auf PubSubHubbub nicht möglich: %s"
|
||||||
|
|
||||||
#: plugins/pubsubhubbub/pubsubhubbub.php:99
|
#: plugins/pubsubhubbub/pubsubhubbub.php:107
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Could not post to %s"
|
msgid "Could not post to %s"
|
||||||
msgstr "Kann nicht posten auf %s"
|
msgstr "Kann nicht posten auf %s"
|
||||||
|
|
||||||
#: plugins/pubsubhubbub/pubsubhubbub.php:103
|
#: plugins/pubsubhubbub/pubsubhubbub.php:111
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Bad response from the hub %s"
|
msgid "Bad response from the hub %s"
|
||||||
msgstr "Ungültige Antwort vom Hub %s"
|
msgstr "Ungültige Antwort vom Hub %s"
|
||||||
|
|
||||||
#: plugins/pubsubhubbub/pubsubhubbub.php:114
|
#: plugins/pubsubhubbub/pubsubhubbub.php:122
|
||||||
msgid "Enable PubSubHubbub feed publishing."
|
msgid "Enable PubSubHubbub feed publishing."
|
||||||
msgstr "Aktiviere PubSubHubbub Feed Veröffentlichung."
|
msgstr "Aktiviere PubSubHubbub Feed Veröffentlichung."
|
||||||
|
|
||||||
#: plugins/qrcode/qrcode.php:74 plugins/wallabag/wallabag.php:72
|
#: plugins/qrcode/qrcode.php:74
|
||||||
msgid "For each link, add a QRCode icon."
|
msgid "For each link, add a QRCode icon."
|
||||||
msgstr "Für jeden Link, füge eine QRCode Icon hinzu."
|
msgstr "Für jeden Link, füge eine QRCode Icon hinzu."
|
||||||
|
|
||||||
|
#: plugins/readitlater/readitlater.php:118
|
||||||
|
msgid "Mark as Read"
|
||||||
|
msgstr "Als gelesen markieren"
|
||||||
|
|
||||||
|
#: plugins/readitlater/readitlater.php:118
|
||||||
|
msgid "Read it later"
|
||||||
|
msgstr "Später lesen"
|
||||||
|
|
||||||
|
#: plugins/readitlater/readitlater.php:130
|
||||||
|
msgid "Filter ReadItLater bookmarks"
|
||||||
|
msgstr "ReadItLater-Lesezeichen filtern"
|
||||||
|
|
||||||
#: plugins/wallabag/wallabag.php:22
|
#: plugins/wallabag/wallabag.php:22
|
||||||
msgid ""
|
msgid ""
|
||||||
"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
|
"Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the "
|
||||||
|
@ -967,6 +995,12 @@ msgstr ""
|
||||||
msgid "Save to wallabag"
|
msgid "Save to wallabag"
|
||||||
msgstr "Auf Wallabag speichern"
|
msgstr "Auf Wallabag speichern"
|
||||||
|
|
||||||
|
#: plugins/wallabag/wallabag.php:72
|
||||||
|
msgid "For each link, add a Wallabag icon to save it in your instance."
|
||||||
|
msgstr ""
|
||||||
|
"Für jeden Link ein Wallabag-Symbol hinzufügen, um es in Deiner Instanz zu "
|
||||||
|
"speichern."
|
||||||
|
|
||||||
#: plugins/wallabag/wallabag.php:73
|
#: plugins/wallabag/wallabag.php:73
|
||||||
msgid "Wallabag API URL"
|
msgid "Wallabag API URL"
|
||||||
msgstr "Wallabag API URL"
|
msgstr "Wallabag API URL"
|
||||||
|
@ -976,8 +1010,8 @@ msgid "Wallabag API version (1 or 2)"
|
||||||
msgstr "Wallabag API version (1 oder 2)"
|
msgstr "Wallabag API version (1 oder 2)"
|
||||||
|
|
||||||
#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
|
#: tests/LanguagesTest.php:214 tests/LanguagesTest.php:227
|
||||||
#: tests/languages/fr/LanguagesFrTest.php:159
|
#: tests/languages/fr/LanguagesFrTest.php:160
|
||||||
#: tests/languages/fr/LanguagesFrTest.php:172
|
#: tests/languages/fr/LanguagesFrTest.php:173
|
||||||
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
|
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:46
|
||||||
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
|
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87
|
||||||
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
|
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
|
||||||
|
@ -1071,6 +1105,7 @@ msgid "Rename tag"
|
||||||
msgstr "Tag umbenennen"
|
msgstr "Tag umbenennen"
|
||||||
|
|
||||||
#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
|
#: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:195
|
||||||
msgid "Delete tag"
|
msgid "Delete tag"
|
||||||
msgstr "Lösche Tag"
|
msgstr "Lösche Tag"
|
||||||
|
|
||||||
|
@ -1108,6 +1143,38 @@ msgstr ""
|
||||||
"Beachten Sie, dass Hashtags nicht vollständig mit einem Nicht-"
|
"Beachten Sie, dass Hashtags nicht vollständig mit einem Nicht-"
|
||||||
"Leerraumtrennzeichen funktionieren."
|
"Leerraumtrennzeichen funktionieren."
|
||||||
|
|
||||||
|
#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:167
|
||||||
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339
|
||||||
|
#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
|
||||||
|
#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
|
||||||
|
msgid "All"
|
||||||
|
msgstr "Alle"
|
||||||
|
|
||||||
|
#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:171
|
||||||
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343
|
||||||
|
#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
|
||||||
|
msgid "Only common media hosts"
|
||||||
|
msgstr "Nur gängige Medienhosts"
|
||||||
|
|
||||||
|
#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:175
|
||||||
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347
|
||||||
|
#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
|
||||||
|
msgid "None"
|
||||||
|
msgstr "Keine"
|
||||||
|
|
||||||
|
#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:182
|
||||||
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:324
|
||||||
|
msgid "You need to enable the extension <code>php-gd</code> to use thumbnails."
|
||||||
|
msgstr ""
|
||||||
|
"Sie müssen die Erweiterung <code>php-gd</code> aktivieren, um "
|
||||||
|
"Miniaturansichten zu verwenden."
|
||||||
|
|
||||||
|
#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:186
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Synchronize thumbnails"
|
||||||
|
msgid "Synchonize thumbnails"
|
||||||
|
msgstr "Thumbnails synchronisieren"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
|
||||||
msgid "title"
|
msgid "title"
|
||||||
msgstr "Titel"
|
msgstr "Titel"
|
||||||
|
@ -1121,17 +1188,16 @@ msgid "Default value"
|
||||||
msgstr "Standardwert"
|
msgstr "Standardwert"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58
|
||||||
msgid "Theme"
|
msgid "Themes"
|
||||||
msgstr "Thema"
|
msgstr "Themen"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:85
|
||||||
msgid "Description formatter"
|
msgid "Description formatter"
|
||||||
msgstr "Beschreibungsformatierer"
|
msgstr "Beschreibungsformatierer"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114
|
||||||
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
|
msgid "Languages"
|
||||||
msgid "Language"
|
msgstr "Sprachen"
|
||||||
msgstr "Sprache"
|
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:143
|
||||||
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
|
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:101
|
||||||
|
@ -1185,8 +1251,9 @@ msgid "Do not show any links if the user is not logged in"
|
||||||
msgstr "Zeige keine Links, wenn der Benutzer nicht angemeldet ist"
|
msgstr "Zeige keine Links, wenn der Benutzer nicht angemeldet ist"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:258
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:258
|
||||||
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:149
|
#, fuzzy
|
||||||
msgid "Check updates"
|
#| msgid "Check updates"
|
||||||
|
msgid "Check for updates"
|
||||||
msgstr "Auf Updates prüfen"
|
msgstr "Auf Updates prüfen"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:259
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:259
|
||||||
|
@ -1215,40 +1282,18 @@ msgstr ""
|
||||||
"Anwendung"
|
"Anwendung"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:306
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:306
|
||||||
msgid "API secret"
|
msgid "REST API secret"
|
||||||
msgstr "API-Geheimnis"
|
msgstr "REST API-Geheimnis"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320
|
||||||
msgid "Enable thumbnails"
|
msgid "Enable thumbnails"
|
||||||
msgstr "Aktivierte Thunbnails"
|
msgstr "Aktivierte Thumbnails"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:324
|
|
||||||
msgid "You need to enable the extension <code>php-gd</code> to use thumbnails."
|
|
||||||
msgstr ""
|
|
||||||
"Sie müssen die Erweiterung <code>php-gd</code> aktivieren, um "
|
|
||||||
"Miniaturansichten zu verwenden."
|
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328
|
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328
|
||||||
#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
|
#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
|
||||||
msgid "Synchronize thumbnails"
|
msgid "Synchronize thumbnails"
|
||||||
msgstr "Thumbnails synchronisieren"
|
msgstr "Thumbnails synchronisieren"
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339
|
|
||||||
#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30
|
|
||||||
#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102
|
|
||||||
msgid "All"
|
|
||||||
msgstr "Alle"
|
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343
|
|
||||||
#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106
|
|
||||||
msgid "Only common media hosts"
|
|
||||||
msgstr "Nur gängige Medienhosts"
|
|
||||||
|
|
||||||
#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347
|
|
||||||
#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110
|
|
||||||
msgid "None"
|
|
||||||
msgstr "Keine"
|
|
||||||
|
|
||||||
#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
|
#: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
|
||||||
msgid "1 RSS entry per :type"
|
msgid "1 RSS entry per :type"
|
||||||
msgid_plural ""
|
msgid_plural ""
|
||||||
|
@ -1315,6 +1360,7 @@ msgid "Markdown syntax"
|
||||||
msgstr "Markdown-Syntax"
|
msgstr "Markdown-Syntax"
|
||||||
|
|
||||||
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:115
|
#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:115
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:198
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Abbruch"
|
msgstr "Abbruch"
|
||||||
|
|
||||||
|
@ -1400,14 +1446,14 @@ msgstr ""
|
||||||
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
|
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32
|
||||||
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
|
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16
|
||||||
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167
|
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:167
|
||||||
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:167
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:209
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr "Benutzername"
|
msgstr "Benutzername"
|
||||||
|
|
||||||
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
|
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47
|
||||||
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
|
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20
|
||||||
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
|
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:168
|
||||||
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:168
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:210
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Passwort"
|
msgstr "Passwort"
|
||||||
|
|
||||||
|
@ -1419,6 +1465,14 @@ msgstr "Shaarli Titel"
|
||||||
msgid "My links"
|
msgid "My links"
|
||||||
msgstr "Meine Links"
|
msgstr "Meine Links"
|
||||||
|
|
||||||
|
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
|
||||||
|
msgid "Language"
|
||||||
|
msgstr "Sprache"
|
||||||
|
|
||||||
|
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:149
|
||||||
|
msgid "Check updates"
|
||||||
|
msgstr "Auf Updates prüfen"
|
||||||
|
|
||||||
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181
|
#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:181
|
||||||
msgid "Install"
|
msgid "Install"
|
||||||
msgstr "Installiere"
|
msgstr "Installiere"
|
||||||
|
@ -1504,6 +1558,7 @@ msgid "permalink"
|
||||||
msgstr "Permalink"
|
msgstr "Permalink"
|
||||||
|
|
||||||
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
|
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:183
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:195
|
||||||
msgid "Add tag"
|
msgid "Add tag"
|
||||||
msgstr "Tag hinzufügen"
|
msgstr "Tag hinzufügen"
|
||||||
|
|
||||||
|
@ -1560,7 +1615,7 @@ msgstr "Links pro Seite"
|
||||||
|
|
||||||
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
|
#: tmp/loginform.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25
|
||||||
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
|
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
|
||||||
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:171
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:213
|
||||||
msgid "Remember me"
|
msgid "Remember me"
|
||||||
msgstr "Erinnere dich an mich"
|
msgstr "Erinnere dich an mich"
|
||||||
|
|
||||||
|
@ -1632,15 +1687,33 @@ msgid "Set private"
|
||||||
msgstr "Setze Status auf Privat"
|
msgstr "Setze Status auf Privat"
|
||||||
|
|
||||||
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189
|
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189
|
||||||
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:189
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:231
|
||||||
msgid "is available"
|
msgid "is available"
|
||||||
msgstr "ist verfügbar"
|
msgstr "ist verfügbar"
|
||||||
|
|
||||||
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:196
|
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:196
|
||||||
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:196
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:238
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Fehler"
|
msgstr "Fehler"
|
||||||
|
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:162
|
||||||
|
msgid "Add tags"
|
||||||
|
msgstr "Tags hinzufügen"
|
||||||
|
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:167
|
||||||
|
msgid "Delete tags"
|
||||||
|
msgstr "Lösche Tags"
|
||||||
|
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:184
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:185
|
||||||
|
msgid "Tag to add"
|
||||||
|
msgstr "Tag hinzufügen"
|
||||||
|
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:184
|
||||||
|
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:185
|
||||||
|
msgid "Tag to delete"
|
||||||
|
msgstr "Tag zur Löschung"
|
||||||
|
|
||||||
#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
|
#: tmp/picwall.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
|
||||||
msgid "There is no cached thumbnail."
|
msgid "There is no cached thumbnail."
|
||||||
msgstr "Es gibt keine zwischengespeicherte Miniaturansicht / Thumbnail."
|
msgstr "Es gibt keine zwischengespeicherte Miniaturansicht / Thumbnail."
|
||||||
|
@ -1658,7 +1731,9 @@ msgid "pics"
|
||||||
msgstr "Bilder"
|
msgstr "Bilder"
|
||||||
|
|
||||||
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
|
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15
|
||||||
msgid "You need to enable Javascript to change plugin loading order."
|
#, fuzzy
|
||||||
|
#| msgid "You need to enable Javascript to change plugin loading order."
|
||||||
|
msgid "You have to enable JavaScript to change plugin loading order."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Du musst Javascript aktivieren um die Ladereihenfolge der Plugins zu ändern."
|
"Du musst Javascript aktivieren um die Ladereihenfolge der Plugins zu ändern."
|
||||||
|
|
||||||
|
@ -1678,8 +1753,10 @@ msgstr "Kein Plugin aktiviert."
|
||||||
|
|
||||||
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
|
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:40
|
||||||
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:73
|
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:73
|
||||||
msgid "Disable"
|
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:97
|
||||||
msgstr "Deaktivieren"
|
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr "Aktiviert"
|
||||||
|
|
||||||
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
|
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41
|
||||||
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
|
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74
|
||||||
|
@ -1701,11 +1778,6 @@ msgstr "Deaktivierte Plugins"
|
||||||
msgid "No plugin disabled."
|
msgid "No plugin disabled."
|
||||||
msgstr "Kein Plugin deaktiviert."
|
msgstr "Kein Plugin deaktiviert."
|
||||||
|
|
||||||
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:97
|
|
||||||
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122
|
|
||||||
msgid "Enable"
|
|
||||||
msgstr "Aktiviere"
|
|
||||||
|
|
||||||
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
|
#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:134
|
||||||
msgid "More plugins available"
|
msgid "More plugins available"
|
||||||
msgstr "Weitere Plugins verfügbar"
|
msgstr "Weitere Plugins verfügbar"
|
||||||
|
@ -1934,6 +2006,10 @@ msgstr ""
|
||||||
msgid "Export database"
|
msgid "Export database"
|
||||||
msgstr "Exportiere Datenbank"
|
msgstr "Exportiere Datenbank"
|
||||||
|
|
||||||
|
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75
|
||||||
|
msgid "Bookmarklets"
|
||||||
|
msgstr "Lesezeichen (Bookmarklets)"
|
||||||
|
|
||||||
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
|
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77
|
||||||
msgid ""
|
msgid ""
|
||||||
"Drag one of these button to your bookmarks toolbar or right-click it and "
|
"Drag one of these button to your bookmarks toolbar or right-click it and "
|
||||||
|
@ -1985,15 +2061,14 @@ msgid "Add Note"
|
||||||
msgstr "Notiz hinzufügen"
|
msgstr "Notiz hinzufügen"
|
||||||
|
|
||||||
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:132
|
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:132
|
||||||
msgid "3rd party"
|
msgid "Third-party resources"
|
||||||
msgstr "Von Dritten"
|
msgstr "Ressourcen von Dritt-Anbietern"
|
||||||
|
|
||||||
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:135
|
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:135
|
||||||
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:140
|
msgid "Community and related software"
|
||||||
msgid "plugin"
|
msgstr "Gemeinschaft und zugehörige Software"
|
||||||
msgstr "Plugin"
|
|
||||||
|
|
||||||
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:165
|
#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144
|
||||||
msgid ""
|
msgid ""
|
||||||
"Drag this link to your bookmarks toolbar, or right-click it and choose "
|
"Drag this link to your bookmarks toolbar, or right-click it and choose "
|
||||||
"Bookmark This Link"
|
"Bookmark This Link"
|
||||||
|
@ -2001,6 +2076,15 @@ msgstr ""
|
||||||
"Ziehe diesen Link in deine Lesezeichen-Symbolleiste oder klicke mit der "
|
"Ziehe diesen Link in deine Lesezeichen-Symbolleiste oder klicke mit der "
|
||||||
"rechten Maustaste darauf und wähle \"Speichere diesen Link als Lesezeichen\""
|
"rechten Maustaste darauf und wähle \"Speichere diesen Link als Lesezeichen\""
|
||||||
|
|
||||||
|
#~ msgid "Disable"
|
||||||
|
#~ msgstr "Deaktivieren"
|
||||||
|
|
||||||
|
#~ msgid "3rd party"
|
||||||
|
#~ msgstr "Von Dritten"
|
||||||
|
|
||||||
|
#~ msgid "plugin"
|
||||||
|
#~ msgstr "Plugin"
|
||||||
|
|
||||||
#~ msgid "Rename"
|
#~ msgid "Rename"
|
||||||
#~ msgstr "Umbenennen"
|
#~ msgstr "Umbenennen"
|
||||||
|
|
||||||
|
|
|
@ -193,7 +193,7 @@ msgstr ""
|
||||||
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
|
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
|
||||||
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
|
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
|
||||||
msgid ""
|
msgid ""
|
||||||
"The personal, minimalist, super-fast, database free, bookmarking service"
|
"The personal, minimalist, super fast, database-free, bookmarking service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Le gestionnaire de marque-pages personnel, minimaliste, et sans base de "
|
"Le gestionnaire de marque-pages personnel, minimaliste, et sans base de "
|
||||||
"données"
|
"données"
|
||||||
|
|
|
@ -194,7 +194,7 @@ msgstr ""
|
||||||
#: application/bookmark/BookmarkInitializer.php:94
|
#: application/bookmark/BookmarkInitializer.php:94
|
||||||
#: application/legacy/LegacyLinkDB.php:246
|
#: application/legacy/LegacyLinkDB.php:246
|
||||||
msgid ""
|
msgid ""
|
||||||
"The personal, minimalist, super-fast, database free, bookmarking service"
|
"The personal, minimalist, super fast, database-free, bookmarking service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"個人向けの、ミニマムで高速でかつデータベースのいらないブックマークサービス"
|
"個人向けの、ミニマムで高速でかつデータベースのいらないブックマークサービス"
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@ msgstr ""
|
||||||
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
|
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
|
||||||
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
|
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
|
||||||
msgid ""
|
msgid ""
|
||||||
"The personal, minimalist, super-fast, database free, bookmarking service"
|
"The personal, minimalist, super fast, database-free, bookmarking service"
|
||||||
msgstr "Личный, минималистичный, сверхбыстрый сервис закладок без баз данных"
|
msgstr "Личный, минималистичный, сверхбыстрый сервис закладок без баз данных"
|
||||||
|
|
||||||
#: application/bookmark/BookmarkInitializer.php:97
|
#: application/bookmark/BookmarkInitializer.php:97
|
||||||
|
|
|
@ -195,7 +195,7 @@ msgstr ""
|
||||||
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
|
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:15
|
||||||
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
|
#: tmp/page.footer.cedf684561d925457130839629000a81.rtpl.php:48
|
||||||
msgid ""
|
msgid ""
|
||||||
"The personal, minimalist, super-fast, database free, bookmarking service"
|
"The personal, minimalist, super fast, database-free, bookmarking service"
|
||||||
msgstr "个人、超快、极简、掌握数据库的书签服务"
|
msgstr "个人、超快、极简、掌握数据库的书签服务"
|
||||||
|
|
||||||
#: application/bookmark/BookmarkInitializer.php:97
|
#: application/bookmark/BookmarkInitializer.php:97
|
||||||
|
|