Compare commits
591 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4cda95f8e5 | ||
|
30576bd897 | ||
|
3aeb288b29 | ||
|
3692b7c75e | ||
|
01400a10ec | ||
|
cb1f49e7f6 | ||
|
8729263ea0 | ||
|
91bed9297e | ||
|
e1dff88920 | ||
|
12682fe229 | ||
|
8bea495cef | ||
|
b5bea64195 | ||
|
edc9c7ae40 | ||
|
746425114d | ||
|
f647e4fb38 | ||
|
047b751f84 | ||
|
32e70e22d1 | ||
|
b685c84209 | ||
|
410ae399de | ||
|
ec6d950d76 | ||
|
705f31ea4e | ||
|
9d87109a80 | ||
|
3ac30a24b9 | ||
|
6c7cfe66d6 | ||
|
e8040656e8 | ||
|
0b12bbf2a3 | ||
|
8a8fef4b38 | ||
|
0d50d68da0 | ||
|
980df44dd8 | ||
|
55f223ead1 | ||
|
21fdbdddba | ||
|
1a3fa47217 | ||
|
101cd38e1a | ||
|
f4dc0a5764 | ||
|
96fc3f7e94 | ||
|
84ba3c10ab | ||
|
06c61978d7 | ||
|
ef2e94e70a | ||
|
992316d10c | ||
|
b32f2de040 | ||
|
0ff432abf6 | ||
|
068ff1ac62 | ||
|
e577ffa785 | ||
|
9b5c65f11f | ||
|
063cdd4746 | ||
|
81ea250fdf | ||
|
ebf25505eb | ||
|
926ecf3974 | ||
|
f6863c7fb8 | ||
|
b294486b0e | ||
|
6720f639c8 | ||
|
ef073290c0 | ||
|
501a8485ce | ||
|
9e05a34ac9 | ||
|
4e48e02856 | ||
|
4eb19631b7 | ||
|
d31760a334 | ||
|
85178ff127 | ||
|
65c26e5825 | ||
|
e00e5ff0d1 | ||
|
8a0eda15eb | ||
|
b1f78d0f33 | ||
|
d2469191f9 | ||
|
62fd2952ff | ||
|
c276063389 | ||
|
dcd4b21a01 | ||
|
7943cd7682 | ||
|
d57d9ee2de | ||
|
c82350da05 | ||
|
68ce20ae8b | ||
|
918c645829 | ||
|
c555dca40f | ||
|
806352c0cb | ||
|
6fdc478b0f | ||
|
a8da2c14d6 | ||
|
c0ba0c3f3e | ||
|
aea5058d18 | ||
|
306d99d56c | ||
|
9918c1119c | ||
|
449adbaff8 | ||
|
fdd8e7cb7b | ||
|
aad087cea8 | ||
|
4457de3cda | ||
|
d521b3739c | ||
|
09d08fa0e3 | ||
|
7e0c39fe78 | ||
|
d2875b98f1 | ||
|
b9474d9aa4 | ||
|
b46d806fb6 | ||
|
393a88ca14 | ||
|
f57aa7ef5c | ||
|
40ca6316fd | ||
|
1ca3b24973 | ||
|
d8eb1a6b20 | ||
|
67a3776cef | ||
|
f33f1b4d77 | ||
|
ea1e8249b7 | ||
|
b0150b0b93 | ||
|
7b14e24475 | ||
|
81c661fe8b | ||
|
64ca3d6018 | ||
|
d5d59ce608 | ||
|
9ce1ccbe6d | ||
|
727941bc6b | ||
|
5dda40513c | ||
|
794eff56b4 | ||
|
b7ca52165b | ||
|
9e7b1b1e79 | ||
|
fd3ff15485 | ||
|
40b10a06e1 | ||
|
230d253142 | ||
|
1f9b0a8f71 | ||
|
a1ca6b8b1b | ||
|
91d423b22b | ||
|
2045d12bed | ||
|
0e2c73ec2a | ||
|
b45875619e | ||
|
06bd68e128 | ||
|
d92ee0f8b8 | ||
|
e7bf4f9a8a | ||
|
0b90d02318 | ||
|
d47b903449 | ||
|
14e5e96480 | ||
|
a4c93959f7 | ||
|
1d3d923449 | ||
|
67698b9c35 | ||
|
4d5a8c9303 | ||
|
c3cc2f6097 | ||
|
08fd957933 | ||
|
30779e0afc | ||
|
bbdfc1fa89 | ||
|
ce488eb7e9 | ||
|
cf51d26d1a | ||
|
59b25a0fe1 | ||
|
f6d0c4df1b | ||
|
6fa8442288 | ||
|
bf2c629864 | ||
|
c38a66ae8f | ||
|
f9411207d1 | ||
|
dcf2623dfe | ||
|
82145b853f | ||
|
00b0cd7a39 | ||
|
d1e1554827 | ||
|
6e1eca8aeb | ||
|
21d0eecebf | ||
|
b635a6441e | ||
|
0ba2a49730 | ||
|
9b2299cc23 | ||
|
bcb98db9fb | ||
|
c69f6c454c | ||
|
3682f84f04 | ||
|
f93b2ad65b | ||
|
db5a0c682e | ||
|
134e4f8824 | ||
|
a4b6ebbf89 | ||
|
0f85240af7 | ||
|
2f31c5995a | ||
|
51a695f185 | ||
|
2d66fafcf0 | ||
|
6f3b992e9d | ||
|
a5efff0ce9 | ||
|
0f66c9d0cb | ||
|
958efc2101 | ||
|
23b44d2860 | ||
|
d52aff3396 | ||
|
f6687fcca0 | ||
|
cd6fd1e87a | ||
|
b4584f7003 | ||
|
abebbb36b8 | ||
|
252dc45298 | ||
|
5e09a0dee4 | ||
|
c4a58d4d48 | ||
|
d4c25d0684 | ||
|
7d07ad28ef | ||
|
3f4f455ad6 | ||
|
b865e696bc | ||
|
164a0490b1 | ||
|
74000231dd | ||
|
d3234ad96f | ||
|
43871d5cff | ||
|
d20a6c17bb | ||
|
9c88160f50 | ||
|
1ac292697b | ||
|
b2edef6a83 | ||
|
37619e9776 | ||
|
9d88f44d07 | ||
|
1efb08b0fc | ||
|
e4a91ec215 | ||
|
039f721725 | ||
|
66ce235436 | ||
|
ac3d7960d6 | ||
|
ba466412e4 | ||
|
011e23b2fc | ||
|
60e6f9b3da | ||
|
8e54831873 | ||
|
91c06bd20e | ||
|
b4624e912a | ||
|
17218b0b08 | ||
|
983f8451cf | ||
|
24fcc208aa | ||
|
48a4dbd6e1 | ||
|
a02e440048 | ||
|
8de01a6184 | ||
|
cea83d65ee | ||
|
f21817b0d1 | ||
|
fe43af788f | ||
|
454ade1769 | ||
|
2be4d32869 | ||
|
70498bb07f | ||
|
9fbea9b440 | ||
|
54f44c7893 | ||
|
8ba3d43128 | ||
|
50683e929e | ||
|
411b8b3bf7 | ||
|
83e5fb847a | ||
|
3df4c712fa | ||
|
181bbcc9b7 | ||
|
c59ef1f355 | ||
|
5690ab8078 | ||
|
a74e4bb0c8 | ||
|
41e17a16ba | ||
|
dc7de96ed5 | ||
|
fbfa45fadf | ||
|
102c8b413d | ||
|
a681aae873 | ||
|
2a6978abd9 | ||
|
306a1bf093 | ||
|
0f38ac7a3c | ||
|
78b85cb769 | ||
|
41811385fa | ||
|
891fb0478e | ||
|
8a26b4e9d1 | ||
|
cd931fb036 | ||
|
f26f5b2d89 | ||
|
147075f539 | ||
|
f8765de7a9 | ||
|
0ca0a546e5 | ||
|
695a5a3a4b | ||
|
35dccaf181 | ||
|
e9b49a794f | ||
|
2f32024f71 | ||
|
dc28be215b | ||
|
93d3f471c4 | ||
|
cd150f1d6d | ||
|
43b957d98a | ||
|
e0dd4b3a3a | ||
|
d46421c538 | ||
|
8ec9ba0493 | ||
|
fbc06a1973 | ||
|
ba5f436cf3 | ||
|
cc8c8161d8 | ||
|
270cdf2da5 | ||
|
647e97ba86 | ||
|
fada3d8750 | ||
|
3c40ca8c47 | ||
|
02c9c7eced | ||
|
618f739a59 | ||
|
cab222c348 | ||
|
da4431e58d | ||
|
10f6874164 | ||
|
d638dc4e53 | ||
|
02b7333eb8 | ||
|
f720a4e56b | ||
|
a84055a5e1 | ||
|
fcf39e933b | ||
|
f496bc3a54 | ||
|
652cc475b1 | ||
|
dbbcb51e2c | ||
|
f0c18d3547 | ||
|
fa3706243c | ||
|
08320c5b3f | ||
|
8275222ae6 | ||
|
0633ad1f81 | ||
|
64eb907810 | ||
|
7c2d0473d2 | ||
|
3890fbb8ee | ||
|
3026e9b2cc | ||
|
73abe594d1 | ||
|
ec0d732a26 | ||
|
3b758e4075 | ||
|
3d8361ab71 | ||
|
97af29d7b6 | ||
|
0df9672063 | ||
|
5c2515151a | ||
|
36812d085a | ||
|
cc5a6a119a | ||
|
035f8bda6e | ||
|
eeee22863d | ||
|
ddd91196d9 | ||
|
d6280617b6 | ||
|
e7f3d015c6 | ||
|
b42aa936d6 | ||
|
c36a49edc4 | ||
|
fafe81b258 | ||
|
5813e23da9 | ||
|
11d686586d | ||
|
e0584f6e3e | ||
|
7c946db89f | ||
|
4b9f311ffd | ||
|
535bcc2a00 | ||
|
3bc10bf64f | ||
|
33dc03fb80 | ||
|
6c1a218353 | ||
|
831dc3449c | ||
|
180afca67a | ||
|
d70e00754f | ||
|
2b691408ed | ||
|
e0ab091d09 | ||
|
d91acbb03f | ||
|
0f34d28c33 | ||
|
92c8738747 | ||
|
5d88c4bff0 | ||
|
68df7b45e0 | ||
|
9fb32d9f66 | ||
|
fa404edb11 | ||
|
06f9ee1834 | ||
|
33f6695e28 | ||
|
a5df66f6bc | ||
|
e8861e4768 | ||
|
58febcd400 | ||
|
2ff8dbe54c | ||
|
c6f940664f | ||
|
d46dca7fab | ||
|
4a2297bd22 | ||
|
08bb4a7671 | ||
|
4a75b8a7a3 | ||
|
2f7565cbcb | ||
|
1087409364 | ||
|
1ccf760709 | ||
|
f4d167809b | ||
|
a424b3632d | ||
|
eeaebd6e3a | ||
|
f718193449 | ||
|
e0fd446d84 | ||
|
3b2a05ac2d | ||
|
e18c8d27db | ||
|
0d34c9b422 | ||
|
7b3cef4a9b | ||
|
49502e82c1 | ||
|
6af8fa8d72 | ||
|
74b471c30a | ||
|
3e9bf58335 | ||
|
d49e16ed9d | ||
|
efec93833a | ||
|
8a2914ca74 | ||
|
8966cc6345 | ||
|
c83f872723 | ||
|
a32c7997b4 | ||
|
c3f5ca032b | ||
|
8f17ef521c | ||
|
5f7cba6940 | ||
|
2e3967bcb1 | ||
|
223f0cc4eb | ||
|
b44f2919db | ||
|
486a3ae08e | ||
|
da46e000fb | ||
|
bd71a71020 | ||
|
e2cb8f8cb0 | ||
|
2ba033de32 | ||
|
a47bc67c50 | ||
|
016f62dda4 | ||
|
b9fabb5d8a | ||
|
fc6ed22988 | ||
|
fd619d15d7 | ||
|
4a4bc1a37a | ||
|
362891b4ed | ||
|
80e450ff0f | ||
|
9f9986970e | ||
|
283b2e974f | ||
|
ad772677a8 | ||
|
e4f987c7ab | ||
|
695352ad16 | ||
|
046fb2c33a | ||
|
ca97036735 | ||
|
fc28b7a663 | ||
|
cc07077621 | ||
|
81afbb30be | ||
|
0c9bd105cc | ||
|
45f6f89aed | ||
|
35dd7fdf11 | ||
|
0ed1db7838 | ||
|
7fcf6c8de1 | ||
|
3677bcd793 | ||
|
64d8b52b7b | ||
|
1c81ee280a | ||
|
9aa87ba8ea | ||
|
36f1fbc8bc | ||
|
9ca3e6e03f | ||
|
49030ecffd | ||
|
e694d69eb6 | ||
|
8ad176ade5 | ||
|
eacc3af131 | ||
|
40f04677d0 | ||
|
2e63812024 | ||
|
2661e26d81 | ||
|
00b66e383c | ||
|
b13ccccd69 | ||
|
ea03506471 | ||
|
cc3af2b76f | ||
|
dd5ffa26ef | ||
|
ae0498e4f0 | ||
|
612cb3768b | ||
|
e9ab06e1e9 | ||
|
88eecb5a6b | ||
|
dca588450b | ||
|
8b29aa80fe | ||
|
994fa2a9b6 | ||
|
fa30a08246 | ||
|
f83b8b473e | ||
|
bdd0299cf0 | ||
|
bc706fde05 | ||
|
374a85e545 | ||
|
0528d0a765 | ||
|
be3e582de6 | ||
|
8e00495f7e | ||
|
c2c7e245d8 | ||
|
047166a92c | ||
|
b1407028b1 | ||
|
2eeb0b1caf | ||
|
b2c2368cfc | ||
|
6322589265 | ||
|
83d92c6413 | ||
|
80cb9d0083 | ||
|
632bf99261 | ||
|
33fbc52862 | ||
|
f12c963ee0 | ||
|
5a8bcad72c | ||
|
f44634b67d | ||
|
8f7041f1d7 | ||
|
1b63cffd2d | ||
|
50acd8c6ad | ||
|
c6636265cb | ||
|
c525be6b31 | ||
|
02ea593091 | ||
|
98fa324786 | ||
|
613be41240 | ||
|
008709af26 | ||
|
c2a56728d6 | ||
|
aaa379930b | ||
|
0d2c597010 | ||
|
efe4852dc4 | ||
|
9575a06c68 | ||
|
85e65075e6 | ||
|
57d8a8bfd6 | ||
|
0e4eac909c | ||
|
9913937a25 | ||
|
476d2a33c7 | ||
|
2758b13601 | ||
|
b1dbb3a792 | ||
|
4cc77668e6 | ||
|
0563d3fb1f | ||
|
450102233a | ||
|
353970a902 | ||
|
e29db93834 | ||
|
7336ff2b22 | ||
|
f8feecf5e2 | ||
|
8fd6d44958 | ||
|
a69bf05e36 | ||
|
fdd1ffaf0f | ||
|
66ef82e460 | ||
|
56072075f7 | ||
|
adffa0f18f | ||
|
8680f27bf1 | ||
|
1052b5d356 | ||
|
8a64e171b7 | ||
|
cc9cefe41e | ||
|
2c5a688335 | ||
|
fc7fd8d1f7 | ||
|
fd85f390e5 | ||
|
2b4733bf06 | ||
|
ce76dfbb2b | ||
|
58f2ba539a | ||
|
88c7233ced | ||
|
cff145d72b | ||
|
6f2711f422 | ||
|
67f3ce0543 | ||
|
f712147553 | ||
|
c31342a1c7 | ||
|
f27fa1f314 | ||
|
05449ef8d3 | ||
|
2cd3b0812b | ||
|
8b0d1dc3db | ||
|
a348e3df29 | ||
|
9a1de36a23 | ||
|
281638c295 | ||
|
487a59b0db | ||
|
bec162293e | ||
|
3ebf861441 | ||
|
d9ec5056d4 | ||
|
16fe1424dd | ||
|
96ce9c70a7 | ||
|
cdeacb9765 | ||
|
96352152b2 | ||
|
eda3b0aa61 | ||
|
2da1cd8c9d | ||
|
9c6d0dc394 | ||
|
8cd6dbc2a0 | ||
|
af8c59fcd1 | ||
|
4148106ff0 | ||
|
9724b13f81 | ||
|
2082334c24 | ||
|
8230a36565 | ||
|
e81c3e288d | ||
|
c9f743bf8f | ||
|
c917bf0434 | ||
|
cb56587970 | ||
|
7a3e032096 | ||
|
5922fb6f25 | ||
|
bf2fdffc75 | ||
|
75d4c6cf73 | ||
|
d08b7faf63 | ||
|
d7ce9add6a | ||
|
9d968b630d | ||
|
4f14197577 | ||
|
179e18abb2 | ||
|
9483cc59f3 | ||
|
ead9e27dea | ||
|
d2cad188b4 | ||
|
f59e39d42b | ||
|
819fb722c3 | ||
|
412b71c2d3 | ||
|
4ac62e5544 | ||
|
6b88e5fd5a | ||
|
5c1093bcf0 | ||
|
fe09b18832 | ||
|
ac6812d184 | ||
|
b9d80f8d38 | ||
|
8db522b916 | ||
|
ae85d76f15 | ||
|
2ad2689d28 | ||
|
20ed4f1503 | ||
|
99534afe08 | ||
|
4fbd714930 | ||
|
c953d5bd6c | ||
|
9a830edbb7 | ||
|
784296d22c | ||
|
2644eed5bc | ||
|
ac0dad4093 | ||
|
8af3c4f24e | ||
|
f8ec3baeea | ||
|
ab1d755a01 | ||
|
351c3469dc | ||
|
7d827d3392 | ||
|
ef5d7474c5 | ||
|
ca675fcbb0 | ||
|
d240a29aa9 | ||
|
b6a4911b67 | ||
|
91829cc2ea | ||
|
43c98e51c2 | ||
|
5c88b84378 | ||
|
3da11198af | ||
|
18f9a72851 | ||
|
de7f1f9cfe | ||
|
06cfda990d | ||
|
dd60840e4b | ||
|
5ab092946a | ||
|
e29d475dea | ||
|
0ac787c3e3 | ||
|
aeba6665f0 | ||
|
74a0bc642e | ||
|
2bdfec3e76 | ||
|
7fcaf851f1 | ||
|
bc23362bda | ||
|
74655bac27 | ||
|
5384e11735 | ||
|
509ea8614c | ||
|
a1cc9d1953 | ||
|
70173468a5 | ||
|
7229b2669a | ||
|
482e22c90e | ||
|
40b2a4cb05 | ||
|
52cc6c95db | ||
|
d525fc6894 | ||
|
4bdd544ff1 | ||
|
47bc2570b6 | ||
|
09b1bb3554 | ||
|
8512f45c9d | ||
|
8b207b84fe | ||
|
25e77bb7c1 | ||
|
d15e8d231f | ||
|
dfc7411975 | ||
|
a543f75c99 | ||
|
bb38904cdf | ||
|
fbd7039ad3 | ||
|
963535b748 | ||
|
b49bc84c51 | ||
|
6d8ac0b4e2 | ||
|
275e0b3b64 | ||
|
cca47a1df9 | ||
|
26e1383b0a | ||
|
199a0d709c |
@ -1,8 +0,0 @@
|
||||
vendor
|
||||
.git
|
||||
*.phar
|
||||
data
|
||||
images
|
||||
thumbs
|
||||
*.sqlite
|
||||
Dockerfile
|
@ -1,21 +0,0 @@
|
||||
# In retrospect I'm less of a fan of tabs for indentation, because
|
||||
# while they're better when they work, they're worse when they don't
|
||||
# work, and so many people use terrible editors when they don't work
|
||||
# that everything is inconsistent... but tabs are what Shimmie went
|
||||
# with back in the 90's, so that's what we use now, and we deal with
|
||||
# the pain of making sure everybody configures their editor properly
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,css,php}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
1
.gitattributes
vendored
@ -1 +0,0 @@
|
||||
*.php text eol=lf
|
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Server Software**
|
||||
(You can get all these stats from `http://<your site>/system_info`)
|
||||
|
||||
- Shimmie version:
|
||||
- Database: [mysql, postgres, ...]
|
||||
- Web server: [apache, nginx, ...]
|
||||
|
||||
**Client Software (please complete the following information)**
|
||||
- Device: [e.g. iphone, windows desktop]
|
||||
- Browser: [e.g. chrome, safari]
|
||||
|
||||
**What steps trigger this bug**
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
|
||||
**What did you expect to happen?**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**What actually happened?**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
62
.github/workflows/release.yml
vendored
@ -1,62 +0,0 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Get version from tag
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
|
||||
|
||||
- name: Test version in sys_config
|
||||
run: grep ${{ steps.get_version.outputs.VERSION }} core/sys_config.php
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
composer install --no-dev
|
||||
cd ..
|
||||
tar cvzf shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz shimmie2
|
||||
zip -r shimmie2-${{ steps.get_version.outputs.VERSION }}.zip shimmie2
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Shimmie ${{ steps.get_version.outputs.VERSION }}
|
||||
body: Automated release from tags
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Zip
|
||||
id: upload-release-asset-zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../shimmie2-${{ steps.get_version.outputs.VERSION }}.zip
|
||||
asset_name: shimmie2-${{ steps.get_version.outputs.VERSION }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Tar
|
||||
id: upload-release-asset-tar
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz
|
||||
asset_name: shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz
|
||||
asset_content_type: application/gzip
|
95
.github/workflows/test_and_publish.yml
vendored
@ -1,95 +0,0 @@
|
||||
name: Test & Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 2 * * 0' # Weekly on Sundays at 02:00
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: PHP ${{ matrix.php }} / DB ${{ matrix.database }}
|
||||
strategy:
|
||||
max-parallel: 3
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ['7.3']
|
||||
database: ['pgsql', 'mysql', 'sqlite']
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
coverage: pcov
|
||||
extensions: mbstring
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
mkdir -p data/config
|
||||
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
|
||||
sudo systemctl start postgresql ;
|
||||
psql --version ;
|
||||
sudo -u postgres psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres ;
|
||||
sudo -u postgres psql -c "CREATE USER shimmie WITH PASSWORD 'shimmie';" -U postgres ;
|
||||
sudo -u postgres psql -c "CREATE DATABASE shimmie WITH OWNER shimmie;" -U postgres ;
|
||||
fi
|
||||
if [[ "${{ matrix.database }}" == "mysql" ]]; then
|
||||
sudo systemctl start mysql ;
|
||||
mysql --version ;
|
||||
mysql -e "SET GLOBAL general_log = 'ON';" -uroot -proot ;
|
||||
mysql -e "CREATE DATABASE shimmie;" -uroot -proot ;
|
||||
fi
|
||||
if [[ "${{ matrix.database }}" == "sqlite" ]]; then
|
||||
sudo apt update && sudo apt-get install -y sqlite3 ;
|
||||
sqlite3 --version ;
|
||||
fi
|
||||
|
||||
- name: Check versions
|
||||
run: php -v && composer -V
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer install --prefer-dist --no-progress --no-suggest
|
||||
|
||||
- name: Install shimmie
|
||||
run: php index.php
|
||||
|
||||
- name: Run test suite
|
||||
run: |
|
||||
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
|
||||
export TEST_DSN="pgsql:user=shimmie;password=shimmie;host=127.0.0.1;dbname=shimmie"
|
||||
fi
|
||||
if [[ "${{ matrix.database }}" == "mysql" ]]; then
|
||||
export TEST_DSN="mysql:user=root;password=root;host=127.0.0.1;dbname=shimmie"
|
||||
fi
|
||||
if [[ "${{ matrix.database }}" == "sqlite" ]]; then
|
||||
export TEST_DSN="sqlite:data/shimmie.sqlite"
|
||||
fi
|
||||
vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
|
||||
|
||||
- name: Upload coverage
|
||||
run: |
|
||||
wget https://scrutinizer-ci.com/ocular.phar
|
||||
php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
if: github.event_name == 'push'
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Publish to Registry
|
||||
uses: elgohr/Publish-Docker-Github-Action@master
|
||||
with:
|
||||
name: shish2k/shimmie2
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
cache: ${{ github.event_name != 'schedule' }}
|
||||
buildoptions: "--build-arg RUN_TESTS=false"
|
145
.gitignore
vendored
@ -1,89 +1,60 @@
|
||||
backup
|
||||
data
|
||||
.svn
|
||||
config.php
|
||||
images
|
||||
thumbs
|
||||
*.phar
|
||||
*.sqlite
|
||||
*.cache
|
||||
.devcontainer
|
||||
trace.json
|
||||
|
||||
#Composer
|
||||
composer.phar
|
||||
composer.lock
|
||||
/vendor/
|
||||
|
||||
# Created by http://www.gitignore.io
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must ends with two \r.
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
|
||||
### vim ###
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
*.un~
|
||||
Session.vim
|
||||
.netrwhist
|
||||
|
||||
|
||||
### PhpStorm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
|
||||
|
||||
## Directory-based project format
|
||||
.idea/
|
||||
# if you remove the above rule, at least ignore user-specific stuff:
|
||||
# .idea/workspace.xml
|
||||
# .idea/tasks.xml
|
||||
# and these sensitive or high-churn files:
|
||||
# .idea/dataSources.ids
|
||||
# .idea/dataSources.xml
|
||||
# .idea/sqlDataSources.xml
|
||||
# .idea/dynamic.xml
|
||||
|
||||
## File-based project format
|
||||
*.ipr
|
||||
*.iws
|
||||
*.iml
|
||||
|
||||
## Additional for IntelliJ
|
||||
out/
|
||||
|
||||
# generated by mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# generated by JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# generated by Crashlytics plugin (for Android Studio and Intellij)
|
||||
com_crashlytics_export_strings.xml
|
||||
data
|
||||
sql.log
|
||||
shimmie.log
|
||||
ext/admin
|
||||
ext/autocomplete
|
||||
ext/ban_words
|
||||
ext/blotter
|
||||
ext/browser_search
|
||||
ext/bulk_add
|
||||
ext/danbooru_api
|
||||
ext/downtime
|
||||
ext/emoticons
|
||||
ext/et
|
||||
ext/event_log
|
||||
ext/favorites
|
||||
ext/featured
|
||||
ext/forum
|
||||
ext/handle_archive
|
||||
ext/handle_flash
|
||||
ext/handle_ico
|
||||
ext/handle_mp3
|
||||
ext/handle_svg
|
||||
ext/home
|
||||
ext/image_hash_ban
|
||||
ext/ipban
|
||||
ext/link_image
|
||||
ext/log_db
|
||||
ext/news
|
||||
ext/notes
|
||||
ext/notes
|
||||
ext/numeric_score
|
||||
ext/piclens
|
||||
ext/pm
|
||||
ext/pools
|
||||
ext/qr_code
|
||||
ext/random_image
|
||||
ext/rating
|
||||
ext/regen_thumb
|
||||
ext/report_image
|
||||
ext/res_limit
|
||||
ext/rss_comments
|
||||
ext/rss_images
|
||||
ext/shimmie_api
|
||||
ext/simpletest
|
||||
ext/site_description
|
||||
ext/sitemap
|
||||
ext/svn_update
|
||||
ext/tagger
|
||||
ext/tag_history
|
||||
ext/text_score
|
||||
ext/tips
|
||||
ext/amazon_s3
|
||||
ext/upload_cmd
|
||||
ext/wiki
|
||||
ext/word_filter
|
||||
ext/zoom
|
||||
|
60
.htaccess
@ -3,65 +3,39 @@
|
||||
</IfModule>
|
||||
|
||||
<FilesMatch "\.(sqlite|sdb|s3db|db)$">
|
||||
<IfModule mod_authz_host.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_host.c>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteEngine on
|
||||
|
||||
# rather than link to images/ha/hash and have an ugly filename,
|
||||
# we link to images/hash/tags.ext; mod_rewrite splits things so
|
||||
# that shimmie sees hash and the user sees tags.ext
|
||||
RewriteRule ^_(images|thumbs)/([0-9a-f]{2})([0-9a-f]{30}).*$ data/$1/$2/$2$3 [L]
|
||||
RewriteRule ^_images/([0-9a-f]{2})([0-9a-f]{30}).*$ images/$1/$1$2 [L]
|
||||
RewriteRule ^_thumbs/([0-9a-f]{2})([0-9a-f]{30}).*$ thumbs/$1/$1$2 [L]
|
||||
|
||||
# any requests for files which don't physically exist should be handled by index.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.*)$ index.php?q=$1&%{QUERY_STRING} [L]
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php5.c>
|
||||
php_flag register_globals 0
|
||||
php_flag magic_quotes_gpc 0
|
||||
php_flag magic_quotes_runtime 0
|
||||
</IfModule>
|
||||
|
||||
DefaultType image/jpeg
|
||||
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
<FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|webp|css|js))$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "public, max-age=2629743"
|
||||
</IfModule>
|
||||
ExpiresDefault "access plus 1 month"
|
||||
</FilesMatch>
|
||||
#ExpiresByType text/html "now"
|
||||
#ExpiresByType text/plain "now"
|
||||
ExpiresDefault "access plus 1 month"
|
||||
ExpiresByType text/html "now"
|
||||
ExpiresByType text/plain "now"
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
<ifmodule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
|
||||
AddOutputFilterByType DEFLATE application/x-javascript application/javascript
|
||||
</IfModule>
|
||||
|
||||
#EXT: handle_pixel
|
||||
AddType image/jpeg jpg jpeg
|
||||
AddType image/gif gif
|
||||
AddType image/png png
|
||||
AddType image/webp webp
|
||||
|
||||
#EXT: handle_ico
|
||||
AddType image/x-icon ico ani cur
|
||||
|
||||
#EXT: handle_flash
|
||||
AddType application/x-shockwave-flash swf
|
||||
|
||||
#EXT: handle_mp3
|
||||
AddType audio/mpeg mp3
|
||||
|
||||
#EXT: handle_svg
|
||||
AddType image/svg+xml svg svgz
|
||||
|
||||
#EXT: handle_video
|
||||
AddType video/x-flv flv
|
||||
AddType video/mp4 f4v f4p m4v mp4
|
||||
AddType audio/mp4 f4a f4b m4a
|
||||
AddType video/ogg ogv
|
||||
AddType video/webm webm
|
||||
</ifmodule>
|
||||
|
19
.php_cs.dist
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->exclude('ext/amazon_s3/lib')
|
||||
->exclude('vendor')
|
||||
->exclude('data')
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
return PhpCsFixer\Config::create()
|
||||
->setRules([
|
||||
'@PSR2' => true,
|
||||
//'strict_param' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
||||
|
||||
?>
|
@ -1,19 +0,0 @@
|
||||
imports:
|
||||
- javascript
|
||||
- php
|
||||
|
||||
filter:
|
||||
excluded_paths: [ext/*/lib/*,ext/tagger/script.js,tests/*]
|
||||
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
tests:
|
||||
before:
|
||||
- mkdir -p data/config
|
||||
- cp tests/defines.php data/config/shimmie.conf.php
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
|
||||
tools:
|
||||
external_code_coverage: true
|
49
Dockerfile
@ -1,49 +0,0 @@
|
||||
# "Build" shimmie (composer install - done in its own stage so that we don't
|
||||
# need to include all the composer fluff in the final image)
|
||||
FROM debian:stable-slim AS app
|
||||
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
|
||||
COPY composer.json composer.lock /app/
|
||||
WORKDIR /app
|
||||
RUN composer install --no-dev
|
||||
COPY . /app/
|
||||
|
||||
# Tests in their own image. Really we should inherit from app and then
|
||||
# `composer install` phpunit on top of that; but for some reason
|
||||
# `composer install --no-dev && composer install` doesn't install dev
|
||||
FROM debian:stable-slim AS tests
|
||||
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
|
||||
COPY composer.json composer.lock /app/
|
||||
WORKDIR /app
|
||||
RUN composer install
|
||||
COPY . /app/
|
||||
ARG RUN_TESTS=true
|
||||
RUN [ $RUN_TESTS = false ] || (\
|
||||
echo '=== Installing ===' && mkdir -p data/config && INSTALL_DSN="sqlite:data/shimmie.sqlite" php index.php && \
|
||||
echo '=== Smoke Test ===' && php index.php get-page /post/list && \
|
||||
echo '=== Unit Tests ===' && ./vendor/bin/phpunit --configuration tests/phpunit.xml && \
|
||||
echo '=== Coverage ===' && ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \
|
||||
echo '=== Cleaning ===' && rm -rf data)
|
||||
|
||||
# Build su-exec so that our final image can be nicer
|
||||
FROM debian:stable-slim AS suexec
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc libc-dev curl
|
||||
RUN curl -k -o /usr/local/bin/su-exec.c https://raw.githubusercontent.com/ncopa/su-exec/master/su-exec.c; \
|
||||
gcc -Wall /usr/local/bin/su-exec.c -o/usr/local/bin/su-exec; \
|
||||
chown root:root /usr/local/bin/su-exec; \
|
||||
chmod 0755 /usr/local/bin/su-exec;
|
||||
|
||||
# Actually run shimmie
|
||||
FROM debian:stable-slim
|
||||
EXPOSE 8000
|
||||
HEALTHCHECK --interval=5m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1
|
||||
ENV UID=1000 \
|
||||
GID=1000
|
||||
RUN apt update && apt install -y curl \
|
||||
php7.3-cli php7.3-gd php7.3-pgsql php7.3-mysql php7.3-sqlite3 php7.3-zip php7.3-dom php7.3-mbstring \
|
||||
imagemagick zip unzip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=app /app /app
|
||||
COPY --from=suexec /usr/local/bin/su-exec /usr/local/bin/su-exec
|
||||
|
||||
WORKDIR /app
|
||||
CMD ["/bin/sh", "/app/tests/docker-init.sh"]
|
339
LICENSE.txt
@ -1,339 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
43
README.md
@ -1,43 +0,0 @@
|
||||
```
|
||||
_________.__ .__ .__ ________
|
||||
/ _____/| |__ |__| _____ _____ |__| ____ \_____ \
|
||||
\_____ \ | | \ | | / \ / \ | |_/ __ \ / ____/
|
||||
/ \| Y \| || Y Y \| Y Y \| |\ ___/ / \
|
||||
/_______ /|___| /|__||__|_| /|__|_| /|__| \___ >\_______ \
|
||||
\/ \/ \/ \/ \/ \/
|
||||
|
||||
```
|
||||
|
||||
# Shimmie
|
||||
|
||||
[](https://github.com/shish/shimmie2/actions)
|
||||
[](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=master)
|
||||
[](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=master)
|
||||
|
||||
|
||||
# Documentation
|
||||
|
||||
* [Install straight on disk](https://github.com/shish/shimmie2/wiki/Install)
|
||||
* [Install in docker container](https://github.com/shish/shimmie2/wiki/Docker)
|
||||
* [Upgrade process](https://github.com/shish/shimmie2/wiki/Upgrade)
|
||||
* [Basic settings](https://github.com/shish/shimmie2/wiki/Settings)
|
||||
* [Advanced config](https://github.com/shish/shimmie2/wiki/Advanced-Config)
|
||||
* [Developer notes](https://github.com/shish/shimmie2/wiki/Development-Info)
|
||||
* [High-performance notes](https://github.com/shish/shimmie2/wiki/Performance)
|
||||
|
||||
|
||||
# Contact
|
||||
|
||||
Email: webmaster at shishnet.org
|
||||
|
||||
Issue/Bug tracker: https://github.com/shish/shimmie2/issues
|
||||
|
||||
|
||||
# Licence
|
||||
|
||||
All code is released under the [GNU GPL Version 2](https://www.gnu.org/licenses/gpl-2.0.html) unless mentioned otherwise.
|
||||
|
||||
If you give shimmie to someone else, you have to give them the source (which
|
||||
should be easy, as PHP is an interpreted language...). If you want to add
|
||||
customisations to your own site, then those customisations belong to you,
|
||||
and you can do what you want with them.
|
119
README.txt
Normal file
@ -0,0 +1,119 @@
|
||||
|
||||
_________.__ .__ .__ ________
|
||||
/ _____/| |__ |__| _____ _____ |__| ____ \_____ \
|
||||
\_____ \ | | \| |/ \ / \| |/ __ \ / ____/
|
||||
/ \| Y \ | Y Y \ Y Y \ \ ___// \
|
||||
/_______ /|___| /__|__|_| /__|_| /__|\___ >_______ \
|
||||
\/ \/ \/ \/ \/ \/
|
||||
|
||||
Shimmie 2.3
|
||||
~~~~~~~~~~~
|
||||
1000 days since the first import was converted to git \o/
|
||||
|
||||
And over a year since the last non-beta release x_x
|
||||
|
||||
|
||||
New since 2.2
|
||||
~~~~~~~~~~~~~
|
||||
For admins:
|
||||
o) Simplified installer, only one question
|
||||
o) Improved mass tag editor, allows searching and replacing multiple tags
|
||||
o) Automated installation
|
||||
o) Automatic detection of niceurl support
|
||||
o) Support for caching data, to lower database load
|
||||
o) Improved extension management, built-in documentation function
|
||||
o) Built-in documentation for extensions
|
||||
o) Better spam filtering
|
||||
o) CAPTCHA tests for signup and anonymous comments
|
||||
o) Detailed event logging
|
||||
o) Delete by query
|
||||
|
||||
For users:
|
||||
o) New default theme
|
||||
o) Better BBCode support
|
||||
o) Image ratings, with eg. hiding of explicit images to anonymous users
|
||||
o) Theme improvements, new themes
|
||||
o) Better searching
|
||||
o) Cooliris support
|
||||
o) Search for images you've voted for (simple "favourites" system)
|
||||
o) Gravatar support
|
||||
o) Nicer date formatting
|
||||
o) Random image extension
|
||||
o) The current featured image is linkable
|
||||
o) Private message system
|
||||
o) Only the most recent posts for each image shown on the comment list
|
||||
o) Image pools extension
|
||||
o) Tagger cloud
|
||||
|
||||
For developers:
|
||||
o) More sensible APIs
|
||||
o) Automated testing
|
||||
o) Preliminary SQLite & Postgres support, for people with very small and
|
||||
very large sites (Not all extensions are compatible yet though)
|
||||
|
||||
And much more that I can't remember \o/
|
||||
|
||||
|
||||
Requirements
|
||||
~~~~~~~~~~~~
|
||||
MySQL 4.1+
|
||||
PHP 5.0+
|
||||
GD or ImageMagick
|
||||
|
||||
There is no PHP4 support, because it lacks many useful features which make
|
||||
shimmie development easier, faster, and less bug prone. PHP4 is officially
|
||||
dead, if your host hasn't upgraded to at least 5, I would suggest switching
|
||||
hosts. I'll even host galleries for free if you can't get hosting elsewhere
|
||||
for whatever reason~
|
||||
|
||||
|
||||
Installation
|
||||
~~~~~~~~~~~~
|
||||
1) Create a blank database
|
||||
2) Unzip shimmie into a folder on the web host
|
||||
3) Visit the folder with a web browser
|
||||
4) Enter the location of the database
|
||||
5) Click "install". Hopefully you'll end up at the welcome screen; if
|
||||
not, you should be given instructions on how to fix any errors~
|
||||
|
||||
|
||||
Upgrade from 2.2.X
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Should be automatic, just unzip into a clean folder and copy across
|
||||
config.php, images and thumbs folders from the old version. This
|
||||
includes automatically messing with the database -- back it up first!
|
||||
|
||||
|
||||
Upgrade from earlier versions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
I very much recommend going via each major release in turn (eg, 2.0.6
|
||||
-> 2.1.3 -> 2.2.4 -> 2.3.0 rather than 2.0.6 -> 2.3.0). While the basic
|
||||
database and file formats haven't changed *completely*, it's different
|
||||
enough to be a pain.
|
||||
|
||||
|
||||
Development Info
|
||||
~~~~~~~~~~~~~~~~
|
||||
http://shimmie.shishnet.org/doc/
|
||||
|
||||
Please tell me if those docs are lacking in any way, so that they can be
|
||||
improved for the next person who uses them
|
||||
|
||||
|
||||
Contact
|
||||
~~~~~~~
|
||||
#shimmie on Freenode -- IRC
|
||||
webmaster at shishnet.org -- email
|
||||
https://github.com/shish/shimmie2/issues -- bug tracker
|
||||
|
||||
|
||||
Licence
|
||||
~~~~~~~
|
||||
All code is GPLv2 unless mentioned otherwise; ie, if you give shimmie to
|
||||
someone else, you have to give them the source (which should be easy, as PHP
|
||||
is an interpreted language...). If you want to add customisations to your own
|
||||
site, then those customisations belong to you, and you can do what you want
|
||||
with them.
|
||||
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
{
|
||||
"name": "shish/shimmie2",
|
||||
"description": "A tag-based image gallery",
|
||||
"type" : "project",
|
||||
"license" : "GPL-2.0-or-later",
|
||||
"minimum-stability" : "dev",
|
||||
|
||||
"repositories" : [
|
||||
{
|
||||
"type" : "package",
|
||||
"package" : {
|
||||
"name" : "ifixit/php-akismet",
|
||||
"version" : "1.1",
|
||||
"source" : {
|
||||
"url" : "https://github.com/iFixit/php-akismet.git",
|
||||
"type" : "git",
|
||||
"reference" : "fd4ff50eb577457c1b7b887401663e91e77625ae"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"require" : {
|
||||
"php" : "^7.3",
|
||||
"ext-pdo": "*",
|
||||
"ext-json": "*",
|
||||
"ext-fileinfo": "*",
|
||||
|
||||
"flexihash/flexihash" : "^2.0.0",
|
||||
"ifixit/php-akismet" : "1.*",
|
||||
"google/recaptcha" : "~1.1",
|
||||
"dapphp/securimage" : "3.6.*",
|
||||
"shish/eventtracer-php" : "^2.0.0",
|
||||
"shish/ffsphp" : "^1.0.0",
|
||||
"shish/microcrud" : "^2.0.0",
|
||||
"shish/microhtml" : "^2.0.0",
|
||||
"enshrined/svg-sanitize" : "0.13.*",
|
||||
|
||||
"bower-asset/jquery" : "1.12.*",
|
||||
"bower-asset/jquery-timeago" : "1.5.*",
|
||||
"bower-asset/mediaelement" : "2.21.*",
|
||||
"bower-asset/js-cookie" : "2.1.*"
|
||||
},
|
||||
|
||||
"require-dev" : {
|
||||
},
|
||||
|
||||
"suggest": {
|
||||
"ext-memcache": "memcache caching",
|
||||
"ext-memcached": "memcached caching",
|
||||
"ext-apc": "apc caching",
|
||||
"ext-redis": "redis caching",
|
||||
"ext-dom": "some extensions",
|
||||
"ext-curl": "some extensions",
|
||||
"ext-ctype": "some extensions",
|
||||
"ext-json": "some extensions",
|
||||
"ext-zip": "self-updater extension, bulk import/export",
|
||||
"ext-zlib": "anti-spam",
|
||||
"ext-xml": "some extensions",
|
||||
"ext-gd": "GD-based thumbnailing"
|
||||
},"replace": {
|
||||
"bower-asset/jquery": ">=1.11.0",
|
||||
"bower-asset/inputmask": ">=3.2.0",
|
||||
"bower-asset/punycode": ">=1.3.0",
|
||||
"bower-asset/yii2-pjax": ">=2.0.0"
|
||||
}
|
||||
}
|
461
composer.lock
generated
@ -1,461 +0,0 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ca096500833679c5fa819eb5cfbd1d80",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bower-asset/jquery-timeago",
|
||||
"version": "v1.5.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:rmm5t/jquery-timeago.git",
|
||||
"reference": "180864a9c544a49e43719b457250af216d5e4c3a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/rmm5t/jquery-timeago/zipball/180864a9c544a49e43719b457250af216d5e4c3a",
|
||||
"reference": "180864a9c544a49e43719b457250af216d5e4c3a"
|
||||
},
|
||||
"require": {
|
||||
"bower-asset/jquery": ">=1.4"
|
||||
},
|
||||
"type": "bower-asset",
|
||||
"license": [
|
||||
"MIT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bower-asset/js-cookie",
|
||||
"version": "v2.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:js-cookie/js-cookie.git",
|
||||
"reference": "8b70250875f7e07445b6a457f9c2474ead4cba44"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/js-cookie/js-cookie/zipball/8b70250875f7e07445b6a457f9c2474ead4cba44",
|
||||
"reference": "8b70250875f7e07445b6a457f9c2474ead4cba44"
|
||||
},
|
||||
"type": "bower-asset",
|
||||
"license": [
|
||||
"MIT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bower-asset/mediaelement",
|
||||
"version": "2.21.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:johndyer/mediaelement.git",
|
||||
"reference": "394db3b4a2e3f5f7988cacdefe62ed973bf4a3ce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/johndyer/mediaelement/zipball/394db3b4a2e3f5f7988cacdefe62ed973bf4a3ce",
|
||||
"reference": "394db3b4a2e3f5f7988cacdefe62ed973bf4a3ce"
|
||||
},
|
||||
"type": "bower-asset",
|
||||
"license": [
|
||||
"MIT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dapphp/securimage",
|
||||
"version": "3.6.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dapphp/securimage.git",
|
||||
"reference": "5fc5953c4ffba1eb214cc83100672f238c184ca4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dapphp/securimage/zipball/5fc5953c4ffba1eb214cc83100672f238c184ca4",
|
||||
"reference": "5fc5953c4ffba1eb214cc83100672f238c184ca4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-gd": "*",
|
||||
"php": ">=5.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pdo": "For database storage support",
|
||||
"ext-pdo_mysql": "For MySQL database support",
|
||||
"ext-pdo_sqlite": "For SQLite3 database support"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"securimage.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Drew Phillips",
|
||||
"email": "drew@drew-phillips.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP CAPTCHA Library",
|
||||
"homepage": "https://www.phpcaptcha.org",
|
||||
"keywords": [
|
||||
"Forms",
|
||||
"anti-spam",
|
||||
"captcha",
|
||||
"security"
|
||||
],
|
||||
"time": "2020-05-30T09:43:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "enshrined/svg-sanitize",
|
||||
"version": "0.13.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/darylldoyle/svg-sanitizer.git",
|
||||
"reference": "bc66593f255b7d2613d8f22041180036979b6403"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/bc66593f255b7d2613d8f22041180036979b6403",
|
||||
"reference": "bc66593f255b7d2613d8f22041180036979b6403",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeclimate/php-test-reporter": "^0.1.2",
|
||||
"phpunit/phpunit": "^6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"enshrined\\svgSanitize\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daryll Doyle",
|
||||
"email": "daryll@enshrined.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "An SVG sanitizer for PHP",
|
||||
"time": "2020-01-20T01:34:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "flexihash/flexihash",
|
||||
"version": "v2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pda/flexihash.git",
|
||||
"reference": "497ba5782606d998f8ab0b4e5942e3a799bec018"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pda/flexihash/zipball/497ba5782606d998f8ab0b4e5942e3a799bec018",
|
||||
"reference": "497ba5782606d998f8ab0b4e5942e3a799bec018",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8",
|
||||
"satooshi/php-coveralls": "~1.0",
|
||||
"squizlabs/php_codesniffer": "^2.3",
|
||||
"symfony/config": "^2.0.0",
|
||||
"symfony/console": "^2.0.0",
|
||||
"symfony/filesystem": "^2.0.0",
|
||||
"symfony/stopwatch": "^2.0.0",
|
||||
"symfony/yaml": "^2.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Flexihash\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paul Annesley",
|
||||
"email": "paul@annesley.cc",
|
||||
"homepage": "http://paul.annesley.cc"
|
||||
},
|
||||
{
|
||||
"name": "Dom Morgan",
|
||||
"email": "dom@d3r.com",
|
||||
"homepage": "https://d3r.com"
|
||||
}
|
||||
],
|
||||
"description": "Flexihash is a small PHP library which implements consistent hashing",
|
||||
"homepage": "https://github.com/pda/flexihash",
|
||||
"time": "2016-04-22T21:03:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "google/recaptcha",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/google/recaptcha.git",
|
||||
"reference": "79ccf652575e138d51742d04e78a5adbb9892f9d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/google/recaptcha/zipball/79ccf652575e138d51742d04e78a5adbb9892f9d",
|
||||
"reference": "79ccf652575e138d51742d04e78a5adbb9892f9d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.2.20|^2.15",
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^4.8.36|^5.7.27|^6.59|^7.5.11"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ReCaptcha\\": "src/ReCaptcha"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.",
|
||||
"homepage": "https://www.google.com/recaptcha/",
|
||||
"keywords": [
|
||||
"Abuse",
|
||||
"captcha",
|
||||
"recaptcha",
|
||||
"spam"
|
||||
],
|
||||
"time": "2020-05-21T08:56:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ifixit/php-akismet",
|
||||
"version": "1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iFixit/php-akismet.git",
|
||||
"reference": "fd4ff50eb577457c1b7b887401663e91e77625ae"
|
||||
},
|
||||
"type": "library"
|
||||
},
|
||||
{
|
||||
"name": "shish/eventtracer-php",
|
||||
"version": "v2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/shish/eventtracer-php.git",
|
||||
"reference": "0328454c58d240667f004f3efd863b8ef521ba2a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/shish/eventtracer-php/zipball/0328454c58d240667f004f3efd863b8ef521ba2a",
|
||||
"reference": "0328454c58d240667f004f3efd863b8ef521ba2a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Shish",
|
||||
"email": "webmaster@shishnet.org",
|
||||
"homepage": "http://shishnet.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "An API to write JSON traces as used by the Chrome Trace Viewer",
|
||||
"homepage": "https://github.com/shish/eventtracer-php",
|
||||
"time": "2020-09-20T11:46:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "shish/ffsphp",
|
||||
"version": "v1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/shish/ffsphp.git",
|
||||
"reference": "82d45b2691da11c82a28f85b07752334a06b8a8e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/shish/ffsphp/zipball/82d45b2691da11c82a28f85b07752334a06b8a8e",
|
||||
"reference": "82d45b2691da11c82a28f85b07752334a06b8a8e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FFSPHP\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Shish",
|
||||
"email": "webmaster@shishnet.org",
|
||||
"homepage": "http://shishnet.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A collection of workarounds for stupid PHP things",
|
||||
"homepage": "https://github.com/shish/ffsphp",
|
||||
"time": "2020-09-20T13:15:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "shish/microcrud",
|
||||
"version": "v2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/shish/microcrud.git",
|
||||
"reference": "1d55c02c405fc75ad6a0e952e9333552bef492f0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/shish/microcrud/zipball/1d55c02c405fc75ad6a0e952e9333552bef492f0",
|
||||
"reference": "1d55c02c405fc75ad6a0e952e9333552bef492f0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pdo": "*",
|
||||
"php": "^7.3",
|
||||
"shish/ffsphp": "^1.0",
|
||||
"shish/microhtml": "^2.0.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MicroCRUD\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Shish",
|
||||
"email": "webmaster@shishnet.org",
|
||||
"homepage": "http://shishnet.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A minimal CRUD generating library",
|
||||
"homepage": "https://github.com/shish/microcrud",
|
||||
"keywords": [
|
||||
"crud",
|
||||
"generator"
|
||||
],
|
||||
"time": "2020-09-20T13:10:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "shish/microhtml",
|
||||
"version": "v2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/shish/microhtml.git",
|
||||
"reference": "d5c393d5a47bb3b3febdfde9ebf6e91cb9b41947"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/shish/microhtml/zipball/d5c393d5a47bb3b3febdfde9ebf6e91cb9b41947",
|
||||
"reference": "d5c393d5a47bb3b3febdfde9ebf6e91cb9b41947",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/microhtml.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Shish",
|
||||
"email": "webmaster@shishnet.org",
|
||||
"homepage": "http://shishnet.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A minimal HTML generating library",
|
||||
"homepage": "https://github.com/shish/microhtml",
|
||||
"keywords": [
|
||||
"generator",
|
||||
"html"
|
||||
],
|
||||
"time": "2020-09-20T13:05:01+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^7.3",
|
||||
"ext-pdo": "*",
|
||||
"ext-json": "*",
|
||||
"ext-fileinfo": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "1.1.0"
|
||||
}
|
181
contrib/admin/main.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/**
|
||||
* Name: Admin Controls
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Link: http://code.shishnet.org/shimmie2/
|
||||
* License: GPLv2
|
||||
* Description: Various things to make admins' lives easier
|
||||
* Documentation:
|
||||
* <p>Lowercase all tags:
|
||||
* <br>Set all tags to lowercase for consistency
|
||||
* <p>Recount tag use:
|
||||
* <br>If the counts of images per tag get messed up somehow, this will reset them
|
||||
* <p>Purge unused tags:
|
||||
* <br>Get rid of all the tags that don't have any images associated with
|
||||
* them (normally they were created as typos or spam); this is mostly for
|
||||
* neatness, the performance gain is tiny...
|
||||
* <p>Convert to InnoDB:
|
||||
* <br>Convert your database tables to InnoDB, thus allowing shimmie to
|
||||
* take advantage of useful InnoDB-only features (this should be done
|
||||
* automatically, this button only exists as a backup). This only applies
|
||||
* to MySQL -- all other databases come with useful features enabled
|
||||
* as standard.
|
||||
* <p>Database dump:
|
||||
* <br>Download the contents of the database in plain text format, useful
|
||||
* for backups.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sent when the admin page is ready to be added to
|
||||
*/
|
||||
class AdminBuildingEvent extends Event {
|
||||
var $page;
|
||||
public function AdminBuildingEvent($page) {
|
||||
$this->page = $page;
|
||||
}
|
||||
}
|
||||
|
||||
class AdminPage implements Extension {
|
||||
var $theme;
|
||||
|
||||
public function receive_event(Event $event) {
|
||||
global $config, $database, $page, $user;
|
||||
if(is_null($this->theme)) $this->theme = get_theme_object($this);
|
||||
|
||||
if(($event instanceof PageRequestEvent) && $event->page_matches("admin")) {
|
||||
if(!$user->is_admin()) {
|
||||
$this->theme->display_permission_denied($page);
|
||||
}
|
||||
else {
|
||||
send_event(new AdminBuildingEvent($page));
|
||||
}
|
||||
}
|
||||
|
||||
if(($event instanceof PageRequestEvent) && $event->page_matches("admin_utils")) {
|
||||
if($user->is_admin() && $user->check_auth_token()) {
|
||||
log_info("admin", "Util: {$_POST['action']}");
|
||||
set_time_limit(0);
|
||||
$redirect = false;
|
||||
|
||||
switch($_POST['action']) {
|
||||
case 'delete by query':
|
||||
$this->delete_by_query($_POST['query']);
|
||||
$redirect = true;
|
||||
break;
|
||||
case 'lowercase all tags':
|
||||
$this->lowercase_all_tags();
|
||||
$redirect = true;
|
||||
break;
|
||||
case 'recount tag use':
|
||||
$this->recount_tag_use();
|
||||
$redirect = true;
|
||||
break;
|
||||
case 'purge unused tags':
|
||||
$this->purge_unused_tags();
|
||||
$redirect = true;
|
||||
break;
|
||||
case 'convert to innodb':
|
||||
$this->convert_to_innodb();
|
||||
$redirect = true;
|
||||
break;
|
||||
case 'database dump':
|
||||
$this->dbdump($page);
|
||||
break;
|
||||
}
|
||||
|
||||
if($redirect) {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("admin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($event instanceof AdminBuildingEvent) {
|
||||
$this->theme->display_page($page);
|
||||
$this->theme->display_form($page);
|
||||
}
|
||||
|
||||
if($event instanceof UserBlockBuildingEvent) {
|
||||
if($user->is_admin()) {
|
||||
$event->add_link("Board Admin", make_link("admin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function delete_by_query($query) {
|
||||
global $page, $user;
|
||||
assert(strlen($query) > 1);
|
||||
foreach(Image::find_images(0, 1000000, Tag::explode($query)) as $image) {
|
||||
send_event(new ImageDeletionEvent($image));
|
||||
}
|
||||
}
|
||||
|
||||
private function lowercase_all_tags() {
|
||||
global $database;
|
||||
$database->execute("UPDATE tags SET tag=lower(tag)");
|
||||
}
|
||||
|
||||
private function recount_tag_use() {
|
||||
global $database;
|
||||
$database->Execute("
|
||||
UPDATE tags
|
||||
SET count = COALESCE(
|
||||
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
|
||||
0
|
||||
)");
|
||||
}
|
||||
|
||||
private function purge_unused_tags() {
|
||||
global $database;
|
||||
$this->recount_tag_use();
|
||||
$database->Execute("DELETE FROM tags WHERE count=0");
|
||||
}
|
||||
|
||||
private function dbdump($page) {
|
||||
include "config.php";
|
||||
|
||||
$matches = array();
|
||||
preg_match("#(\w+)://(\w+):(\w+)@([\w\.\-]+)/([\w_]+)(\?.*)?#", $database_dsn, $matches);
|
||||
$software = $matches[1];
|
||||
$username = $matches[2];
|
||||
$password = $matches[3];
|
||||
$hostname = $matches[4];
|
||||
$database = $matches[5];
|
||||
|
||||
switch($software) {
|
||||
case 'mysql':
|
||||
$cmd = "mysqldump -h$hostname -u$username -p$password $database";
|
||||
break;
|
||||
}
|
||||
|
||||
$page->set_mode("data");
|
||||
$page->set_type("application/x-unknown");
|
||||
$page->set_filename('shimmie-'.date('Ymd').'.sql');
|
||||
$page->set_data(shell_exec($cmd));
|
||||
}
|
||||
|
||||
private function check_for_orphanned_images() {
|
||||
$orphans = array();
|
||||
foreach(glob("images/*") as $dir) {
|
||||
foreach(glob("$dir/*") as $file) {
|
||||
$hash = str_replace("$dir/", "", $file);
|
||||
if(!$this->db_has_hash($hash)) {
|
||||
$orphans[] = $hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function convert_to_innodb() {
|
||||
global $database;
|
||||
if($database->engine->name == "mysql") {
|
||||
$tables = $database->db->MetaTables();
|
||||
foreach($tables as $table) {
|
||||
log_info("upgrade", "converting $table to innodb");
|
||||
$database->execute("ALTER TABLE $table TYPE=INNODB");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
add_event_listener(new AdminPage());
|
||||
?>
|
75
contrib/admin/test.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
class AdminPageTest extends ShimmieWebTestCase {
|
||||
function testAuth() {
|
||||
$this->get_page('admin');
|
||||
$this->assert_response(403);
|
||||
$this->assert_title("Permission Denied");
|
||||
|
||||
$this->log_in_as_user();
|
||||
$this->get_page('admin');
|
||||
$this->assert_response(403);
|
||||
$this->assert_title("Permission Denied");
|
||||
$this->log_out();
|
||||
}
|
||||
|
||||
function testLowercase() {
|
||||
$ts = time(); // we need a tag that hasn't been used before
|
||||
|
||||
$this->log_in_as_admin();
|
||||
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "TeStCase$ts");
|
||||
|
||||
$this->get_page("post/view/$image_id_1");
|
||||
$this->assert_title("Image $image_id_1: TeStCase$ts");
|
||||
|
||||
$this->get_page('admin');
|
||||
$this->assert_title("Admin Tools");
|
||||
$this->set_field("action", "lowercase all tags");
|
||||
$this->click("Go");
|
||||
$this->log_out();
|
||||
|
||||
$this->get_page("post/view/$image_id_1");
|
||||
$this->assert_title("Image $image_id_1: testcase$ts");
|
||||
|
||||
$this->delete_image($image_id_1);
|
||||
$this->log_out();
|
||||
}
|
||||
|
||||
# FIXME: make sure the admin tools actually work
|
||||
function testRecount() {
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page('admin');
|
||||
$this->assert_title("Admin Tools");
|
||||
$this->set_field("action", "recount tag use");
|
||||
$this->click("Go");
|
||||
$this->log_out();
|
||||
}
|
||||
|
||||
function testPurge() {
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page('admin');
|
||||
$this->assert_title("Admin Tools");
|
||||
$this->set_field("action", "purge unused tags");
|
||||
$this->click("Go");
|
||||
$this->log_out();
|
||||
}
|
||||
|
||||
function testConvert() {
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page('admin');
|
||||
$this->assert_title("Admin Tools");
|
||||
$this->set_field("action", "convert to inodb");
|
||||
$this->click("Go");
|
||||
$this->log_out();
|
||||
}
|
||||
|
||||
function testDump() {
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page('admin');
|
||||
$this->assert_title("Admin Tools");
|
||||
$this->set_field("action", "database dump");
|
||||
$this->click("Go");
|
||||
$this->log_out();
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
46
contrib/admin/theme.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
class AdminPageTheme extends Themelet {
|
||||
/*
|
||||
* Show the basics of a page, for other extensions to add to
|
||||
*/
|
||||
public function display_page(Page $page) {
|
||||
$page->set_title("Admin Tools");
|
||||
$page->set_heading("Admin Tools");
|
||||
$page->add_block(new NavBlock());
|
||||
}
|
||||
|
||||
/*
|
||||
* Show a form which links to admin_utils with POST[action] set to one of:
|
||||
* 'lowercase all tags'
|
||||
* 'recount tag use'
|
||||
* 'purge unused tags'
|
||||
*/
|
||||
public function display_form(Page $page) {
|
||||
global $user;
|
||||
|
||||
$html = "
|
||||
".make_form(make_link("admin_utils"))."
|
||||
<select name='action'>
|
||||
<option value='lowercase all tags'>All tags to lowercase</option>
|
||||
<option value='recount tag use'>Recount tag use</option>
|
||||
<option value='purge unused tags'>Purge unused tags</option>
|
||||
<option value='database dump'>Download database contents</option>
|
||||
<option value='convert to innodb'>Convert database to InnoDB (MySQL only)</option>
|
||||
</select>
|
||||
<input type='submit' value='Go'>
|
||||
</form>
|
||||
";
|
||||
$page->add_block(new Block("Misc Admin Tools", $html));
|
||||
|
||||
$html = "
|
||||
".make_form(make_link("admin_utils"))."
|
||||
<input type='hidden' name='action' value='delete by query'>
|
||||
<input type='text' name='query'>
|
||||
<input type='submit' value='Go'>
|
||||
</form>
|
||||
";
|
||||
$page->add_block(new Block("Delete by Query", $html));
|
||||
}
|
||||
}
|
||||
?>
|
75
contrib/amazon_s3/main.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Amazon S3 Mirror
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* License: GPLv2
|
||||
* Description: Copy uploaded files to S3
|
||||
* Documentation:
|
||||
*/
|
||||
|
||||
require_once "lib/S3.php";
|
||||
|
||||
class UploadS3 extends SimpleExtension {
|
||||
public function onInitExt(InitExtEvent $event) {
|
||||
global $config;
|
||||
$config->set_default_string("amazon_s3_access", "");
|
||||
$config->set_default_string("amazon_s3_secret", "");
|
||||
$config->set_default_string("amazon_s3_bucket", "");
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event) {
|
||||
$sb = new SetupBlock("Amazon S3");
|
||||
$sb->add_text_option("amazon_s3_access", "Access key: ");
|
||||
$sb->add_text_option("amazon_s3_secret", "<br>Secret key: ");
|
||||
$sb->add_text_option("amazon_s3_bucket", "<br>Bucket: ");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
public function onImageAddition(ImageAdditionEvent $event) {
|
||||
global $config;
|
||||
$access = $config->get_string("amazon_s3_access");
|
||||
$secret = $config->get_string("amazon_s3_secret");
|
||||
$bucket = $config->get_string("amazon_s3_bucket");
|
||||
if(!empty($bucket)) {
|
||||
log_debug("amazon_s3", "Mirroring Image #".$event->image->id." to S3 #$bucket");
|
||||
$s3 = new S3($access, $secret);
|
||||
$s3->putBucket($bucket, S3::ACL_PUBLIC_READ);
|
||||
$s3->putObjectFile(
|
||||
warehouse_path("thumbs", $event->image->hash),
|
||||
$bucket,
|
||||
'thumbs/'.$event->image->hash,
|
||||
S3::ACL_PUBLIC_READ,
|
||||
array(),
|
||||
array(
|
||||
"Content-Type" => "image/jpeg",
|
||||
"Content-Disposition" => "inline; filename=image-" . $event->image->id . ".jpg",
|
||||
)
|
||||
);
|
||||
$s3->putObjectFile(
|
||||
warehouse_path("images", $event->image->hash),
|
||||
$bucket,
|
||||
'images/'.$event->image->hash,
|
||||
S3::ACL_PUBLIC_READ,
|
||||
array(),
|
||||
array(
|
||||
"Content-Type" => $event->image->get_mime_type(),
|
||||
"Content-Disposition" => "inline; filename=image-" . $event->image->id . "." . $event->image->ext,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function onImageDeletion(ImageDeletionEvent $event) {
|
||||
global $config;
|
||||
$access = $config->get_string("amazon_s3_access");
|
||||
$secret = $config->get_string("amazon_s3_secret");
|
||||
$bucket = $config->get_string("amazon_s3_bucket");
|
||||
if(!empty($bucket)) {
|
||||
log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3");
|
||||
$s3 = new S3($access, $secret);
|
||||
$s3->deleteObject($bucket, "images/"+$event->image->hash);
|
||||
$s3->deleteObject($bucket, "thumbs/"+$event->image->hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
90
contrib/ban_words/main.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Comment Word Ban
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* License: GPLv2
|
||||
* Description: For stopping spam and other comment abuse
|
||||
* Documentation:
|
||||
* Allows an administrator to ban certain words
|
||||
* from comments. This can be a very simple but effective way
|
||||
* of stopping spam; just add "viagra", "porn", etc to the
|
||||
* banned words list.
|
||||
* <p>Regex bans are also supported, allowing more complicated
|
||||
* bans like <code>/http:.*\.cn\//</code> to block links to
|
||||
* chinese websites, or <code>/.*?http.*?http.*?http.*?http.*?/</code>
|
||||
* to block comments with four (or more) links in.
|
||||
* <p>Note that for non-regex matches, only whole words are
|
||||
* matched, eg banning "sex" would block the comment "get free
|
||||
* sex call this number", but allow "This is a photo of Bob
|
||||
* from Essex"
|
||||
*/
|
||||
|
||||
class BanWords extends SimpleExtension {
|
||||
public function onInitExt(InitExtEvent $event) {
|
||||
global $config;
|
||||
$config->set_default_string('banned_words', "
|
||||
a href=
|
||||
anal
|
||||
blowjob
|
||||
/buy-.*-online/
|
||||
casino
|
||||
cialis
|
||||
doors.txt
|
||||
fuck
|
||||
hot video
|
||||
kaboodle.com
|
||||
lesbian
|
||||
nexium
|
||||
penis
|
||||
/pokerst.*/
|
||||
pornhub
|
||||
porno
|
||||
purchase
|
||||
sex
|
||||
sex tape
|
||||
spinnenwerk.de
|
||||
thx for all
|
||||
TRAMADOL
|
||||
ultram
|
||||
very nice site
|
||||
viagra
|
||||
xanax
|
||||
");
|
||||
}
|
||||
|
||||
public function onCommentPosting(CommentPostingEvent $event) {
|
||||
global $config;
|
||||
$banned = $config->get_string("banned_words");
|
||||
$comment = strtolower($event->comment);
|
||||
|
||||
foreach(explode("\n", $banned) as $word) {
|
||||
$word = trim(strtolower($word));
|
||||
if(strlen($word) == 0) {
|
||||
// line is blank
|
||||
continue;
|
||||
}
|
||||
else if($word[0] == '/') {
|
||||
// lines that start with slash are regex
|
||||
if(preg_match($word, $comment)) {
|
||||
throw new CommentPostingException("Comment contains banned terms");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// other words are literal
|
||||
if(strpos($comment, $word) !== false) {
|
||||
throw new CommentPostingException("Comment contains banned terms");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event) {
|
||||
$sb = new SetupBlock("Banned Phrases");
|
||||
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
|
||||
$sb->add_longtext_option("banned_words");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
public function get_priority() {return 30;}
|
||||
}
|
||||
?>
|
45
contrib/ban_words/test.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class BanWordsTest extends ShimmieWebTestCase {
|
||||
function testWordBan() {
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page("setup");
|
||||
$this->set_field("_config_banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
|
||||
$this->click("Save Settings");
|
||||
$this->log_out();
|
||||
|
||||
$this->log_in_as_user();
|
||||
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
|
||||
|
||||
$this->get_page("post/view/$image_id");
|
||||
$this->set_field('comment', "kittens and viagra");
|
||||
$this->click("Post Comment");
|
||||
$this->assert_title("Comment Blocked");
|
||||
|
||||
$this->get_page("post/view/$image_id");
|
||||
$this->set_field('comment', "kittens and ViagrA");
|
||||
$this->click("Post Comment");
|
||||
$this->assert_title("Comment Blocked");
|
||||
|
||||
$this->get_page("post/view/$image_id");
|
||||
$this->set_field('comment', "kittens and viagra!");
|
||||
$this->click("Post Comment");
|
||||
$this->assert_title("Comment Blocked");
|
||||
|
||||
$this->get_page("post/view/$image_id");
|
||||
$this->set_field('comment', "some link to http://something.cn/");
|
||||
$this->click("Post Comment");
|
||||
$this->assert_title("Comment Blocked");
|
||||
|
||||
$this->get_page('comment/list');
|
||||
$this->assert_title('Comments');
|
||||
$this->assert_no_text('viagra');
|
||||
$this->assert_no_text('ViagrA');
|
||||
$this->assert_no_text('http://something.cn/');
|
||||
$this->log_out();
|
||||
|
||||
$this->log_in_as_admin();
|
||||
$this->delete_image($image_id);
|
||||
$this->log_out();
|
||||
}
|
||||
}
|
||||
?>
|
130
contrib/blotter/main.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Blotter
|
||||
* Author: Zach Hall <zach@sosguy.net> [http://seemslegit.com/]
|
||||
* License: GPLv2
|
||||
* Description: Displays brief updates about whatever you want on every page.
|
||||
* Colors and positioning can be configured to match your site's design.
|
||||
*
|
||||
* Development TODO at http://github.com/zshall/shimmie2/issues
|
||||
*/
|
||||
class Blotter extends SimpleExtension {
|
||||
public function onInitExt(Event $event) {
|
||||
/**
|
||||
* I love re-using this installer don't I...
|
||||
*/
|
||||
global $config;
|
||||
$version = $config->get_int("blotter_version", 0);
|
||||
/**
|
||||
* If this version is less than "1", it's time to install.
|
||||
*
|
||||
* REMINDER: If I change the database tables, I must change up version by 1.
|
||||
*/
|
||||
if($version < 1) {
|
||||
/**
|
||||
* Installer
|
||||
*/
|
||||
global $database, $config;
|
||||
$database->create_table("blotter", "
|
||||
id SCORE_AIPK,
|
||||
entry_date SCORE_DATETIME DEFAULT SCORE_NOW,
|
||||
entry_text TEXT NOT NULL,
|
||||
important SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N
|
||||
");
|
||||
// Insert sample data:
|
||||
$database->execute("INSERT INTO blotter (id, entry_date, entry_text, important) VALUES (?, now(), ?, ?)",
|
||||
array(NULL, "Installed the blotter extension!", "Y"));
|
||||
log_info("blotter", "Installed tables for blotter extension.");
|
||||
$config->set_int("blotter_version", 1);
|
||||
}
|
||||
// Set default config:
|
||||
$config->set_default_int("blotter_recent", 5);
|
||||
$config->set_default_string("blotter_color", "FF0000");
|
||||
$config->set_default_string("blotter_position", "subheading");
|
||||
|
||||
}
|
||||
public function onSetupBuilding(Event $event) {
|
||||
global $config;
|
||||
$sb = new SetupBlock("Blotter");
|
||||
$sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: ");
|
||||
$sb->add_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) ");
|
||||
$sb->add_choice_option("blotter_position", array("Top of page" => "subheading", "In navigation bar" => "left"), "<br>Position: ");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
public function onUserBlockBuilding(Event $event) {
|
||||
global $user;
|
||||
if($user->is_admin()) {
|
||||
$event->add_link("Blotter Editor", make_link("blotter/editor"));
|
||||
}
|
||||
}
|
||||
public function onPageRequest(Event $event) {
|
||||
global $page, $database, $user;
|
||||
if($event->page_matches("blotter")) {
|
||||
switch($event->get_arg(0)) {
|
||||
case "editor":
|
||||
/**
|
||||
* Displays the blotter editor.
|
||||
*/
|
||||
if(!$user->is_admin()) {
|
||||
$this->theme->display_permission_denied($page);
|
||||
} else {
|
||||
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC");
|
||||
$this->theme->display_editor($entries);
|
||||
}
|
||||
break;
|
||||
case "add":
|
||||
/**
|
||||
* Adds an entry
|
||||
*/
|
||||
if(!$user->is_admin() || !$user->check_auth_token()) {
|
||||
$this->theme->display_permission_denied($page);
|
||||
} else {
|
||||
$entry_text = $_POST['entry_text'];
|
||||
if($entry_text == "") { die("No entry message!"); }
|
||||
if(isset($_POST['important'])) { $important = 'Y'; } else { $important = 'N'; }
|
||||
// Now insert into db:
|
||||
$database->execute("INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), ?, ?)",
|
||||
array($entry_text, $important));
|
||||
log_info("blotter", "Added Message: $entry_text");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("blotter/editor"));
|
||||
}
|
||||
break;
|
||||
case "remove":
|
||||
/**
|
||||
* Removes an entry
|
||||
*/
|
||||
if(!$user->is_admin() || !$user->check_auth_token()) {
|
||||
$this->theme->display_permission_denied($page);
|
||||
} else {
|
||||
$id = int_escape($_POST['id']);
|
||||
if(!isset($id)) { die("No ID!"); }
|
||||
$database->Execute("DELETE FROM blotter WHERE id=:id", array("id"=>$id));
|
||||
log_info("blotter", "Removed Entry #$id");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("blotter/editor"));
|
||||
}
|
||||
break;
|
||||
case "":
|
||||
/**
|
||||
* Displays all blotter entries
|
||||
*/
|
||||
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC");
|
||||
$this->theme->display_blotter_page($entries);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Finally, display the blotter on whatever page we're viewing.
|
||||
*/
|
||||
$this->display_blotter();
|
||||
}
|
||||
|
||||
private function display_blotter() {
|
||||
global $database, $config;
|
||||
$limit = $config->get_int("blotter_recent", 5);
|
||||
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC LIMIT :lim", array("lim"=>$limit));
|
||||
$this->theme->display_blotter($entries);
|
||||
}
|
||||
}
|
||||
?>
|
37
contrib/blotter/test.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
class BlotterTest extends SCoreWebTestCase {
|
||||
function testLogin() {
|
||||
$this->log_in_as_admin();
|
||||
$this->assert_text("Blotter Editor");
|
||||
$this->click("Blotter Editor");
|
||||
$this->log_out();
|
||||
}
|
||||
|
||||
function testDenial() {
|
||||
$this->get_page("blotter/editor");
|
||||
$this->assert_response(403);
|
||||
$this->get_page("blotter/add");
|
||||
$this->assert_response(403);
|
||||
$this->get_page("blotter/remove");
|
||||
$this->assert_response(403);
|
||||
}
|
||||
|
||||
function testAddViewRemove() {
|
||||
$this->log_in_as_admin();
|
||||
|
||||
$this->get_page("blotter/editor");
|
||||
$this->set_field("entry_text", "blotter testing");
|
||||
$this->click("Add");
|
||||
$this->assert_text("blotter testing");
|
||||
|
||||
$this->get_page("blotter");
|
||||
$this->assert_text("blotter testing");
|
||||
|
||||
$this->get_page("blotter/editor");
|
||||
$this->click("Remove");
|
||||
$this->assert_no_text("blotter testing");
|
||||
|
||||
$this->log_out();
|
||||
}
|
||||
}
|
||||
?>
|
189
contrib/blotter/theme.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
class BlotterTheme extends Themelet {
|
||||
public function display_editor($entries) {
|
||||
global $page;
|
||||
$html = $this->get_html_for_blotter_editor($entries);
|
||||
$page->set_title("Blotter Editor");
|
||||
$page->set_heading("Blotter Editor");
|
||||
$page->add_block(new Block("Welcome to the Blotter Editor!", $html, "main", 10));
|
||||
$page->add_block(new Block("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0));
|
||||
}
|
||||
|
||||
public function display_blotter_page($entries) {
|
||||
global $page;
|
||||
$html = $this->get_html_for_blotter_page($entries);
|
||||
$page->set_mode("data");
|
||||
$page->set_data($html);
|
||||
}
|
||||
|
||||
public function display_blotter($entries) {
|
||||
global $page, $config;
|
||||
$html = $this->get_html_for_blotter($entries);
|
||||
$position = $config->get_string("blotter_position", "subheading");
|
||||
$page->add_block(new Block(null, $html, $position, 20));
|
||||
}
|
||||
|
||||
private function is_odd($number) {
|
||||
return $number & 1; // 0 = even, 1 = odd
|
||||
}
|
||||
|
||||
private function get_html_for_blotter_editor($entries) {
|
||||
global $user;
|
||||
|
||||
/**
|
||||
* Long function name, but at least I won't confuse it with something else ^_^
|
||||
*/
|
||||
|
||||
$html = "";
|
||||
// Add_new stuff goes here.
|
||||
$table_header = "
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Message</th>
|
||||
<th>Important?</th>
|
||||
<th>Action</th>
|
||||
</tr>";
|
||||
$add_new = "
|
||||
<tr class='even'>
|
||||
".make_form(make_link("blotter/add"))."
|
||||
<td colspan='2'><textarea style='text-align:left;' name='entry_text' rows='2' /></textarea></td>
|
||||
<td><input type='checkbox' name='important' /></td>
|
||||
<td><input type='submit' value='Add'></td>
|
||||
</form>
|
||||
</tr>";
|
||||
|
||||
|
||||
// Now, time for entries list.
|
||||
$table_rows = "";
|
||||
for ($i = 0 ; $i < count($entries) ; $i++)
|
||||
{
|
||||
/**
|
||||
* Add table rows
|
||||
*/
|
||||
$id = $entries[$i]['id'];
|
||||
$entry_date = $entries[$i]['entry_date'];
|
||||
$entry_text = $entries[$i]['entry_text'];
|
||||
if($entries[$i]['important'] == 'Y') { $important = 'Y'; } else { $important = 'N'; }
|
||||
|
||||
if(!$this->is_odd($i)) {$tr_class = "odd";}
|
||||
if($this->is_odd($i)) {$tr_class = "even";}
|
||||
// Add the new table row(s)
|
||||
$table_rows .=
|
||||
"<tr class='{$tr_class}'>
|
||||
<td>$entry_date</td>
|
||||
<td>$entry_text</td>
|
||||
<td>$important</td>
|
||||
<td><form name='remove$id' method='post' action='".make_link("blotter/remove")."'>
|
||||
".$user->get_auth_html()."
|
||||
<input type='hidden' name='id' value='$id' />
|
||||
<input type='submit' style='width: 100%;' value='Remove' />
|
||||
</form>
|
||||
</td>
|
||||
</tr>";
|
||||
}
|
||||
|
||||
$html = "
|
||||
<table id='blotter_entries' class='zebra'>
|
||||
<thead>$table_header</thead>
|
||||
<tbody>$add_new</tbody>
|
||||
<tfoot>$table_rows</tfoot>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
<b>Help:</b><br />
|
||||
<blockquote>Add entries to the blotter, and they will be displayed.</blockquote>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function get_html_for_blotter_page($entries) {
|
||||
/**
|
||||
* This one displays a list of all blotter entries.
|
||||
*/
|
||||
global $config;
|
||||
$i_color = $config->get_string("blotter_color","#FF0000");
|
||||
$html = "";
|
||||
$html .= "<html><head><title>Blotter</title></head>
|
||||
<body><pre>";
|
||||
|
||||
for ($i = 0 ; $i < count($entries) ; $i++)
|
||||
{
|
||||
/**
|
||||
* Blotter entries
|
||||
*/
|
||||
// Reset variables:
|
||||
$i_open = "";
|
||||
$i_close = "";
|
||||
$id = $entries[$i]['id'];
|
||||
$messy_date = $entries[$i]['entry_date'];
|
||||
$clean_date = date("m/d/y",strtotime($messy_date));
|
||||
$entry_text = $entries[$i]['entry_text'];
|
||||
if($entries[$i]['important'] == 'Y') { $i_open = "<font color='#{$i_color}'>"; $i_close="</font>"; }
|
||||
$html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}<br /><br />";
|
||||
}
|
||||
|
||||
$html .= "</pre></body></html>";
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function get_html_for_blotter($entries) {
|
||||
/**
|
||||
* Show the blotter widget
|
||||
* Although I am starting to learn PHP, I got no idea how to do javascript... to the tutorials!
|
||||
*/
|
||||
global $config;
|
||||
$i_color = $config->get_string("blotter_color","#FF0000");
|
||||
$position = $config->get_string("blotter_position", "subheading");
|
||||
$html = "<style type='text/css'>
|
||||
#blotter1 {font-size: 80%; position: relative;}
|
||||
#blotter2 {font-size: 80%;}
|
||||
</style>";
|
||||
$html .= "<script><!--
|
||||
$(document).ready(function() {
|
||||
$(\"#blotter2-toggle\").click(function() {
|
||||
$(\"#blotter2\").slideToggle(\"slow\", function() {
|
||||
if($(\"#blotter2\").is(\":hidden\")) {
|
||||
$.cookie(\"blotter2-hidden\", 'true', {path: '/'});
|
||||
}
|
||||
else {
|
||||
$.cookie(\"blotter2-hidden\", 'false', {path: '/'});
|
||||
}
|
||||
});
|
||||
});
|
||||
if($.cookie(\"blotter2-hidden\") == 'true') {
|
||||
$(\"#blotter2\").hide();
|
||||
}
|
||||
});
|
||||
//--></script>";
|
||||
$entries_list = "";
|
||||
for ($i = 0 ; $i < count($entries) ; $i++)
|
||||
{
|
||||
/**
|
||||
* Blotter entries
|
||||
*/
|
||||
// Reset variables:
|
||||
$i_open = "";
|
||||
$i_close = "";
|
||||
$id = $entries[$i]['id'];
|
||||
$messy_date = $entries[$i]['entry_date'];
|
||||
$clean_date = date("m/d/y",strtotime($messy_date));
|
||||
$entry_text = $entries[$i]['entry_text'];
|
||||
if($entries[$i]['important'] == 'Y') { $i_open = "<font color='#{$i_color}'>"; $i_close="</font>"; }
|
||||
$entries_list .= "<li>{$i_open}{$clean_date} - {$entry_text}{$i_close}</li>";
|
||||
}
|
||||
$out_text = "";
|
||||
$in_text = "";
|
||||
$pos_break = "";
|
||||
$pos_align = "text-align: right; position: absolute; right: 0px;";
|
||||
if($position == "left") { $pos_break = "<br />"; $pos_align = ""; }
|
||||
if(count($entries) == 0) { $out_text = "No blotter entries yet."; $in_text = "Empty.";}
|
||||
else { $clean_date = date("m/d/y",strtotime($entries[0]['entry_date']));
|
||||
$out_text = "Blotter updated: {$clean_date}";
|
||||
$in_text = "<ul>$entries_list</ul>";
|
||||
}
|
||||
$html .= "<div id='blotter1'><span>$out_text</span>{$pos_break}<span style='{$pos_align}'><a href='#' id='blotter2-toggle'>Show/Hide</a> <a href='".make_link("blotter")."'>Show All</a></span></div>";
|
||||
$html .= "<div id='blotter2'>$in_text</div>";
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
?>
|
117
contrib/browser_search/main.php
Executable file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Browser Search
|
||||
* Author: ATravelingGeek <atg@atravelinggeek.com>
|
||||
* Some code (and lots of help) by Artanis (Erik Youngren <artanis.00@gmail.com>) from the 'tagger' extention - Used with permission
|
||||
* Link: http://atravelinggeek.com/
|
||||
* License: GPLv2
|
||||
* Description: Allows the user to add a browser 'plugin' to search the site with real-time suggestions
|
||||
* Version: 0.1c, October 26, 2007
|
||||
* Documentation:
|
||||
* Once installed, users with an opensearch compatible browser should see
|
||||
* their search box light up with whatever "click here to add a search
|
||||
* engine" notification they have
|
||||
*/
|
||||
|
||||
class BrowserSearch implements Extension {
|
||||
public function receive_event(Event $event) {
|
||||
global $page;
|
||||
global $config;
|
||||
|
||||
if($event instanceof InitExtEvent) {
|
||||
$config->set_default_string("search_suggestions_results_order", 'a');
|
||||
}
|
||||
|
||||
// Add in header code to let the browser know that the search plugin exists
|
||||
if($event instanceof PageRequestEvent) {
|
||||
// We need to build the data for the header
|
||||
global $config;
|
||||
$search_title = $config->get_string('title');
|
||||
$search_file_url = make_link('browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml');
|
||||
$page->add_header("<link rel='search' type='application/opensearchdescription+xml' title='$search_title' href='$search_file_url'>");
|
||||
}
|
||||
|
||||
// The search.xml file that is generated on the fly
|
||||
if(($event instanceof PageRequestEvent) && $event->page_matches("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml")) {
|
||||
// First, we need to build all the variables we'll need
|
||||
|
||||
$search_title = $config->get_string('title');
|
||||
//$search_form_url = $config->get_string('base_href'); //make_link('post/list');
|
||||
$search_form_url = make_link('post/list/{searchTerms}');
|
||||
$suggenton_url = make_link('browser_search/')."{searchTerms}";
|
||||
$icon_b64 = base64_encode(file_get_contents("favicon.ico"));
|
||||
|
||||
|
||||
// Now for the XML
|
||||
$xml = "
|
||||
<SearchPlugin xmlns='http://www.mozilla.org/2006/browser/search/' xmlns:os='http://a9.com/-/spec/opensearch/1.1/'>
|
||||
<os:ShortName>$search_title</os:ShortName>
|
||||
<os:InputEncoding>UTF-8</os:InputEncoding>
|
||||
<os:Image width='16' height='16'>data:image/x-icon;base64,$icon_b64</os:Image>
|
||||
<SearchForm>$search_form_url</SearchForm>
|
||||
<os:Url type='text/html' method='GET' template='$search_form_url'>
|
||||
<os:Param name='search' value='{searchTerms}'/>
|
||||
</os:Url>
|
||||
<Url type='application/x-suggestions+json' template='$suggenton_url'/>
|
||||
</SearchPlugin>
|
||||
";
|
||||
|
||||
// And now to send it to the browser
|
||||
$page->set_mode("data");
|
||||
$page->set_type("text/xml");
|
||||
$page->set_data($xml);
|
||||
}
|
||||
|
||||
else if(($event instanceof PageRequestEvent) && (
|
||||
$event->page_matches("browser_search") &&
|
||||
!$config->get_bool("disable_search_suggestions")
|
||||
)) {
|
||||
global $database;
|
||||
|
||||
// We have to build some json stuff
|
||||
$tag_search = $event->get_arg(0);
|
||||
|
||||
// Now to get DB results
|
||||
if($config->get_string("search_suggestions_results_order") == "a") {
|
||||
$tags = $database->execute("SELECT tag FROM tags WHERE tag LIKE ? AND count > 0 ORDER BY tag ASC LIMIT 30",array($tag_search."%"));
|
||||
} else {
|
||||
$tags = $database->execute("SELECT tag FROM tags WHERE tag LIKE ? AND count > 0 ORDER BY count DESC LIMIT 30",array($tag_search."%"));
|
||||
}
|
||||
|
||||
|
||||
// And to do stuff with it. We want our output to look like:
|
||||
// ["shimmie",["shimmies","shimmy","shimmie","21 shimmies","hip shimmies","skea shimmies"],[],[]]
|
||||
$json_tag_list = "";
|
||||
|
||||
$tags_array = array();
|
||||
foreach($tags as $tag) {
|
||||
array_push($tags_array,$tag['tag']);
|
||||
}
|
||||
|
||||
|
||||
$json_tag_list .= implode("\",\"", $tags_array);
|
||||
// $json_tag_list = implode($tags_array,", ");
|
||||
// $json_tag_list = "\"".implode($tags_array,"\", \"")."\"";
|
||||
|
||||
|
||||
// And now for the final output
|
||||
$json_string = "[\"$tag_search\",[\"$json_tag_list\"],[],[]]";
|
||||
$page->set_mode("data");
|
||||
$page->set_data($json_string);
|
||||
}
|
||||
|
||||
if($event instanceof SetupBuildingEvent) {
|
||||
$sort_by = array();
|
||||
$sort_by['Alphabetical'] = 'a';
|
||||
$sort_by['Tag Count'] = 't';
|
||||
|
||||
$sb = new SetupBlock("Browser Search");
|
||||
$sb->add_bool_option("disable_search_suggestions", "Disable search suggestions: ");
|
||||
$sb->add_label("<br>");
|
||||
$sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
add_event_listener(new BrowserSearch());
|
||||
?>
|
8
contrib/browser_search/test.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
class BrowserSearchTest extends SCoreWebTestCase {
|
||||
function testBasic() {
|
||||
$this->get_page("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml");
|
||||
$this->get_page("browser_search/test");
|
||||
}
|
||||
}
|
||||
?>
|
95
contrib/bulk_add/main.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Bulk Add
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* License: GPLv2
|
||||
* Description: Bulk add server-side images
|
||||
* Documentation:
|
||||
* Upload the images into a new directory via ftp or similar, go to
|
||||
* shimmie's admin page and put that directory in the bulk add box.
|
||||
* If there are subdirectories, they get used as tags (eg if you
|
||||
* upload into <code>/home/bob/uploads/holiday/2008/</code> and point
|
||||
* shimmie at <code>/home/bob/uploads</code>, then images will be
|
||||
* tagged "holiday 2008")
|
||||
* <p><b>Note:</b> requires the "admin" extension to be enabled
|
||||
*/
|
||||
|
||||
class BulkAdd extends SimpleExtension {
|
||||
public function onPageRequest($event) {
|
||||
global $page, $user;
|
||||
if($event->page_matches("bulk_add")) {
|
||||
if($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) {
|
||||
set_time_limit(0);
|
||||
$this->add_dir($_POST['dir']);
|
||||
$this->theme->display_upload_results($page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onAdminBuilding($event) {
|
||||
$this->theme->display_admin_block();
|
||||
}
|
||||
|
||||
|
||||
private function add_image($tmpname, $filename, $tags) {
|
||||
assert(file_exists($tmpname));
|
||||
|
||||
global $user;
|
||||
$pathinfo = pathinfo($filename);
|
||||
if(!array_key_exists('extension', $pathinfo)) {
|
||||
throw new UploadException("File has no extension");
|
||||
}
|
||||
$metadata['filename'] = $pathinfo['basename'];
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
$metadata['tags'] = $tags;
|
||||
$metadata['source'] = null;
|
||||
$event = new DataUploadEvent($user, $tmpname, $metadata);
|
||||
send_event($event);
|
||||
if($event->image_id == -1) {
|
||||
throw new UploadException("File type not recognised");
|
||||
}
|
||||
}
|
||||
|
||||
private function add_dir($base, $subdir="") {
|
||||
global $page;
|
||||
|
||||
if(!is_dir($base)) {
|
||||
$this->theme->add_status("Error", "$base is not a directory");
|
||||
return;
|
||||
}
|
||||
|
||||
$list = "";
|
||||
|
||||
foreach(glob("$base/$subdir/*") as $fullpath) {
|
||||
$fullpath = str_replace("//", "/", $fullpath);
|
||||
$shortpath = str_replace($base, "", $fullpath);
|
||||
|
||||
if(is_link($fullpath)) {
|
||||
// ignore
|
||||
}
|
||||
else if(is_dir($fullpath)) {
|
||||
$this->add_dir($base, str_replace($base, "", $fullpath));
|
||||
}
|
||||
else {
|
||||
$pathinfo = pathinfo($fullpath);
|
||||
$tags = $subdir;
|
||||
$tags = str_replace("/", " ", $tags);
|
||||
$tags = str_replace("__", " ", $tags);
|
||||
$tags = trim($tags);
|
||||
$list .= "<br>".html_escape("$shortpath (".str_replace(" ", ", ", $tags).")... ");
|
||||
try{
|
||||
$this->add_image($fullpath, $pathinfo["basename"], $tags);
|
||||
$list .= "ok\n";
|
||||
}
|
||||
catch(Exception $ex) {
|
||||
$list .= "failed:<br>". $ex->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(strlen($list) > 0) {
|
||||
$this->theme->add_status("Adding $subdir", $list);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
30
contrib/bulk_add/test.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
class BulkAddTest extends ShimmieWebTestCase {
|
||||
function testBulkAdd() {
|
||||
$this->log_in_as_admin();
|
||||
|
||||
$this->get_page('admin');
|
||||
$this->assert_title("Admin Tools");
|
||||
$this->set_field('dir', "asdf");
|
||||
$this->click("Add");
|
||||
$this->assert_text("is not a directory");
|
||||
|
||||
$this->get_page('admin');
|
||||
$this->assert_title("Admin Tools");
|
||||
$this->set_field('dir', "contrib/simpletest");
|
||||
$this->click("Add");
|
||||
|
||||
# FIXME: test that the output here makes sense, no "adding foo.php ... ok"
|
||||
|
||||
$this->get_page("post/list/hash=17fc89f372ed3636e28bd25cc7f3bac1/1");
|
||||
$this->assert_title(new PatternExpectation("/^Image \d+: data/"));
|
||||
$this->click("Delete");
|
||||
|
||||
$this->get_page("post/list/hash=feb01bab5698a11dd87416724c7a89e3/1");
|
||||
$this->assert_title(new PatternExpectation("/^Image \d+: data/"));
|
||||
$this->click("Delete");
|
||||
|
||||
$this->log_out();
|
||||
}
|
||||
}
|
||||
?>
|
43
contrib/bulk_add/theme.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
class BulkAddTheme extends Themelet {
|
||||
var $messages = array();
|
||||
|
||||
/*
|
||||
* Show a standard page for results to be put into
|
||||
*/
|
||||
public function display_upload_results(Page $page) {
|
||||
$page->set_title("Adding folder");
|
||||
$page->set_heading("Adding folder");
|
||||
$page->add_block(new NavBlock());
|
||||
foreach($this->messages as $block) {
|
||||
$page->add_block($block);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a section to the admin page. This should contain a form which
|
||||
* links to bulk_add with POST[dir] set to the name of a server-side
|
||||
* directory full of images
|
||||
*/
|
||||
public function display_admin_block() {
|
||||
global $page, $user;
|
||||
$html = "
|
||||
Add a folder full of images; any subfolders will have their names
|
||||
used as tags for the images within.
|
||||
<br>Note: this is the folder as seen by the server -- you need to
|
||||
upload via FTP or something first.
|
||||
|
||||
<p>".make_form(make_link("bulk_add"))."
|
||||
Directory to add: <input type='text' name='dir' size='40'>
|
||||
<input type='submit' value='Add'>
|
||||
</form>
|
||||
";
|
||||
$page->add_block(new Block("Bulk Add", $html));
|
||||
}
|
||||
|
||||
public function add_status($title, $body) {
|
||||
$this->messages[] = new Block($title, $body);
|
||||
}
|
||||
}
|
||||
?>
|
429
contrib/danbooru_api/main.php
Normal file
@ -0,0 +1,429 @@
|
||||
<?php
|
||||
/*
|
||||
Name: Danbooru Client API
|
||||
Author: JJS <jsutinen@gmail.com>
|
||||
Description: Allow Danbooru apps like Danbooru Uploader for Firefox to communicate with Shimmie
|
||||
Documentation:
|
||||
<p>Notes:
|
||||
<br>danbooru API based on documentation from danbooru 1.0 -
|
||||
http://attachr.com/7569
|
||||
<br>I've only been able to test add_post and find_tags because I use the
|
||||
old danbooru firefox extension for firefox 1.5
|
||||
<p>Functions currently implemented:
|
||||
<ul>
|
||||
<li>add_post - title and rating are currently ignored because shimmie does not support them
|
||||
<li>find_posts - sort of works, filename is returned as the original filename and probably won't help when it comes to actually downloading it
|
||||
<li>find_tags - id, name, and after_id all work but the tags parameter is ignored just like danbooru 1.0 ignores it
|
||||
</ul>
|
||||
|
||||
CHANGELOG
|
||||
13-OCT-08 8:00PM CST - JJS
|
||||
Bugfix - Properly escape source attribute
|
||||
|
||||
17-SEP-08 10:00PM CST - JJS
|
||||
Bugfix for changed page name checker in PageRequestEvent
|
||||
|
||||
13-APR-08 10:00PM CST - JJS
|
||||
Properly escape the tags returned in find_tags and find_posts - Caught by ATravelingGeek
|
||||
Updated extension info to be a bit more clear about its purpose
|
||||
Deleted add_comment code as it didn't do anything anyway
|
||||
|
||||
01-MAR-08 7:00PM CST - JJS
|
||||
Rewrote to make it compatible with Shimmie trunk again (r723 at least)
|
||||
It may or may not support the new file handling stuff correctly, I'm only testing with images and the danbooru uploader for firefox
|
||||
|
||||
21-OCT-07 9:07PM CST - JJS
|
||||
Turns out I actually did need to implement the new parameter names
|
||||
for danbooru api v1.8.1. Now danbooruup should work when used with /api/danbooru/post/create.xml
|
||||
Also correctly redirects the url provided by danbooruup in the event
|
||||
of a duplicate image.
|
||||
|
||||
19-OCT-07 4:46PM CST - JJS
|
||||
Add compatibility with danbooru api v1.8.1 style urls
|
||||
for find_posts and add_post. NOTE: This does not implement
|
||||
the changes to the parameter names, it is simply a
|
||||
workaround for the latest danbooruup firefox extension.
|
||||
Completely compatibility will probably involve a rewrite with a different URL
|
||||
|
||||
*/
|
||||
|
||||
class DanbooruApi implements Extension
|
||||
{
|
||||
// Receive the event
|
||||
public function receive_event(Event $event)
|
||||
{
|
||||
// Check if someone is accessing /api/danbooru (us)
|
||||
if(($event instanceof PageRequestEvent) && ($event->page_matches("api")) && ($event->get_arg(0) == 'danbooru'))
|
||||
{
|
||||
// execute the danbooru processing code
|
||||
$this->api_danbooru($event);
|
||||
}
|
||||
if($event instanceof SearchTermParseEvent)
|
||||
{
|
||||
$matches = array();
|
||||
if(preg_match("/^md5:([0-9a-fA-F]*)$/i", $event->term, $matches))
|
||||
{
|
||||
$hash = strtolower($matches[1]);
|
||||
$event->add_querylet(new Querylet("images.hash = '$hash'")); // :-O
|
||||
// $event->set_querylet(new Querylet("images.hash = '$hash'"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Danbooru API
|
||||
private function api_danbooru($event)
|
||||
{
|
||||
global $page;
|
||||
global $config;
|
||||
global $database;
|
||||
global $user;
|
||||
$page->set_mode("data");
|
||||
$page->set_type("application/xml");
|
||||
//debug
|
||||
//$page->set_type("text/plain");
|
||||
|
||||
$results = array();
|
||||
|
||||
$danboorup_kludge=1; // danboorup for firefox makes broken links out of location: /path
|
||||
|
||||
/*
|
||||
add_post()
|
||||
Adds a post to the database.
|
||||
Parameters
|
||||
* login: login
|
||||
* password: password
|
||||
* file: file as a multipart form
|
||||
* source: source url
|
||||
* title: title **IGNORED**
|
||||
* tags: list of tags as a string, delimited by whitespace
|
||||
* md5: MD5 hash of upload in hexadecimal format
|
||||
* rating: rating of the post. can be explicit, questionable, or safe. **IGNORED**
|
||||
Notes
|
||||
* The only necessary parameter is tags and either file or source.
|
||||
* If you want to sign your post, you need a way to authenticate your account, either by supplying login and password, or by supplying a cookie.
|
||||
* If an account is not supplied or if it doesn‘t authenticate, he post will be added anonymously.
|
||||
* If the md5 parameter is supplied and does not match the hash of what‘s on the server, the post is rejected.
|
||||
Response
|
||||
The response depends on the method used:
|
||||
Post
|
||||
* X-Danbooru-Location set to the URL for newly uploaded post.
|
||||
Get
|
||||
* Redirected to the newly uploaded post.
|
||||
*/
|
||||
if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml')))
|
||||
{
|
||||
// No XML data is returned from this function
|
||||
$page->set_type("text/plain");
|
||||
// Check first if a login was supplied, if it wasn't check if the user is logged in via cookie
|
||||
// If all that fails, it's an anonymous upload
|
||||
$this->authenticate_user();
|
||||
// Now we check if a file was uploaded or a url was provided to transload
|
||||
// Much of this code is borrowed from /ext/upload
|
||||
|
||||
if($config->get_bool("upload_anon") || !$user->is_anonymous())
|
||||
{
|
||||
$file = null;
|
||||
$filename = "";
|
||||
$source = "";
|
||||
if(isset($_FILES['file']))
|
||||
{ // A file was POST'd in
|
||||
$file = $_FILES['file']['tmp_name'];
|
||||
$filename = $_FILES['file']['name'];
|
||||
// If both a file is posted and a source provided, I'm assuming source is the source of the file
|
||||
if(isset($_REQUEST['source']) && !empty($_REQUEST['source']))
|
||||
{
|
||||
$source = $_REQUEST['source'];
|
||||
} else
|
||||
{
|
||||
$source = null;
|
||||
}
|
||||
} elseif(isset($_FILES['post']))
|
||||
{
|
||||
$file = $_FILES['post']['tmp_name']['file'];
|
||||
$filename = $_FILES['post']['name']['file'];
|
||||
if(isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source']))
|
||||
{
|
||||
$source = $_REQUEST['post']['source'];
|
||||
} else
|
||||
{
|
||||
$source = null;
|
||||
}
|
||||
} elseif(isset($_REQUEST['source']) || isset($_REQUEST['post']['source']))
|
||||
{ // A url was provided
|
||||
$url = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
|
||||
$source = $url;
|
||||
$tmp_filename = tempnam("/tmp", "shimmie_transload");
|
||||
|
||||
// Are we using fopen wrappers or curl?
|
||||
if($config->get_string("transload_engine") == "fopen")
|
||||
{
|
||||
$fp = fopen($url, "r");
|
||||
if(!$fp) {
|
||||
header("HTTP/1.0 409 Conflict");
|
||||
header("X-Danbooru-Errors: fopen read error");
|
||||
}
|
||||
|
||||
$data = "";
|
||||
$length = 0;
|
||||
while(!feof($fp) && $length <= $config->get_int('upload_size'))
|
||||
{
|
||||
$data .= fread($fp, 8192);
|
||||
$length = strlen($data);
|
||||
}
|
||||
fclose($fp);
|
||||
|
||||
$fp = fopen($tmp_filename, "w");
|
||||
fwrite($fp, $data);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
if($config->get_string("transload_engine") == "curl")
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
$fp = fopen($tmp_filename, "w");
|
||||
|
||||
curl_setopt($ch, CURLOPT_FILE, $fp);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
fclose($fp);
|
||||
}
|
||||
$file = $tmp_filename;
|
||||
$filename = basename($url);
|
||||
} else
|
||||
{ // Nothing was specified at all
|
||||
header("HTTP/1.0 409 Conflict");
|
||||
header("X-Danbooru-Errors: no input files");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get tags out of url
|
||||
$posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']);
|
||||
$hash = md5_file($file);
|
||||
// Was an md5 supplied? Does it match the file hash?
|
||||
if(isset($_REQUEST['md5']))
|
||||
{
|
||||
if(strtolower($_REQUEST['md5']) != $hash)
|
||||
{
|
||||
header("HTTP/1.0 409 Conflict");
|
||||
header("X-Danbooru-Errors: md5 mismatch");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Upload size checking is now performed in the upload extension
|
||||
// It is also currently broken due to some confusion over file variable ($tmp_filename?)
|
||||
|
||||
// Does it exist already?
|
||||
$existing = Image::by_hash($hash);
|
||||
if(!is_null($existing)) {
|
||||
header("HTTP/1.0 409 Conflict");
|
||||
header("X-Danbooru-Errors: duplicate");
|
||||
$existinglink = make_link("post/view/" . $existing->id);
|
||||
if($danboorup_kludge) $existinglink=make_http($existinglink);
|
||||
header("X-Danbooru-Location: $existinglink");
|
||||
return; // wut!
|
||||
}
|
||||
|
||||
// Fire off an event which should process the new file and add it to the db
|
||||
$fileinfo = pathinfo($filename);
|
||||
$metadata['filename'] = $fileinfo['basename'];
|
||||
$metadata['extension'] = $fileinfo['extension'];
|
||||
$metadata['tags'] = $posttags;
|
||||
$metadata['source'] = $source;
|
||||
//log_debug("danbooru_api","========== NEW($filename) =========");
|
||||
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
|
||||
|
||||
try {
|
||||
$nevent = new DataUploadEvent($user, $file, $metadata);
|
||||
//log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
|
||||
send_event($nevent);
|
||||
// If it went ok, grab the id for the newly uploaded image and pass it in the header
|
||||
$newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error?
|
||||
$newid = make_link("post/view/" . $newimg->id);
|
||||
if($danboorup_kludge) $newid=make_http($newid);
|
||||
|
||||
// Did we POST or GET this call?
|
||||
if($_SERVER['REQUEST_METHOD'] == 'POST')
|
||||
{
|
||||
header("X-Danbooru-Location: $newid");
|
||||
}
|
||||
else
|
||||
header("Location: $newid");
|
||||
}
|
||||
catch(UploadException $ex) {
|
||||
// Did something screw up?
|
||||
header("HTTP/1.0 409 Conflict");
|
||||
header("X-Danbooru-Errors: exception - " . $ex->getMessage());
|
||||
return;
|
||||
}
|
||||
} else
|
||||
{
|
||||
header("HTTP/1.0 409 Conflict");
|
||||
header("X-Danbooru-Errors: authentication error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
find_posts()
|
||||
Find all posts that match the search criteria. Posts will be ordered by id descending.
|
||||
Parameters
|
||||
* md5: md5 hash to search for (comma delimited)
|
||||
* id: id to search for (comma delimited)
|
||||
* tags: what tags to search for
|
||||
* limit: limit
|
||||
* offset: offset
|
||||
* after_id: limit results to posts added after this id
|
||||
*/
|
||||
if(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml')))
|
||||
{
|
||||
$this->authenticate_user();
|
||||
if(isset($_GET['md5']))
|
||||
{
|
||||
$md5list = explode(",",$_GET['md5']);
|
||||
foreach($md5list as $md5)
|
||||
{
|
||||
$results[] = Image::by_hash($md5);
|
||||
}
|
||||
} elseif(isset($_GET['id']))
|
||||
{
|
||||
$idlist = explode(",",$_GET['id']);
|
||||
foreach($idlist as $id)
|
||||
{
|
||||
$results[] = Image::by_id($id);
|
||||
}
|
||||
} else
|
||||
{
|
||||
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
|
||||
$start = isset($_GET['offset']) ? int_escape($_GET['offset']) : 0;
|
||||
$tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array();
|
||||
$results = Image::find_images($start, $limit, $tags);
|
||||
}
|
||||
|
||||
// Now we have the array $results filled with Image objects
|
||||
// Let's display them
|
||||
$xml = "<posts>\n";
|
||||
foreach($results as $img)
|
||||
{
|
||||
// Sanity check to see if $img is really an image object
|
||||
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
|
||||
if(!is_object($img))
|
||||
continue;
|
||||
$taglist = $img->get_tag_list();
|
||||
$owner = $img->get_owner();
|
||||
$xml .= "<post md5=\"$img->hash\" rating=\"Questionable\" date=\"$img->posted\" is_warehoused=\"false\" file_name=\"$img->filename\" tags=\"" . $this->xmlspecialchars($taglist) . "\" source=\"" . $this->xmlspecialchars($img->source) . "\" score=\"0\" id=\"$img->id\" author=\"$owner->name\"/>\n";
|
||||
}
|
||||
$xml .= "</posts>";
|
||||
$page->set_data($xml);
|
||||
}
|
||||
|
||||
/*
|
||||
find_tags() Find all tags that match the search criteria.
|
||||
Parameters
|
||||
* id: A comma delimited list of tag id numbers.
|
||||
* name: A comma delimited list of tag names.
|
||||
* tags: any typical tag query. See Tag#parse_query for details.
|
||||
* after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh
|
||||
*/
|
||||
if($event->get_arg(1) == 'find_tags')
|
||||
{
|
||||
if(isset($_GET['id']))
|
||||
{
|
||||
$idlist = explode(",",$_GET['id']);
|
||||
foreach($idlist as $id)
|
||||
{
|
||||
$sqlresult = $database->execute("SELECT id,tag,count FROM tags WHERE id = ?", array($id));
|
||||
if(!$sqlresult->EOF)
|
||||
{
|
||||
$results[] = array($sqlresult->fields['count'], $sqlresult->fields['tag'], $sqlresult->fields['id']);
|
||||
}
|
||||
}
|
||||
} elseif(isset($_GET['name']))
|
||||
{
|
||||
$namelist = explode(",",$_GET['name']);
|
||||
foreach($namelist as $name)
|
||||
{
|
||||
$sqlresult = $database->execute("SELECT id,tag,count FROM tags WHERE tag = ?", array($name));
|
||||
if(!$sqlresult->EOF)
|
||||
{
|
||||
$results[] = array($sqlresult->fields['count'], $sqlresult->fields['tag'], $sqlresult->fields['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags
|
||||
elseif(isset($_GET['tags']))
|
||||
{
|
||||
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
|
||||
$tags = Tag::explode($_GET['tags']);
|
||||
|
||||
}
|
||||
*/
|
||||
else
|
||||
{
|
||||
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
|
||||
$sqlresult = $database->execute("SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC",array($start));
|
||||
while(!$sqlresult->EOF)
|
||||
{
|
||||
$results[] = array($sqlresult->fields['count'], $sqlresult->fields['tag'], $sqlresult->fields['id']);
|
||||
$sqlresult->MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
// Tag results collected, build XML output
|
||||
$xml = "<tags>\n";
|
||||
foreach($results as $tag)
|
||||
{
|
||||
$xml .= "<tag type=\"0\" count=\"$tag[0]\" name=\"" . $this->xmlspecialchars($tag[1]) . "\" id=\"$tag[2]\"/>\n";
|
||||
}
|
||||
$xml .= "</tags>";
|
||||
$page->set_data($xml);
|
||||
}
|
||||
|
||||
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
|
||||
// Shimmie view page
|
||||
// Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123
|
||||
// This redirects that to http://shimmie/post/view/123
|
||||
if(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show'))
|
||||
{
|
||||
$fixedlocation = make_link("post/view/" . $event->get_arg(3));
|
||||
header("Location: $fixedlocation");
|
||||
}
|
||||
}
|
||||
|
||||
// Turns out I use this a couple times so let's make it a utility function
|
||||
// Authenticates a user based on the contents of the login and password parameters
|
||||
// or makes them anonymous. Does not set any cookies or anything permanent.
|
||||
private function authenticate_user()
|
||||
{
|
||||
global $config;
|
||||
global $database;
|
||||
global $user;
|
||||
|
||||
if(isset($_REQUEST['login']) && isset($_REQUEST['password']))
|
||||
{
|
||||
// Get this user from the db, if it fails the user becomes anonymous
|
||||
// Code borrowed from /ext/user
|
||||
$name = $_REQUEST['login'];
|
||||
$pass = $_REQUEST['password'];
|
||||
$hash = md5( strtolower($name) . $pass );
|
||||
$duser = User::by_name_and_hash($name, $hash);
|
||||
if(!is_null($duser)) {
|
||||
$user = $duser;
|
||||
} else
|
||||
{
|
||||
$user = User::by_id($config->get_int("anon_id", 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From htmlspecialchars man page on php.net comments
|
||||
// If tags contain quotes they need to be htmlified
|
||||
private function xmlspecialchars($text)
|
||||
{
|
||||
return str_replace(''', ''', htmlspecialchars($text, ENT_QUOTES));
|
||||
}
|
||||
}
|
||||
|
||||
add_event_listener(new DanbooruApi());
|
||||
?>
|
46
contrib/downtime/main.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Downtime
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* License: GPLv2
|
||||
* Description: Show a "down for maintenance" page
|
||||
* Documentation:
|
||||
* Once installed there will be some more options on the config page --
|
||||
* Ticking "disable non-admin access" will mean that regular and anonymous
|
||||
* users will be blocked from accessing the site, only able to view the
|
||||
* message specified in the box.
|
||||
*/
|
||||
|
||||
class Downtime implements Extension {
|
||||
var $theme;
|
||||
|
||||
public function receive_event(Event $event) {
|
||||
global $config, $database, $page, $user;
|
||||
if(is_null($this->theme)) $this->theme = get_theme_object($this);
|
||||
|
||||
if($event instanceof SetupBuildingEvent) {
|
||||
$sb = new SetupBlock("Downtime");
|
||||
$sb->add_bool_option("downtime", "Disable non-admin access: ");
|
||||
$sb->add_longtext_option("downtime_message", "<br>");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
if($event instanceof PageRequestEvent) {
|
||||
if($config->get_bool("downtime")) {
|
||||
if(!$user->is_admin() && !$this->is_safe_page($event)) {
|
||||
$msg = $config->get_string("downtime_message");
|
||||
$this->theme->display_message($msg);
|
||||
exit;
|
||||
}
|
||||
$this->theme->display_notification($page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function is_safe_page(PageRequestEvent $event) {
|
||||
if($event->page_matches("user_admin/login")) return true;
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
add_event_listener(new Downtime(), 10);
|
||||
?>
|
23
contrib/downtime/test.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
class DowntimeTest extends SCoreWebTestCase {
|
||||
function testDowntime() {
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page("setup");
|
||||
$this->set_field("_config_downtime", true);
|
||||
$this->set_field("_config_downtime_message", "brb, unit testing");
|
||||
$this->click("Save Settings");
|
||||
$this->assert_text("DOWNTIME MODE IS ON!");
|
||||
$this->log_out();
|
||||
|
||||
$this->get_page("post/list");
|
||||
$this->assert_text("brb, unit testing");
|
||||
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page("setup");
|
||||
$this->set_field("_config_downtime", false);
|
||||
$this->click("Save Settings");
|
||||
$this->assert_no_text("DOWNTIME MODE IS ON!");
|
||||
$this->log_out();
|
||||
}
|
||||
}
|
||||
?>
|
58
contrib/downtime/theme.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
class DowntimeTheme extends Themelet {
|
||||
/**
|
||||
* Show the admin that downtime mode is enabled
|
||||
*/
|
||||
public function display_notification(Page $page) {
|
||||
$page->add_block(new Block("Downtime",
|
||||
"<span style='font-size: 1.5em'><b>DOWNTIME MODE IS ON!</b></span>", "left", 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display $message and exit
|
||||
*/
|
||||
public function display_message($message) {
|
||||
global $config, $user;
|
||||
$theme_name = $config->get_string('theme');
|
||||
$data_href = get_base_href();
|
||||
$login_link = make_link("user_admin/login");
|
||||
header("HTTP/1.0 503 Service Temporarily Unavailable");
|
||||
|
||||
$auth = $user->get_auth_html();
|
||||
print <<<EOD
|
||||
<html>
|
||||
<head>
|
||||
<title>Downtime</title>
|
||||
<link rel="stylesheet" href="$data_href/themes/$theme_name/style.css" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="downtime">
|
||||
<h1>Down for Maintenance</h1>
|
||||
<div id="message">
|
||||
$message
|
||||
</div>
|
||||
<h3>Admin Login</h3>
|
||||
<div id="login">
|
||||
<form action="$login_link" method="POST">
|
||||
$auth
|
||||
<table id="login_table" summary="Login Form">
|
||||
<tr>
|
||||
<td width="70"><label for="user">Name</label></td>
|
||||
<td width="70"><input id="user" type="text" name="user"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="pass">Password</label></td>
|
||||
<td><input id="pass" type="password" name="pass"></td>
|
||||
</tr>
|
||||
<tr><td colspan="2"><input type="submit" value="Log In"></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
?>
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 498 B |
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
Before Width: | Height: | Size: 236 B After Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 236 B After Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 349 B |
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
Before Width: | Height: | Size: 248 B After Width: | Height: | Size: 248 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 650 B After Width: | Height: | Size: 650 B |
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 238 B |
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
35
contrib/emoticons/main.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Emoticon Filter
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* License: GPLv2
|
||||
* Description: Lets users use graphical smilies
|
||||
* Documentation:
|
||||
* This extension will turn colon-something-colon into a link
|
||||
* to an image with that something as the name, eg :smile:
|
||||
* becomes a link to smile.gif
|
||||
* <p>Images are stored in /ext/emoticons/default/, and you can
|
||||
* add more emoticons by uploading images into that folder.
|
||||
*/
|
||||
|
||||
class Emoticons extends FormatterExtension {
|
||||
public function format($text) {
|
||||
$data_href = get_base_href();
|
||||
$text = preg_replace("/:([a-z]*?):/s", "<img src='$data_href/ext/emoticons/default/\\1.gif'>", $text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function strip($text) {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
add_event_listener(new Emoticons());
|
||||
|
||||
class EmoticonList extends SimpleExtension {
|
||||
public function onPageRequest($event) {
|
||||
if($event->page_matches("emote/list")) {
|
||||
$this->theme->display_emotes(glob("ext/emoticons/default/*"));
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
22
contrib/emoticons/test.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
class EmoticonTest extends ShimmieWebTestCase {
|
||||
function testEmoticons() {
|
||||
$this->log_in_as_user();
|
||||
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
|
||||
$this->get_page("post/view/$image_id");
|
||||
|
||||
$this->set_field('comment', ":cool: :beans:");
|
||||
$this->click("Post Comment");
|
||||
$this->assert_no_text(":cool:"); # FIXME: test for working image link
|
||||
#$this->assert_text(":beans:"); # FIXME: this should be left as-is
|
||||
|
||||
$this->get_page("emote/list");
|
||||
$this->assert_text(":arrow:");
|
||||
|
||||
$this->log_out();
|
||||
$this->log_in_as_admin();
|
||||
$this->delete_image($image_id);
|
||||
$this->log_out();
|
||||
}
|
||||
}
|
||||
?>
|
21
contrib/emoticons/theme.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
class EmoticonListTheme extends Themelet {
|
||||
public function display_emotes($list) {
|
||||
global $page;
|
||||
$data_href = get_base_href();
|
||||
$html = "<html><head><title>Emoticon list</title></head><body>";
|
||||
$html .= "<table><tr>";
|
||||
$n = 1;
|
||||
foreach($list as $item) {
|
||||
$pathinfo = pathinfo($item);
|
||||
$name = $pathinfo["filename"];
|
||||
$html .= "<td><img src='$data_href/$item'> :$name:</td>";
|
||||
if($n++ % 3 == 0) $html .= "</tr><tr>";
|
||||
}
|
||||
$html .= "</tr></table>";
|
||||
$html .= "</body></html>";
|
||||
$page->set_mode("data");
|
||||
$page->set_data($html);
|
||||
}
|
||||
}
|
||||
?>
|
79
contrib/et/main.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: System Info
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* License: GPLv2
|
||||
* Description: Show various bits of system information
|
||||
* Documentation:
|
||||
* Knowing the information that this extension shows can be
|
||||
* very useful for debugging. There's also an option to send
|
||||
* your stats to my database, so I can get some idea of how
|
||||
* shimmie is used, which servers I need to support, which
|
||||
* versions of PHP I should test with, etc.
|
||||
*/
|
||||
|
||||
class ET implements Extension {
|
||||
var $theme;
|
||||
|
||||
public function receive_event(Event $event) {
|
||||
global $config, $database, $page, $user;
|
||||
if(is_null($this->theme)) $this->theme = get_theme_object($this);
|
||||
|
||||
if(($event instanceof PageRequestEvent) && $event->page_matches("system_info")) {
|
||||
if($user->is_admin()) {
|
||||
$this->theme->display_info_page($page, $this->get_info());
|
||||
}
|
||||
}
|
||||
|
||||
if($event instanceof UserBlockBuildingEvent) {
|
||||
if($user->is_admin()) {
|
||||
$event->add_link("System Info", make_link("system_info"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function get_info() {
|
||||
global $config, $database;
|
||||
global $_event_listeners; // yay for using secret globals \o/
|
||||
|
||||
$info = array();
|
||||
$info['site_title'] = $config->get_string("title");
|
||||
$info['site_theme'] = $config->get_string("theme");
|
||||
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
|
||||
|
||||
$info['sys_shimmie'] = VERSION;
|
||||
$info['sys_schema'] = $config->get_string("db_version");
|
||||
$info['sys_php'] = phpversion();
|
||||
$info['sys_os'] = php_uname();
|
||||
$info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " .
|
||||
to_shorthand_int(disk_total_space("./"));
|
||||
$info['sys_server'] = $_SERVER["SERVER_SOFTWARE"];
|
||||
include "config.php"; // more magical hax
|
||||
$proto = preg_replace("#(.*)://.*#", "$1", $database_dsn);
|
||||
$db = $database->db->ServerInfo();
|
||||
$info['sys_db'] = "$proto / {$db['version']}";
|
||||
|
||||
$info['stat_images'] = $database->db->GetOne("SELECT COUNT(*) FROM images");
|
||||
$info['stat_comments'] = $database->db->GetOne("SELECT COUNT(*) FROM comments");
|
||||
$info['stat_users'] = $database->db->GetOne("SELECT COUNT(*) FROM users");
|
||||
$info['stat_tags'] = $database->db->GetOne("SELECT COUNT(*) FROM tags");
|
||||
$info['stat_image_tags'] = $database->db->GetOne("SELECT COUNT(*) FROM image_tags");
|
||||
|
||||
$els = array();
|
||||
foreach($_event_listeners as $el) {
|
||||
$els[] = get_class($el);
|
||||
}
|
||||
$info['sys_extensions'] = join(', ', $els);
|
||||
|
||||
//$cfs = array();
|
||||
//foreach($database->get_all("SELECT name, value FROM config") as $pair) {
|
||||
// $cfs[] = $pair['name']."=".$pair['value'];
|
||||
//}
|
||||
//$info[''] = "Config: ".join(", ", $cfs);
|
||||
|
||||
return $info;
|
||||
}
|
||||
// }}}
|
||||
}
|
||||
add_event_listener(new ET());
|
||||
?>
|
10
contrib/et/test.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
class ETTest extends ShimmieWebTestCase {
|
||||
function testET() {
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page("system_info");
|
||||
$this->assert_title("System Info");
|
||||
$this->log_out();
|
||||
}
|
||||
}
|
||||
?>
|
53
contrib/et/theme.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
class ETTheme extends Themelet {
|
||||
/*
|
||||
* Create a page showing info
|
||||
*
|
||||
* $info = an array of ($name => $value)
|
||||
*/
|
||||
public function display_info_page(Page $page, $info) {
|
||||
$page->set_title("System Info");
|
||||
$page->set_heading("System Info");
|
||||
$page->add_block(new NavBlock());
|
||||
$page->add_block(new Block("Information:", $this->build_data_form($info)));
|
||||
}
|
||||
|
||||
protected function build_data_form($info) {
|
||||
$data = <<<EOD
|
||||
Optional:
|
||||
Site title: {$info['site_title']}
|
||||
Theme: {$info['site_theme']}
|
||||
Genre: [describe your site here]
|
||||
URL: {$info['site_url']}
|
||||
|
||||
System stats:
|
||||
Shimmie: {$info['sys_shimmie']}
|
||||
Schema: {$info['sys_schema']}
|
||||
PHP: {$info['sys_php']}
|
||||
OS: {$info['sys_os']}
|
||||
Server: {$info['sys_server']}
|
||||
Database: {$info['sys_db']}
|
||||
Disk use: {$info['sys_disk']}
|
||||
|
||||
Shimmie stats:
|
||||
Images: {$info['stat_images']}
|
||||
Comments: {$info['stat_comments']}
|
||||
Users: {$info['stat_users']}
|
||||
Tags: {$info['stat_tags']}
|
||||
Applications: {$info['stat_image_tags']}
|
||||
Extensions: {$info['sys_extensions']}
|
||||
EOD;
|
||||
$html = <<<EOD
|
||||
<form action='http://shimmie.shishnet.org/register.php' method='POST'>
|
||||
<input type='hidden' name='registration_api' value='1'>
|
||||
<textarea name='data' rows='20' cols='80'>$data</textarea>
|
||||
<br><input type='submit' value='Click to send to Shish'>
|
||||
<br>Your stats are useful so that I know which combinations
|
||||
of web servers / databases / etc I need to support.
|
||||
</form>
|
||||
EOD;
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
?>
|
186
contrib/favorites/main.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Favorites
|
||||
* Author: Daniel Marschall <info@daniel-marschall.de>
|
||||
* License: GPLv2
|
||||
* Description: Allow users to favorite images
|
||||
* Documentation:
|
||||
* Gives users a "favorite this image" button that they can press
|
||||
* <p>Favorites for a user can then be retrieved by searching for
|
||||
* "favorited_by=UserName"
|
||||
* <p>Popular images can be searched for by eg. "favorites>5"
|
||||
* <p>Favorite info can be added to an image's filename or tooltip
|
||||
* using the $favorites placeholder
|
||||
*/
|
||||
|
||||
class FavoriteSetEvent extends Event {
|
||||
var $image_id, $user, $do_set;
|
||||
|
||||
public function FavoriteSetEvent($image_id, User $user, $do_set) {
|
||||
assert(is_numeric($image_id));
|
||||
assert(is_bool($do_set));
|
||||
|
||||
$this->image_id = $image_id;
|
||||
$this->user = $user;
|
||||
$this->do_set = $do_set;
|
||||
}
|
||||
}
|
||||
|
||||
class Favorites extends SimpleExtension {
|
||||
public function onInitExt($event) {
|
||||
global $config;
|
||||
if($config->get_int("ext_favorites_version", 0) < 1) {
|
||||
$this->install();
|
||||
}
|
||||
}
|
||||
|
||||
public function onImageAdminBlockBuilding($event) {
|
||||
global $database, $page, $user;
|
||||
if(!$user->is_anonymous()) {
|
||||
$user_id = $user->id;
|
||||
$image_id = $event->image->id;
|
||||
|
||||
$is_favorited = $database->db->GetOne(
|
||||
"SELECT COUNT(*) AS ct FROM user_favorites WHERE user_id = ? AND image_id = ?",
|
||||
array($user_id, $image_id)) > 0;
|
||||
|
||||
$event->add_part($this->theme->get_voter_html($event->image, $is_favorited));
|
||||
}
|
||||
}
|
||||
|
||||
public function onDisplayingImage($event) {
|
||||
$people = $this->list_persons_who_have_favorited($event->image);
|
||||
if(count($people) > 0) {
|
||||
$html = $this->theme->display_people($people);
|
||||
}
|
||||
}
|
||||
|
||||
public function onPageRequest($event) {
|
||||
global $page, $user;
|
||||
if($event->page_matches("change_favorite") && !$user->is_anonymous() && $user->check_auth_token()) {
|
||||
$image_id = int_escape($_POST['image_id']);
|
||||
if((($_POST['favorite_action'] == "set") || ($_POST['favorite_action'] == "unset")) && ($image_id > 0)) {
|
||||
send_event(new FavoriteSetEvent($image_id, $user, ($_POST['favorite_action'] == "set")));
|
||||
}
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("post/view/$image_id"));
|
||||
}
|
||||
}
|
||||
|
||||
public function onUserPageBuilding($event) {
|
||||
$i_favorites_count = Image::count_images(array("favorited_by={$event->display_user->name}"));
|
||||
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
||||
$h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old));
|
||||
$favorites_link = make_link("post/list/favorited_by={$event->display_user->name}/1");
|
||||
$event->add_stats("<a href='$favorites_link'>Images favorited</a>: $i_favorites_count, $h_favorites_rate per day");
|
||||
}
|
||||
|
||||
public function onImageInfoSet($event) {
|
||||
global $user;
|
||||
if(
|
||||
in_array('favorite_action', $_POST) &&
|
||||
(($_POST['favorite_action'] == "set") || ($_POST['favorite_action'] == "unset"))
|
||||
) {
|
||||
send_event(new FavoriteSetEvent($event->image_id, $user, ($_POST['favorite_action'] == "set")));
|
||||
}
|
||||
}
|
||||
|
||||
public function onFavoriteSet($event) {
|
||||
global $user;
|
||||
$this->add_vote($event->image_id, $user->id, $event->do_set);
|
||||
}
|
||||
|
||||
public function onImageDeletion($event) {
|
||||
global $database;
|
||||
$database->execute("DELETE FROM user_favorites WHERE image_id=?", array($event->image->id));
|
||||
}
|
||||
|
||||
public function onParseLinkTemplate($event) {
|
||||
$event->replace('$favorites', $event->image->favorites);
|
||||
}
|
||||
|
||||
public function onUserBlockBuilding($event) {
|
||||
global $user;
|
||||
if(strpos($user->name, ' ') === false) {
|
||||
$username = url_escape($user->name);
|
||||
$link = make_link("post/list/favorited_by=$username/1");
|
||||
} else {
|
||||
$userid = $user->id;
|
||||
$link = make_link("post/list/favorited_by_userno=$userid/1");
|
||||
}
|
||||
$event->add_link("My Favorites", $link);
|
||||
}
|
||||
|
||||
public function onSearchTermParse($event) {
|
||||
$matches = array();
|
||||
if(preg_match("/favorites(<|>|<=|>=|=)(\d+)/", $event->term, $matches)) {
|
||||
$cmp = $matches[1];
|
||||
$favorites = $matches[2];
|
||||
$event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE favorites $cmp $favorites)"));
|
||||
}
|
||||
else if(preg_match("/favorited_by=(.*)/i", $event->term, $matches)) {
|
||||
global $database;
|
||||
$user = User::by_name($matches[1]);
|
||||
if(!is_null($user)) {
|
||||
$user_id = $user->id;
|
||||
}
|
||||
else {
|
||||
$user_id = -1;
|
||||
}
|
||||
|
||||
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)"));
|
||||
}
|
||||
else if(preg_match("/favorited_by_userno=([0-9]+)/i", $event->term, $matches)) {
|
||||
$user_id = int_escape($matches[1]);
|
||||
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function install() {
|
||||
global $database;
|
||||
global $config;
|
||||
|
||||
if($config->get_int("ext_favorites_version") < 1) {
|
||||
$database->Execute("ALTER TABLE images ADD COLUMN favorites INTEGER NOT NULL DEFAULT 0");
|
||||
$database->Execute("CREATE INDEX images__favorites ON images(favorites)");
|
||||
$database->Execute("
|
||||
CREATE TABLE user_favorites (
|
||||
image_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
UNIQUE(image_id, user_id),
|
||||
INDEX(image_id)
|
||||
)
|
||||
");
|
||||
$config->set_int("ext_favorites_version", 1);
|
||||
}
|
||||
}
|
||||
|
||||
private function add_vote($image_id, $user_id, $do_set) {
|
||||
global $database;
|
||||
if ($do_set) {
|
||||
$database->Execute(
|
||||
"INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(?, ?, NOW())",
|
||||
array($image_id, $user_id));
|
||||
} else {
|
||||
$database->Execute(
|
||||
"DELETE FROM user_favorites WHERE image_id = ? AND user_id = ?",
|
||||
array($image_id, $user_id));
|
||||
}
|
||||
$database->Execute(
|
||||
"UPDATE images SET favorites=(SELECT COUNT(*) FROM user_favorites WHERE image_id=?) WHERE id=?",
|
||||
array($image_id, $image_id));
|
||||
}
|
||||
|
||||
private function list_persons_who_have_favorited($image) {
|
||||
global $database;
|
||||
|
||||
$result = $database->execute(
|
||||
"SELECT name FROM users WHERE id IN (SELECT user_id FROM user_favorites WHERE image_id = ?) ORDER BY name",
|
||||
array($image->id));
|
||||
|
||||
return $result->GetArray();
|
||||
}
|
||||
}
|
||||
?>
|
33
contrib/favorites/test.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
class FavoritesTest extends ShimmieWebTestCase {
|
||||
function testFavorites() {
|
||||
$this->log_in_as_user();
|
||||
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test");
|
||||
|
||||
$this->get_page("post/view/$image_id");
|
||||
$this->assert_title("Image $image_id: test");
|
||||
$this->assert_no_text("Favorited By");
|
||||
|
||||
$this->click("Favorite");
|
||||
$this->assert_text("Favorited By");
|
||||
|
||||
$this->get_page("post/list/favorited_by=test/1");
|
||||
$this->assert_title("Image $image_id: test");
|
||||
$this->assert_text("Favorited By");
|
||||
|
||||
$this->get_page("user/test");
|
||||
$this->assert_text("Images favorited: 1");
|
||||
$this->click("Images favorited");
|
||||
$this->assert_title("Image $image_id: test");
|
||||
|
||||
$this->click("Un-Favorite");
|
||||
$this->assert_no_text("Favorited By");
|
||||
|
||||
$this->log_out();
|
||||
|
||||
$this->log_in_as_admin();
|
||||
$this->delete_image($image_id);
|
||||
$this->log_out();
|
||||
}
|
||||
}
|
||||
?>
|
36
contrib/favorites/theme.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
class FavoritesTheme extends Themelet {
|
||||
public function get_voter_html(Image $image, $is_favorited) {
|
||||
global $page, $user;
|
||||
|
||||
$i_image_id = int_escape($image->id);
|
||||
$name = $is_favorited ? "unset" : "set";
|
||||
$label = $is_favorited ? "Un-Favorite" : "Favorite";
|
||||
$html = "
|
||||
".make_form(make_link("change_favorite"))."
|
||||
<input type='hidden' name='image_id' value='$i_image_id'>
|
||||
<input type='hidden' name='favorite_action' value='$name'>
|
||||
<input type='submit' value='$label'>
|
||||
</form>
|
||||
";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function display_people($username_array) {
|
||||
global $page;
|
||||
|
||||
$i_favorites = count($username_array);
|
||||
$html = "$i_favorites people:";
|
||||
|
||||
foreach($username_array as $row) {
|
||||
$username = html_escape($row['name']);
|
||||
$html .= "<br><a href='".make_link("user/$username")."'>$username</a>";
|
||||
}
|
||||
|
||||
$page->add_block(new Block("Favorited By", $html, "left", 25));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
84
contrib/featured/main.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Featured Image
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* License: GPLv2
|
||||
* Description: Bring a specific image to the users' attentions
|
||||
* Documentation:
|
||||
* Once enabled, a new "feature this" button will appear next
|
||||
* to the other image control buttons (delete, rotate, etc).
|
||||
* Clicking it will set the image as the site's current feature,
|
||||
* which will be shown in the side bar of the post list.
|
||||
* <p><b>Viewing a featured image</b>
|
||||
* <br>Visit <code>/featured_image/view</code>
|
||||
* <p><b>Downloading a featured image</b>
|
||||
* <br>Link to <code>/featured_image/download</code>. This will give
|
||||
* the raw data for an image (no HTML). This is useful so that you
|
||||
* can set your desktop wallpaper to be the download URL, refreshed
|
||||
* every couple of hours.
|
||||
*/
|
||||
|
||||
class Featured extends SimpleExtension {
|
||||
public function onInitExt($event) {
|
||||
global $config;
|
||||
$config->set_default_int('featured_id', 0);
|
||||
}
|
||||
|
||||
public function onPageRequest($event) {
|
||||
global $config, $page, $user;
|
||||
if($event->page_matches("featured_image")) {
|
||||
if($event->get_arg(0) == "set" && $user->check_auth_token()) {
|
||||
if($user->is_admin() && isset($_POST['image_id'])) {
|
||||
$id = int_escape($_POST['image_id']);
|
||||
if($id > 0) {
|
||||
$config->set_int("featured_id", $id);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("post/view/$id"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if($event->get_arg(0) == "download") {
|
||||
$image = Image::by_id($config->get_int("featured_id"));
|
||||
if(!is_null($image)) {
|
||||
$page->set_mode("data");
|
||||
$page->set_type("image/jpeg");
|
||||
$page->set_data(file_get_contents($image->get_image_filename()));
|
||||
}
|
||||
}
|
||||
if($event->get_arg(0) == "view") {
|
||||
$image = Image::by_id($config->get_int("featured_id"));
|
||||
if(!is_null($image)) {
|
||||
send_event(new DisplayingImageEvent($image, $page));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onPostListBuilding($event) {
|
||||
global $config, $database, $page, $user;
|
||||
$fid = $config->get_int("featured_id");
|
||||
if($fid > 0) {
|
||||
$image = $database->cache->get("featured_image_object");
|
||||
if(empty($image)) {
|
||||
$image = Image::by_id($fid);
|
||||
$database->cache->set("featured_image_object", $image, 60);
|
||||
}
|
||||
if(!is_null($image)) {
|
||||
if(class_exists("Ratings")) {
|
||||
if(strpos(Ratings::get_user_privs($user), $image->rating) === FALSE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->theme->display_featured($page, $image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onImageAdminBlockBuilding($event) {
|
||||
global $user;
|
||||
if($user->is_admin()) {
|
||||
$event->add_part($this->theme->get_buttons_html($event->image->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
26
contrib/featured/test.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
class FeaturedTest extends ShimmieWebTestCase {
|
||||
function testFeatured() {
|
||||
$this->log_in_as_user();
|
||||
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx");
|
||||
$this->log_out();
|
||||
|
||||
# FIXME: test that regular users can't feature things
|
||||
|
||||
$this->log_in_as_admin();
|
||||
$this->get_page("post/view/$image_id");
|
||||
$this->assert_title("Image $image_id: pbx");
|
||||
$this->click("Feature This");
|
||||
$this->get_page("post/list");
|
||||
$this->assert_text("Featured Image");
|
||||
$this->delete_image($image_id);
|
||||
$this->log_out();
|
||||
|
||||
# after deletion, there should be no feature
|
||||
$this->get_page("post/list");
|
||||
$this->assert_no_text("Featured Image");
|
||||
|
||||
# FIXME: test changing from one feature to another
|
||||
}
|
||||
}
|
||||
?>
|
22
contrib/featured/theme.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
class FeaturedTheme extends Themelet {
|
||||
/*
|
||||
* Show $text on the $page
|
||||
*/
|
||||
public function display_featured(Page $page, Image $image) {
|
||||
$page->add_block(new Block("Featured Image", $this->build_thumb_html($image), "left", 3));
|
||||
}
|
||||
|
||||
public function get_buttons_html($image_id) {
|
||||
global $user;
|
||||
return "
|
||||
".make_form(make_link("featured_image/set"))."
|
||||
".$user->get_auth_html()."
|
||||
<input type='hidden' name='image_id' value='$image_id'>
|
||||
<input type='submit' value='Feature This'>
|
||||
</form>
|
||||
";
|
||||
}
|
||||
}
|
||||
?>
|
107
contrib/handle_archive/main.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Handle Archives
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Description: Allow users to upload archives (zip, etc)
|
||||
* Documentation:
|
||||
* Note: requires exec() access and an external unzip command
|
||||
* <p>Any command line unzipper should work, some examples:
|
||||
* <p>unzip: <code>unzip -d "%d" "%f"</code>
|
||||
* <br>7-zip: <code>7zr x -o"%d" "%f"</code>
|
||||
*/
|
||||
|
||||
class ArchiveFileHandler extends SimpleExtension {
|
||||
public function onInitExt($event) {
|
||||
global $config;
|
||||
$config->set_default_string('archive_extract_command', 'unzip -d "%d" "%f"');
|
||||
}
|
||||
|
||||
public function onSetupBuilding($event) {
|
||||
$sb = new SetupBlock("Archive Handler Options");
|
||||
$sb->add_text_option("archive_tmp_dir", "Temporary folder: ");
|
||||
$sb->add_text_option("archive_extract_command", "<br>Extraction command: ");
|
||||
$sb->add_label("<br>%f for archive, %d for temporary directory");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
public function onDataUpload($event) {
|
||||
if($this->supported_ext($event->type)) {
|
||||
global $config;
|
||||
$tmp = sys_get_temp_dir();
|
||||
$tmpdir = "$tmp/shimmie-archive-{$event->hash}";
|
||||
$cmd = $config->get_string('archive_extract_command');
|
||||
$cmd = str_replace('%f', $event->tmpname, $cmd);
|
||||
$cmd = str_replace('%d', $tmpdir, $cmd);
|
||||
exec($cmd);
|
||||
$this->add_dir($tmpdir);
|
||||
deltree($tmpdir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function supported_ext($ext) {
|
||||
$exts = array("zip");
|
||||
return in_array(strtolower($ext), $exts);
|
||||
}
|
||||
|
||||
// copied from bulk add extension
|
||||
private function add_image($tmpname, $filename, $tags) {
|
||||
assert(file_exists($tmpname));
|
||||
|
||||
try {
|
||||
global $user;
|
||||
$pathinfo = pathinfo($filename);
|
||||
if(!array_key_exists('extension', $pathinfo)) {
|
||||
throw new UploadException("File has no extension");
|
||||
}
|
||||
$metadata['filename'] = $pathinfo['basename'];
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
$metadata['tags'] = $tags;
|
||||
$metadata['source'] = null;
|
||||
$event = new DataUploadEvent($user, $tmpname, $metadata);
|
||||
send_event($event);
|
||||
}
|
||||
catch(UploadException $ex) {
|
||||
return $ex->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// copied from bulk add extension
|
||||
private function add_dir($base, $subdir="") {
|
||||
global $page;
|
||||
|
||||
$list = "";
|
||||
|
||||
$dir = opendir("$base/$subdir");
|
||||
while($filename = readdir($dir)) {
|
||||
$fullpath = "$base/$subdir/$filename";
|
||||
|
||||
if(is_link($fullpath)) {
|
||||
// ignore
|
||||
}
|
||||
else if(is_dir($fullpath)) {
|
||||
if($filename[0] != ".") {
|
||||
$this->add_dir($base, "$subdir/$filename");
|
||||
}
|
||||
}
|
||||
else {
|
||||
$tmpfile = $fullpath;
|
||||
$tags = $subdir;
|
||||
$tags = str_replace("/", " ", $tags);
|
||||
$tags = str_replace("__", " ", $tags);
|
||||
$list .= "<br>".html_escape("$subdir/$filename (".str_replace(" ", ",", $tags).")...");
|
||||
$error = $this->add_image($tmpfile, $filename, $tags);
|
||||
if(is_null($error)) {
|
||||
$list .= "ok\n";
|
||||
}
|
||||
else {
|
||||
$list .= "failed: $error\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
|
||||
// $this->theme->add_status("Adding $subdir", $list);
|
||||
}
|
||||
}
|
||||
?>
|
105
contrib/handle_flash/main.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Handle Flash
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Description: Handle Flash files
|
||||
*/
|
||||
|
||||
class FlashFileHandler extends DataHandlerExtension {
|
||||
protected function create_thumb($hash) {
|
||||
// FIXME: scale image, as not all boards use 192x192
|
||||
copy("ext/handle_flash/thumb.jpg", warehouse_path("thumbs", $hash));
|
||||
}
|
||||
|
||||
protected function supported_ext($ext) {
|
||||
$exts = array("swf");
|
||||
return in_array(strtolower($ext), $exts);
|
||||
}
|
||||
|
||||
protected function create_image_from_data($filename, $metadata) {
|
||||
global $config;
|
||||
|
||||
$image = new Image();
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = $metadata['filename'];
|
||||
$image->ext = $metadata['extension'];
|
||||
$image->tag_array = Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
// redundant, since getimagesize() works on SWF o_O
|
||||
// $rect = $this->swf_get_bounds($filename);
|
||||
// if(is_null($rect)) {
|
||||
// return $null;
|
||||
// }
|
||||
// $image->width = $rect[1];
|
||||
// $image->height = $rect[3];
|
||||
|
||||
if(!($info = getimagesize($filename))) return null;
|
||||
|
||||
$image->width = $info[0];
|
||||
$image->height = $info[1];
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
protected function check_contents($file) {
|
||||
if(!file_exists($file)) return false;
|
||||
|
||||
$fp = fopen($file, "r");
|
||||
$head = fread($fp, 3);
|
||||
fclose($fp);
|
||||
if(!in_array($head, array("CWS", "FWS"))) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function str_to_binarray($string) {
|
||||
$binary = array();
|
||||
for($j=0; $j<strlen($string); $j++) {
|
||||
$c = ord($string[$j]);
|
||||
for($i=7; $i>=0; $i--) {
|
||||
$binary[] = ($c >> $i) & 0x01;
|
||||
}
|
||||
}
|
||||
return $binary;
|
||||
}
|
||||
|
||||
private function binarray_to_int($binarray, $start=0, $length=32) {
|
||||
$int = 0;
|
||||
for($i=$start; $i<$start + $length; $i++) {
|
||||
$int = $int << 1;
|
||||
$int = $int + ($binarray[$i] == "1" ? 1 : 0);
|
||||
}
|
||||
return $int;
|
||||
}
|
||||
|
||||
private function swf_get_bounds($filename) {
|
||||
$fp = fopen($filename, "r");
|
||||
$head = fread($fp, 3);
|
||||
$version = fread($fp, 1);
|
||||
$length = fread($fp, 4);
|
||||
|
||||
if($head == "FWS") {
|
||||
$data = fread($fp, 16);
|
||||
}
|
||||
else if($head == "CWS") {
|
||||
$data = fread($fp, 128*1024);
|
||||
$data = gzuncompress($data);
|
||||
$data = substr($data, 0, 16);
|
||||
}
|
||||
|
||||
$bounds = array();
|
||||
$rect_bin = $this->str_to_binarray($data);
|
||||
$nbits = $this->binarray_to_int($rect_bin, 0, 5);
|
||||
$bounds[] = $this->binarray_to_int($rect_bin, 5 + 0 * $nbits, $nbits) / 20;
|
||||
$bounds[] = $this->binarray_to_int($rect_bin, 5 + 1 * $nbits, $nbits) / 20;
|
||||
$bounds[] = $this->binarray_to_int($rect_bin, 5 + 2 * $nbits, $nbits) / 20;
|
||||
$bounds[] = $this->binarray_to_int($rect_bin, 5 + 3 * $nbits, $nbits) / 20;
|
||||
|
||||
return $bounds;
|
||||
}
|
||||
}
|
||||
add_event_listener(new FlashFileHandler());
|
||||
?>
|
@ -1,17 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
<?php
|
||||
|
||||
class FlashFileHandlerTheme extends Themelet
|
||||
{
|
||||
public function display_image(Page $page, Image $image)
|
||||
{
|
||||
$ilink = $image->get_image_link();
|
||||
// FIXME: object and embed have "height" and "width"
|
||||
$html = "
|
||||
class FlashFileHandlerTheme extends Themelet {
|
||||
public function display_image(Page $page, Image $image) {
|
||||
$ilink = $image->get_image_link();
|
||||
// FIXME: object and embed have "height" and "width"
|
||||
$html = "
|
||||
<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'
|
||||
codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0'
|
||||
height='{$image->height}'
|
||||
width='{$image->width}'
|
||||
wmode='opaque'
|
||||
>
|
||||
<param name='movie' value='$ilink'/>
|
||||
<param name='quality' value='high' />
|
||||
@ -19,9 +16,9 @@ class FlashFileHandlerTheme extends Themelet
|
||||
pluginspage='http://www.macromedia.com/go/getflashplayer'
|
||||
height='{$image->height}'
|
||||
width='{$image->width}'
|
||||
wmode='opaque'
|
||||
type='application/x-shockwave-flash' />
|
||||
type='application/x-shockwave-flash'></embed>
|
||||
</object>";
|
||||
$page->add_block(new Block("Flash Animation", $html, "main", 10));
|
||||
}
|
||||
$page->add_block(new Block("Flash Animation", $html, "main", 0));
|
||||
}
|
||||
}
|
||||
?>
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
113
contrib/handle_ico/main.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Handle ICO
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Description: Handle windows icons
|
||||
*/
|
||||
|
||||
class IcoFileHandler extends SimpleExtension {
|
||||
public function onDataUpload($event) {
|
||||
if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
|
||||
$hash = $event->hash;
|
||||
$ha = substr($hash, 0, 2);
|
||||
if(!move_upload_to_archive($event)) return;
|
||||
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
|
||||
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
|
||||
if(is_null($image)) {
|
||||
throw new UploadException("Icon handler failed to create image object from data");
|
||||
}
|
||||
$iae = new ImageAdditionEvent($event->user, $image);
|
||||
send_event($iae);
|
||||
$event->image_id = $iae->image->id;
|
||||
}
|
||||
}
|
||||
|
||||
public function onThumbnailGeneration($event) {
|
||||
if($this->supported_ext($event->type)) {
|
||||
$this->create_thumb($event->hash);
|
||||
}
|
||||
}
|
||||
|
||||
public function onDisplayingImage($event) {
|
||||
global $page;
|
||||
if($this->supported_ext($event->image->ext)) {
|
||||
$this->theme->display_image($page, $event->image);
|
||||
}
|
||||
}
|
||||
|
||||
public function onPageRequest($event) {
|
||||
global $config, $database, $page;
|
||||
if($event->page_matches("get_ico")) {
|
||||
$id = int_escape($event->get_arg(0));
|
||||
$image = Image::by_id($id);
|
||||
$hash = $image->hash;
|
||||
$ha = substr($hash, 0, 2);
|
||||
|
||||
$page->set_type("image/x-icon");
|
||||
$page->set_mode("data");
|
||||
$page->set_data(file_get_contents("images/$ha/$hash"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function supported_ext($ext) {
|
||||
$exts = array("ico", "ani", "cur");
|
||||
return in_array(strtolower($ext), $exts);
|
||||
}
|
||||
|
||||
private function create_image_from_data($filename, $metadata) {
|
||||
global $config;
|
||||
|
||||
$image = new Image();
|
||||
|
||||
$info = "";
|
||||
$fp = fopen($filename, "r");
|
||||
$header = unpack("snull/stype/scount", fread($fp, 6));
|
||||
|
||||
$subheader = unpack("cwidth/cheight/ccolours/cnull/splanes/sbpp/lsize/loffset", fread($fp, 16));
|
||||
fclose($fp);
|
||||
|
||||
$image->width = $subheader['width'];
|
||||
$image->height = $subheader['height'];
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = $metadata['filename'];
|
||||
$image->ext = $metadata['extension'];
|
||||
$image->tag_array = Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function check_contents($file) {
|
||||
if(!file_exists($file)) return false;
|
||||
$fp = fopen($file, "r");
|
||||
$header = unpack("snull/stype/scount", fread($fp, 6));
|
||||
fclose($fp);
|
||||
return ($header['null'] == 0 && ($header['type'] == 0 || $header['type'] == 1));
|
||||
}
|
||||
|
||||
private function create_thumb($hash) {
|
||||
global $config;
|
||||
|
||||
$inname = warehouse_path("images", $hash);
|
||||
$outname = warehouse_path("thumbs", $hash);
|
||||
|
||||
$w = $config->get_int("thumb_width");
|
||||
$h = $config->get_int("thumb_height");
|
||||
$q = $config->get_int("thumb_quality");
|
||||
$mem = $config->get_int("thumb_max_memory") / 1024 / 1024; // IM takes memory in MB
|
||||
|
||||
if($config->get_bool("ico_convert")) {
|
||||
// "-limit memory $mem" broken?
|
||||
exec("convert {$inname}[0] -geometry {$w}x{$h} -quality {$q} jpg:$outname");
|
||||
}
|
||||
else {
|
||||
copy($inname, $outname);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
?>
|
19
contrib/handle_ico/test.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
class IcoHandlerTest extends ShimmieWebTestCase {
|
||||
function testPixelHander() {
|
||||
$this->log_in_as_user();
|
||||
$image_id = $this->post_image("favicon.ico", "shimmie favicon");
|
||||
$this->assert_response(302);
|
||||
$this->log_out();
|
||||
|
||||
$raw = $this->get_page("get_ico/$image_id"); // test for no crash
|
||||
|
||||
$this->log_in_as_admin();
|
||||
$this->delete_image($image_id);
|
||||
$this->log_out();
|
||||
|
||||
# FIXME: test that the thumb works
|
||||
# FIXME: test that it gets displayed properly
|
||||
}
|
||||
}
|
||||
?>
|
12
contrib/handle_ico/theme.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
class IcoFileHandlerTheme extends Themelet {
|
||||
public function display_image(Page $page, Image $image) {
|
||||
$ilink = make_link("get_ico/{$image->id}/{$image->id}.ico");
|
||||
$html = "
|
||||
<img id='main_image' src='$ilink'>
|
||||
";
|
||||
$page->add_block(new Block("Image", $html, "main", 0));
|
||||
}
|
||||
}
|
||||
?>
|
44
contrib/handle_mp3/main.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Handle MP3
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Description: Handle MP3 files
|
||||
*/
|
||||
|
||||
class MP3FileHandler extends DataHandlerExtension {
|
||||
protected function create_thumb($hash) {
|
||||
// FIXME: scale image, as not all boards use 192x192
|
||||
copy("ext/handle_mp3/thumb.jpg", warehouse_path("thumbs", $hash));
|
||||
}
|
||||
|
||||
protected function supported_ext($ext) {
|
||||
$exts = array("mp3");
|
||||
return in_array(strtolower($ext), $exts);
|
||||
}
|
||||
|
||||
protected function create_image_from_data($filename, $metadata) {
|
||||
global $config;
|
||||
|
||||
$image = new Image();
|
||||
|
||||
// FIXME: need more flash format specs :|
|
||||
$image->width = 0;
|
||||
$image->height = 0;
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = $metadata['filename'];
|
||||
$image->ext = $metadata['extension'];
|
||||
$image->tag_array = Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
protected function check_contents($file) {
|
||||
// FIXME: mp3 magic header?
|
||||
return (file_exists($file));
|
||||
}
|
||||
}
|
||||
add_event_listener(new MP3FileHandler());
|
||||
?>
|
22
contrib/handle_mp3/theme.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
class MP3FileHandlerTheme extends Themelet {
|
||||
public function display_image(Page $page, Image $image) {
|
||||
$data_href = get_base_href();
|
||||
$ilink = $image->get_image_link();
|
||||
$html = "
|
||||
<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'
|
||||
codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0'
|
||||
width='400' height='15'>
|
||||
<param name='movie' value='$data_href/ext/handle_mp3/xspf_player_slim.swf?song_url=$ilink'/>
|
||||
<param name='quality' value='high' />
|
||||
<embed src='$data_href/ext/handle_mp3/xspf_player_slim.swf?song_url=$ilink' quality='high'
|
||||
pluginspage='http://www.macromedia.com/go/getflashplayer'
|
||||
width='400' height='15'
|
||||
type='application/x-shockwave-flash'></embed>
|
||||
</object>
|
||||
<p><a href='$ilink'>Download</a>";
|
||||
$page->add_block(new Block("Music", $html, "main", 0));
|
||||
}
|
||||
}
|
||||
?>
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
10
contrib/handle_mp3/xspf_player_license.txt
Normal file
@ -0,0 +1,10 @@
|
||||
Copyright (c) 2005, Fabricio Zuardi
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
BIN
contrib/handle_mp3/xspf_player_slim.swf
Normal file
119
contrib/handle_svg/main.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Handle SVG
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Description: Handle SVG files
|
||||
*/
|
||||
|
||||
class SVGFileHandler implements Extension {
|
||||
var $theme;
|
||||
|
||||
public function receive_event(Event $event) {
|
||||
if(is_null($this->theme)) $this->theme = get_theme_object($this);
|
||||
|
||||
if(($event instanceof DataUploadEvent) && $this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
|
||||
$hash = $event->hash;
|
||||
$ha = substr($hash, 0, 2);
|
||||
if(!move_upload_to_archive($event)) return;
|
||||
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
|
||||
$image = $this->create_image_from_data(warehouse_path("images", $hash), $event->metadata);
|
||||
if(is_null($image)) {
|
||||
throw new UploadException("SVG handler failed to create image object from data");
|
||||
}
|
||||
$iae = new ImageAdditionEvent($event->user, $image);
|
||||
send_event($iae);
|
||||
$event->image_id = $iae->image->id;
|
||||
}
|
||||
|
||||
if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) {
|
||||
$hash = $event->hash;
|
||||
$ha = substr($hash, 0, 2);
|
||||
|
||||
global $config;
|
||||
|
||||
// if($config->get_string("thumb_engine") == "convert") {
|
||||
// $w = $config->get_int("thumb_width");
|
||||
// $h = $config->get_int("thumb_height");
|
||||
// $q = $config->get_int("thumb_quality");
|
||||
// $mem = $config->get_int("thumb_max_memory") / 1024 / 1024; // IM takes memory in MB
|
||||
//
|
||||
// exec("convert images/{$ha}/{$hash}[0] -geometry {$w}x{$h} -quality {$q} jpg:thumbs/{$ha}/{$hash}");
|
||||
// }
|
||||
// else {
|
||||
// FIXME: scale image, as not all boards use 192x192
|
||||
copy("ext/handle_svg/thumb.jpg", warehouse_path("thumbs", $hash));
|
||||
// }
|
||||
}
|
||||
|
||||
if(($event instanceof DisplayingImageEvent) && $this->supported_ext($event->image->ext)) {
|
||||
global $page;
|
||||
$this->theme->display_image($page, $event->image);
|
||||
}
|
||||
|
||||
if(($event instanceof PageRequestEvent) && $event->page_matches("get_svg")) {
|
||||
global $config, $database, $page;
|
||||
$id = int_escape($event->get_arg(0));
|
||||
$image = Image::by_id($id);
|
||||
$hash = $image->hash;
|
||||
|
||||
$page->set_type("image/svg+xml");
|
||||
$page->set_mode("data");
|
||||
$page->set_data(file_get_contents(warehouse_path("images", $hash)));
|
||||
}
|
||||
}
|
||||
|
||||
private function supported_ext($ext) {
|
||||
$exts = array("svg");
|
||||
return in_array(strtolower($ext), $exts);
|
||||
}
|
||||
|
||||
private function create_image_from_data($filename, $metadata) {
|
||||
global $config;
|
||||
|
||||
$image = new Image();
|
||||
|
||||
$msp = new MiniSVGParser($filename);
|
||||
$image->width = $msp->width;
|
||||
$image->height = $msp->height;
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = $metadata['filename'];
|
||||
$image->ext = $metadata['extension'];
|
||||
$image->tag_array = Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function check_contents($file) {
|
||||
if(!file_exists($file)) return false;
|
||||
|
||||
$msp = new MiniSVGParser($file);
|
||||
return $msp->valid;
|
||||
}
|
||||
}
|
||||
|
||||
class MiniSVGParser {
|
||||
var $valid=false, $width=0, $height=0;
|
||||
|
||||
function MiniSVGParser($file) {
|
||||
$xml_parser = xml_parser_create();
|
||||
xml_set_element_handler($xml_parser, array($this, "startElement"), array($this, "endElement"));
|
||||
$this->valid = xml_parse($xml_parser, file_get_contents($file), true);
|
||||
xml_parser_free($xml_parser);
|
||||
}
|
||||
|
||||
function startElement($parser, $name, $attrs) {
|
||||
if($name == "SVG") {
|
||||
$this->width = int_escape($attrs["WIDTH"]);
|
||||
$this->height = int_escape($attrs["HEIGHT"]);
|
||||
}
|
||||
}
|
||||
|
||||
function endElement($parser, $name) {
|
||||
}
|
||||
}
|
||||
|
||||
add_event_listener(new SVGFileHandler());
|
||||
?>
|
37
contrib/handle_svg/test.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
class SVGHandlerTest extends ShimmieWebTestCase {
|
||||
function testSVGHander() {
|
||||
file_put_contents("test.svg", '<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="128"
|
||||
height="128"
|
||||
id="svg2"
|
||||
version="1.0">
|
||||
<g id="layer1">
|
||||
<path
|
||||
style="fill:#0000ff;stroke:#213847;stroke-opacity:1"
|
||||
id="path2383"
|
||||
d="M 120.07832,64.983688 A 55.573441,53.092484 0 1 1 8.9314423,64.983688 A 55.573441,53.092484 0 1 1 120.07832,64.983688 z" />
|
||||
</g>
|
||||
</svg>');
|
||||
|
||||
$this->log_in_as_user();
|
||||
$image_id = $this->post_image("test.svg", "something");
|
||||
$this->assert_response(302);
|
||||
$this->log_out();
|
||||
|
||||
$raw = $this->get_page("get_svg/$image_id");
|
||||
$this->assertTrue(strpos($raw, "www.w3.org") > 0);
|
||||
|
||||
$this->log_in_as_admin();
|
||||
$this->delete_image($image_id);
|
||||
$this->log_out();
|
||||
|
||||
unlink("test.svg");
|
||||
|
||||
# FIXME: test that the thumb works
|
||||
# FIXME: test that it gets displayed properly
|
||||
}
|
||||
}
|
||||
?>
|
15
contrib/handle_svg/theme.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
class SVGFileHandlerTheme extends Themelet {
|
||||
public function display_image(Page $page, Image $image) {
|
||||
$ilink = make_link("get_svg/{$image->id}/{$image->id}.svg");
|
||||
// $ilink = $image->get_image_link();
|
||||
$html = "
|
||||
<object data='$ilink' type='image/svg+xml' width='{$image->width}' height='{$image->height}'>
|
||||
<embed src='$ilink' type='image/svg+xml' width='{$image->width}' height='{$image->height}' />
|
||||
</object>
|
||||
";
|
||||
$page->add_block(new Block("Image", $html, "main", 0));
|
||||
}
|
||||
}
|
||||
?>
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |