Compare commits
370 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b859e1fc60 | ||
|
3f553501f9 | ||
|
0092572ea7 | ||
|
895207bf6b | ||
|
bb99133322 | ||
|
891c69d94d | ||
|
5c79e05f08 | ||
|
6bc1ec4f81 | ||
|
dfaf9b5a2f | ||
|
4b086a8c70 | ||
|
6a248a0a5c | ||
|
cefad7a786 | ||
|
15f4e79198 | ||
|
eead00c601 | ||
|
f15407bc75 | ||
|
3061a9d7d5 | ||
|
e0f1165b6c | ||
|
f83588fdcd | ||
|
7b7596167a | ||
|
78349b3ae5 | ||
|
61068bc0d0 | ||
|
a7e775de2b | ||
|
aca5e7b9bb | ||
|
79612405e8 | ||
|
3b53ddbbc7 | ||
|
9c05622d6e | ||
|
217a36a8c5 | ||
|
5f771c0138 | ||
|
3d9e32e919 | ||
|
b2ceb36499 | ||
|
674a821783 | ||
|
88dcfb607b | ||
|
4d1ba5f9e7 | ||
|
1b7e505f19 | ||
|
e114079b96 | ||
|
bed0db40d7 | ||
|
68a128c0ea | ||
|
3cb0a6a2c0 | ||
|
0f708e7a1b | ||
|
6b6bea6bcc | ||
|
23d160cb33 | ||
|
52fa31df3e | ||
|
58db685b29 | ||
|
4c4b26f098 | ||
|
6377ea19cc | ||
|
34f75cfb22 | ||
|
6de6287663 | ||
|
d932178670 | ||
|
2d0c942084 | ||
|
679360dd9c | ||
|
3f0a8399d1 | ||
|
e7808096ff | ||
|
3bb1566df2 | ||
|
9e52434480 | ||
|
f5b3276e62 | ||
|
d79430be1e | ||
|
8bd781cc8c | ||
|
ee0f0be535 | ||
|
6385f67e42 | ||
|
32a308a07a | ||
|
1e0c248710 | ||
|
c4ae68fb47 | ||
|
7149af7df9 | ||
|
f5a69e8fa9 | ||
|
faf35cc884 | ||
|
89bf741ed4 | ||
|
17080645d1 | ||
|
a8391eb1e5 | ||
|
8bac027139 | ||
|
7babe9d2a6 | ||
|
ac981c5eab | ||
|
45cf45ed77 | ||
|
77f7121e26 | ||
|
c558ee3bdb | ||
|
6221fbb096 | ||
|
3e60774e4b | ||
|
8a9a2dd96d | ||
|
45b1a381b8 | ||
|
8d478b9c39 | ||
|
253d75ae82 | ||
|
e88ca1fb05 | ||
|
05798f9cad | ||
|
e65fcb9975 | ||
|
c7d214189e | ||
|
dd94c7eda6 | ||
|
4c3b68b7d5 | ||
|
8c379c023e | ||
|
f6b6c3d335 | ||
|
984b85f60c | ||
|
02e0f925ac | ||
|
9f402b6f9d | ||
|
03cebf9d68 | ||
|
e043f01cfb | ||
|
875c40ef00 | ||
|
2025acd482 | ||
|
91971b140a | ||
|
0e81f92e0f | ||
|
bd079722c0 | ||
|
32927aea3d | ||
|
bb891f3bd0 | ||
|
7cea8592ee | ||
|
fe7b93d6d3 | ||
|
6282881c4a | ||
|
e7d11f2310 | ||
|
e82b9ea811 | ||
|
1b469d9919 | ||
|
6489b388a0 | ||
|
4f82dce662 | ||
|
76d8416324 | ||
|
72268d529d | ||
|
3ad6ce74d9 | ||
|
d65bf2a322 | ||
|
7068ee9a15 | ||
|
db13624ff3 | ||
|
89864f7d53 | ||
|
6f2febde92 | ||
|
32cdb95d00 | ||
|
4aabb77a4f | ||
|
dedee166a9 | ||
|
a17e2eca15 | ||
|
8871591cab | ||
|
df797745e6 | ||
|
3a9fd38cb0 | ||
|
b03880c11d | ||
|
070d04503d | ||
|
6789bd5fdd | ||
|
8a58ede5b3 | ||
|
0961ad465f | ||
|
b4c4de58a2 | ||
|
b9e1ce1b8d | ||
|
3f5930b4cb | ||
|
7cb18568e3 | ||
|
ecafd4e131 | ||
|
338bbcdbcb | ||
|
08b9729c72 | ||
|
81fe37de50 | ||
|
a4ff796b35 | ||
|
6c223d16bd | ||
|
e8561f6a04 | ||
|
8bc44f6cb5 | ||
|
c13835d2ef | ||
|
2863ff7508 | ||
|
b6151ce714 | ||
|
088276ce3a | ||
|
3ac7ab2cf3 | ||
|
754f0b2f39 | ||
|
10f563ee37 | ||
|
ee7a4f178c | ||
|
ed735e17be | ||
|
a9f0d764ae | ||
|
0cf35db00c | ||
|
607803c04f | ||
|
ea29e29a06 | ||
|
73022f06dd | ||
|
533ea85da9 | ||
|
2c8432c643 | ||
|
0977efa0d9 | ||
|
aceb3e23f3 | ||
|
56f1fac4c5 | ||
|
634124e17f | ||
|
cc06df171a | ||
|
55e3cb5d63 | ||
|
e6e9d6db1c | ||
|
06cb261aa6 | ||
|
56f9891828 | ||
|
e61ef97197 | ||
|
164637188e | ||
|
68cdfc21ee | ||
|
a378f7f73b | ||
|
d3dd2f7738 | ||
|
6599973b01 | ||
|
7820096bd9 | ||
|
4ed7a1d529 | ||
|
19d85e415d | ||
|
ed64cea8f6 | ||
|
3a8817bf57 | ||
|
0c8c31b6c9 | ||
|
4a5863b750 | ||
|
631cf29424 | ||
|
08a4a6d41f | ||
|
d18ac39e29 | ||
|
f91bdfac02 | ||
|
4d6dc7e98b | ||
|
7b3555eaa7 | ||
|
89f5d5524f | ||
|
006c53f499 | ||
|
345c64b821 | ||
|
ba982e4451 | ||
|
b4169821ed | ||
|
c18c7347bf | ||
|
f869eaefcd | ||
|
992d97d278 | ||
|
de084d2c55 | ||
|
15c9cf453b | ||
|
cef07afcbd | ||
|
ee30c6d06e | ||
|
d6a2b3840c | ||
|
2aa4634ef1 | ||
|
4e2faa97f9 | ||
|
8d2fe22358 | ||
|
8910de48c1 | ||
|
1dcb694f3d | ||
|
13582d842f | ||
|
08d28a2513 | ||
|
497df530dd | ||
|
cc8f32a65e | ||
|
fb4a1391df | ||
|
3ac5d05a25 | ||
|
a1fc842af6 | ||
|
fa2a982303 | ||
|
93259db601 | ||
|
13ad9d3b6d | ||
|
3fe9774158 | ||
|
badfaa6400 | ||
|
1520e6cfe3 | ||
|
df4f2821b2 | ||
|
eecb737501 | ||
|
e6e4e4c962 | ||
|
e4ab2e5d39 | ||
|
3aa1926f72 | ||
|
886d7cfd99 | ||
|
21f48456a2 | ||
|
61250bd3a3 | ||
|
456176ac11 | ||
|
f497094482 | ||
|
5371669e54 | ||
|
9d7b0db3d3 | ||
|
0db32f2ccc | ||
|
bfe95a09e7 | ||
|
64649133e2 | ||
|
708acd461c | ||
|
83f8b61c4e | ||
|
d7c16176d3 | ||
|
dadef22d90 | ||
|
a8c39085cb | ||
|
4f48be8e15 | ||
|
439c3a8320 | ||
|
2b6e28683f | ||
|
773be88994 | ||
|
77ea8617d7 | ||
|
840e661a42 | ||
|
82e88969d6 | ||
|
27b2988bd8 | ||
|
52bce9ffd0 | ||
|
7e0349164a | ||
|
769fbecbb0 | ||
|
e7b109a7f2 | ||
|
dbb90c7a99 | ||
|
360a46e09b | ||
|
df93e2e5d5 | ||
|
cb81e46f9e | ||
|
8d34fc360c | ||
|
c05fb2106c | ||
|
91ac271a2b | ||
|
45d38d8833 | ||
|
ac2652e729 | ||
|
3a0f172a8c | ||
|
8ad783ff4c | ||
|
5816aa3084 | ||
|
7cb36da4c6 | ||
|
0dd7f62b54 | ||
|
19a6b39c70 | ||
|
c783ff0e8d | ||
|
4ac9ab2ad6 | ||
|
91d04bad5d | ||
|
723f316512 | ||
|
a617ed4331 | ||
|
c8bd5e8d49 | ||
|
0dca09c230 | ||
|
19d5cfe8b9 | ||
|
f88445e639 | ||
|
03dda8144c | ||
|
d19f9cb287 | ||
|
6d074cfef8 | ||
|
0e1f6d0ef0 | ||
|
7f37607d9d | ||
|
ec8734cf45 | ||
|
44636c4de3 | ||
|
548bc7aa64 | ||
|
b45bc1d61c | ||
|
b8c6736327 | ||
|
c11f0dafd0 | ||
|
583cf6751a | ||
|
9ec8c2f807 | ||
|
574ec0b4c0 | ||
|
e208f9d297 | ||
|
58a0f060ce | ||
|
69112fdee3 | ||
|
4bf2cb8ee7 | ||
|
ad88679e61 | ||
|
78463709cd | ||
|
7308514e53 | ||
|
8ff70134ae | ||
|
9b878d98d6 | ||
|
c29fe0583f | ||
|
a51cab58aa | ||
|
ce26a87aa6 | ||
|
3c10d1cfee | ||
|
b8c7714ac9 | ||
|
0899f552f8 | ||
|
7b3ab71b1e | ||
|
d00569431b | ||
|
bf2a34c106 | ||
|
547c6d98da | ||
|
45511cbcda | ||
|
49a3277cca | ||
|
59bb8a31a2 | ||
|
6bc8b791d2 | ||
|
79a2fe23b3 | ||
|
0fc0b8e723 | ||
|
81af2e99f8 | ||
|
33d5693c5e | ||
|
1ea9ceff85 | ||
|
e696357c06 | ||
|
2ac695c135 | ||
|
3e017818dc | ||
|
9be859a01e | ||
|
1159ba2fa1 | ||
|
6284f3bcb8 | ||
|
cae24210cc | ||
|
a93c66515b | ||
|
4131bcbd64 | ||
|
111c4e3fb5 | ||
|
5d86314fc2 | ||
|
d320efea3d | ||
|
16652399a2 | ||
|
ecd860ec6d | ||
|
65aca09203 | ||
|
4c5ef85906 | ||
|
2ddfbe69a6 | ||
|
2dedeb06e0 | ||
|
ab008e351d | ||
|
b8b33eecfe | ||
|
988bc831b2 | ||
|
d5993c02f3 | ||
|
cb6c9ba987 | ||
|
49e4c3f9c7 | ||
|
804fc038bf | ||
|
446e7e0841 | ||
|
b1f37f1ccb | ||
|
aeb9829c9a | ||
|
8c713af952 | ||
|
e0519bb125 | ||
|
e6ec2340af | ||
|
cbe1f792f9 | ||
|
de26b7f373 | ||
|
153aa481da | ||
|
065f9da2d6 | ||
|
0362d07cd5 | ||
|
05a1b63061 | ||
|
926cdc31a8 | ||
|
3297e71636 | ||
|
35626d3989 | ||
|
a3a3aba040 | ||
|
688fb8b04b | ||
|
7f68ef1cfd | ||
|
984c9702ec | ||
|
8dd5ad16f3 | ||
|
d14c2ce8ea | ||
|
95ef714447 | ||
|
b893da927f | ||
|
bd9808081e | ||
|
9e1aabe17f | ||
|
7a009541ce | ||
|
e18fe295b4 | ||
|
e61bbbe03d | ||
|
846d978a52 | ||
|
9b5d963aa3 | ||
|
40b80bca93 | ||
|
fc3c6625bd |
24
.github/workflows/publish.yml
vendored
Normal file
24
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: Tests
|
||||||
|
branches: master
|
||||||
|
types: completed
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||||
|
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"
|
@ -1,4 +1,4 @@
|
|||||||
name: Test & Publish
|
name: Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -7,13 +7,40 @@ on:
|
|||||||
- cron: '0 2 * * 0' # Weekly on Sundays at 02:00
|
- cron: '0 2 * * 0' # Weekly on Sundays at 02:00
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
format:
|
||||||
|
name: Format
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set Up Cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
vendor
|
||||||
|
key: php-cs-fixer-${{ hashFiles('composer.lock') }}
|
||||||
|
|
||||||
|
- name: Validate composer.json and composer.lock
|
||||||
|
run: composer validate
|
||||||
|
|
||||||
|
- name: Install PHP dependencies
|
||||||
|
run: composer update && composer install --prefer-dist --no-progress
|
||||||
|
|
||||||
|
- name: Set up PHP
|
||||||
|
uses: shivammathur/setup-php@master
|
||||||
|
with:
|
||||||
|
php-version: 7.4
|
||||||
|
|
||||||
|
- name: Check format
|
||||||
|
run: ./vendor/bin/php-cs-fixer fix --dry-run
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: PHP ${{ matrix.php }} / DB ${{ matrix.database }}
|
name: PHP ${{ matrix.php }} / DB ${{ matrix.database }}
|
||||||
strategy:
|
strategy:
|
||||||
max-parallel: 3
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php: ['7.3']
|
php: ['7.4', '8.0']
|
||||||
database: ['pgsql', 'mysql', 'sqlite']
|
database: ['pgsql', 'mysql', 'sqlite']
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -21,6 +48,13 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set Up Cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
vendor
|
||||||
|
key: vendor-${{ matrix.php }}-${{ hashFiles('composer.lock') }}
|
||||||
|
|
||||||
- name: Set up PHP
|
- name: Set up PHP
|
||||||
uses: shivammathur/setup-php@master
|
uses: shivammathur/setup-php@master
|
||||||
with:
|
with:
|
||||||
@ -56,10 +90,7 @@ jobs:
|
|||||||
run: composer validate
|
run: composer validate
|
||||||
|
|
||||||
- name: Install PHP dependencies
|
- name: Install PHP dependencies
|
||||||
run: composer install --prefer-dist --no-progress --no-suggest
|
run: composer update && composer install --prefer-dist --no-progress
|
||||||
|
|
||||||
- name: Install shimmie
|
|
||||||
run: php index.php
|
|
||||||
|
|
||||||
- name: Run test suite
|
- name: Run test suite
|
||||||
run: |
|
run: |
|
||||||
@ -75,21 +106,7 @@ jobs:
|
|||||||
vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
|
vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
|
if: matrix.php == '7.4'
|
||||||
run: |
|
run: |
|
||||||
wget https://scrutinizer-ci.com/ocular.phar
|
wget https://scrutinizer-ci.com/ocular.phar
|
||||||
php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover
|
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"
|
|
19
.php-cs-fixer.dist.php
Normal file
19
.php-cs-fixer.dist.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$finder = PhpCsFixer\Finder::create()
|
||||||
|
->exclude('ext/amazon_s3/lib')
|
||||||
|
->exclude('vendor')
|
||||||
|
->exclude('data')
|
||||||
|
->in(__DIR__)
|
||||||
|
;
|
||||||
|
|
||||||
|
$config = new PhpCsFixer\Config();
|
||||||
|
return $config->setRules([
|
||||||
|
'@PSR12' => true,
|
||||||
|
//'strict_param' => true,
|
||||||
|
'array_syntax' => ['syntax' => 'short'],
|
||||||
|
])
|
||||||
|
->setFinder($finder)
|
||||||
|
;
|
||||||
|
|
||||||
|
?>
|
19
.php_cs.dist
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)
|
|
||||||
;
|
|
||||||
|
|
||||||
?>
|
|
16
Dockerfile
16
Dockerfile
@ -1,7 +1,7 @@
|
|||||||
# "Build" shimmie (composer install - done in its own stage so that we don't
|
# "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)
|
# need to include all the composer fluff in the final image)
|
||||||
FROM debian:stable-slim AS app
|
FROM debian:stable AS app
|
||||||
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
|
RUN apt update && apt install -y composer php7.4-gd php7.4-dom php7.4-sqlite3 php-xdebug imagemagick
|
||||||
COPY composer.json composer.lock /app/
|
COPY composer.json composer.lock /app/
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN composer install --no-dev
|
RUN composer install --no-dev
|
||||||
@ -10,8 +10,8 @@ COPY . /app/
|
|||||||
# Tests in their own image. Really we should inherit from app and then
|
# 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` phpunit on top of that; but for some reason
|
||||||
# `composer install --no-dev && composer install` doesn't install dev
|
# `composer install --no-dev && composer install` doesn't install dev
|
||||||
FROM debian:stable-slim AS tests
|
FROM debian:stable AS tests
|
||||||
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
|
RUN apt update && apt install -y composer php7.4-gd php7.4-dom php7.4-sqlite3 php-xdebug imagemagick
|
||||||
COPY composer.json composer.lock /app/
|
COPY composer.json composer.lock /app/
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN composer install
|
RUN composer install
|
||||||
@ -25,7 +25,7 @@ RUN [ $RUN_TESTS = false ] || (\
|
|||||||
echo '=== Cleaning ===' && rm -rf data)
|
echo '=== Cleaning ===' && rm -rf data)
|
||||||
|
|
||||||
# Build su-exec so that our final image can be nicer
|
# Build su-exec so that our final image can be nicer
|
||||||
FROM debian:stable-slim AS suexec
|
FROM debian:stable AS suexec
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc libc-dev curl
|
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; \
|
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; \
|
gcc -Wall /usr/local/bin/su-exec.c -o/usr/local/bin/su-exec; \
|
||||||
@ -33,13 +33,13 @@ RUN curl -k -o /usr/local/bin/su-exec.c https://raw.githubusercontent.com/ncopa
|
|||||||
chmod 0755 /usr/local/bin/su-exec;
|
chmod 0755 /usr/local/bin/su-exec;
|
||||||
|
|
||||||
# Actually run shimmie
|
# Actually run shimmie
|
||||||
FROM debian:stable-slim
|
FROM debian:stable
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
HEALTHCHECK --interval=5m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1
|
HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1
|
||||||
ENV UID=1000 \
|
ENV UID=1000 \
|
||||||
GID=1000
|
GID=1000
|
||||||
RUN apt update && apt install -y curl \
|
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 \
|
php7.4-cli php7.4-gd php7.4-pgsql php7.4-mysql php7.4-sqlite3 php7.4-zip php7.4-dom php7.4-mbstring \
|
||||||
imagemagick zip unzip && \
|
imagemagick zip unzip && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
COPY --from=app /app /app
|
COPY --from=app /app /app
|
||||||
|
@ -5,6 +5,12 @@
|
|||||||
"license" : "GPL-2.0-or-later",
|
"license" : "GPL-2.0-or-later",
|
||||||
"minimum-stability" : "dev",
|
"minimum-stability" : "dev",
|
||||||
|
|
||||||
|
"config": {
|
||||||
|
"platform": {
|
||||||
|
"php": "7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"repositories" : [
|
"repositories" : [
|
||||||
{
|
{
|
||||||
"type": "composer",
|
"type": "composer",
|
||||||
@ -25,30 +31,30 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"require" : {
|
"require" : {
|
||||||
"php" : ">=7.3",
|
"php" : "^7.4 | ^8.0",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
|
"ext-fileinfo": "*",
|
||||||
|
|
||||||
"flexihash/flexihash" : "^2.0.0",
|
"flexihash/flexihash" : "^2.0",
|
||||||
"ifixit/php-akismet" : "1.*",
|
"ifixit/php-akismet" : "^1.0",
|
||||||
"google/recaptcha" : "~1.1",
|
"google/recaptcha" : "^1.1",
|
||||||
"dapphp/securimage" : "3.6.*",
|
"dapphp/securimage" : "^3.6",
|
||||||
"shish/eventtracer-php" : "^1.0.0",
|
"shish/eventtracer-php" : "^2.0",
|
||||||
"shish/ffsphp" : "0.0.*",
|
"shish/ffsphp" : "^1.0",
|
||||||
"shish/microcrud" : "^1.0.0",
|
"shish/microcrud" : "^2.0",
|
||||||
"shish/microhtml" : "^1.0.0",
|
"shish/microhtml" : "^2.0",
|
||||||
"enshrined/svg-sanitize" : "0.13.*",
|
"enshrined/svg-sanitize" : "^0.15",
|
||||||
|
|
||||||
"bower-asset/jquery" : "1.12.*",
|
"bower-asset/jquery" : "^1.12",
|
||||||
"bower-asset/jquery-timeago" : "1.5.*",
|
"bower-asset/jquery-timeago" : "^1.5",
|
||||||
"bower-asset/mediaelement" : "2.21.*",
|
"bower-asset/js-cookie" : "^2.1"
|
||||||
"bower-asset/js-cookie" : "2.1.*"
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"require-dev" : {
|
"require-dev" : {
|
||||||
"phpunit/phpunit" : "8.*"
|
"phpunit/phpunit" : "^9.0",
|
||||||
|
"friendsofphp/php-cs-fixer" : "^3.4"
|
||||||
},
|
},
|
||||||
|
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-memcache": "memcache caching",
|
"ext-memcache": "memcache caching",
|
||||||
"ext-memcached": "memcached caching",
|
"ext-memcached": "memcached caching",
|
||||||
|
3614
composer.lock
generated
3614
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,15 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
require_once "core/event.php";
|
require_once "core/event.php";
|
||||||
|
|
||||||
abstract class PageMode
|
abstract class PageMode
|
||||||
{
|
{
|
||||||
const REDIRECT = 'redirect';
|
public const REDIRECT = 'redirect';
|
||||||
const DATA = 'data';
|
public const DATA = 'data';
|
||||||
const PAGE = 'page';
|
public const PAGE = 'page';
|
||||||
const FILE = 'file';
|
public const FILE = 'file';
|
||||||
const MANUAL = 'manual';
|
public const MANUAL = 'manual';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,10 +22,8 @@ abstract class PageMode
|
|||||||
*/
|
*/
|
||||||
class BasePage
|
class BasePage
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $mode = PageMode::PAGE;
|
||||||
public $mode = PageMode::PAGE;
|
private string $mime;
|
||||||
/** @var string */
|
|
||||||
private $type = "text/html; charset=utf-8";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set what this page should do; "page", "data", or "redirect".
|
* Set what this page should do; "page", "data", or "redirect".
|
||||||
@ -36,13 +36,14 @@ class BasePage
|
|||||||
/**
|
/**
|
||||||
* Set the page's MIME type.
|
* Set the page's MIME type.
|
||||||
*/
|
*/
|
||||||
public function set_type(string $type): void
|
public function set_mime(string $mime): void
|
||||||
{
|
{
|
||||||
$this->type = $type;
|
$this->mime = $mime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
$this->mime = MimeType::add_parameters(MimeType::HTML, MimeType::CHARSET_UTF8);
|
||||||
if (@$_GET["flash"]) {
|
if (@$_GET["flash"]) {
|
||||||
$this->flash[] = $_GET['flash'];
|
$this->flash[] = $_GET['flash'];
|
||||||
unset($_GET["flash"]);
|
unset($_GET["flash"]);
|
||||||
@ -51,19 +52,11 @@ class BasePage
|
|||||||
|
|
||||||
// ==============================================
|
// ==============================================
|
||||||
|
|
||||||
/** @var string; public only for unit test */
|
public string $data = ""; // public only for unit test
|
||||||
public $data = "";
|
private ?string $file = null;
|
||||||
|
private bool $file_delete = false;
|
||||||
/** @var string */
|
private ?string $filename = null;
|
||||||
private $file = null;
|
private ?string $disposition = null;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $file_delete = false;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
private $filename = null;
|
|
||||||
|
|
||||||
private $disposition = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the raw data to be sent.
|
* Set the raw data to be sent.
|
||||||
@ -90,8 +83,7 @@ class BasePage
|
|||||||
|
|
||||||
// ==============================================
|
// ==============================================
|
||||||
|
|
||||||
/** @var string */
|
public string $redirect = "";
|
||||||
public $redirect = "";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the URL to redirect to (remember to use make_link() if linking
|
* Set the URL to redirect to (remember to use make_link() if linking
|
||||||
@ -104,32 +96,25 @@ class BasePage
|
|||||||
|
|
||||||
// ==============================================
|
// ==============================================
|
||||||
|
|
||||||
/** @var int */
|
public int $code = 200;
|
||||||
public $code = 200;
|
public string $title = "";
|
||||||
|
public string $heading = "";
|
||||||
/** @var string */
|
public string $subheading = "";
|
||||||
public $title = "";
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $heading = "";
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $subheading = "";
|
|
||||||
|
|
||||||
/** @var string[] */
|
/** @var string[] */
|
||||||
public $html_headers = [];
|
public array $html_headers = [];
|
||||||
|
|
||||||
/** @var string[] */
|
/** @var string[] */
|
||||||
public $http_headers = [];
|
public array $http_headers = [];
|
||||||
|
|
||||||
/** @var string[][] */
|
/** @var string[][] */
|
||||||
public $cookies = [];
|
public array $cookies = [];
|
||||||
|
|
||||||
/** @var Block[] */
|
/** @var Block[] */
|
||||||
public $blocks = [];
|
public array $blocks = [];
|
||||||
|
|
||||||
/** @var string[] */
|
/** @var string[] */
|
||||||
public $flash = [];
|
public array $flash = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the HTTP status code
|
* Set the HTTP status code
|
||||||
@ -243,7 +228,7 @@ class BasePage
|
|||||||
{
|
{
|
||||||
if (!headers_sent()) {
|
if (!headers_sent()) {
|
||||||
header("HTTP/1.0 {$this->code} Shimmie");
|
header("HTTP/1.0 {$this->code} Shimmie");
|
||||||
header("Content-type: " . $this->type);
|
header("Content-type: " . $this->mime);
|
||||||
header("X-Powered-By: Shimmie-" . VERSION);
|
header("X-Powered-By: Shimmie-" . VERSION);
|
||||||
|
|
||||||
foreach ($this->http_headers as $head) {
|
foreach ($this->http_headers as $head) {
|
||||||
@ -297,7 +282,7 @@ class BasePage
|
|||||||
|
|
||||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||||
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
|
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
|
||||||
if (strpos($range, ',') !== false) {
|
if (str_contains($range, ',')) {
|
||||||
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||||||
header("Content-Range: bytes $start-$end/$size");
|
header("Content-Range: bytes $start-$end/$size");
|
||||||
break;
|
break;
|
||||||
@ -334,7 +319,7 @@ class BasePage
|
|||||||
break;
|
break;
|
||||||
case PageMode::REDIRECT:
|
case PageMode::REDIRECT:
|
||||||
if ($this->flash) {
|
if ($this->flash) {
|
||||||
$this->redirect .= (strpos($this->redirect, "?") === false) ? "?" : "&";
|
$this->redirect .= str_contains($this->redirect, "?") ? "&" : "?";
|
||||||
$this->redirect .= "flash=" . url_escape(implode("\n", $this->flash));
|
$this->redirect .= "flash=" . url_escape(implode("\n", $this->flash));
|
||||||
}
|
}
|
||||||
header('Location: ' . $this->redirect);
|
header('Location: ' . $this->redirect);
|
||||||
@ -354,8 +339,6 @@ class BasePage
|
|||||||
* Why do this? Two reasons:
|
* Why do this? Two reasons:
|
||||||
* 1. Reduces the number of files the user's browser needs to download.
|
* 1. Reduces the number of files the user's browser needs to download.
|
||||||
* 2. Allows these cached files to be compressed/minified by the admin.
|
* 2. Allows these cached files to be compressed/minified by the admin.
|
||||||
*
|
|
||||||
* TODO: This should really be configurable somehow...
|
|
||||||
*/
|
*/
|
||||||
public function add_auto_html_headers(): void
|
public function add_auto_html_headers(): void
|
||||||
{
|
{
|
||||||
@ -380,7 +363,7 @@ class BasePage
|
|||||||
$css_latest = $config_latest;
|
$css_latest = $config_latest;
|
||||||
$css_files = array_merge(
|
$css_files = array_merge(
|
||||||
zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/style.css"),
|
zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/style.css"),
|
||||||
zglob("themes/$theme_name/style.css")
|
zglob("themes/$theme_name/{" . implode(",", $this->get_theme_stylesheets()) . "}")
|
||||||
);
|
);
|
||||||
foreach ($css_files as $css) {
|
foreach ($css_files as $css) {
|
||||||
$css_latest = max($css_latest, filemtime($css));
|
$css_latest = max($css_latest, filemtime($css));
|
||||||
@ -410,7 +393,7 @@ class BasePage
|
|||||||
"ext/static_files/modernizr-3.3.1.custom.js",
|
"ext/static_files/modernizr-3.3.1.custom.js",
|
||||||
],
|
],
|
||||||
zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/script.js"),
|
zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/script.js"),
|
||||||
zglob("themes/$theme_name/script.js")
|
zglob("themes/$theme_name/{" . implode(",", $this->get_theme_scripts()) . "}")
|
||||||
);
|
);
|
||||||
foreach ($js_files as $js) {
|
foreach ($js_files as $js) {
|
||||||
$js_latest = max($js_latest, filemtime($js));
|
$js_latest = max($js_latest, filemtime($js));
|
||||||
@ -427,7 +410,25 @@ class BasePage
|
|||||||
$this->add_html_header("<script defer src='$data_href/$js_cache_file' type='text/javascript'></script>", 44);
|
$this->add_html_header("<script defer src='$data_href/$js_cache_file' type='text/javascript'></script>", 44);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function get_nav_links()
|
|
||||||
|
/**
|
||||||
|
* @return array A list of stylesheets relative to the theme root.
|
||||||
|
*/
|
||||||
|
protected function get_theme_stylesheets(): array
|
||||||
|
{
|
||||||
|
return ["style.css"];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array A list of script files relative to the theme root.
|
||||||
|
*/
|
||||||
|
protected function get_theme_scripts(): array
|
||||||
|
{
|
||||||
|
return ["script.js"];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function get_nav_links(): array
|
||||||
{
|
{
|
||||||
$pnbe = send_event(new PageNavBuildingEvent());
|
$pnbe = send_event(new PageNavBuildingEvent());
|
||||||
|
|
||||||
@ -559,7 +560,7 @@ EOD;
|
|||||||
$contact = empty($contact_link) ? "" : "<br><a href='$contact_link'>Contact</a>";
|
$contact = empty($contact_link) ? "" : "<br><a href='$contact_link'>Contact</a>";
|
||||||
|
|
||||||
return "
|
return "
|
||||||
Images © their respective owners,
|
Media © their respective owners,
|
||||||
<a href=\"https://code.shishnet.org/shimmie2/\">Shimmie</a> ©
|
<a href=\"https://code.shishnet.org/shimmie2/\">Shimmie</a> ©
|
||||||
<a href=\"https://www.shishnet.org/\">Shish</a> &
|
<a href=\"https://www.shishnet.org/\">Shish</a> &
|
||||||
<a href=\"https://github.com/shish/shimmie2/graphs/contributors\">The Team</a>
|
<a href=\"https://github.com/shish/shimmie2/graphs/contributors\">The Team</a>
|
||||||
@ -573,7 +574,7 @@ EOD;
|
|||||||
|
|
||||||
class PageNavBuildingEvent extends Event
|
class PageNavBuildingEvent extends Event
|
||||||
{
|
{
|
||||||
public $links = [];
|
public array $links = [];
|
||||||
|
|
||||||
public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
|
public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
|
||||||
{
|
{
|
||||||
@ -583,9 +584,9 @@ class PageNavBuildingEvent extends Event
|
|||||||
|
|
||||||
class PageSubNavBuildingEvent extends Event
|
class PageSubNavBuildingEvent extends Event
|
||||||
{
|
{
|
||||||
public $parent;
|
public string $parent;
|
||||||
|
|
||||||
public $links = [];
|
public array $links = [];
|
||||||
|
|
||||||
public function __construct(string $parent)
|
public function __construct(string $parent)
|
||||||
{
|
{
|
||||||
@ -601,11 +602,11 @@ class PageSubNavBuildingEvent extends Event
|
|||||||
|
|
||||||
class NavLink
|
class NavLink
|
||||||
{
|
{
|
||||||
public $name;
|
public string $name;
|
||||||
public $link;
|
public Link $link;
|
||||||
public $description;
|
public string $description;
|
||||||
public $order;
|
public int $order;
|
||||||
public $active = false;
|
public bool $active = false;
|
||||||
|
|
||||||
public function __construct(String $name, Link $link, String $description, ?bool $active = null, int $order = 50)
|
public function __construct(String $name, Link $link, String $description, ?bool $active = null, int $order = 50)
|
||||||
{
|
{
|
||||||
@ -662,7 +663,7 @@ class NavLink
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sort_nav_links(NavLink $a, NavLink $b)
|
function sort_nav_links(NavLink $a, NavLink $b): int
|
||||||
{
|
{
|
||||||
return $a->order - $b->order;
|
return $a->order - $b->order;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class BaseThemelet
|
* Class BaseThemelet
|
||||||
@ -7,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
class BaseThemelet
|
class BaseThemelet
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic error message display
|
* Generic error message display
|
||||||
*/
|
*/
|
||||||
@ -53,8 +54,9 @@ class BaseThemelet
|
|||||||
$h_tip = html_escape($image->get_tooltip());
|
$h_tip = html_escape($image->get_tooltip());
|
||||||
$h_tags = html_escape(strtolower($image->get_tag_list()));
|
$h_tags = html_escape(strtolower($image->get_tag_list()));
|
||||||
|
|
||||||
$extArr = array_flip([EXTENSION_FLASH, EXTENSION_SVG, EXTENSION_MP3]); //List of thumbless filetypes
|
// TODO: Set up a function for fetching what kind of files are currently thumbnailable
|
||||||
if (!isset($extArr[$image->ext])) {
|
$mimeArr = array_flip([MimeType::MP3]); //List of thumbless filetypes
|
||||||
|
if (!isset($mimeArr[$image->get_mime()])) {
|
||||||
$tsize = get_thumbnail_size($image->width, $image->height);
|
$tsize = get_thumbnail_size($image->width, $image->height);
|
||||||
} else {
|
} else {
|
||||||
//Use max thumbnail size if using thumbless filetype
|
//Use max thumbnail size if using thumbless filetype
|
||||||
@ -71,7 +73,7 @@ class BaseThemelet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>".
|
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-height='$image->height' data-width='$image->width' data-mime='{$image->get_mime()}' data-post-id='$i_id'>".
|
||||||
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>".
|
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>".
|
||||||
"</a>\n";
|
"</a>\n";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Block
|
* Class Block
|
||||||
@ -9,49 +11,37 @@ class Block
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The block's title.
|
* The block's title.
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $header;
|
public ?string $header;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The content of the block.
|
* The content of the block.
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $body;
|
public ?string $body;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where the block should be placed. The default theme supports
|
* Where the block should be placed. The default theme supports
|
||||||
* "main" and "left", other themes can add their own areas.
|
* "main" and "left", other themes can add their own areas.
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $section;
|
public string $section;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How far down the section the block should appear, higher
|
* How far down the section the block should appear, higher
|
||||||
* numbers appear lower. The scale is 0-100 by convention,
|
* numbers appear lower. The scale is 0-100 by convention,
|
||||||
* though any number will work.
|
* though any number will work.
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
*/
|
||||||
public $position;
|
public int $position;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A unique ID for the block.
|
* A unique ID for the block.
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $id;
|
public string $id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should this block count as content for the sake of
|
* Should this block count as content for the sake of
|
||||||
* the 404 handler
|
* the 404 handler
|
||||||
*
|
|
||||||
* @var boolean
|
|
||||||
*/
|
*/
|
||||||
public $is_content = true;
|
public bool $is_content = true;
|
||||||
|
|
||||||
public function __construct(string $header=null, string $body=null, string $section="main", int $position=50, string $id=null)
|
public function __construct(string $header=null, string $body=null, string $section="main", int $position=50, string $id=null)
|
||||||
{
|
{
|
||||||
@ -63,7 +53,9 @@ class Block
|
|||||||
if (is_null($id)) {
|
if (is_null($id)) {
|
||||||
$id = (empty($header) ? md5($body ?? '') : $header) . $section;
|
$id = (empty($header) ? md5($body ?? '') : $header) . $section;
|
||||||
}
|
}
|
||||||
$this->id = preg_replace('/[^\w-]/', '', str_replace(' ', '_', $id));
|
$str_id = preg_replace('/[^\w-]/', '', str_replace(' ', '_', $id));
|
||||||
|
assert(is_string($str_id));
|
||||||
|
$this->id = $str_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
interface CacheEngine
|
interface CacheEngine
|
||||||
{
|
{
|
||||||
public function get(string $key);
|
public function get(string $key);
|
||||||
public function set(string $key, $val, int $time=0);
|
public function set(string $key, $val, int $time=0): void;
|
||||||
public function delete(string $key);
|
public function delete(string $key): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoCache implements CacheEngine
|
class NoCache implements CacheEngine
|
||||||
@ -12,23 +14,22 @@ class NoCache implements CacheEngine
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
public function set(string $key, $val, int $time=0)
|
public function set(string $key, $val, int $time=0): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
public function delete(string $key)
|
public function delete(string $key): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MemcachedCache implements CacheEngine
|
class MemcachedCache implements CacheEngine
|
||||||
{
|
{
|
||||||
/** @var ?Memcached */
|
public ?Memcached $memcache=null;
|
||||||
public $memcache=null;
|
|
||||||
|
|
||||||
public function __construct(string $args)
|
public function __construct(string $args)
|
||||||
{
|
{
|
||||||
$hp = explode(":", $args);
|
$hp = explode(":", $args);
|
||||||
$this->memcache = new Memcached;
|
$this->memcache = new Memcached();
|
||||||
#$this->memcache->setOption(Memcached::OPT_COMPRESSION, False);
|
#$this->memcache->setOption(Memcached::OPT_COMPRESSION, False);
|
||||||
#$this->memcache->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP);
|
#$this->memcache->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP);
|
||||||
#$this->memcache->setOption(Memcached::OPT_PREFIX_KEY, phpversion());
|
#$this->memcache->setOption(Memcached::OPT_PREFIX_KEY, phpversion());
|
||||||
@ -52,7 +53,7 @@ class MemcachedCache implements CacheEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set(string $key, $val, int $time=0)
|
public function set(string $key, $val, int $time=0): void
|
||||||
{
|
{
|
||||||
$key = urlencode($key);
|
$key = urlencode($key);
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ class MemcachedCache implements CacheEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(string $key)
|
public function delete(string $key): void
|
||||||
{
|
{
|
||||||
$key = urlencode($key);
|
$key = urlencode($key);
|
||||||
|
|
||||||
@ -87,12 +88,12 @@ class APCCache implements CacheEngine
|
|||||||
return apc_fetch($key);
|
return apc_fetch($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set(string $key, $val, int $time=0)
|
public function set(string $key, $val, int $time=0): void
|
||||||
{
|
{
|
||||||
apc_store($key, $val, $time);
|
apc_store($key, $val, $time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(string $key)
|
public function delete(string $key): void
|
||||||
{
|
{
|
||||||
apc_delete($key);
|
apc_delete($key);
|
||||||
}
|
}
|
||||||
@ -100,7 +101,7 @@ class APCCache implements CacheEngine
|
|||||||
|
|
||||||
class RedisCache implements CacheEngine
|
class RedisCache implements CacheEngine
|
||||||
{
|
{
|
||||||
private $redis=null;
|
private Redis $redis;
|
||||||
|
|
||||||
public function __construct(string $args)
|
public function __construct(string $args)
|
||||||
{
|
{
|
||||||
@ -116,7 +117,7 @@ class RedisCache implements CacheEngine
|
|||||||
return $this->redis->get($key);
|
return $this->redis->get($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set(string $key, $val, int $time=0)
|
public function set(string $key, $val, int $time=0): void
|
||||||
{
|
{
|
||||||
if ($time > 0) {
|
if ($time > 0) {
|
||||||
$this->redis->setEx($key, $time, $val);
|
$this->redis->setEx($key, $time, $val);
|
||||||
@ -125,7 +126,7 @@ class RedisCache implements CacheEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(string $key)
|
public function delete(string $key): void
|
||||||
{
|
{
|
||||||
$this->redis->del($key);
|
$this->redis->del($key);
|
||||||
}
|
}
|
||||||
@ -134,9 +135,9 @@ class RedisCache implements CacheEngine
|
|||||||
class Cache
|
class Cache
|
||||||
{
|
{
|
||||||
public $engine;
|
public $engine;
|
||||||
public $hits=0;
|
public int $hits=0;
|
||||||
public $misses=0;
|
public int $misses=0;
|
||||||
public $time=0;
|
public int $time=0;
|
||||||
|
|
||||||
public function __construct(?string $dsn)
|
public function __construct(?string $dsn)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||||
* CAPTCHA abstraction *
|
* CAPTCHA abstraction *
|
||||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
@ -9,7 +11,7 @@ function captcha_get_html(): string
|
|||||||
{
|
{
|
||||||
global $config, $user;
|
global $config, $user;
|
||||||
|
|
||||||
if (DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) {
|
if (DEBUG && ip_in_range(get_real_ip(), "127.0.0.0/8")) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ function captcha_check(): bool
|
|||||||
{
|
{
|
||||||
global $config, $user;
|
global $config, $user;
|
||||||
|
|
||||||
if (DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) {
|
if (DEBUG && ip_in_range(get_real_ip(), "127.0.0.0/8")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ function captcha_check(): bool
|
|||||||
$r_privatekey = $config->get_string('api_recaptcha_privkey');
|
$r_privatekey = $config->get_string('api_recaptcha_privkey');
|
||||||
if (!empty($r_privatekey)) {
|
if (!empty($r_privatekey)) {
|
||||||
$recaptcha = new ReCaptcha($r_privatekey);
|
$recaptcha = new ReCaptcha($r_privatekey);
|
||||||
$resp = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
|
$resp = $recaptcha->verify($_POST['g-recaptcha-response'] ?? "", get_real_ip());
|
||||||
|
|
||||||
if (!$resp->isSuccess()) {
|
if (!$resp->isSuccess()) {
|
||||||
log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes()));
|
log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes()));
|
||||||
|
67
core/command_builder.php
Normal file
67
core/command_builder.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// Provides mechanisms for cleanly executing command-line applications
|
||||||
|
// Was created to try to centralize a solution for whatever caused this:
|
||||||
|
// quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27
|
||||||
|
class CommandBuilder
|
||||||
|
{
|
||||||
|
private string $executable;
|
||||||
|
private array $args = [];
|
||||||
|
public array $output;
|
||||||
|
|
||||||
|
public function __construct(String $executable)
|
||||||
|
{
|
||||||
|
if (empty($executable)) {
|
||||||
|
throw new InvalidArgumentException("executable cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->executable = $executable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add_flag(string $value): void
|
||||||
|
{
|
||||||
|
$this->args[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add_escaped_arg(string $value): void
|
||||||
|
{
|
||||||
|
$this->args[] = escapeshellarg($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(): string
|
||||||
|
{
|
||||||
|
$command = escapeshellarg($this->executable);
|
||||||
|
if (!empty($this->args)) {
|
||||||
|
$command .= " ";
|
||||||
|
$command .= join(" ", $this->args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return escapeshellcmd($command)." 2>&1";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function combineOutput(string $empty_output = ""): string
|
||||||
|
{
|
||||||
|
if (empty($this->output)) {
|
||||||
|
return $empty_output;
|
||||||
|
} else {
|
||||||
|
return implode("\r\n", $this->output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(bool $fail_on_non_zero_return = false): int
|
||||||
|
{
|
||||||
|
$cmd = $this->generate();
|
||||||
|
exec($cmd, $this->output, $ret);
|
||||||
|
|
||||||
|
$output = $this->combineOutput("nothing");
|
||||||
|
|
||||||
|
log_debug('command_builder', "Command `$cmd` returned $ret and outputted $output");
|
||||||
|
|
||||||
|
if ($fail_on_non_zero_return&&(int)$ret!==(int)0) {
|
||||||
|
throw new SCoreException("Command `$cmd` failed, returning $ret and outputting $output");
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface Config
|
* Interface Config
|
||||||
@ -130,7 +132,7 @@ interface Config
|
|||||||
*/
|
*/
|
||||||
abstract class BaseConfig implements Config
|
abstract class BaseConfig implements Config
|
||||||
{
|
{
|
||||||
public $values = [];
|
public array $values = [];
|
||||||
|
|
||||||
public function set_int(string $name, ?int $value): void
|
public function set_int(string $name, ?int $value): void
|
||||||
{
|
{
|
||||||
@ -256,12 +258,10 @@ abstract class BaseConfig implements Config
|
|||||||
*/
|
*/
|
||||||
class DatabaseConfig extends BaseConfig
|
class DatabaseConfig extends BaseConfig
|
||||||
{
|
{
|
||||||
/** @var Database */
|
private Database $database;
|
||||||
private $database = null;
|
private string $table_name;
|
||||||
|
private ?string $sub_column;
|
||||||
private $table_name;
|
private ?string $sub_value;
|
||||||
private $sub_column;
|
|
||||||
private $sub_value;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Database $database,
|
Database $database,
|
||||||
@ -323,10 +323,10 @@ class DatabaseConfig extends BaseConfig
|
|||||||
$params[] = ":sub_value";
|
$params[] = ":sub_value";
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->database->Execute($query, $args);
|
$this->database->execute($query, $args);
|
||||||
|
|
||||||
$args["value"] =$this->values[$name];
|
$args["value"] =$this->values[$name];
|
||||||
$this->database->Execute(
|
$this->database->execute(
|
||||||
"INSERT INTO {$this->table_name} (".join(",", $cols).") VALUES (".join(",", $params).")",
|
"INSERT INTO {$this->table_name} (".join(",", $cols).") VALUES (".join(",", $params).")",
|
||||||
$args
|
$args
|
||||||
);
|
);
|
||||||
@ -334,5 +334,6 @@ class DatabaseConfig extends BaseConfig
|
|||||||
// rather than deleting and having some other request(s) do a thundering
|
// rather than deleting and having some other request(s) do a thundering
|
||||||
// herd of race-conditioned updates, just save the updated version once here
|
// herd of race-conditioned updates, just save the updated version once here
|
||||||
$cache->set("config", $this->values);
|
$cache->set("config", $this->values);
|
||||||
|
$this->database->notify("config");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
use FFSPHP\PDO;
|
use FFSPHP\PDO;
|
||||||
|
|
||||||
abstract class DatabaseDriver
|
abstract class DatabaseDriver
|
||||||
@ -13,38 +15,23 @@ abstract class DatabaseDriver
|
|||||||
*/
|
*/
|
||||||
class Database
|
class Database
|
||||||
{
|
{
|
||||||
/** @var string */
|
private string $dsn;
|
||||||
private $dsn;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PDO database connection object, for anyone who wants direct access.
|
* The PDO database connection object, for anyone who wants direct access.
|
||||||
* @var null|PDO
|
|
||||||
*/
|
*/
|
||||||
private $db = null;
|
private ?PDO $db = null;
|
||||||
|
public float $dbtime = 0.0;
|
||||||
/**
|
|
||||||
* @var float
|
|
||||||
*/
|
|
||||||
public $dbtime = 0.0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Meta info about the database engine.
|
* Meta info about the database engine.
|
||||||
* @var DBEngine|null
|
|
||||||
*/
|
*/
|
||||||
private $engine = null;
|
private ?DBEngine $engine = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* A boolean flag to track if we already have an active transaction.
|
|
||||||
* (ie: True if beginTransaction() already called)
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
public $transaction = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How many queries this DB object has run
|
* How many queries this DB object has run
|
||||||
*/
|
*/
|
||||||
public $query_count = 0;
|
public int $query_count = 0;
|
||||||
|
|
||||||
public function __construct(string $dsn)
|
public function __construct(string $dsn)
|
||||||
{
|
{
|
||||||
@ -53,13 +40,9 @@ class Database
|
|||||||
|
|
||||||
private function connect_db(): void
|
private function connect_db(): void
|
||||||
{
|
{
|
||||||
$this->db = new PDO($this->dsn, [
|
$this->db = new PDO($this->dsn);
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->connect_engine();
|
$this->connect_engine();
|
||||||
$this->engine->init($this->db);
|
$this->engine->init($this->db);
|
||||||
|
|
||||||
$this->begin_transaction();
|
$this->begin_transaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,21 +70,19 @@ class Database
|
|||||||
|
|
||||||
public function begin_transaction(): void
|
public function begin_transaction(): void
|
||||||
{
|
{
|
||||||
if ($this->transaction === false) {
|
if ($this->is_transaction_open() === false) {
|
||||||
$this->db->beginTransaction();
|
$this->db->beginTransaction();
|
||||||
$this->transaction = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function is_transaction_open(): bool
|
public function is_transaction_open(): bool
|
||||||
{
|
{
|
||||||
return !is_null($this->db) && $this->transaction === true;
|
return !is_null($this->db) && $this->db->inTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function commit(): bool
|
public function commit(): bool
|
||||||
{
|
{
|
||||||
if ($this->is_transaction_open()) {
|
if ($this->is_transaction_open()) {
|
||||||
$this->transaction = false;
|
|
||||||
return $this->db->commit();
|
return $this->db->commit();
|
||||||
} else {
|
} else {
|
||||||
throw new SCoreException("Unable to call commit() as there is no transaction currently open.");
|
throw new SCoreException("Unable to call commit() as there is no transaction currently open.");
|
||||||
@ -111,7 +92,6 @@ class Database
|
|||||||
public function rollback(): bool
|
public function rollback(): bool
|
||||||
{
|
{
|
||||||
if ($this->is_transaction_open()) {
|
if ($this->is_transaction_open()) {
|
||||||
$this->transaction = false;
|
|
||||||
return $this->db->rollback();
|
return $this->db->rollback();
|
||||||
} else {
|
} else {
|
||||||
throw new SCoreException("Unable to call rollback() as there is no transaction currently open.");
|
throw new SCoreException("Unable to call rollback() as there is no transaction currently open.");
|
||||||
@ -126,19 +106,6 @@ class Database
|
|||||||
return $this->engine->scoreql_to_sql($input);
|
return $this->engine->scoreql_to_sql($input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scoresql_value_prepare($input)
|
|
||||||
{
|
|
||||||
if (is_null($this->engine)) {
|
|
||||||
$this->connect_engine();
|
|
||||||
}
|
|
||||||
if ($input===true) {
|
|
||||||
return $this->engine->BOOL_Y;
|
|
||||||
} elseif ($input===false) {
|
|
||||||
return $this->engine->BOOL_N;
|
|
||||||
}
|
|
||||||
return $input;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get_driver_name(): string
|
public function get_driver_name(): string
|
||||||
{
|
{
|
||||||
if (is_null($this->engine)) {
|
if (is_null($this->engine)) {
|
||||||
@ -164,11 +131,16 @@ class Database
|
|||||||
$this->dbtime += $dur;
|
$this->dbtime += $dur;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_timeout(int $time): void
|
public function set_timeout(?int $time): void
|
||||||
{
|
{
|
||||||
$this->engine->set_timeout($this->db, $time);
|
$this->engine->set_timeout($this->db, $time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function notify(string $channel, ?string $data=null): void
|
||||||
|
{
|
||||||
|
$this->engine->notify($this->db, $channel, $data);
|
||||||
|
}
|
||||||
|
|
||||||
public function execute(string $query, array $args = []): PDOStatement
|
public function execute(string $query, array $args = []): PDOStatement
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -253,6 +225,20 @@ class Database
|
|||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an SQL query and return the the first column => the second column as an iterable object.
|
||||||
|
*/
|
||||||
|
public function get_pairs_iterable(string $query, array $args = []): Generator
|
||||||
|
{
|
||||||
|
$_start = microtime(true);
|
||||||
|
$stmt = $this->execute($query, $args);
|
||||||
|
$this->count_time("get_pairs_iterable", $_start, $query, $args);
|
||||||
|
foreach ($stmt as $row) {
|
||||||
|
yield $row[0] => $row[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute an SQL query and return a single value, or null.
|
* Execute an SQL query and return a single value, or null.
|
||||||
*/
|
*/
|
||||||
@ -336,4 +322,29 @@ class Database
|
|||||||
{
|
{
|
||||||
return $this->db;
|
return $this->db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function standardise_boolean(string $table, string $column, bool $include_postgres=false): void
|
||||||
|
{
|
||||||
|
$d = $this->get_driver_name();
|
||||||
|
if ($d == DatabaseDriver::MYSQL) {
|
||||||
|
# In mysql, ENUM('Y', 'N') is secretly INTEGER where Y=1 and N=2.
|
||||||
|
# BOOLEAN is secretly TINYINT where true=1 and false=0.
|
||||||
|
# So we can cast directly from ENUM to BOOLEAN which gives us a
|
||||||
|
# column of values 'true' and 'invalid but who cares lol', which
|
||||||
|
# we can then UPDATE to be 'true' and 'false'.
|
||||||
|
$this->execute("ALTER TABLE $table MODIFY COLUMN $column BOOLEAN;");
|
||||||
|
$this->execute("UPDATE $table SET $column=0 WHERE $column=2;");
|
||||||
|
}
|
||||||
|
if ($d == DatabaseDriver::SQLITE) {
|
||||||
|
# SQLite doesn't care about column types at all, everything is
|
||||||
|
# text, so we can in-place replace a char with a bool
|
||||||
|
$this->execute("UPDATE $table SET $column = ($column IN ('Y', 1))");
|
||||||
|
}
|
||||||
|
if ($d == DatabaseDriver::PGSQL && $include_postgres) {
|
||||||
|
$this->execute("ALTER TABLE $table ADD COLUMN ${column}_b BOOLEAN DEFAULT FALSE NOT NULL");
|
||||||
|
$this->execute("UPDATE $table SET ${column}_b = ($column = 'Y')");
|
||||||
|
$this->execute("ALTER TABLE $table DROP COLUMN $column");
|
||||||
|
$this->execute("ALTER TABLE $table RENAME COLUMN ${column}_b TO $column");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,23 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
abstract class SCORE
|
abstract class SCORE
|
||||||
{
|
{
|
||||||
const AIPK = "SCORE_AIPK";
|
public const AIPK = "SCORE_AIPK";
|
||||||
const INET = "SCORE_INET";
|
public const INET = "SCORE_INET";
|
||||||
const BOOL_Y = "SCORE_BOOL_Y";
|
|
||||||
const BOOL_N = "SCORE_BOOL_N";
|
|
||||||
const BOOL = "SCORE_BOOL";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class DBEngine
|
abstract class DBEngine
|
||||||
{
|
{
|
||||||
/** @var null|string */
|
public ?string $name = null;
|
||||||
public $name = null;
|
|
||||||
|
|
||||||
public $BOOL_Y = null;
|
|
||||||
public $BOOL_N = null;
|
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scoreql_to_sql(string $scoreql): string
|
public function scoreql_to_sql(string $data): string
|
||||||
{
|
{
|
||||||
return $scoreql;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create_table_sql(string $name, string $data): string
|
public function create_table_sql(string $name, string $data): string
|
||||||
@ -30,18 +25,16 @@ abstract class DBEngine
|
|||||||
return 'CREATE TABLE '.$name.' ('.$data.')';
|
return 'CREATE TABLE '.$name.' ('.$data.')';
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public function set_timeout(PDO $db, int $time);
|
abstract public function set_timeout(PDO $db, ?int $time);
|
||||||
|
|
||||||
abstract public function get_version(PDO $db): string;
|
abstract public function get_version(PDO $db): string;
|
||||||
|
|
||||||
|
abstract public function notify(PDO $db, string $channel, ?string $data=null): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MySQL extends DBEngine
|
class MySQL extends DBEngine
|
||||||
{
|
{
|
||||||
/** @var string */
|
public ?string $name = DatabaseDriver::MYSQL;
|
||||||
public $name = DatabaseDriver::MYSQL;
|
|
||||||
|
|
||||||
public $BOOL_Y = 'Y';
|
|
||||||
public $BOOL_N = 'N';
|
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
@ -52,9 +45,6 @@ class MySQL extends DBEngine
|
|||||||
{
|
{
|
||||||
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY auto_increment", $data);
|
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY auto_increment", $data);
|
||||||
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
|
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
|
||||||
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
|
|
||||||
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
|
|
||||||
$data = str_replace(SCORE::BOOL, "ENUM('Y', 'N')", $data);
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,12 +55,16 @@ class MySQL extends DBEngine
|
|||||||
return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes;
|
return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_timeout(PDO $db, int $time): void
|
public function set_timeout(PDO $db, ?int $time): void
|
||||||
{
|
{
|
||||||
// These only apply to read-only queries, which appears to be the best we can to mysql-wise
|
// These only apply to read-only queries, which appears to be the best we can to mysql-wise
|
||||||
// $db->exec("SET SESSION MAX_EXECUTION_TIME=".$time.";");
|
// $db->exec("SET SESSION MAX_EXECUTION_TIME=".$time.";");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function notify(PDO $db, string $channel, ?string $data=null): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function get_version(PDO $db): string
|
public function get_version(PDO $db): string
|
||||||
{
|
{
|
||||||
return $db->query('select version()')->fetch()[0];
|
return $db->query('select version()')->fetch()[0];
|
||||||
@ -79,11 +73,7 @@ class MySQL extends DBEngine
|
|||||||
|
|
||||||
class PostgreSQL extends DBEngine
|
class PostgreSQL extends DBEngine
|
||||||
{
|
{
|
||||||
/** @var string */
|
public ?string $name = DatabaseDriver::PGSQL;
|
||||||
public $name = DatabaseDriver::PGSQL;
|
|
||||||
|
|
||||||
public $BOOL_Y = "true";
|
|
||||||
public $BOOL_N = "false";
|
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
@ -101,9 +91,6 @@ class PostgreSQL extends DBEngine
|
|||||||
{
|
{
|
||||||
$data = str_replace(SCORE::AIPK, "INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY", $data);
|
$data = str_replace(SCORE::AIPK, "INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY", $data);
|
||||||
$data = str_replace(SCORE::INET, "INET", $data);
|
$data = str_replace(SCORE::INET, "INET", $data);
|
||||||
$data = str_replace(SCORE::BOOL_Y, "true", $data);
|
|
||||||
$data = str_replace(SCORE::BOOL_N, "false", $data);
|
|
||||||
$data = str_replace(SCORE::BOOL, "BOOL", $data);
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,11 +100,23 @@ class PostgreSQL extends DBEngine
|
|||||||
return "CREATE TABLE $name ($data)";
|
return "CREATE TABLE $name ($data)";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_timeout(PDO $db, int $time): void
|
public function set_timeout(PDO $db, ?int $time): void
|
||||||
{
|
{
|
||||||
|
if (is_null($time)) {
|
||||||
|
$time = 0;
|
||||||
|
}
|
||||||
$db->exec("SET statement_timeout TO ".$time.";");
|
$db->exec("SET statement_timeout TO ".$time.";");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function notify(PDO $db, string $channel, ?string $data=null): void
|
||||||
|
{
|
||||||
|
if ($data) {
|
||||||
|
$db->exec("NOTIFY $channel, '$data';");
|
||||||
|
} else {
|
||||||
|
$db->exec("NOTIFY $channel;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function get_version(PDO $db): string
|
public function get_version(PDO $db): string
|
||||||
{
|
{
|
||||||
return $db->query('select version()')->fetch()[0];
|
return $db->query('select version()')->fetch()[0];
|
||||||
@ -125,19 +124,19 @@ class PostgreSQL extends DBEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
// shimmie functions for export to sqlite
|
// shimmie functions for export to sqlite
|
||||||
function _unix_timestamp($date)
|
function _unix_timestamp($date): int
|
||||||
{
|
{
|
||||||
return strtotime($date);
|
return strtotime($date);
|
||||||
}
|
}
|
||||||
function _now()
|
function _now(): string
|
||||||
{
|
{
|
||||||
return date("Y-m-d H:i:s");
|
return date("Y-m-d H:i:s");
|
||||||
}
|
}
|
||||||
function _floor($a)
|
function _floor($a): float
|
||||||
{
|
{
|
||||||
return floor($a);
|
return floor($a);
|
||||||
}
|
}
|
||||||
function _log($a, $b=null)
|
function _log($a, $b=null): float
|
||||||
{
|
{
|
||||||
if (is_null($b)) {
|
if (is_null($b)) {
|
||||||
return log($a);
|
return log($a);
|
||||||
@ -145,39 +144,34 @@ function _log($a, $b=null)
|
|||||||
return log($a, $b);
|
return log($a, $b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function _isnull($a)
|
function _isnull($a): bool
|
||||||
{
|
{
|
||||||
return is_null($a);
|
return is_null($a);
|
||||||
}
|
}
|
||||||
function _md5($a)
|
function _md5($a): string
|
||||||
{
|
{
|
||||||
return md5($a);
|
return md5($a);
|
||||||
}
|
}
|
||||||
function _concat($a, $b)
|
function _concat($a, $b): string
|
||||||
{
|
{
|
||||||
return $a . $b;
|
return $a . $b;
|
||||||
}
|
}
|
||||||
function _lower($a)
|
function _lower($a): string
|
||||||
{
|
{
|
||||||
return strtolower($a);
|
return strtolower($a);
|
||||||
}
|
}
|
||||||
function _rand()
|
function _rand(): int
|
||||||
{
|
{
|
||||||
return rand();
|
return rand();
|
||||||
}
|
}
|
||||||
function _ln($n)
|
function _ln($n): float
|
||||||
{
|
{
|
||||||
return log($n);
|
return log($n);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SQLite extends DBEngine
|
class SQLite extends DBEngine
|
||||||
{
|
{
|
||||||
/** @var string */
|
public ?string $name = DatabaseDriver::SQLITE;
|
||||||
public $name = DatabaseDriver::SQLITE;
|
|
||||||
|
|
||||||
public $BOOL_Y = 'Y';
|
|
||||||
public $BOOL_N = 'N';
|
|
||||||
|
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
@ -199,9 +193,6 @@ class SQLite extends DBEngine
|
|||||||
{
|
{
|
||||||
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY", $data);
|
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY", $data);
|
||||||
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
|
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
|
||||||
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
|
|
||||||
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
|
|
||||||
$data = str_replace(SCORE::BOOL, "CHAR(1)", $data);
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,11 +215,15 @@ class SQLite extends DBEngine
|
|||||||
return "CREATE TABLE $name ($cols_redone); $extras";
|
return "CREATE TABLE $name ($cols_redone); $extras";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_timeout(PDO $db, int $time): void
|
public function set_timeout(PDO $db, ?int $time): void
|
||||||
{
|
{
|
||||||
// There doesn't seem to be such a thing for SQLite, so it does nothing
|
// There doesn't seem to be such a thing for SQLite, so it does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function notify(PDO $db, string $channel, ?string $data=null): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function get_version(PDO $db): string
|
public function get_version(PDO $db): string
|
||||||
{
|
{
|
||||||
return $db->query('select sqlite_version()')->fetch()[0];
|
return $db->query('select sqlite_version()')->fetch()[0];
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Generic parent class for all events.
|
* Generic parent class for all events.
|
||||||
*
|
*
|
||||||
@ -6,13 +8,13 @@
|
|||||||
*/
|
*/
|
||||||
abstract class Event
|
abstract class Event
|
||||||
{
|
{
|
||||||
public $stop_processing = false;
|
public bool $stop_processing = false;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString()
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return var_export($this, true);
|
return var_export($this, true);
|
||||||
}
|
}
|
||||||
@ -42,19 +44,11 @@ class InitExtEvent extends Event
|
|||||||
class PageRequestEvent extends Event
|
class PageRequestEvent extends Event
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public $args;
|
public $args;
|
||||||
|
public int $arg_count;
|
||||||
/**
|
public int $part_count;
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $arg_count;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $part_count;
|
|
||||||
|
|
||||||
public function __construct(string $path)
|
public function __construct(string $path)
|
||||||
{
|
{
|
||||||
@ -109,7 +103,7 @@ class PageRequestEvent extends Event
|
|||||||
return $this->args[$offset];
|
return $this->args[$offset];
|
||||||
} else {
|
} else {
|
||||||
$nm1 = $this->arg_count - 1;
|
$nm1 = $this->arg_count - 1;
|
||||||
throw new SCoreException("Requested an invalid page argument {$offset} / {$nm1}");
|
throw new UserErrorException("Requested an invalid page argument {$offset} / {$nm1}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,15 +173,12 @@ class PageRequestEvent extends Event
|
|||||||
*/
|
*/
|
||||||
class CommandEvent extends Event
|
class CommandEvent extends Event
|
||||||
{
|
{
|
||||||
/**
|
public string $cmd = "help";
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $cmd = "help";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public $args = [];
|
public array $args = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* #param string[] $args
|
* #param string[] $args
|
||||||
@ -256,24 +247,18 @@ class TextFormattingEvent extends Event
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* For reference
|
* For reference
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $original;
|
public string $original;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* with formatting applied
|
* with formatting applied
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $formatted;
|
public string $formatted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* with formatting removed
|
* with formatting removed
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $stripped;
|
public string $stripped;
|
||||||
|
|
||||||
public function __construct(string $text)
|
public function __construct(string $text)
|
||||||
{
|
{
|
||||||
@ -296,38 +281,30 @@ class LogEvent extends Event
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* a category, normally the extension name
|
* a category, normally the extension name
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $section;
|
public string $section;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See python...
|
* See python...
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
*/
|
||||||
public $priority = 0;
|
public int $priority = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free text to be logged
|
* Free text to be logged
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $message;
|
public string $message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time that the event was created
|
* The time that the event was created
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
*/
|
||||||
public $time;
|
public int $time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extra data to be held separate
|
* Extra data to be held separate
|
||||||
*
|
*
|
||||||
* @var array
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public $args;
|
public array $args;
|
||||||
|
|
||||||
public function __construct(string $section, int $priority, string $message)
|
public function __construct(string $section, int $priority, string $message)
|
||||||
{
|
{
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SCoreException
|
|
||||||
*
|
|
||||||
* A base exception to be caught by the upper levels.
|
* A base exception to be caught by the upper levels.
|
||||||
*/
|
*/
|
||||||
class SCoreException extends RuntimeException
|
class SCoreException extends RuntimeException
|
||||||
{
|
{
|
||||||
/** @var string|null */
|
public ?string $query;
|
||||||
public $query;
|
public string $error;
|
||||||
|
public int $http_code = 500;
|
||||||
/** @var string */
|
|
||||||
public $error;
|
|
||||||
|
|
||||||
public function __construct(string $msg, ?string $query=null)
|
public function __construct(string $msg, ?string $query=null)
|
||||||
{
|
{
|
||||||
@ -23,61 +21,71 @@ class SCoreException extends RuntimeException
|
|||||||
|
|
||||||
class InstallerException extends RuntimeException
|
class InstallerException extends RuntimeException
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $title;
|
||||||
public $title;
|
public string $body;
|
||||||
|
public int $exit_code;
|
||||||
|
|
||||||
/** @var string */
|
public function __construct(string $title, string $body, int $exit_code)
|
||||||
public $body;
|
|
||||||
|
|
||||||
/** @var int */
|
|
||||||
public $code;
|
|
||||||
|
|
||||||
public function __construct(string $title, string $body, int $code)
|
|
||||||
{
|
{
|
||||||
parent::__construct($body);
|
parent::__construct($body);
|
||||||
$this->title = $title;
|
$this->title = $title;
|
||||||
$this->body = $body;
|
$this->body = $body;
|
||||||
$this->code = $code;
|
$this->exit_code = $exit_code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class UserErrorException extends SCoreException
|
||||||
* Class PermissionDeniedException
|
|
||||||
*
|
|
||||||
* A fairly common, generic exception.
|
|
||||||
*/
|
|
||||||
class PermissionDeniedException extends SCoreException
|
|
||||||
{
|
{
|
||||||
|
public int $http_code = 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerErrorException extends SCoreException
|
||||||
|
{
|
||||||
|
public int $http_code = 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ImageDoesNotExist
|
* A fairly common, generic exception.
|
||||||
*
|
|
||||||
* This exception is used when an Image cannot be found by ID.
|
|
||||||
*
|
|
||||||
* Example: Image::by_id(-1) returns null
|
|
||||||
*/
|
*/
|
||||||
class ImageDoesNotExist extends SCoreException
|
class PermissionDeniedException extends UserErrorException
|
||||||
{
|
{
|
||||||
|
public int $http_code = 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is used when an Image cannot be found by ID.
|
||||||
|
*/
|
||||||
|
class ImageDoesNotExist extends UserErrorException
|
||||||
|
{
|
||||||
|
public int $http_code = 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is used when a User cannot be found by some criteria.
|
||||||
|
*/
|
||||||
|
class UserDoesNotExist extends UserErrorException
|
||||||
|
{
|
||||||
|
public int $http_code = 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For validate_input()
|
* For validate_input()
|
||||||
*/
|
*/
|
||||||
class InvalidInput extends SCoreException
|
class InvalidInput extends UserErrorException
|
||||||
{
|
{
|
||||||
|
public int $http_code = 402;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is used by the image resizing code when there is not enough memory to perform a resize.
|
* This is used by the image resizing code when there is not enough memory to perform a resize.
|
||||||
*/
|
*/
|
||||||
class InsufficientMemoryException extends SCoreException
|
class InsufficientMemoryException extends ServerErrorException
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is used by the image resizing code when there is an error while resizing
|
* This is used by the image resizing code when there is an error while resizing
|
||||||
*/
|
*/
|
||||||
class ImageResizeException extends SCoreException
|
class ImageResizeException extends ServerErrorException
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Class Extension
|
* Class Extension
|
||||||
*
|
*
|
||||||
@ -13,16 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
abstract class Extension
|
abstract class Extension
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $key;
|
||||||
public $key;
|
protected ?Themelet $theme;
|
||||||
|
public ?ExtensionInfo $info;
|
||||||
|
|
||||||
/** @var Themelet */
|
private static array $enabled_extensions = [];
|
||||||
protected $theme;
|
|
||||||
|
|
||||||
/** @var ExtensionInfo */
|
|
||||||
public $info;
|
|
||||||
|
|
||||||
private static $enabled_extensions = [];
|
|
||||||
|
|
||||||
public function __construct($class = null)
|
public function __construct($class = null)
|
||||||
{
|
{
|
||||||
@ -61,7 +58,7 @@ abstract class Extension
|
|||||||
return 50;
|
return 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function determine_enabled_extensions()
|
public static function determine_enabled_extensions(): void
|
||||||
{
|
{
|
||||||
self::$enabled_extensions = [];
|
self::$enabled_extensions = [];
|
||||||
foreach (array_merge(
|
foreach (array_merge(
|
||||||
@ -122,34 +119,31 @@ abstract class ExtensionInfo
|
|||||||
public const LICENSE_MIT = "MIT";
|
public const LICENSE_MIT = "MIT";
|
||||||
public const LICENSE_WTFPL = "WTFPL";
|
public const LICENSE_WTFPL = "WTFPL";
|
||||||
|
|
||||||
|
public const VISIBLE_DEFAULT = "default";
|
||||||
public const VISIBLE_ADMIN = "admin";
|
public const VISIBLE_ADMIN = "admin";
|
||||||
public const VISIBLE_HIDDEN = "hidden";
|
public const VISIBLE_HIDDEN = "hidden";
|
||||||
private const VALID_VISIBILITY = [self::VISIBLE_ADMIN, self::VISIBLE_HIDDEN];
|
private const VALID_VISIBILITY = [self::VISIBLE_DEFAULT, self::VISIBLE_ADMIN, self::VISIBLE_HIDDEN];
|
||||||
|
|
||||||
public $key;
|
public string $key;
|
||||||
|
|
||||||
public $core = false;
|
public bool $core = false;
|
||||||
|
public bool $beta = false;
|
||||||
|
|
||||||
public $beta = false;
|
public string $name;
|
||||||
|
public string $license;
|
||||||
|
public string $description;
|
||||||
|
public array $authors = [];
|
||||||
|
public array $dependencies = [];
|
||||||
|
public array $conflicts = [];
|
||||||
|
public string $visibility = self::VISIBLE_DEFAULT;
|
||||||
|
public ?string $link = null;
|
||||||
|
public ?string $version = null;
|
||||||
|
public ?string $documentation = null;
|
||||||
|
|
||||||
public $name;
|
/** @var string[] which DBs this ext supports (blank for 'all') */
|
||||||
public $authors = [];
|
public array $db_support = [];
|
||||||
public $link;
|
private ?bool $supported = null;
|
||||||
public $license;
|
private ?string $support_info = null;
|
||||||
public $version;
|
|
||||||
public $dependencies = [];
|
|
||||||
public $visibility;
|
|
||||||
public $description;
|
|
||||||
public $documentation;
|
|
||||||
|
|
||||||
/** @var array which DBs this ext supports (blank for 'all') */
|
|
||||||
public $db_support = [];
|
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
private $supported = null;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
private $support_info = null;
|
|
||||||
|
|
||||||
public function is_supported(): bool
|
public function is_supported(): bool
|
||||||
{
|
{
|
||||||
@ -167,9 +161,9 @@ abstract class ExtensionInfo
|
|||||||
return $this->support_info;
|
return $this->support_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static $all_info_by_key = [];
|
private static array $all_info_by_key = [];
|
||||||
private static $all_info_by_class = [];
|
private static array $all_info_by_class = [];
|
||||||
private static $core_extensions = [];
|
private static array $core_extensions = [];
|
||||||
|
|
||||||
protected function __construct()
|
protected function __construct()
|
||||||
{
|
{
|
||||||
@ -193,6 +187,13 @@ abstract class ExtensionInfo
|
|||||||
if (!empty($this->db_support) && !in_array($database->get_driver_name(), $this->db_support)) {
|
if (!empty($this->db_support) && !in_array($database->get_driver_name(), $this->db_support)) {
|
||||||
$this->support_info .= "Database not supported. ";
|
$this->support_info .= "Database not supported. ";
|
||||||
}
|
}
|
||||||
|
if (!empty($this->conflicts)) {
|
||||||
|
$intersects = array_intersect($this->conflicts, Extension::get_enabled_extensions());
|
||||||
|
if (!empty($intersects)) {
|
||||||
|
$this->support_info .= "Conflicts with other extension(s): " . join(", ", $intersects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Additional checks here as needed
|
// Additional checks here as needed
|
||||||
|
|
||||||
$this->supported = empty($this->support_info);
|
$this->supported = empty($this->support_info);
|
||||||
@ -235,7 +236,7 @@ abstract class ExtensionInfo
|
|||||||
|
|
||||||
public static function load_all_extension_info()
|
public static function load_all_extension_info()
|
||||||
{
|
{
|
||||||
foreach (getSubclassesOf("ExtensionInfo") as $class) {
|
foreach (get_subclasses_of("ExtensionInfo") as $class) {
|
||||||
$extension_info = new $class();
|
$extension_info = new $class();
|
||||||
if (array_key_exists($extension_info->key, self::$all_info_by_key)) {
|
if (array_key_exists($extension_info->key, self::$all_info_by_key)) {
|
||||||
throw new ScoreException("Extension Info $class with key $extension_info->key has already been loaded");
|
throw new ScoreException("Extension Info $class with key $extension_info->key has already been loaded");
|
||||||
@ -275,7 +276,7 @@ abstract class FormatterExtension extends Extension
|
|||||||
*/
|
*/
|
||||||
abstract class DataHandlerExtension extends Extension
|
abstract class DataHandlerExtension extends Extension
|
||||||
{
|
{
|
||||||
protected $SUPPORTED_MIME = [];
|
protected array $SUPPORTED_MIME = [];
|
||||||
|
|
||||||
protected function move_upload_to_archive(DataUploadEvent $event)
|
protected function move_upload_to_archive(DataUploadEvent $event)
|
||||||
{
|
{
|
||||||
@ -291,11 +292,11 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
|
|
||||||
public function onDataUpload(DataUploadEvent $event)
|
public function onDataUpload(DataUploadEvent $event)
|
||||||
{
|
{
|
||||||
$supported_ext = $this->supported_ext($event->type);
|
$supported_mime = $this->supported_mime($event->mime);
|
||||||
$check_contents = $this->check_contents($event->tmpname);
|
$check_contents = $this->check_contents($event->tmpname);
|
||||||
if ($supported_ext && $check_contents) {
|
if ($supported_mime && $check_contents) {
|
||||||
$this->move_upload_to_archive($event);
|
$this->move_upload_to_archive($event);
|
||||||
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
|
send_event(new ThumbnailGenerationEvent($event->hash, $event->mime));
|
||||||
|
|
||||||
/* Check if we are replacing an image */
|
/* Check if we are replacing an image */
|
||||||
if (!is_null($event->replace_id)) {
|
if (!is_null($event->replace_id)) {
|
||||||
@ -305,20 +306,20 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
$existing = Image::by_id($event->replace_id);
|
$existing = Image::by_id($event->replace_id);
|
||||||
|
|
||||||
if (is_null($existing)) {
|
if (is_null($existing)) {
|
||||||
throw new UploadException("Image to replace does not exist!");
|
throw new UploadException("Post to replace does not exist!");
|
||||||
}
|
}
|
||||||
if ($existing->hash === $event->metadata['hash']) {
|
if ($existing->hash === $event->metadata['hash']) {
|
||||||
throw new UploadException("The uploaded image is the same as the one to replace.");
|
throw new UploadException("The uploaded post is the same as the one to replace.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// even more hax..
|
// even more hax..
|
||||||
$event->metadata['tags'] = $existing->get_tag_list();
|
$event->metadata['tags'] = $existing->get_tag_list();
|
||||||
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->metadata['hash']), $event->metadata);
|
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->metadata['hash']), $event->metadata);
|
||||||
if (is_null($image)) {
|
if (is_null($image)) {
|
||||||
throw new UploadException("Data handler failed to create image object from data");
|
throw new UploadException("Data handler failed to create post object from data");
|
||||||
}
|
}
|
||||||
if (empty($image->ext)) {
|
if (empty($image->get_mime())) {
|
||||||
throw new UploadException("Unable to determine extension for ". $event->tmpname);
|
throw new UploadException("Unable to determine MIME for ". $event->tmpname);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
send_event(new MediaCheckPropertiesEvent($image));
|
send_event(new MediaCheckPropertiesEvent($image));
|
||||||
@ -327,14 +328,16 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
send_event(new ImageReplaceEvent($event->replace_id, $image));
|
send_event(new ImageReplaceEvent($event->replace_id, $image));
|
||||||
$event->image_id = $event->replace_id;
|
$_id = $event->replace_id;
|
||||||
|
assert(!is_null($_id));
|
||||||
|
$event->image_id = $_id;
|
||||||
} else {
|
} else {
|
||||||
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata);
|
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata);
|
||||||
if (is_null($image)) {
|
if (is_null($image)) {
|
||||||
throw new UploadException("Data handler failed to create image object from data");
|
throw new UploadException("Data handler failed to create post object from data");
|
||||||
}
|
}
|
||||||
if (empty($image->ext)) {
|
if (empty($image->get_mime())) {
|
||||||
throw new UploadException("Unable to determine extension for ". $event->tmpname);
|
throw new UploadException("Unable to determine MIME for ". $event->tmpname);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
send_event(new MediaCheckPropertiesEvent($image));
|
send_event(new MediaCheckPropertiesEvent($image));
|
||||||
@ -358,7 +361,7 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
send_event(new LockSetEvent($image, !empty($locked)));
|
send_event(new LockSetEvent($image, !empty($locked)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($supported_ext && !$check_contents) {
|
} elseif ($supported_mime && !$check_contents) {
|
||||||
// We DO support this extension - but the file looks corrupt
|
// We DO support this extension - but the file looks corrupt
|
||||||
throw new UploadException("Invalid or corrupted file");
|
throw new UploadException("Invalid or corrupted file");
|
||||||
}
|
}
|
||||||
@ -367,15 +370,15 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
public function onThumbnailGeneration(ThumbnailGenerationEvent $event)
|
public function onThumbnailGeneration(ThumbnailGenerationEvent $event)
|
||||||
{
|
{
|
||||||
$result = false;
|
$result = false;
|
||||||
if ($this->supported_ext($event->type)) {
|
if ($this->supported_mime($event->mime)) {
|
||||||
if ($event->force) {
|
if ($event->force) {
|
||||||
$result = $this->create_thumb($event->hash, $event->type);
|
$result = $this->create_thumb($event->hash, $event->mime);
|
||||||
} else {
|
} else {
|
||||||
$outname = warehouse_path(Image::THUMBNAIL_DIR, $event->hash);
|
$outname = warehouse_path(Image::THUMBNAIL_DIR, $event->hash);
|
||||||
if (file_exists($outname)) {
|
if (file_exists($outname)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$result = $this->create_thumb($event->hash, $event->type);
|
$result = $this->create_thumb($event->hash, $event->mime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($result) {
|
if ($result) {
|
||||||
@ -386,7 +389,7 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||||
{
|
{
|
||||||
global $page;
|
global $page;
|
||||||
if ($this->supported_ext($event->image->ext)) {
|
if ($this->supported_mime($event->image->get_mime())) {
|
||||||
/** @noinspection PhpPossiblePolymorphicInvocationInspection */
|
/** @noinspection PhpPossiblePolymorphicInvocationInspection */
|
||||||
$this->theme->display_image($page, $event->image);
|
$this->theme->display_image($page, $event->image);
|
||||||
}
|
}
|
||||||
@ -394,25 +397,23 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
|
|
||||||
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
||||||
{
|
{
|
||||||
if ($this->supported_ext($event->ext)) {
|
if ($this->supported_mime($event->mime)) {
|
||||||
$this->media_check_properties($event);
|
$this->media_check_properties($event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function create_image_from_data(string $filename, array $metadata): Image
|
protected function create_image_from_data(string $filename, array $metadata): Image
|
||||||
{
|
{
|
||||||
global $config;
|
|
||||||
|
|
||||||
$image = new Image();
|
$image = new Image();
|
||||||
|
|
||||||
$image->filesize = $metadata['size'];
|
$image->filesize = $metadata['size'];
|
||||||
$image->hash = $metadata['hash'];
|
$image->hash = $metadata['hash'];
|
||||||
$image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename'];
|
$image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename'];
|
||||||
if ($config->get_bool("upload_use_mime")) {
|
|
||||||
$image->ext = get_extension_for_file($filename);
|
if (array_key_exists("extension", $metadata)) {
|
||||||
}
|
$image->set_mime(MimeType::get_for_file($filename, $metadata["extension"]));
|
||||||
if (empty($image->ext)) {
|
} else {
|
||||||
$image->ext = (($pos = strpos($metadata['extension'], '?')) !== false) ? substr($metadata['extension'], 0, $pos) : $metadata['extension'];
|
$image->set_mime(MimeType::get_for_file($filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||||
@ -423,22 +424,35 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
|
|
||||||
abstract protected function media_check_properties(MediaCheckPropertiesEvent $event): void;
|
abstract protected function media_check_properties(MediaCheckPropertiesEvent $event): void;
|
||||||
abstract protected function check_contents(string $tmpname): bool;
|
abstract protected function check_contents(string $tmpname): bool;
|
||||||
abstract protected function create_thumb(string $hash, string $type): bool;
|
abstract protected function create_thumb(string $hash, string $mime): bool;
|
||||||
|
|
||||||
protected function supported_ext(string $ext): bool
|
protected function supported_mime(string $mime): bool
|
||||||
{
|
{
|
||||||
return in_array(get_mime_for_extension($ext), $this->SUPPORTED_MIME);
|
return MimeType::matches_array($mime, $this->SUPPORTED_MIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_all_supported_mimes(): array
|
||||||
|
{
|
||||||
|
$arr = [];
|
||||||
|
foreach (get_subclasses_of("DataHandlerExtension") as $handler) {
|
||||||
|
$handler = (new $handler());
|
||||||
|
$arr = array_merge($arr, $handler->SUPPORTED_MIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not sure how to handle this otherwise, don't want to set up a whole other event for this one class
|
||||||
|
if (class_exists("TranscodeImage")) {
|
||||||
|
$arr = array_merge($arr, TranscodeImage::get_enabled_mimes());
|
||||||
|
}
|
||||||
|
|
||||||
|
$arr = array_unique($arr);
|
||||||
|
return $arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_all_supported_exts(): array
|
public static function get_all_supported_exts(): array
|
||||||
{
|
{
|
||||||
$arr = [];
|
$arr = [];
|
||||||
foreach (getSubclassesOf("DataHandlerExtension") as $handler) {
|
foreach (self::get_all_supported_mimes() as $mime) {
|
||||||
$handler = (new $handler());
|
$arr = array_merge($arr, FileExtension::get_all_for_mime($mime));
|
||||||
|
|
||||||
foreach ($handler->SUPPORTED_MIME as $mime) {
|
|
||||||
$arr = array_merge($arr, get_all_extension_for_mime($mime));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$arr = array_unique($arr);
|
$arr = array_unique($arr);
|
||||||
return $arr;
|
return $arr;
|
||||||
|
@ -1,448 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
|
||||||
* MIME types and extension information and resolvers *
|
|
||||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
const EXTENSION_ANI = 'ani';
|
|
||||||
const EXTENSION_ASC = 'asc';
|
|
||||||
const EXTENSION_ASF = 'asf';
|
|
||||||
const EXTENSION_AVI = 'avi';
|
|
||||||
const EXTENSION_BMP = 'bmp';
|
|
||||||
const EXTENSION_BZIP = 'bz';
|
|
||||||
const EXTENSION_BZIP2 = 'bz2';
|
|
||||||
const EXTENSION_CBR = 'cbr';
|
|
||||||
const EXTENSION_CBZ = 'cbz';
|
|
||||||
const EXTENSION_CBT = 'cbt';
|
|
||||||
const EXTENSION_CBA = 'cbA';
|
|
||||||
const EXTENSION_CB7 = 'cb7';
|
|
||||||
const EXTENSION_CSS = 'css';
|
|
||||||
const EXTENSION_CSV = 'csv';
|
|
||||||
const EXTENSION_CUR = 'cur';
|
|
||||||
const EXTENSION_FLASH = 'swf';
|
|
||||||
const EXTENSION_FLASH_VIDEO = 'flv';
|
|
||||||
const EXTENSION_GIF = 'gif';
|
|
||||||
const EXTENSION_GZIP = 'gz';
|
|
||||||
const EXTENSION_HTML = 'html';
|
|
||||||
const EXTENSION_HTM = 'htm';
|
|
||||||
const EXTENSION_ICO = 'ico';
|
|
||||||
const EXTENSION_JFIF = 'jfif';
|
|
||||||
const EXTENSION_JFI = 'jfi';
|
|
||||||
const EXTENSION_JPEG = 'jpeg';
|
|
||||||
const EXTENSION_JPG = 'jpg';
|
|
||||||
const EXTENSION_JS = 'js';
|
|
||||||
const EXTENSION_JSON = 'json';
|
|
||||||
const EXTENSION_MKV = 'mkv';
|
|
||||||
const EXTENSION_MP3 = 'mp3';
|
|
||||||
const EXTENSION_MP4 = 'mp4';
|
|
||||||
const EXTENSION_M4V = 'm4v';
|
|
||||||
const EXTENSION_M4A = 'm4a';
|
|
||||||
const EXTENSION_MPEG = 'mpeg';
|
|
||||||
const EXTENSION_MPG = 'mpg';
|
|
||||||
const EXTENSION_OGG = 'ogg';
|
|
||||||
const EXTENSION_OGG_VIDEO = 'ogv';
|
|
||||||
const EXTENSION_OGG_AUDIO = 'oga';
|
|
||||||
const EXTENSION_PDF = 'pdf';
|
|
||||||
const EXTENSION_PHP = 'php';
|
|
||||||
const EXTENSION_PHP5 = 'php5';
|
|
||||||
const EXTENSION_PNG = 'png';
|
|
||||||
const EXTENSION_PSD = 'psd';
|
|
||||||
const EXTENSION_MOV = 'mov';
|
|
||||||
const EXTENSION_RSS = 'rss';
|
|
||||||
const EXTENSION_SVG = 'svg';
|
|
||||||
const EXTENSION_TAR = 'tar';
|
|
||||||
const EXTENSION_TEXT = 'txt';
|
|
||||||
const EXTENSION_TIFF = 'tiff';
|
|
||||||
const EXTENSION_TIF = 'tif';
|
|
||||||
const EXTENSION_WAV = 'wav';
|
|
||||||
const EXTENSION_WEBM = 'webm';
|
|
||||||
const EXTENSION_WEBP = 'webp';
|
|
||||||
const EXTENSION_WMA = 'wma';
|
|
||||||
const EXTENSION_WMV = 'wmv';
|
|
||||||
const EXTENSION_XML = 'xml';
|
|
||||||
const EXTENSION_XSL = 'xsl';
|
|
||||||
const EXTENSION_ZIP = 'zip';
|
|
||||||
|
|
||||||
|
|
||||||
// Couldn't find a mimetype for ani, so made one up based on it being a riff container
|
|
||||||
const MIME_TYPE_ANI = 'application/riff+ani';
|
|
||||||
const MIME_TYPE_ASF = 'video/x-ms-asf';
|
|
||||||
const MIME_TYPE_AVI = 'video/x-msvideo';
|
|
||||||
// Went with mime types from http://fileformats.archiveteam.org/wiki/Comic_Book_Archive
|
|
||||||
const MIME_TYPE_COMIC_ZIP = 'application/vnd.comicbook+zip';
|
|
||||||
const MIME_TYPE_COMIC_RAR = 'application/vnd.comicbook-rar';
|
|
||||||
const MIME_TYPE_BMP = 'image/x-ms-bmp';
|
|
||||||
const MIME_TYPE_BZIP = 'application/x-bzip';
|
|
||||||
const MIME_TYPE_BZIP2 = 'application/x-bzip2';
|
|
||||||
const MIME_TYPE_CSS = 'text/css';
|
|
||||||
const MIME_TYPE_CSV = 'text/csv';
|
|
||||||
const MIME_TYPE_FLASH = 'application/x-shockwave-flash';
|
|
||||||
const MIME_TYPE_FLASH_VIDEO = 'video/x-flv';
|
|
||||||
const MIME_TYPE_GIF = 'image/gif';
|
|
||||||
const MIME_TYPE_GZIP = 'application/x-gzip';
|
|
||||||
const MIME_TYPE_HTML = 'text/html';
|
|
||||||
const MIME_TYPE_ICO = 'image/x-icon';
|
|
||||||
const MIME_TYPE_JPEG = 'image/jpeg';
|
|
||||||
const MIME_TYPE_JS = 'text/javascript';
|
|
||||||
const MIME_TYPE_JSON = 'application/json';
|
|
||||||
const MIME_TYPE_MKV = 'video/x-matroska';
|
|
||||||
const MIME_TYPE_MP3 = 'audio/mpeg';
|
|
||||||
const MIME_TYPE_MP4_AUDIO = 'audio/mp4';
|
|
||||||
const MIME_TYPE_MP4_VIDEO = 'video/mp4';
|
|
||||||
const MIME_TYPE_MPEG = 'video/mpeg';
|
|
||||||
const MIME_TYPE_OCTET_STREAM = 'application/octet-stream';
|
|
||||||
const MIME_TYPE_OGG = 'application/ogg';
|
|
||||||
const MIME_TYPE_OGG_VIDEO = 'video/ogg';
|
|
||||||
const MIME_TYPE_OGG_AUDIO = 'audio/ogg';
|
|
||||||
const MIME_TYPE_PDF = 'application/pdf';
|
|
||||||
const MIME_TYPE_PHP = 'text/x-php';
|
|
||||||
const MIME_TYPE_PNG = 'image/png';
|
|
||||||
const MIME_TYPE_PSD = 'image/vnd.adobe.photoshop';
|
|
||||||
const MIME_TYPE_QUICKTIME = 'video/quicktime';
|
|
||||||
const MIME_TYPE_RSS = 'application/rss+xml';
|
|
||||||
const MIME_TYPE_SVG = 'image/svg+xml';
|
|
||||||
const MIME_TYPE_TAR = 'application/x-tar';
|
|
||||||
const MIME_TYPE_TEXT = 'text/plain';
|
|
||||||
const MIME_TYPE_TIFF = 'image/tiff';
|
|
||||||
const MIME_TYPE_WAV = 'audio/x-wav';
|
|
||||||
const MIME_TYPE_WEBM = 'video/webm';
|
|
||||||
const MIME_TYPE_WEBP = 'image/webp';
|
|
||||||
const MIME_TYPE_WIN_BITMAP = 'image/x-win-bitmap';
|
|
||||||
const MIME_TYPE_XML = 'text/xml';
|
|
||||||
const MIME_TYPE_XML_APPLICATION = 'application/xml';
|
|
||||||
const MIME_TYPE_XSL = 'application/xsl+xml';
|
|
||||||
const MIME_TYPE_ZIP = 'application/zip';
|
|
||||||
|
|
||||||
const MIME_TYPE_MAP_NAME = 'name';
|
|
||||||
const MIME_TYPE_MAP_EXT = 'ext';
|
|
||||||
const MIME_TYPE_MAP_MIME = 'mime';
|
|
||||||
|
|
||||||
// Mime type map. Each entry in the MIME_TYPE_ARRAY represents a kind of file, identified by the "correct" mimetype as the key.
|
|
||||||
// The value for each entry is a map of twokeys, ext and mime.
|
|
||||||
// ext's value is an array of all of the extensions that the file type can use, with the "correct" one being first.
|
|
||||||
// mime's value is an array of all mime types that the file type is known to use, with the current "correct" one being first.
|
|
||||||
|
|
||||||
const MIME_TYPE_MAP = [
|
|
||||||
MIME_TYPE_ANI => [
|
|
||||||
MIME_TYPE_MAP_NAME => "ANI Cursor",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_ANI],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_ANI],
|
|
||||||
],
|
|
||||||
MIME_TYPE_AVI => [
|
|
||||||
MIME_TYPE_MAP_NAME => "AVI",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_AVI],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_AVI,'video/avi','video/msvideo'],
|
|
||||||
],
|
|
||||||
MIME_TYPE_ASF => [
|
|
||||||
MIME_TYPE_MAP_NAME => "ASF/WMV",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_ASF,EXTENSION_WMA,EXTENSION_WMV],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_ASF,'audio/x-ms-wma','video/x-ms-wmv'],
|
|
||||||
],
|
|
||||||
MIME_TYPE_BMP => [
|
|
||||||
MIME_TYPE_MAP_NAME => "BMP",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_BMP],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_BMP],
|
|
||||||
],
|
|
||||||
MIME_TYPE_BZIP => [
|
|
||||||
MIME_TYPE_MAP_NAME => "BZIP",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_BZIP],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP],
|
|
||||||
],
|
|
||||||
MIME_TYPE_BZIP2 => [
|
|
||||||
MIME_TYPE_MAP_NAME => "BZIP2",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_BZIP2],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP2],
|
|
||||||
],
|
|
||||||
MIME_TYPE_COMIC_ZIP => [
|
|
||||||
MIME_TYPE_MAP_NAME => "CBZ",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_CBZ],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_COMIC_ZIP],
|
|
||||||
],
|
|
||||||
MIME_TYPE_CSS => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Cascading Style Sheet",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_CSS],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_CSS],
|
|
||||||
],
|
|
||||||
MIME_TYPE_CSV => [
|
|
||||||
MIME_TYPE_MAP_NAME => "CSV",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_CSV],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_CSV],
|
|
||||||
],
|
|
||||||
MIME_TYPE_FLASH => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Flash",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_FLASH],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH],
|
|
||||||
],
|
|
||||||
MIME_TYPE_FLASH_VIDEO => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Flash Video",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_FLASH_VIDEO],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH_VIDEO,'video/flv'],
|
|
||||||
],
|
|
||||||
MIME_TYPE_GIF => [
|
|
||||||
MIME_TYPE_MAP_NAME => "GIF",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_GIF],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_GIF],
|
|
||||||
],
|
|
||||||
MIME_TYPE_GZIP => [
|
|
||||||
MIME_TYPE_MAP_NAME => "GZIP",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_GZIP],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR],
|
|
||||||
],
|
|
||||||
MIME_TYPE_HTML => [
|
|
||||||
MIME_TYPE_MAP_NAME => "HTML",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_HTM, EXTENSION_HTML],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_HTML],
|
|
||||||
],
|
|
||||||
MIME_TYPE_ICO => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Icon",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_ICO, EXTENSION_CUR],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_ICO, MIME_TYPE_WIN_BITMAP],
|
|
||||||
],
|
|
||||||
MIME_TYPE_JPEG => [
|
|
||||||
MIME_TYPE_MAP_NAME => "JPEG",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_JPG, EXTENSION_JPEG, EXTENSION_JFIF, EXTENSION_JFI],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_JPEG],
|
|
||||||
],
|
|
||||||
MIME_TYPE_JS => [
|
|
||||||
MIME_TYPE_MAP_NAME => "JavaScript",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_JS],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_JS],
|
|
||||||
],
|
|
||||||
MIME_TYPE_JSON => [
|
|
||||||
MIME_TYPE_MAP_NAME => "JSON",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_JSON],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_JSON],
|
|
||||||
],
|
|
||||||
MIME_TYPE_MKV => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Matroska",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_MKV],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MKV],
|
|
||||||
],
|
|
||||||
MIME_TYPE_MP3 => [
|
|
||||||
MIME_TYPE_MAP_NAME => "MP3",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_MP3],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP3],
|
|
||||||
],
|
|
||||||
MIME_TYPE_MP4_AUDIO => [
|
|
||||||
MIME_TYPE_MAP_NAME => "MP4 Audio",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_M4A],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_AUDIO,"audio/m4a"],
|
|
||||||
],
|
|
||||||
MIME_TYPE_MP4_VIDEO => [
|
|
||||||
MIME_TYPE_MAP_NAME => "MP4 Video",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_MP4,EXTENSION_M4V],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_VIDEO,'video/x-m4v'],
|
|
||||||
],
|
|
||||||
MIME_TYPE_MPEG => [
|
|
||||||
MIME_TYPE_MAP_NAME => "MPEG",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_MPG,EXTENSION_MPEG],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MPEG],
|
|
||||||
],
|
|
||||||
MIME_TYPE_PDF => [
|
|
||||||
MIME_TYPE_MAP_NAME => "PDF",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_PDF],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_PDF],
|
|
||||||
],
|
|
||||||
MIME_TYPE_PHP => [
|
|
||||||
MIME_TYPE_MAP_NAME => "PHP",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_PHP,EXTENSION_PHP5],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_PHP],
|
|
||||||
],
|
|
||||||
MIME_TYPE_PNG => [
|
|
||||||
MIME_TYPE_MAP_NAME => "PNG",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_PNG],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_PNG],
|
|
||||||
],
|
|
||||||
MIME_TYPE_PSD => [
|
|
||||||
MIME_TYPE_MAP_NAME => "PSD",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_PSD],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_PSD],
|
|
||||||
],
|
|
||||||
MIME_TYPE_OGG_AUDIO => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Ogg Vorbis",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_OGG_AUDIO,EXTENSION_OGG],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_AUDIO,MIME_TYPE_OGG],
|
|
||||||
],
|
|
||||||
MIME_TYPE_OGG_VIDEO => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Ogg Theora",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_OGG_VIDEO],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_VIDEO],
|
|
||||||
],
|
|
||||||
MIME_TYPE_QUICKTIME => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Quicktime",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_MOV],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_QUICKTIME],
|
|
||||||
],
|
|
||||||
MIME_TYPE_RSS => [
|
|
||||||
MIME_TYPE_MAP_NAME => "RSS",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_RSS],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_RSS],
|
|
||||||
],
|
|
||||||
MIME_TYPE_SVG => [
|
|
||||||
MIME_TYPE_MAP_NAME => "SVG",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_SVG],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_SVG],
|
|
||||||
],
|
|
||||||
MIME_TYPE_TAR => [
|
|
||||||
MIME_TYPE_MAP_NAME => "TAR",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_TAR],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR],
|
|
||||||
],
|
|
||||||
MIME_TYPE_TEXT => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Text",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_TEXT, EXTENSION_ASC],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_TEXT],
|
|
||||||
],
|
|
||||||
MIME_TYPE_TIFF => [
|
|
||||||
MIME_TYPE_MAP_NAME => "TIFF",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_TIF,EXTENSION_TIFF],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_TIFF],
|
|
||||||
],
|
|
||||||
MIME_TYPE_WAV => [
|
|
||||||
MIME_TYPE_MAP_NAME => "Wave",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_WAV],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_WAV],
|
|
||||||
],
|
|
||||||
MIME_TYPE_WEBM => [
|
|
||||||
MIME_TYPE_MAP_NAME => "WebM",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_WEBM],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBM],
|
|
||||||
],
|
|
||||||
MIME_TYPE_WEBP => [
|
|
||||||
MIME_TYPE_MAP_NAME => "WebP",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_WEBP],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBP],
|
|
||||||
],
|
|
||||||
MIME_TYPE_XML => [
|
|
||||||
MIME_TYPE_MAP_NAME => "XML",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_XML],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_XML,MIME_TYPE_XML_APPLICATION],
|
|
||||||
],
|
|
||||||
MIME_TYPE_XSL => [
|
|
||||||
MIME_TYPE_MAP_NAME => "XSL",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_XSL],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_XSL],
|
|
||||||
],
|
|
||||||
MIME_TYPE_ZIP => [
|
|
||||||
MIME_TYPE_MAP_NAME => "ZIP",
|
|
||||||
MIME_TYPE_MAP_EXT => [EXTENSION_ZIP],
|
|
||||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_ZIP],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the mimetype that matches the provided extension.
|
|
||||||
*/
|
|
||||||
function get_mime_for_extension(string $ext): ?string
|
|
||||||
{
|
|
||||||
$ext = strtolower($ext);
|
|
||||||
|
|
||||||
foreach (MIME_TYPE_MAP as $key=>$value) {
|
|
||||||
if (in_array($ext, $value[MIME_TYPE_MAP_EXT])) {
|
|
||||||
return $key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the mimetype for the specified file, trying file inspection methods before falling back on extension-based detection.
|
|
||||||
* @param String $file
|
|
||||||
* @param String $ext The files extension, for if the current filename somehow lacks the extension
|
|
||||||
* @return String The extension that was found.
|
|
||||||
*/
|
|
||||||
function get_mime(string $file, string $ext=""): string
|
|
||||||
{
|
|
||||||
if (!file_exists($file)) {
|
|
||||||
throw new SCoreException("File not found: ".$file);
|
|
||||||
}
|
|
||||||
|
|
||||||
$type = false;
|
|
||||||
|
|
||||||
if (extension_loaded('fileinfo')) {
|
|
||||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
||||||
try {
|
|
||||||
$type = finfo_file($finfo, $file);
|
|
||||||
} finally {
|
|
||||||
finfo_close($finfo);
|
|
||||||
}
|
|
||||||
} elseif (function_exists('mime_content_type')) {
|
|
||||||
// If anyone is still using mime_content_type()
|
|
||||||
$type = trim(mime_content_type($file));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($type===false || empty($type)) {
|
|
||||||
// Checking by extension is our last resort
|
|
||||||
if ($ext==null||strlen($ext) == 0) {
|
|
||||||
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
$type = get_mime_for_extension($ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($type !== false && strlen($type) > 0) {
|
|
||||||
return $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MIME_TYPE_OCTET_STREAM;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the file extension associated with the specified mimetype.
|
|
||||||
*/
|
|
||||||
function get_extension(?string $mime_type): ?string
|
|
||||||
{
|
|
||||||
if (empty($mime_type)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($mime_type==MIME_TYPE_OCTET_STREAM) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (MIME_TYPE_MAP as $key=>$value) {
|
|
||||||
if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) {
|
|
||||||
return $value[MIME_TYPE_MAP_EXT][0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all of the file extensions associated with the specified mimetype.
|
|
||||||
*/
|
|
||||||
function get_all_extension_for_mime(?string $mime_type): array
|
|
||||||
{
|
|
||||||
$output = [];
|
|
||||||
if (empty($mime_type)) {
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (MIME_TYPE_MAP as $key=>$value) {
|
|
||||||
if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) {
|
|
||||||
$output = array_merge($output, $value[MIME_TYPE_MAP_EXT]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an the extension defined in MIME_TYPE_MAP for a file.
|
|
||||||
*
|
|
||||||
* @param String $file_path
|
|
||||||
* @return String The extension that was found, or null if one can not be found.
|
|
||||||
*/
|
|
||||||
function get_extension_for_file(String $file_path): ?String
|
|
||||||
{
|
|
||||||
$mime = get_mime($file_path);
|
|
||||||
if (!empty($mime)) {
|
|
||||||
if ($mime==MIME_TYPE_OCTET_STREAM) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
$ext = get_extension($mime);
|
|
||||||
}
|
|
||||||
if (!empty($ext)) {
|
|
||||||
return $ext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
@ -1,17 +1,15 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An image is being added to the database.
|
* An image is being added to the database.
|
||||||
*/
|
*/
|
||||||
class ImageAdditionEvent extends Event
|
class ImageAdditionEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var User */
|
public User $user;
|
||||||
public $user;
|
public Image $image;
|
||||||
|
public bool $merged = false;
|
||||||
/** @var Image */
|
|
||||||
public $image;
|
|
||||||
|
|
||||||
public $merged = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a new image into the database with its associated
|
* Inserts a new image into the database with its associated
|
||||||
@ -34,11 +32,8 @@ class ImageAdditionException extends SCoreException
|
|||||||
*/
|
*/
|
||||||
class ImageDeletionEvent extends Event
|
class ImageDeletionEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var Image */
|
public Image $image;
|
||||||
public $image;
|
public bool $force = false;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
public $force = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an image.
|
* Deletes an image.
|
||||||
@ -59,10 +54,8 @@ class ImageDeletionEvent extends Event
|
|||||||
*/
|
*/
|
||||||
class ImageReplaceEvent extends Event
|
class ImageReplaceEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var int */
|
public int $id;
|
||||||
public $id;
|
public Image $image;
|
||||||
/** @var Image */
|
|
||||||
public $image;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces an image.
|
* Replaces an image.
|
||||||
@ -88,24 +81,19 @@ class ImageReplaceException extends SCoreException
|
|||||||
*/
|
*/
|
||||||
class ThumbnailGenerationEvent extends Event
|
class ThumbnailGenerationEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $hash;
|
||||||
public $hash;
|
public string $mime;
|
||||||
/** @var string */
|
public bool $force;
|
||||||
public $type;
|
public bool $generated;
|
||||||
/** @var bool */
|
|
||||||
public $force;
|
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
public $generated;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request a thumbnail be made for an image object
|
* Request a thumbnail be made for an image object
|
||||||
*/
|
*/
|
||||||
public function __construct(string $hash, string $type, bool $force=false)
|
public function __construct(string $hash, string $mime, bool $force=false)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->hash = $hash;
|
$this->hash = $hash;
|
||||||
$this->type = $type;
|
$this->mime = $mime;
|
||||||
$this->force = $force;
|
$this->force = $force;
|
||||||
$this->generated = false;
|
$this->generated = false;
|
||||||
}
|
}
|
||||||
@ -121,14 +109,10 @@ class ThumbnailGenerationEvent extends Event
|
|||||||
*/
|
*/
|
||||||
class ParseLinkTemplateEvent extends Event
|
class ParseLinkTemplateEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $link;
|
||||||
public $link;
|
public string $text;
|
||||||
/** @var string */
|
public string $original;
|
||||||
public $text;
|
public Image $image;
|
||||||
/** @var string */
|
|
||||||
public $original;
|
|
||||||
/** @var Image */
|
|
||||||
public $image;
|
|
||||||
|
|
||||||
public function __construct(string $link, Image $image)
|
public function __construct(string $link, Image $image)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Class Image
|
* Class Image
|
||||||
*
|
*
|
||||||
@ -13,64 +15,31 @@ class Image
|
|||||||
public const IMAGE_DIR = "images";
|
public const IMAGE_DIR = "images";
|
||||||
public const THUMBNAIL_DIR = "thumbs";
|
public const THUMBNAIL_DIR = "thumbs";
|
||||||
|
|
||||||
public static $order_sql = null; // this feels ugly
|
public ?int $id = null;
|
||||||
|
public int $height = 0;
|
||||||
|
public int $width = 0;
|
||||||
|
public string $hash;
|
||||||
|
public int $filesize;
|
||||||
|
public string $filename;
|
||||||
|
private string $ext;
|
||||||
|
private string $mime;
|
||||||
|
|
||||||
/** @var null|int */
|
/** @var ?string[] */
|
||||||
public $id = null;
|
public ?array $tag_array;
|
||||||
|
public int $owner_id;
|
||||||
|
public string $owner_ip;
|
||||||
|
public ?string $posted = null;
|
||||||
|
public ?string $source;
|
||||||
|
public bool $locked = false;
|
||||||
|
public ?bool $lossless = null;
|
||||||
|
public ?bool $video = null;
|
||||||
|
public ?string $video_codec = null;
|
||||||
|
public ?bool $image = null;
|
||||||
|
public ?bool $audio = null;
|
||||||
|
public ?int $length = null;
|
||||||
|
|
||||||
/** @var int */
|
public static array $bool_props = ["locked", "lossless", "video", "audio", "image"];
|
||||||
public $height;
|
public static array $int_props = ["id", "owner_id", "height", "width", "filesize", "length"];
|
||||||
|
|
||||||
/** @var int */
|
|
||||||
public $width;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $hash;
|
|
||||||
|
|
||||||
/** @var int */
|
|
||||||
public $filesize;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $filename;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $ext;
|
|
||||||
|
|
||||||
/** @var string[]|null */
|
|
||||||
public $tag_array;
|
|
||||||
|
|
||||||
/** @var int */
|
|
||||||
public $owner_id;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $owner_ip;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $posted;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $source;
|
|
||||||
|
|
||||||
/** @var boolean */
|
|
||||||
public $locked = false;
|
|
||||||
|
|
||||||
/** @var boolean */
|
|
||||||
public $lossless = null;
|
|
||||||
|
|
||||||
/** @var boolean */
|
|
||||||
public $video = null;
|
|
||||||
|
|
||||||
/** @var boolean */
|
|
||||||
public $image = null;
|
|
||||||
|
|
||||||
/** @var boolean */
|
|
||||||
public $audio = null;
|
|
||||||
|
|
||||||
/** @var int */
|
|
||||||
public $length = null;
|
|
||||||
|
|
||||||
public static $bool_props = ["locked", "lossless", "video", "audio"];
|
|
||||||
public static $int_props = ["id", "owner_id", "height", "width", "filesize", "length"];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One will very rarely construct an image directly, more common
|
* One will very rarely construct an image directly, more common
|
||||||
@ -80,6 +49,10 @@ class Image
|
|||||||
{
|
{
|
||||||
if (!is_null($row)) {
|
if (!is_null($row)) {
|
||||||
foreach ($row as $name => $value) {
|
foreach ($row as $name => $value) {
|
||||||
|
if (is_numeric($name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// some databases use table.name rather than name
|
// some databases use table.name rather than name
|
||||||
$name = str_replace("images.", "", $name);
|
$name = str_replace("images.", "", $name);
|
||||||
|
|
||||||
@ -137,7 +110,7 @@ class Image
|
|||||||
|
|
||||||
private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags=[]): iterable
|
private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags=[]): iterable
|
||||||
{
|
{
|
||||||
global $database, $user, $config;
|
global $database, $user;
|
||||||
|
|
||||||
if ($start < 0) {
|
if ($start < 0) {
|
||||||
$start = 0;
|
$start = 0;
|
||||||
@ -148,17 +121,12 @@ class Image
|
|||||||
|
|
||||||
if (SPEED_HAX) {
|
if (SPEED_HAX) {
|
||||||
if (!$user->can(Permissions::BIG_SEARCH) and count($tags) > 3) {
|
if (!$user->can(Permissions::BIG_SEARCH) and count($tags) > 3) {
|
||||||
throw new SCoreException("Anonymous users may only search for up to 3 tags at a time");
|
throw new PermissionDeniedException("Anonymous users may only search for up to 3 tags at a time");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$order = (Image::$order_sql ?: "images.".$config->get_string(IndexConfig::ORDER));
|
$querylet = Image::build_search_querylet($tags, $limit, $start);
|
||||||
$querylet = Image::build_search_querylet($tags, $order, $limit, $start);
|
return $database->get_all_iterable($querylet->sql, $querylet->variables);
|
||||||
$result = $database->get_all_iterable($querylet->sql, $querylet->variables);
|
|
||||||
|
|
||||||
Image::$order_sql = null;
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,11 +191,11 @@ class Image
|
|||||||
global $cache, $database;
|
global $cache, $database;
|
||||||
$tag_count = count($tags);
|
$tag_count = count($tags);
|
||||||
|
|
||||||
if ($tag_count === 0) {
|
if (SPEED_HAX && $tag_count === 0) {
|
||||||
// total number of images in the DB
|
// total number of images in the DB
|
||||||
$total = self::count_total_images();
|
$total = self::count_total_images();
|
||||||
} elseif ($tag_count === 1 && !preg_match("/[:=><\*\?]/", $tags[0])) {
|
} elseif (SPEED_HAX && $tag_count === 1 && !preg_match("/[:=><\*\?]/", $tags[0])) {
|
||||||
if (!startsWith($tags[0], "-")) {
|
if (!str_starts_with($tags[0], "-")) {
|
||||||
// one tag - we can look that up directly
|
// one tag - we can look that up directly
|
||||||
$total = self::count_tag($tags[0]);
|
$total = self::count_tag($tags[0]);
|
||||||
} else {
|
} else {
|
||||||
@ -273,14 +241,19 @@ class Image
|
|||||||
{
|
{
|
||||||
$tag_conditions = [];
|
$tag_conditions = [];
|
||||||
$img_conditions = [];
|
$img_conditions = [];
|
||||||
|
$stpen = 0; // search term parse event number
|
||||||
|
$order = null;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Turn a bunch of strings into a bunch of TagCondition
|
* Turn a bunch of strings into a bunch of TagCondition
|
||||||
* and ImgCondition objects
|
* and ImgCondition objects
|
||||||
*/
|
*/
|
||||||
$stpe = send_event(new SearchTermParseEvent(null, $terms));
|
/** @var $stpe SearchTermParseEvent */
|
||||||
if ($stpe->is_querylet_set()) {
|
$stpe = send_event(new SearchTermParseEvent($stpen++, null, $terms));
|
||||||
foreach ($stpe->get_querylets() as $querylet) {
|
if ($stpe->order) {
|
||||||
|
$order = $stpe->order;
|
||||||
|
} elseif (!empty($stpe->querylets)) {
|
||||||
|
foreach ($stpe->querylets as $querylet) {
|
||||||
$img_conditions[] = new ImgCondition($querylet, true);
|
$img_conditions[] = new ImgCondition($querylet, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,9 +268,12 @@ class Image
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stpe = send_event(new SearchTermParseEvent($term, $terms));
|
/** @var $stpe SearchTermParseEvent */
|
||||||
if ($stpe->is_querylet_set()) {
|
$stpe = send_event(new SearchTermParseEvent($stpen++, $term, $terms));
|
||||||
foreach ($stpe->get_querylets() as $querylet) {
|
if ($stpe->order) {
|
||||||
|
$order = $stpe->order;
|
||||||
|
} elseif (!empty($stpe->querylets)) {
|
||||||
|
foreach ($stpe->querylets as $querylet) {
|
||||||
$img_conditions[] = new ImgCondition($querylet, $positive);
|
$img_conditions[] = new ImgCondition($querylet, $positive);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -307,7 +283,7 @@ class Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [$tag_conditions, $img_conditions];
|
return [$tag_conditions, $img_conditions, $order];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -344,8 +320,9 @@ class Image
|
|||||||
');
|
');
|
||||||
} else {
|
} else {
|
||||||
$tags[] = 'id'. $gtlt . $this->id;
|
$tags[] = 'id'. $gtlt . $this->id;
|
||||||
|
$tags[] = 'order:id_'. strtolower($dir);
|
||||||
$querylet = Image::build_search_querylet($tags);
|
$querylet = Image::build_search_querylet($tags);
|
||||||
$querylet->append_sql(' ORDER BY images.id '.$dir.' LIMIT 1');
|
$querylet->append_sql(' LIMIT 1');
|
||||||
$row = $database->get_row($querylet->sql, $querylet->variables);
|
$row = $database->get_row($querylet->sql, $querylet->variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,7 +359,7 @@ class Image
|
|||||||
SET owner_id=:owner_id
|
SET owner_id=:owner_id
|
||||||
WHERE id=:id
|
WHERE id=:id
|
||||||
", ["owner_id"=>$owner->id, "id"=>$this->id]);
|
", ["owner_id"=>$owner->id, "id"=>$this->id]);
|
||||||
log_info("core_image", "Owner for Image #{$this->id} set to {$owner->name}");
|
log_info("core_image", "Owner for Post #{$this->id} set to {$owner->name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,27 +368,32 @@ class Image
|
|||||||
global $database, $user;
|
global $database, $user;
|
||||||
$cut_name = substr($this->filename, 0, 255);
|
$cut_name = substr($this->filename, 0, 255);
|
||||||
|
|
||||||
|
if (is_null($this->posted) || $this->posted == "") {
|
||||||
|
$this->posted = date('c', time());
|
||||||
|
}
|
||||||
|
|
||||||
if (is_null($this->id)) {
|
if (is_null($this->id)) {
|
||||||
$database->execute(
|
$database->execute(
|
||||||
"INSERT INTO images(
|
"INSERT INTO images(
|
||||||
owner_id, owner_ip,
|
owner_id, owner_ip,
|
||||||
filename, filesize,
|
filename, filesize,
|
||||||
hash, ext,
|
hash, mime, ext,
|
||||||
width, height,
|
width, height,
|
||||||
posted, source
|
posted, source
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
:owner_id, :owner_ip,
|
:owner_id, :owner_ip,
|
||||||
:filename, :filesize,
|
:filename, :filesize,
|
||||||
:hash, :ext,
|
:hash, :mime, :ext,
|
||||||
0, 0,
|
0, 0,
|
||||||
now(), :source
|
:posted, :source
|
||||||
)",
|
)",
|
||||||
[
|
[
|
||||||
"owner_id" => $user->id, "owner_ip" => $_SERVER['REMOTE_ADDR'],
|
"owner_id" => $user->id, "owner_ip" => get_real_ip(),
|
||||||
"filename" => $cut_name, "filesize" => $this->filesize,
|
"filename" => $cut_name, "filesize" => $this->filesize,
|
||||||
"hash" => $this->hash, "ext" => strtolower($this->ext),
|
"hash" => $this->hash, "mime" => strtolower($this->mime),
|
||||||
"source" => $this->source
|
"ext" => strtolower($this->ext),
|
||||||
|
"posted" => $this->posted, "source" => $this->source
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$this->id = $database->get_last_insert_id('images_id_seq');
|
$this->id = $database->get_last_insert_id('images_id_seq');
|
||||||
@ -419,13 +401,16 @@ class Image
|
|||||||
$database->execute(
|
$database->execute(
|
||||||
"UPDATE images SET ".
|
"UPDATE images SET ".
|
||||||
"filename = :filename, filesize = :filesize, hash = :hash, ".
|
"filename = :filename, filesize = :filesize, hash = :hash, ".
|
||||||
"ext = :ext, width = 0, height = 0, source = :source ".
|
"mime = :mime, ext = :ext, width = 0, height = 0, ".
|
||||||
|
"posted = :posted, source = :source ".
|
||||||
"WHERE id = :id",
|
"WHERE id = :id",
|
||||||
[
|
[
|
||||||
"filename" => $cut_name,
|
"filename" => $cut_name,
|
||||||
"filesize" => $this->filesize,
|
"filesize" => $this->filesize,
|
||||||
"hash" => $this->hash,
|
"hash" => $this->hash,
|
||||||
|
"mime" => strtolower($this->mime),
|
||||||
"ext" => strtolower($this->ext),
|
"ext" => strtolower($this->ext),
|
||||||
|
"posted" => $this->posted,
|
||||||
"source" => $this->source,
|
"source" => $this->source,
|
||||||
"id" => $this->id,
|
"id" => $this->id,
|
||||||
]
|
]
|
||||||
@ -435,17 +420,18 @@ class Image
|
|||||||
$database->execute(
|
$database->execute(
|
||||||
"UPDATE images SET ".
|
"UPDATE images SET ".
|
||||||
"lossless = :lossless, ".
|
"lossless = :lossless, ".
|
||||||
"video = :video, audio = :audio,image = :image, ".
|
"video = :video, video_codec = :video_codec, audio = :audio,image = :image, ".
|
||||||
"height = :height, width = :width, ".
|
"height = :height, width = :width, ".
|
||||||
"length = :length WHERE id = :id",
|
"length = :length WHERE id = :id",
|
||||||
[
|
[
|
||||||
"id" => $this->id,
|
"id" => $this->id,
|
||||||
"width" => $this->width ?? 0,
|
"width" => $this->width ?? 0,
|
||||||
"height" => $this->height ?? 0,
|
"height" => $this->height ?? 0,
|
||||||
"lossless" => $database->scoresql_value_prepare($this->lossless),
|
"lossless" => $this->lossless,
|
||||||
"video" => $database->scoresql_value_prepare($this->video),
|
"video" => $this->video,
|
||||||
"image" => $database->scoresql_value_prepare($this->image),
|
"video_codec" => $this->video_codec,
|
||||||
"audio" => $database->scoresql_value_prepare($this->audio),
|
"image" => $this->image,
|
||||||
|
"audio" => $this->audio,
|
||||||
"length" => $this->length
|
"length" => $this->length
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -467,6 +453,7 @@ class Image
|
|||||||
WHERE image_id=:id
|
WHERE image_id=:id
|
||||||
ORDER BY tag
|
ORDER BY tag
|
||||||
", ["id"=>$this->id]);
|
", ["id"=>$this->id]);
|
||||||
|
sort($this->tag_array);
|
||||||
}
|
}
|
||||||
return $this->tag_array;
|
return $this->tag_array;
|
||||||
}
|
}
|
||||||
@ -503,7 +490,8 @@ class Image
|
|||||||
public function get_thumb_link(): string
|
public function get_thumb_link(): string
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
|
$mime = $config->get_string(ImageConfig::THUMB_MIME);
|
||||||
|
$ext = FileExtension::get_for_mime($mime);
|
||||||
return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext);
|
return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,7 +505,7 @@ class Image
|
|||||||
$image_link = $config->get_string($template);
|
$image_link = $config->get_string($template);
|
||||||
|
|
||||||
if (!empty($image_link)) {
|
if (!empty($image_link)) {
|
||||||
if (!(strpos($image_link, "://") > 0) && !startsWith($image_link, "/")) {
|
if (!str_contains($image_link, "://") && !str_starts_with($image_link, "/")) {
|
||||||
$image_link = make_link($image_link);
|
$image_link = make_link($image_link);
|
||||||
}
|
}
|
||||||
$chosen = $image_link;
|
$chosen = $image_link;
|
||||||
@ -541,6 +529,19 @@ class Image
|
|||||||
return $plte->text;
|
return $plte->text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the info for this image, formatted according to the
|
||||||
|
* configured template.
|
||||||
|
*/
|
||||||
|
public function get_info(): string
|
||||||
|
{
|
||||||
|
global $config;
|
||||||
|
$plte = new ParseLinkTemplateEvent($config->get_string(ImageConfig::INFO), $this);
|
||||||
|
send_event($plte);
|
||||||
|
return $plte->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Figure out where the full size image is on disk.
|
* Figure out where the full size image is on disk.
|
||||||
*/
|
*/
|
||||||
@ -566,21 +567,40 @@ class Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the image's mime type.
|
* Get the image's extension.
|
||||||
*/
|
|
||||||
public function get_mime_type(): string
|
|
||||||
{
|
|
||||||
return get_mime($this->get_image_filename(), $this->get_ext());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the image's filename extension
|
|
||||||
*/
|
*/
|
||||||
public function get_ext(): string
|
public function get_ext(): string
|
||||||
{
|
{
|
||||||
return $this->ext;
|
return $this->ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the image's mime type.
|
||||||
|
*/
|
||||||
|
public function get_mime(): ?string
|
||||||
|
{
|
||||||
|
if ($this->mime===MimeType::WEBP&&$this->lossless) {
|
||||||
|
return MimeType::WEBP_LOSSLESS;
|
||||||
|
}
|
||||||
|
$m = $this->mime;
|
||||||
|
if (is_null($m)) {
|
||||||
|
$m = MimeMap::get_for_extension($this->ext)[0];
|
||||||
|
}
|
||||||
|
return $m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the image's mime type.
|
||||||
|
*/
|
||||||
|
public function set_mime($mime): void
|
||||||
|
{
|
||||||
|
$this->mime = $mime;
|
||||||
|
$ext = FileExtension::get_for_mime($this->get_mime());
|
||||||
|
assert($ext != null);
|
||||||
|
$this->ext = $ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the image's source URL
|
* Get the image's source URL
|
||||||
*/
|
*/
|
||||||
@ -601,7 +621,7 @@ class Image
|
|||||||
}
|
}
|
||||||
if ($new_source != $old_source) {
|
if ($new_source != $old_source) {
|
||||||
$database->execute("UPDATE images SET source=:source WHERE id=:id", ["source"=>$new_source, "id"=>$this->id]);
|
$database->execute("UPDATE images SET source=:source WHERE id=:id", ["source"=>$new_source, "id"=>$this->id]);
|
||||||
log_info("core_image", "Source for Image #{$this->id} set to: $new_source (was $old_source)");
|
log_info("core_image", "Source for Post #{$this->id} set to: $new_source (was $old_source)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,16 +633,12 @@ class Image
|
|||||||
return $this->locked;
|
return $this->locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_locked(bool $tf): void
|
public function set_locked(bool $locked): void
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
$ln = $tf ? "Y" : "N";
|
if ($locked !== $this->locked) {
|
||||||
$sln = $database->scoreql_to_sql('SCORE_BOOL_'.$ln);
|
$database->execute("UPDATE images SET locked=:yn WHERE id=:id", ["yn"=>$locked, "id"=>$this->id]);
|
||||||
$sln = str_replace("'", "", $sln);
|
log_info("core_image", "Setting Post #{$this->id} lock to: $locked");
|
||||||
$sln = str_replace('"', "", $sln);
|
|
||||||
if (bool_escape($sln) !== $this->locked) {
|
|
||||||
$database->execute("UPDATE images SET locked=:yn WHERE id=:id", ["yn"=>$sln, "id"=>$this->id]);
|
|
||||||
log_info("core_image", "Setting Image #{$this->id} lock to: $ln");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,7 +693,7 @@ class Image
|
|||||||
$page->flash("Can't set a tag longer than 255 characters");
|
$page->flash("Can't set a tag longer than 255 characters");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (startsWith($tag, "-")) {
|
if (str_starts_with($tag, "-")) {
|
||||||
$page->flash("Can't set a tag which starts with a minus");
|
$page->flash("Can't set a tag which starts with a minus");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -739,7 +755,7 @@ class Image
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("core_image", "Tags for Image #{$this->id} set to: ".Tag::implode($tags));
|
log_info("core_image", "Tags for Post #{$this->id} set to: ".Tag::implode($tags));
|
||||||
$cache->delete("image-{$this->id}-tags");
|
$cache->delete("image-{$this->id}-tags");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -752,7 +768,7 @@ class Image
|
|||||||
global $database;
|
global $database;
|
||||||
$this->delete_tags_from_image();
|
$this->delete_tags_from_image();
|
||||||
$database->execute("DELETE FROM images WHERE id=:id", ["id"=>$this->id]);
|
$database->execute("DELETE FROM images WHERE id=:id", ["id"=>$this->id]);
|
||||||
log_info("core_image", 'Deleted Image #'.$this->id.' ('.$this->hash.')');
|
log_info("core_image", 'Deleted Post #'.$this->id.' ('.$this->hash.')');
|
||||||
|
|
||||||
unlink($this->get_image_filename());
|
unlink($this->get_image_filename());
|
||||||
unlink($this->get_thumb_filename());
|
unlink($this->get_thumb_filename());
|
||||||
@ -764,7 +780,7 @@ class Image
|
|||||||
*/
|
*/
|
||||||
public function remove_image_only(): void
|
public function remove_image_only(): void
|
||||||
{
|
{
|
||||||
log_info("core_image", 'Removed Image File ('.$this->hash.')');
|
log_info("core_image", 'Removed Post File ('.$this->hash.')');
|
||||||
@unlink($this->get_image_filename());
|
@unlink($this->get_image_filename());
|
||||||
@unlink($this->get_thumb_filename());
|
@unlink($this->get_thumb_filename());
|
||||||
}
|
}
|
||||||
@ -790,12 +806,14 @@ class Image
|
|||||||
* #param string[] $terms
|
* #param string[] $terms
|
||||||
*/
|
*/
|
||||||
private static function build_search_querylet(
|
private static function build_search_querylet(
|
||||||
array $tags,
|
array $terms,
|
||||||
?string $order=null,
|
|
||||||
?int $limit=null,
|
?int $limit=null,
|
||||||
?int $offset=null
|
?int $offset=null
|
||||||
): Querylet {
|
): Querylet {
|
||||||
list($tag_conditions, $img_conditions) = self::terms_to_conditions($tags);
|
global $config;
|
||||||
|
|
||||||
|
list($tag_conditions, $img_conditions, $order) = self::terms_to_conditions($terms);
|
||||||
|
$order = ($order ?: "images.".$config->get_string(IndexConfig::ORDER));
|
||||||
|
|
||||||
$positive_tag_count = 0;
|
$positive_tag_count = 0;
|
||||||
$negative_tag_count = 0;
|
$negative_tag_count = 0;
|
||||||
@ -873,12 +891,14 @@ class Image
|
|||||||
$positive_tag_id_array = [];
|
$positive_tag_id_array = [];
|
||||||
$positive_wildcard_id_array = [];
|
$positive_wildcard_id_array = [];
|
||||||
$negative_tag_id_array = [];
|
$negative_tag_id_array = [];
|
||||||
|
$all_nonexistent_negatives = true;
|
||||||
|
|
||||||
foreach ($tag_conditions as $tq) {
|
foreach ($tag_conditions as $tq) {
|
||||||
$tag_ids = self::tag_or_wildcard_to_ids($tq->tag);
|
$tag_ids = self::tag_or_wildcard_to_ids($tq->tag);
|
||||||
$tag_count = count($tag_ids);
|
$tag_count = count($tag_ids);
|
||||||
|
|
||||||
if ($tq->positive) {
|
if ($tq->positive) {
|
||||||
|
$all_nonexistent_negatives = false;
|
||||||
if ($tag_count== 0) {
|
if ($tag_count== 0) {
|
||||||
# one of the positive tags had zero results, therefor there
|
# one of the positive tags had zero results, therefor there
|
||||||
# can be no results; "where 1=0" should shortcut things
|
# can be no results; "where 1=0" should shortcut things
|
||||||
@ -892,14 +912,20 @@ class Image
|
|||||||
$positive_wildcard_id_array[] = $tag_ids;
|
$positive_wildcard_id_array[] = $tag_ids;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if ($tag_count > 0) {
|
||||||
|
$all_nonexistent_negatives = false;
|
||||||
// Unlike positive criteria, negative criteria are all handled in an OR fashion,
|
// Unlike positive criteria, negative criteria are all handled in an OR fashion,
|
||||||
// so we can just compile them all into a single sub-query.
|
// so we can just compile them all into a single sub-query.
|
||||||
$negative_tag_id_array = array_merge($negative_tag_id_array, $tag_ids);
|
$negative_tag_id_array = array_merge($negative_tag_id_array, $tag_ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert($positive_tag_id_array || $positive_wildcard_id_array || $negative_tag_id_array, @$_GET['q']);
|
assert($positive_tag_id_array || $positive_wildcard_id_array || $negative_tag_id_array || $all_nonexistent_negatives, @$_GET['q']);
|
||||||
if (!empty($positive_tag_id_array) || !empty($positive_wildcard_id_array)) {
|
|
||||||
|
if ($all_nonexistent_negatives) {
|
||||||
|
$query = new Querylet("SELECT images.* FROM images WHERE 1=1");
|
||||||
|
} elseif (!empty($positive_tag_id_array) || !empty($positive_wildcard_id_array)) {
|
||||||
$inner_joins = [];
|
$inner_joins = [];
|
||||||
if (!empty($positive_tag_id_array)) {
|
if (!empty($positive_tag_id_array)) {
|
||||||
foreach ($positive_tag_id_array as $tag) {
|
foreach ($positive_tag_id_array as $tag) {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||||
* Misc functions *
|
* Misc functions *
|
||||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
@ -33,11 +35,6 @@ function add_dir(string $base): array
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a DataUploadEvent for a file.
|
* Sends a DataUploadEvent for a file.
|
||||||
*
|
|
||||||
* @param string $tmpname
|
|
||||||
* @param string $filename
|
|
||||||
* @param string $tags
|
|
||||||
* @throws UploadException
|
|
||||||
*/
|
*/
|
||||||
function add_image(string $tmpname, string $filename, string $tags): int
|
function add_image(string $tmpname, string $filename, string $tags): int
|
||||||
{
|
{
|
||||||
@ -75,7 +72,12 @@ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_sca
|
|||||||
|
|
||||||
$fit = $config->get_string(ImageConfig::THUMB_FIT);
|
$fit = $config->get_string(ImageConfig::THUMB_FIT);
|
||||||
|
|
||||||
if (in_array($fit, [Media::RESIZE_TYPE_FILL, Media::RESIZE_TYPE_STRETCH, Media::RESIZE_TYPE_FIT_BLUR])) {
|
if (in_array($fit, [
|
||||||
|
Media::RESIZE_TYPE_FILL,
|
||||||
|
Media::RESIZE_TYPE_STRETCH,
|
||||||
|
Media::RESIZE_TYPE_FIT_BLUR,
|
||||||
|
Media::RESIZE_TYPE_FIT_BLUR_PORTRAIT
|
||||||
|
])) {
|
||||||
return [$config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_HEIGHT)];
|
return [$config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_HEIGHT)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +112,7 @@ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_sca
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_scaled_by_aspect_ratio(int $original_width, int $original_height, int $max_width, int $max_height) : array
|
function get_scaled_by_aspect_ratio(int $original_width, int $original_height, int $max_width, int $max_height): array
|
||||||
{
|
{
|
||||||
$xscale = ($max_width/ $original_width);
|
$xscale = ($max_width/ $original_width);
|
||||||
$yscale = ($max_height/ $original_height);
|
$yscale = ($max_height/ $original_height);
|
||||||
@ -136,7 +138,7 @@ function get_thumbnail_max_size_scaled(): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function create_image_thumb(string $hash, string $type, string $engine = null)
|
function create_image_thumb(string $hash, string $mime, string $engine = null)
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
@ -147,7 +149,7 @@ function create_image_thumb(string $hash, string $type, string $engine = null)
|
|||||||
$inname,
|
$inname,
|
||||||
$outname,
|
$outname,
|
||||||
$tsize,
|
$tsize,
|
||||||
$type,
|
$mime,
|
||||||
$engine,
|
$engine,
|
||||||
$config->get_string(ImageConfig::THUMB_FIT)
|
$config->get_string(ImageConfig::THUMB_FIT)
|
||||||
);
|
);
|
||||||
@ -155,7 +157,7 @@ function create_image_thumb(string $hash, string $type, string $engine = null)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine = null, ?string $resize_type = null)
|
function create_scaled_image(string $inname, string $outname, array $tsize, string $mime, ?string $engine = null, ?string $resize_type = null)
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
if (empty($engine)) {
|
if (empty($engine)) {
|
||||||
@ -165,22 +167,44 @@ function create_scaled_image(string $inname, string $outname, array $tsize, stri
|
|||||||
$resize_type = $config->get_string(ImageConfig::THUMB_FIT);
|
$resize_type = $config->get_string(ImageConfig::THUMB_FIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
$output_format = $config->get_string(ImageConfig::THUMB_TYPE);
|
$output_mime = $config->get_string(ImageConfig::THUMB_MIME);
|
||||||
if ($output_format==EXTENSION_WEBP) {
|
|
||||||
$output_format = Media::WEBP_LOSSY;
|
|
||||||
}
|
|
||||||
|
|
||||||
send_event(new MediaResizeEvent(
|
send_event(new MediaResizeEvent(
|
||||||
$engine,
|
$engine,
|
||||||
$inname,
|
$inname,
|
||||||
$type,
|
$mime,
|
||||||
$outname,
|
$outname,
|
||||||
$tsize[0],
|
$tsize[0],
|
||||||
$tsize[1],
|
$tsize[1],
|
||||||
$resize_type,
|
$resize_type,
|
||||||
$output_format,
|
$output_mime,
|
||||||
|
$config->get_string(ImageConfig::THUMB_ALPHA_COLOR),
|
||||||
$config->get_int(ImageConfig::THUMB_QUALITY),
|
$config->get_int(ImageConfig::THUMB_QUALITY),
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function redirect_to_next_image(Image $image): void
|
||||||
|
{
|
||||||
|
global $page;
|
||||||
|
|
||||||
|
if (isset($_GET['search'])) {
|
||||||
|
$search_terms = Tag::explode(Tag::decaret($_GET['search']));
|
||||||
|
$query = "search=" . url_escape($_GET['search']);
|
||||||
|
} else {
|
||||||
|
$search_terms = [];
|
||||||
|
$query = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$target_image = $image->get_next($search_terms);
|
||||||
|
|
||||||
|
if ($target_image == null) {
|
||||||
|
$redirect_target = referer_or(make_link("post/list"), ['post/view']);
|
||||||
|
} else {
|
||||||
|
$redirect_target = make_link("post/view/{$target_image->id}", null, $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
|
$page->set_redirect($redirect_target);
|
||||||
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class Querylet
|
class Querylet
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $sql;
|
||||||
public $sql;
|
public array $variables;
|
||||||
/** @var array */
|
|
||||||
public $variables;
|
|
||||||
|
|
||||||
public function __construct(string $sql, array $variables=[])
|
public function __construct(string $sql, array $variables=[])
|
||||||
{
|
{
|
||||||
@ -31,10 +31,8 @@ class Querylet
|
|||||||
|
|
||||||
class TagCondition
|
class TagCondition
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $tag;
|
||||||
public $tag;
|
public bool $positive;
|
||||||
/** @var bool */
|
|
||||||
public $positive;
|
|
||||||
|
|
||||||
public function __construct(string $tag, bool $positive)
|
public function __construct(string $tag, bool $positive)
|
||||||
{
|
{
|
||||||
@ -45,10 +43,8 @@ class TagCondition
|
|||||||
|
|
||||||
class ImgCondition
|
class ImgCondition
|
||||||
{
|
{
|
||||||
/** @var Querylet */
|
public Querylet $qlet;
|
||||||
public $qlet;
|
public bool $positive;
|
||||||
/** @var bool */
|
|
||||||
public $positive;
|
|
||||||
|
|
||||||
public function __construct(Querylet $qlet, bool $positive)
|
public function __construct(Querylet $qlet, bool $positive)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Class Tag
|
* Class Tag
|
||||||
*
|
*
|
||||||
|
@ -49,7 +49,6 @@ function get_dsn()
|
|||||||
{
|
{
|
||||||
if (getenv("INSTALL_DSN")) {
|
if (getenv("INSTALL_DSN")) {
|
||||||
$dsn = getenv("INSTALL_DSN");
|
$dsn = getenv("INSTALL_DSN");
|
||||||
;
|
|
||||||
} elseif (@$_POST["database_type"] == DatabaseDriver::SQLITE) {
|
} elseif (@$_POST["database_type"] == DatabaseDriver::SQLITE) {
|
||||||
/** @noinspection PhpUnhandledExceptionInspection */
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
$id = bin2hex(random_bytes(5));
|
$id = bin2hex(random_bytes(5));
|
||||||
@ -69,7 +68,7 @@ function do_install($dsn)
|
|||||||
create_tables(new Database($dsn));
|
create_tables(new Database($dsn));
|
||||||
write_config($dsn);
|
write_config($dsn);
|
||||||
} catch (InstallerException $e) {
|
} catch (InstallerException $e) {
|
||||||
die_nicely($e->title, $e->body, $e->code);
|
die_nicely($e->title, $e->body, $e->exit_code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +255,7 @@ function create_tables(Database $db)
|
|||||||
width INTEGER NOT NULL,
|
width INTEGER NOT NULL,
|
||||||
height INTEGER NOT NULL,
|
height INTEGER NOT NULL,
|
||||||
posted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
posted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
locked SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N,
|
locked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT
|
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT
|
||||||
");
|
");
|
||||||
$db->execute("CREATE INDEX images_owner_id_idx ON images(owner_id)", []);
|
$db->execute("CREATE INDEX images_owner_id_idx ON images(owner_id)", []);
|
||||||
@ -282,13 +281,19 @@ function create_tables(Database $db)
|
|||||||
$db->execute("CREATE INDEX images_tags_tag_id_idx ON image_tags(tag_id)", []);
|
$db->execute("CREATE INDEX images_tags_tag_id_idx ON image_tags(tag_id)", []);
|
||||||
|
|
||||||
$db->execute("INSERT INTO config(name, value) VALUES('db_version', 11)");
|
$db->execute("INSERT INTO config(name, value) VALUES('db_version', 11)");
|
||||||
|
|
||||||
|
// mysql auto-commits when creating a table, so the transaction
|
||||||
|
// is closed; other databases need to commit
|
||||||
|
if ($db->is_transaction_open()) {
|
||||||
$db->commit();
|
$db->commit();
|
||||||
|
}
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
throw new InstallerException(
|
throw new InstallerException(
|
||||||
"PDO Error:",
|
"PDO Error:",
|
||||||
"<p>An error occurred while trying to create the database tables necessary for Shimmie.</p>
|
"<p>An error occurred while trying to create the database tables necessary for Shimmie.</p>
|
||||||
<p>Please check and ensure that the database configuration options are all correct.</p>
|
<p>Please check and ensure that the database configuration options are all correct.</p>
|
||||||
<p>{$e->getMessage()}</p>",
|
<p>{$e->getMessage()}</p>
|
||||||
|
",
|
||||||
3
|
3
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||||
* Logging convenience *
|
* Logging convenience *
|
||||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
// action_object_attribute
|
// action_object_attribute
|
||||||
// action = create / view / edit / delete
|
// action = create / view / edit / delete
|
||||||
@ -7,6 +9,9 @@ abstract class Permissions
|
|||||||
{
|
{
|
||||||
public const CHANGE_SETTING = "change_setting"; # modify web-level settings, eg the config table
|
public const CHANGE_SETTING = "change_setting"; # modify web-level settings, eg the config table
|
||||||
public const OVERRIDE_CONFIG = "override_config"; # modify sys-level settings, eg shimmie.conf.php
|
public const OVERRIDE_CONFIG = "override_config"; # modify sys-level settings, eg shimmie.conf.php
|
||||||
|
public const CHANGE_USER_SETTING = "change_user_setting"; # modify own user-level settings
|
||||||
|
public const CHANGE_OTHER_USER_SETTING = "change_other_user_setting"; # modify own user-level settings
|
||||||
|
|
||||||
public const BIG_SEARCH = "big_search"; # search for more than 3 tags at once (speed mode only)
|
public const BIG_SEARCH = "big_search"; # search for more than 3 tags at once (speed mode only)
|
||||||
|
|
||||||
public const MANAGE_EXTENSION_LIST = "manage_extension_list";
|
public const MANAGE_EXTENSION_LIST = "manage_extension_list";
|
||||||
@ -100,6 +105,7 @@ abstract class Permissions
|
|||||||
public const SET_PRIVATE_IMAGE = "set_private_image";
|
public const SET_PRIVATE_IMAGE = "set_private_image";
|
||||||
public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images";
|
public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images";
|
||||||
|
|
||||||
|
public const CRON_RUN = "cron_run";
|
||||||
public const BULK_IMPORT = "bulk_import";
|
public const BULK_IMPORT = "bulk_import";
|
||||||
public const BULK_EXPORT = "bulk_export";
|
public const BULK_EXPORT = "bulk_export";
|
||||||
public const BULK_DOWNLOAD = "bulk_download";
|
public const BULK_DOWNLOAD = "bulk_download";
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||||
* Things which should be in the core API *
|
* Things which should be in the core API *
|
||||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
require_once "filetypes.php";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the unique elements of an array, case insensitively
|
* Return the unique elements of an array, case insensitively
|
||||||
*/
|
*/
|
||||||
@ -127,10 +126,16 @@ function list_files(string $base, string $_sub_dir=""): array
|
|||||||
|
|
||||||
$files = [];
|
$files = [];
|
||||||
$dir = opendir("$base/$_sub_dir");
|
$dir = opendir("$base/$_sub_dir");
|
||||||
|
if ($dir===false) {
|
||||||
|
throw new SCoreException("Unable to open directory $base/$_sub_dir");
|
||||||
|
}
|
||||||
|
try {
|
||||||
while ($f = readdir($dir)) {
|
while ($f = readdir($dir)) {
|
||||||
$files[] = $f;
|
$files[] = $f;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
closedir($dir);
|
closedir($dir);
|
||||||
|
}
|
||||||
sort($files);
|
sort($files);
|
||||||
|
|
||||||
foreach ($files as $filename) {
|
foreach ($files as $filename) {
|
||||||
@ -187,8 +192,8 @@ function stream_file(string $file, int $start, int $end): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/function.http-parse-headers.php#112917
|
# http://www.php.net/manual/en/function.http-parse-headers.php#112917
|
||||||
|
if (!function_exists('http_parse_headers')) {
|
||||||
/**
|
/**
|
||||||
* #return string[]
|
* #return string[]
|
||||||
*/
|
*/
|
||||||
@ -219,7 +224,7 @@ if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/func
|
|||||||
* HTTP Headers can sometimes be lowercase which will cause issues.
|
* HTTP Headers can sometimes be lowercase which will cause issues.
|
||||||
* In cases like these, we need to make sure to check for them if the camelcase version does not exist.
|
* In cases like these, we need to make sure to check for them if the camelcase version does not exist.
|
||||||
*/
|
*/
|
||||||
function findHeader(array $headers, string $name): ?string
|
function find_header(array $headers, string $name): ?string
|
||||||
{
|
{
|
||||||
if (!is_array($headers)) {
|
if (!is_array($headers)) {
|
||||||
return null;
|
return null;
|
||||||
@ -243,21 +248,22 @@ function findHeader(array $headers, string $name): ?string
|
|||||||
|
|
||||||
if (!function_exists('mb_strlen')) {
|
if (!function_exists('mb_strlen')) {
|
||||||
// TODO: we should warn the admin that they are missing multibyte support
|
// TODO: we should warn the admin that they are missing multibyte support
|
||||||
function mb_strlen($str, $encoding)
|
/** @noinspection PhpUnusedParameterInspection */
|
||||||
|
function mb_strlen($str, $encoding): int
|
||||||
{
|
{
|
||||||
return strlen($str);
|
return strlen($str);
|
||||||
}
|
}
|
||||||
function mb_internal_encoding($encoding)
|
function mb_internal_encoding($encoding): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
function mb_strtolower($str)
|
function mb_strtolower($str): string
|
||||||
{
|
{
|
||||||
return strtolower($str);
|
return strtolower($str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @noinspection PhpUnhandledExceptionInspection */
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
function getSubclassesOf(string $parent)
|
function get_subclasses_of(string $parent): array
|
||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach (get_declared_classes() as $class) {
|
foreach (get_declared_classes() as $class) {
|
||||||
@ -324,7 +330,7 @@ function get_base_href(): string
|
|||||||
/**
|
/**
|
||||||
* The opposite of the standard library's parse_url
|
* The opposite of the standard library's parse_url
|
||||||
*/
|
*/
|
||||||
function unparse_url($parsed_url)
|
function unparse_url(array $parsed_url): string
|
||||||
{
|
{
|
||||||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
|
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
|
||||||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||||||
@ -338,17 +344,26 @@ function unparse_url($parsed_url)
|
|||||||
return "$scheme$user$pass$host$port$path$query$fragment";
|
return "$scheme$user$pass$host$port$path$query$fragment";
|
||||||
}
|
}
|
||||||
|
|
||||||
function startsWith(string $haystack, string $needle): bool
|
# finally in the core library starting from php8
|
||||||
{
|
if (!function_exists('str_starts_with')) {
|
||||||
$length = strlen($needle);
|
function str_starts_with(string $haystack, string $needle): bool
|
||||||
return (substr($haystack, 0, $length) === $needle);
|
{
|
||||||
|
return strncmp($haystack, $needle, strlen($needle)) === 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function endsWith(string $haystack, string $needle): bool
|
if (!function_exists('str_ends_with')) {
|
||||||
{
|
function str_ends_with(string $haystack, string $needle): bool
|
||||||
$length = strlen($needle);
|
{
|
||||||
$start = $length * -1; //negative
|
return $needle === '' || $needle === substr($haystack, - strlen($needle));
|
||||||
return (substr($haystack, $start) === $needle);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('str_contains')) {
|
||||||
|
function str_contains(string $haystack, string $needle): bool
|
||||||
|
{
|
||||||
|
return '' === $needle || false !== strpos($haystack, $needle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||||
@ -475,25 +490,6 @@ function clamp(?int $val, ?int $min=null, ?int $max=null): int
|
|||||||
return $val;
|
return $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
function xml_tag(string $name, array $attrs=[], array $children=[]): string
|
|
||||||
{
|
|
||||||
$xml = "<$name ";
|
|
||||||
foreach ($attrs as $k => $v) {
|
|
||||||
$xv = str_replace(''', ''', htmlspecialchars((string)$v, ENT_QUOTES));
|
|
||||||
$xml .= "$k=\"$xv\" ";
|
|
||||||
}
|
|
||||||
if (count($children) > 0) {
|
|
||||||
$xml .= ">\n";
|
|
||||||
foreach ($children as $child) {
|
|
||||||
$xml .= xml_tag($child);
|
|
||||||
}
|
|
||||||
$xml .= "</$name>\n";
|
|
||||||
} else {
|
|
||||||
$xml .= "/>\n";
|
|
||||||
}
|
|
||||||
return $xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Original PHP code by Chirp Internet: www.chirp.com.au
|
* Original PHP code by Chirp Internet: www.chirp.com.au
|
||||||
* Please acknowledge use of this code by including this header.
|
* Please acknowledge use of this code by including this header.
|
||||||
@ -532,7 +528,6 @@ function parse_shorthand_int(string $limit): int
|
|||||||
/** @noinspection PhpMissingBreakStatementInspection */
|
/** @noinspection PhpMissingBreakStatementInspection */
|
||||||
// no break
|
// no break
|
||||||
case 'm': $value *= 1024; // fall through
|
case 'm': $value *= 1024; // fall through
|
||||||
/** @noinspection PhpMissingBreakStatementInspection */
|
|
||||||
// no break
|
// no break
|
||||||
case 'k': $value *= 1024; break;
|
case 'k': $value *= 1024; break;
|
||||||
default: $value = -1;
|
default: $value = -1;
|
||||||
@ -563,17 +558,41 @@ function to_shorthand_int(int $int): string
|
|||||||
return (string)$int;
|
return (string)$int;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
abstract class TIME_UNITS
|
||||||
const TIME_UNITS = ["s"=>60,"m"=>60,"h"=>24,"d"=>365,"y"=>PHP_INT_MAX];
|
{
|
||||||
function format_milliseconds(int $input): string
|
public const MILLISECONDS = "ms";
|
||||||
|
public const SECONDS = "s";
|
||||||
|
public const MINUTES = "m";
|
||||||
|
public const HOURS = "h";
|
||||||
|
public const DAYS = "d";
|
||||||
|
public const YEARS = "y";
|
||||||
|
public const CONVERSION = [
|
||||||
|
self::MILLISECONDS=>1000,
|
||||||
|
self::SECONDS=>60,
|
||||||
|
self::MINUTES=>60,
|
||||||
|
self::HOURS=>24,
|
||||||
|
self::DAYS=>365,
|
||||||
|
self::YEARS=>PHP_INT_MAX
|
||||||
|
];
|
||||||
|
}
|
||||||
|
function format_milliseconds(int $input, string $min_unit = TIME_UNITS::SECONDS): string
|
||||||
{
|
{
|
||||||
$output = "";
|
$output = "";
|
||||||
|
|
||||||
$remainder = floor($input / 1000);
|
$remainder = $input;
|
||||||
|
|
||||||
foreach (TIME_UNITS as $unit=>$conversion) {
|
$found = false;
|
||||||
|
|
||||||
|
foreach (TIME_UNITS::CONVERSION as $unit=>$conversion) {
|
||||||
$count = $remainder % $conversion;
|
$count = $remainder % $conversion;
|
||||||
$remainder = floor($remainder / $conversion);
|
$remainder = floor($remainder / $conversion);
|
||||||
|
|
||||||
|
if ($found||$unit==$min_unit) {
|
||||||
|
$found = true;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($count==0&&$remainder<1) {
|
if ($count==0&&$remainder<1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -582,6 +601,32 @@ function format_milliseconds(int $input): string
|
|||||||
|
|
||||||
return trim($output);
|
return trim($output);
|
||||||
}
|
}
|
||||||
|
function parse_to_milliseconds(string $input): int
|
||||||
|
{
|
||||||
|
$output = 0;
|
||||||
|
$current_multiplier = 1;
|
||||||
|
|
||||||
|
if (preg_match('/^([0-9]+)$/i', $input, $match)) {
|
||||||
|
// If just a number, then we treat it as milliseconds
|
||||||
|
$length = $match[0];
|
||||||
|
if (is_numeric($length)) {
|
||||||
|
$length = floatval($length);
|
||||||
|
$output += $length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach (TIME_UNITS::CONVERSION as $unit=>$conversion) {
|
||||||
|
if (preg_match('/([0-9]+)'.$unit.'/i', $input, $match)) {
|
||||||
|
$length = $match[1];
|
||||||
|
if (is_numeric($length)) {
|
||||||
|
$length = floatval($length);
|
||||||
|
$output += $length * $current_multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$current_multiplier *= $conversion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return intval($output);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn a date into a time, a date, an "X minutes ago...", etc
|
* Turn a date into a time, a date, an "X minutes ago...", etc
|
||||||
@ -757,7 +802,7 @@ function iterator_map_to_array(callable $callback, iterator $iter): array
|
|||||||
return iterator_to_array(iterator_map($callback, $iter));
|
return iterator_to_array(iterator_map($callback, $iter));
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringer($s)
|
function stringer($s): string
|
||||||
{
|
{
|
||||||
if (is_array($s)) {
|
if (is_array($s)) {
|
||||||
if (isset($s[0])) {
|
if (isset($s[0])) {
|
||||||
|
@ -1,42 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/*
|
/*
|
||||||
* A small number of PHP-sanity things (eg don't silently ignore errors) to
|
* A small number of PHP-sanity things (eg don't silently ignore errors) to
|
||||||
* be included right at the very start of index.php and tests/bootstrap.php
|
* be included right at the very start of index.php and tests/bootstrap.php
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$min_php = "7.3";
|
|
||||||
if (version_compare(phpversion(), $min_php, ">=") === false) {
|
|
||||||
print "
|
|
||||||
Shimmie does not support versions of PHP lower than $min_php
|
|
||||||
(PHP reports that it is version ".phpversion().").
|
|
||||||
If your web host is running an older version, they are dangerously out of
|
|
||||||
date and you should plan on moving elsewhere.
|
|
||||||
";
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
# ini_set('zend.assertions', '1'); // generate assertions
|
|
||||||
ini_set('assert.exception', '1'); // throw exceptions when failed
|
|
||||||
set_error_handler(function ($errNo, $errStr) {
|
|
||||||
// Should we turn ALL notices into errors? PHP allows a lot of
|
|
||||||
// terrible things to happen by default...
|
|
||||||
if (strpos($errStr, 'Use of undefined constant ') === 0) {
|
|
||||||
throw new Exception("PHP Error#$errNo: $errStr");
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ob_start();
|
|
||||||
|
|
||||||
if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
|
|
||||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
|
||||||
die("CLI with remote addr? Confused, not taking the risk.");
|
|
||||||
}
|
|
||||||
$_SERVER['REMOTE_ADDR'] = "0.0.0.0";
|
|
||||||
$_SERVER['HTTP_HOST'] = "<cli command>";
|
|
||||||
}
|
|
||||||
|
|
||||||
function die_nicely($title, $body, $code=0)
|
function die_nicely($title, $body, $code=0)
|
||||||
{
|
{
|
||||||
print("<!DOCTYPE html>
|
print("<!DOCTYPE html>
|
||||||
@ -61,3 +30,33 @@ function die_nicely($title, $body, $code=0)
|
|||||||
}
|
}
|
||||||
exit($code);
|
exit($code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$min_php = "7.3";
|
||||||
|
if (version_compare(phpversion(), $min_php, ">=") === false) {
|
||||||
|
die_nicely("Not Supported", "
|
||||||
|
Shimmie does not support versions of PHP lower than $min_php
|
||||||
|
(PHP reports that it is version ".phpversion().").
|
||||||
|
", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
# ini_set('zend.assertions', '1'); // generate assertions
|
||||||
|
ini_set('assert.exception', '1'); // throw exceptions when failed
|
||||||
|
set_error_handler(function ($errNo, $errStr) {
|
||||||
|
// Should we turn ALL notices into errors? PHP allows a lot of
|
||||||
|
// terrible things to happen by default...
|
||||||
|
if (str_starts_with($errStr, 'Use of undefined constant ')) {
|
||||||
|
throw new Exception("PHP Error#$errNo: $errStr");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
|
||||||
|
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||||
|
die("CLI with remote addr? Confused, not taking the risk.");
|
||||||
|
}
|
||||||
|
$_SERVER['REMOTE_ADDR'] = "0.0.0.0";
|
||||||
|
$_SERVER['HTTP_HOST'] = "cli-command";
|
||||||
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||||
* Event API *
|
* Event API *
|
||||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
@ -35,7 +37,7 @@ function _set_event_listeners(): void
|
|||||||
global $_shm_event_listeners;
|
global $_shm_event_listeners;
|
||||||
$_shm_event_listeners = [];
|
$_shm_event_listeners = [];
|
||||||
|
|
||||||
foreach (getSubclassesOf("Extension") as $class) {
|
foreach (get_subclasses_of("Extension") as $class) {
|
||||||
/** @var Extension $extension */
|
/** @var Extension $extension */
|
||||||
$extension = new $class();
|
$extension = new $class();
|
||||||
|
|
||||||
@ -61,7 +63,7 @@ function _dump_event_listeners(array $event_listeners, string $path): void
|
|||||||
{
|
{
|
||||||
$p = "<"."?php\n";
|
$p = "<"."?php\n";
|
||||||
|
|
||||||
foreach (getSubclassesOf("Extension") as $class) {
|
foreach (get_subclasses_of("Extension") as $class) {
|
||||||
$p .= "\$$class = new $class(); ";
|
$p .= "\$$class = new $class(); ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* For any values that aren't defined in data/config/*.php,
|
* For any values that aren't defined in data/config/*.php,
|
||||||
* Shimmie will set the values to their defaults
|
* Shimmie will set the values to their defaults
|
||||||
@ -20,15 +22,16 @@ function _d(string $name, $value): void
|
|||||||
}
|
}
|
||||||
$_g = file_exists(".git") ? '+' : '';
|
$_g = file_exists(".git") ? '+' : '';
|
||||||
_d("DATABASE_DSN", null); // string PDO database connection details
|
_d("DATABASE_DSN", null); // string PDO database connection details
|
||||||
_d("DATABASE_TIMEOUT", 10000);// int Time to wait for each statement to complete
|
_d("DATABASE_TIMEOUT", 10000); // int Time to wait for each statement to complete
|
||||||
_d("CACHE_DSN", null); // string cache connection details
|
_d("CACHE_DSN", null); // string cache connection details
|
||||||
_d("DEBUG", false); // boolean print various debugging details
|
_d("DEBUG", false); // boolean print various debugging details
|
||||||
_d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-shared logins, give them different prefixes
|
_d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-shared logins, give them different prefixes
|
||||||
_d("SPEED_HAX", false); // boolean do some questionable things in the name of performance
|
_d("SPEED_HAX", false); // boolean do some questionable things in the name of performance
|
||||||
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
|
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
|
||||||
_d("VERSION", "2.8.4$_g"); // string shimmie version
|
_d("VERSION", "2.9.1$_g"); // string shimmie version
|
||||||
_d("TIMEZONE", null); // string timezone
|
_d("TIMEZONE", null); // string timezone
|
||||||
_d("EXTRA_EXTS", ""); // string optional extra extensions
|
_d("EXTRA_EXTS", ""); // string optional extra extensions
|
||||||
_d("BASE_HREF", null); // string force a specific base URL (default is auto-detect)
|
_d("BASE_HREF", null); // string force a specific base URL (default is auto-detect)
|
||||||
_d("TRACE_FILE", null); // string file to log performance data into
|
_d("TRACE_FILE", null); // string file to log performance data into
|
||||||
_d("TRACE_THRESHOLD", 0.0); // float log pages which take more time than this many seconds
|
_d("TRACE_THRESHOLD", 0.0); // float log pages which take more time than this many seconds
|
||||||
|
_d("REVERSE_PROXY_X_HEADERS", false); // boolean get request IPs from "X-Real-IP" and protocol from "X-Forwarded-Proto" HTTP headers
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
@ -9,13 +11,13 @@ class PolyfillsTest extends TestCase
|
|||||||
public function test_html_escape()
|
public function test_html_escape()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
html_escape("Foo & <waffles>"),
|
"Foo & <main>",
|
||||||
"Foo & <waffles>"
|
html_escape("Foo & <main>")
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
html_unescape("Foo & <waffles>"),
|
"Foo & <main>",
|
||||||
"Foo & <waffles>"
|
html_unescape("Foo & <main>")
|
||||||
);
|
);
|
||||||
|
|
||||||
$x = "Foo & <waffles>";
|
$x = "Foo & <waffles>";
|
||||||
@ -24,17 +26,17 @@ class PolyfillsTest extends TestCase
|
|||||||
|
|
||||||
public function test_int_escape()
|
public function test_int_escape()
|
||||||
{
|
{
|
||||||
$this->assertEquals(int_escape(""), 0);
|
$this->assertEquals(0, int_escape(""));
|
||||||
$this->assertEquals(int_escape("1"), 1);
|
$this->assertEquals(1, int_escape("1"));
|
||||||
$this->assertEquals(int_escape("-1"), -1);
|
$this->assertEquals(-1, int_escape("-1"));
|
||||||
$this->assertEquals(int_escape("-1.5"), -1);
|
$this->assertEquals(-1, int_escape("-1.5"));
|
||||||
$this->assertEquals(int_escape(null), 0);
|
$this->assertEquals(0, int_escape(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_url_escape()
|
public function test_url_escape()
|
||||||
{
|
{
|
||||||
$this->assertEquals(url_escape("^\o/^"), "%5E%5Co%2F%5E");
|
$this->assertEquals("%5E%5Co%2F%5E", url_escape("^\o/^"));
|
||||||
$this->assertEquals(url_escape(null), "");
|
$this->assertEquals("", url_escape(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_bool_escape()
|
public function test_bool_escape()
|
||||||
@ -69,41 +71,33 @@ class PolyfillsTest extends TestCase
|
|||||||
|
|
||||||
public function test_clamp()
|
public function test_clamp()
|
||||||
{
|
{
|
||||||
$this->assertEquals(clamp(0, 5, 10), 5);
|
$this->assertEquals(5, clamp(0, 5, 10));
|
||||||
$this->assertEquals(clamp(5, 5, 10), 5);
|
$this->assertEquals(5, clamp(5, 5, 10));
|
||||||
$this->assertEquals(clamp(7, 5, 10), 7);
|
$this->assertEquals(7, clamp(7, 5, 10));
|
||||||
$this->assertEquals(clamp(10, 5, 10), 10);
|
$this->assertEquals(10, clamp(10, 5, 10));
|
||||||
$this->assertEquals(clamp(15, 5, 10), 10);
|
$this->assertEquals(10, clamp(15, 5, 10));
|
||||||
}
|
|
||||||
|
|
||||||
public function test_xml_tag()
|
|
||||||
{
|
|
||||||
$this->assertEquals(
|
|
||||||
"<test foo=\"bar\" >\n<cake />\n</test>\n",
|
|
||||||
xml_tag("test", ["foo"=>"bar"], ["cake"])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_truncate()
|
public function test_truncate()
|
||||||
{
|
{
|
||||||
$this->assertEquals(truncate("test words", 10), "test words");
|
$this->assertEquals("test words", truncate("test words", 10));
|
||||||
$this->assertEquals(truncate("test...", 9), "test...");
|
$this->assertEquals("test...", truncate("test...", 9));
|
||||||
$this->assertEquals(truncate("test...", 6), "test...");
|
$this->assertEquals("test...", truncate("test...", 6));
|
||||||
$this->assertEquals(truncate("te...", 2), "te...");
|
$this->assertEquals("te...", truncate("te...", 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_to_shorthand_int()
|
public function test_to_shorthand_int()
|
||||||
{
|
{
|
||||||
$this->assertEquals(to_shorthand_int(1231231231), "1.1GB");
|
$this->assertEquals("1.1GB", to_shorthand_int(1231231231));
|
||||||
$this->assertEquals(to_shorthand_int(2), "2");
|
$this->assertEquals("2", to_shorthand_int(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_parse_shorthand_int()
|
public function test_parse_shorthand_int()
|
||||||
{
|
{
|
||||||
$this->assertEquals(parse_shorthand_int("foo"), -1);
|
$this->assertEquals(-1, parse_shorthand_int("foo"));
|
||||||
$this->assertEquals(parse_shorthand_int("32M"), 33554432);
|
$this->assertEquals(33554432, parse_shorthand_int("32M"));
|
||||||
$this->assertEquals(parse_shorthand_int("43.4KB"), 44441);
|
$this->assertEquals(44441, parse_shorthand_int("43.4KB"));
|
||||||
$this->assertEquals(parse_shorthand_int("1231231231"), 1231231231);
|
$this->assertEquals(1231231231, parse_shorthand_int("1231231231"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_format_milliseconds()
|
public function test_format_milliseconds()
|
||||||
@ -113,6 +107,13 @@ class PolyfillsTest extends TestCase
|
|||||||
$this->assertEquals("1y 213d 16h 53m 20s", format_milliseconds(50000000000));
|
$this->assertEquals("1y 213d 16h 53m 20s", format_milliseconds(50000000000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_parse_to_milliseconds()
|
||||||
|
{
|
||||||
|
$this->assertEquals(10, parse_to_milliseconds("10"));
|
||||||
|
$this->assertEquals(5000, parse_to_milliseconds("5s"));
|
||||||
|
$this->assertEquals(50000000000, parse_to_milliseconds("1y 213d 16h 53m 20s"));
|
||||||
|
}
|
||||||
|
|
||||||
public function test_autodate()
|
public function test_autodate()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
@ -43,13 +45,13 @@ class UrlsTest extends TestCase
|
|||||||
{
|
{
|
||||||
// relative to shimmie install
|
// relative to shimmie install
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
"http://<cli command>/test/foo",
|
"http://cli-command/test/foo",
|
||||||
make_http("foo")
|
make_http("foo")
|
||||||
);
|
);
|
||||||
|
|
||||||
// relative to web server
|
// relative to web server
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
"http://<cli command>/foo",
|
"http://cli-command/foo",
|
||||||
make_http("/foo")
|
make_http("/foo")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class Link
|
class Link
|
||||||
{
|
{
|
||||||
public $page;
|
public ?string $page;
|
||||||
public $query;
|
public ?string $query;
|
||||||
|
|
||||||
public function __construct(?string $page=null, ?string $query=null)
|
public function __construct(?string $page=null, ?string $query=null)
|
||||||
{
|
{
|
||||||
@ -79,7 +81,7 @@ function modify_url(string $url, array $changes): string
|
|||||||
*/
|
*/
|
||||||
function make_http(string $link): string
|
function make_http(string $link): string
|
||||||
{
|
{
|
||||||
if (strpos($link, "://") > 0) {
|
if (str_contains($link, "://")) {
|
||||||
return $link;
|
return $link;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +107,7 @@ function referer_or(string $dest, ?array $blacklist=null): string
|
|||||||
}
|
}
|
||||||
if ($blacklist) {
|
if ($blacklist) {
|
||||||
foreach ($blacklist as $b) {
|
foreach ($blacklist as $b) {
|
||||||
if (strstr($_SERVER['HTTP_REFERER'], $b)) {
|
if (str_contains($_SERVER['HTTP_REFERER'], $b)) {
|
||||||
return $dest;
|
return $dest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
function _new_user(array $row): User
|
function _new_user(array $row): User
|
||||||
{
|
{
|
||||||
@ -15,22 +17,12 @@ function _new_user(array $row): User
|
|||||||
*/
|
*/
|
||||||
class User
|
class User
|
||||||
{
|
{
|
||||||
/** @var int */
|
public int $id;
|
||||||
public $id;
|
public string $name;
|
||||||
|
public ?string $email;
|
||||||
/** @var string */
|
public string $join_date;
|
||||||
public $name;
|
public ?string $passhash;
|
||||||
|
public UserClass $class;
|
||||||
/** @var string */
|
|
||||||
public $email;
|
|
||||||
|
|
||||||
public $join_date;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
public $passhash;
|
|
||||||
|
|
||||||
/** @var UserClass */
|
|
||||||
public $class;
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
* Initialisation *
|
* Initialisation *
|
||||||
@ -107,7 +99,7 @@ class User
|
|||||||
{
|
{
|
||||||
$u = User::by_name($name);
|
$u = User::by_name($name);
|
||||||
if (is_null($u)) {
|
if (is_null($u)) {
|
||||||
throw new ScoreException("Can't find any user named $name");
|
throw new UserDoesNotExist("Can't find any user named $name");
|
||||||
} else {
|
} else {
|
||||||
return $u->id;
|
return $u->id;
|
||||||
}
|
}
|
||||||
@ -118,7 +110,7 @@ class User
|
|||||||
$my_user = User::by_name($name);
|
$my_user = User::by_name($name);
|
||||||
|
|
||||||
// If user tried to log in as "foo bar" and failed, try "foo_bar"
|
// If user tried to log in as "foo bar" and failed, try "foo_bar"
|
||||||
if (!$my_user && strpos($name, " ") !== false) {
|
if (!$my_user && str_contains($name, " ")) {
|
||||||
$my_user = User::by_name(str_replace(" ", "_", $name));
|
$my_user = User::by_name(str_replace(" ", "_", $name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +155,7 @@ class User
|
|||||||
public function set_class(string $class): void
|
public function set_class(string $class): void
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
$database->Execute("UPDATE users SET class=:class WHERE id=:id", ["class"=>$class, "id"=>$this->id]);
|
$database->execute("UPDATE users SET class=:class WHERE id=:id", ["class"=>$class, "id"=>$this->id]);
|
||||||
log_info("core-user", 'Set class for '.$this->name.' to '.$class);
|
log_info("core-user", 'Set class for '.$this->name.' to '.$class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +167,7 @@ class User
|
|||||||
}
|
}
|
||||||
$old_name = $this->name;
|
$old_name = $this->name;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$database->Execute("UPDATE users SET name=:name WHERE id=:id", ["name"=>$this->name, "id"=>$this->id]);
|
$database->execute("UPDATE users SET name=:name WHERE id=:id", ["name"=>$this->name, "id"=>$this->id]);
|
||||||
log_info("core-user", "Changed username for {$old_name} to {$this->name}");
|
log_info("core-user", "Changed username for {$old_name} to {$this->name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +177,7 @@ class User
|
|||||||
$hash = password_hash($password, PASSWORD_BCRYPT);
|
$hash = password_hash($password, PASSWORD_BCRYPT);
|
||||||
if (is_string($hash)) {
|
if (is_string($hash)) {
|
||||||
$this->passhash = $hash;
|
$this->passhash = $hash;
|
||||||
$database->Execute("UPDATE users SET pass=:hash WHERE id=:id", ["hash"=>$this->passhash, "id"=>$this->id]);
|
$database->execute("UPDATE users SET pass=:hash WHERE id=:id", ["hash"=>$this->passhash, "id"=>$this->id]);
|
||||||
log_info("core-user", 'Set password for '.$this->name);
|
log_info("core-user", 'Set password for '.$this->name);
|
||||||
} else {
|
} else {
|
||||||
throw new SCoreException("Failed to hash password");
|
throw new SCoreException("Failed to hash password");
|
||||||
@ -195,7 +187,7 @@ class User
|
|||||||
public function set_email(string $address): void
|
public function set_email(string $address): void
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
$database->Execute("UPDATE users SET email=:email WHERE id=:id", ["email"=>$address, "id"=>$this->id]);
|
$database->execute("UPDATE users SET email=:email WHERE id=:id", ["email"=>$address, "id"=>$this->id]);
|
||||||
log_info("core-user", 'Set email for '.$this->name);
|
log_info("core-user", 'Set email for '.$this->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* @global UserClass[] $_shm_user_classes
|
* @global UserClass[] $_shm_user_classes
|
||||||
*/
|
*/
|
||||||
@ -10,21 +12,9 @@ $_shm_user_classes = [];
|
|||||||
*/
|
*/
|
||||||
class UserClass
|
class UserClass
|
||||||
{
|
{
|
||||||
|
public ?string $name = null;
|
||||||
/**
|
public ?UserClass $parent = null;
|
||||||
* @var ?string
|
public array $abilities = [];
|
||||||
*/
|
|
||||||
public $name = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ?UserClass
|
|
||||||
*/
|
|
||||||
public $parent = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public $abilities = [];
|
|
||||||
|
|
||||||
public function __construct(string $name, string $parent = null, array $abilities = [])
|
public function __construct(string $name, string $parent = null, array $abilities = [])
|
||||||
{
|
{
|
||||||
@ -100,6 +90,7 @@ new UserClass("user", "base", [
|
|||||||
Permissions::READ_PM => true,
|
Permissions::READ_PM => true,
|
||||||
Permissions::SET_PRIVATE_IMAGE => true,
|
Permissions::SET_PRIVATE_IMAGE => true,
|
||||||
Permissions::BULK_DOWNLOAD => true,
|
Permissions::BULK_DOWNLOAD => true,
|
||||||
|
Permissions::CHANGE_USER_SETTING => true
|
||||||
]);
|
]);
|
||||||
|
|
||||||
new UserClass("hellbanned", "user", [
|
new UserClass("hellbanned", "user", [
|
||||||
@ -108,6 +99,8 @@ new UserClass("hellbanned", "user", [
|
|||||||
|
|
||||||
new UserClass("admin", "base", [
|
new UserClass("admin", "base", [
|
||||||
Permissions::CHANGE_SETTING => true,
|
Permissions::CHANGE_SETTING => true,
|
||||||
|
Permissions::CHANGE_USER_SETTING => true,
|
||||||
|
Permissions::CHANGE_OTHER_USER_SETTING => true,
|
||||||
Permissions::OVERRIDE_CONFIG => true,
|
Permissions::OVERRIDE_CONFIG => true,
|
||||||
Permissions::BIG_SEARCH => true,
|
Permissions::BIG_SEARCH => true,
|
||||||
|
|
||||||
@ -200,6 +193,8 @@ new UserClass("admin", "base", [
|
|||||||
Permissions::APPROVE_IMAGE => true,
|
Permissions::APPROVE_IMAGE => true,
|
||||||
Permissions::APPROVE_COMMENT => true,
|
Permissions::APPROVE_COMMENT => true,
|
||||||
|
|
||||||
|
Permissions::CRON_RUN =>true,
|
||||||
|
|
||||||
Permissions::BULK_IMPORT =>true,
|
Permissions::BULK_IMPORT =>true,
|
||||||
Permissions::BULK_EXPORT =>true,
|
Permissions::BULK_EXPORT =>true,
|
||||||
Permissions::BULK_DOWNLOAD => true,
|
Permissions::BULK_DOWNLOAD => true,
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
use MicroHTML\HTMLElement;
|
||||||
use function MicroHTML\emptyHTML;
|
use function MicroHTML\emptyHTML;
|
||||||
use function MicroHTML\rawHTML;
|
use function MicroHTML\rawHTML;
|
||||||
use function MicroHTML\FORM;
|
use function MicroHTML\FORM;
|
||||||
@ -20,13 +23,6 @@ use function MicroHTML\TD;
|
|||||||
const DATA_DIR = "data";
|
const DATA_DIR = "data";
|
||||||
|
|
||||||
|
|
||||||
function mtimefile(string $file): string
|
|
||||||
{
|
|
||||||
$data_href = get_base_href();
|
|
||||||
$mtime = filemtime($file);
|
|
||||||
return "$data_href/$file?$mtime";
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_theme(): string
|
function get_theme(): string
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
@ -46,18 +42,18 @@ function contact_link(): ?string
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
startsWith($text, "http:") ||
|
str_starts_with($text, "http:") ||
|
||||||
startsWith($text, "https:") ||
|
str_starts_with($text, "https:") ||
|
||||||
startsWith($text, "mailto:")
|
str_starts_with($text, "mailto:")
|
||||||
) {
|
) {
|
||||||
return $text;
|
return $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strpos($text, "@")) {
|
if (str_contains($text, "@")) {
|
||||||
return "mailto:$text";
|
return "mailto:$text";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strpos($text, "/")) {
|
if (str_contains($text, "/")) {
|
||||||
return "http://$text";
|
return "http://$text";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +65,10 @@ function contact_link(): ?string
|
|||||||
*/
|
*/
|
||||||
function is_https_enabled(): bool
|
function is_https_enabled(): bool
|
||||||
{
|
{
|
||||||
|
// check forwarded protocol
|
||||||
|
if (REVERSE_PROXY_X_HEADERS && !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
|
||||||
|
$_SERVER['HTTPS']='on';
|
||||||
|
}
|
||||||
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +160,29 @@ function check_im_version(): int
|
|||||||
return (empty($convert_check) ? 0 : 1);
|
return (empty($convert_check) ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get request IP
|
||||||
|
*/
|
||||||
|
|
||||||
|
function get_remote_addr() {
|
||||||
|
return $_SERVER['REMOTE_ADDR'];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get real IP if behind a reverse proxy
|
||||||
|
*/
|
||||||
|
|
||||||
|
function get_real_ip() {
|
||||||
|
$ip = get_remote_addr();
|
||||||
|
if (REVERSE_PROXY_X_HEADERS && isset($_SERVER['HTTP_X_REAL_IP'])) {
|
||||||
|
$ip = $_SERVER['HTTP_X_REAL_IP'];
|
||||||
|
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||||
|
$ip = "0.0.0.0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the currently active IP, masked to make it not change when the last
|
* Get the currently active IP, masked to make it not change when the last
|
||||||
* octet or two change, for use in session cookies and such
|
* octet or two change, for use in session cookies and such
|
||||||
@ -167,7 +190,7 @@ function check_im_version(): int
|
|||||||
function get_session_ip(Config $config): string
|
function get_session_ip(Config $config): string
|
||||||
{
|
{
|
||||||
$mask = $config->get_string("session_hash_mask", "255.255.0.0");
|
$mask = $config->get_string("session_hash_mask", "255.255.0.0");
|
||||||
$addr = $_SERVER['REMOTE_ADDR'];
|
$addr = get_real_ip();
|
||||||
$addr = inet_ntop(inet_pton($addr) & inet_pton($mask));
|
$addr = inet_ntop(inet_pton($addr) & inet_pton($mask));
|
||||||
return $addr;
|
return $addr;
|
||||||
}
|
}
|
||||||
@ -259,11 +282,11 @@ function load_balance_url(string $tmpl, string $hash, int $n=0): string
|
|||||||
return $tmpl;
|
return $tmpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
function transload(string $url, string $mfile): ?array
|
function fetch_url(string $url, string $mfile): ?array
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
if ($config->get_string("transload_engine") === "curl" && function_exists("curl_init")) {
|
if ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) === "curl" && function_exists("curl_init")) {
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
$fp = fopen($mfile, "w");
|
$fp = fopen($mfile, "w");
|
||||||
|
|
||||||
@ -276,8 +299,7 @@ function transload(string $url, string $mfile): ?array
|
|||||||
|
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
if ($response === false) {
|
if ($response === false) {
|
||||||
log_warning("core-util", "Failed to transload $url");
|
return null;
|
||||||
throw new SCoreException("Failed to fetch $url");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||||
@ -291,7 +313,7 @@ function transload(string $url, string $mfile): ?array
|
|||||||
return $headers;
|
return $headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($config->get_string("transload_engine") === "wget") {
|
if ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) === "wget") {
|
||||||
$s_url = escapeshellarg($url);
|
$s_url = escapeshellarg($url);
|
||||||
$s_mfile = escapeshellarg($mfile);
|
$s_mfile = escapeshellarg($mfile);
|
||||||
system("wget --no-check-certificate $s_url --output-document=$s_mfile");
|
system("wget --no-check-certificate $s_url --output-document=$s_mfile");
|
||||||
@ -299,14 +321,14 @@ function transload(string $url, string $mfile): ?array
|
|||||||
return file_exists($mfile) ? ["ok"=>"true"] : null;
|
return file_exists($mfile) ? ["ok"=>"true"] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($config->get_string("transload_engine") === "fopen") {
|
if ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) === "fopen") {
|
||||||
$fp_in = @fopen($url, "r");
|
$fp_in = @fopen($url, "r");
|
||||||
$fp_out = fopen($mfile, "w");
|
$fp_out = fopen($mfile, "w");
|
||||||
if (!$fp_in || !$fp_out) {
|
if (!$fp_in || !$fp_out) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$length = 0;
|
$length = 0;
|
||||||
while (!feof($fp_in) && $length <= $config->get_int('upload_size')) {
|
while (!feof($fp_in) && $length <= $config->get_int(UploadConfig::SIZE)) {
|
||||||
$data = fread($fp_in, 8192);
|
$data = fread($fp_in, 8192);
|
||||||
$length += strlen($data);
|
$length += strlen($data);
|
||||||
fwrite($fp_out, $data);
|
fwrite($fp_out, $data);
|
||||||
@ -348,7 +370,7 @@ function path_to_tags(string $path): string
|
|||||||
// which is for inheriting to tags on the subfolder
|
// which is for inheriting to tags on the subfolder
|
||||||
$category_to_inherit = $tag;
|
$category_to_inherit = $tag;
|
||||||
} else {
|
} else {
|
||||||
if ($category!=""&&strpos($tag, ":") === false) {
|
if ($category!="" && !str_contains($tag, ":")) {
|
||||||
// This indicates that category inheritance is active,
|
// This indicates that category inheritance is active,
|
||||||
// and we've encountered a tag that does not specify a category.
|
// and we've encountered a tag that does not specify a category.
|
||||||
// So we attach the inherited category to the tag.
|
// So we attach the inherited category to the tag.
|
||||||
@ -367,7 +389,7 @@ function path_to_tags(string $path): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function join_url(string $base, string ...$paths)
|
function join_url(string $base, string ...$paths): string
|
||||||
{
|
{
|
||||||
$output = $base;
|
$output = $base;
|
||||||
foreach ($paths as $path) {
|
foreach ($paths as $path) {
|
||||||
@ -418,7 +440,7 @@ function remove_empty_dirs(string $dir): bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($result===true) {
|
if ($result===true) {
|
||||||
$result = $result && rmdir($dir);
|
$result = rmdir($dir);
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
@ -592,7 +614,6 @@ function _get_themelet_files(string $_theme): array
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to display fatal errors to the web user.
|
* Used to display fatal errors to the web user.
|
||||||
* @noinspection PhpPossiblePolymorphicInvocationInspection
|
|
||||||
*/
|
*/
|
||||||
function _fatal_error(Exception $e): void
|
function _fatal_error(Exception $e): void
|
||||||
{
|
{
|
||||||
@ -600,6 +621,7 @@ function _fatal_error(Exception $e): void
|
|||||||
$message = $e->getMessage();
|
$message = $e->getMessage();
|
||||||
$phpver = phpversion();
|
$phpver = phpversion();
|
||||||
$query = is_subclass_of($e, "SCoreException") ? $e->query : null;
|
$query = is_subclass_of($e, "SCoreException") ? $e->query : null;
|
||||||
|
$code = is_subclass_of($e, "SCoreException") ? $e->http_code : 500;
|
||||||
|
|
||||||
//$hash = exec("git rev-parse HEAD");
|
//$hash = exec("git rev-parse HEAD");
|
||||||
//$h_hash = $hash ? "<p><b>Hash:</b> $hash" : "";
|
//$h_hash = $hash ? "<p><b>Hash:</b> $hash" : "";
|
||||||
@ -624,7 +646,10 @@ function _fatal_error(Exception $e): void
|
|||||||
print("Version: $version (on $phpver)\n");
|
print("Version: $version (on $phpver)\n");
|
||||||
} else {
|
} else {
|
||||||
$q = $query ? "" : "<p><b>Query:</b> " . html_escape($query);
|
$q = $query ? "" : "<p><b>Query:</b> " . html_escape($query);
|
||||||
header("HTTP/1.0 500 Internal Error");
|
if ($code >= 500) {
|
||||||
|
error_log("Shimmie Error: $message (Query: $query)\n{$e->getTraceAsString()}");
|
||||||
|
}
|
||||||
|
header("HTTP/1.0 $code Error");
|
||||||
echo '
|
echo '
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -663,7 +688,7 @@ function _get_user(): User
|
|||||||
|
|
||||||
function _get_query(): string
|
function _get_query(): string
|
||||||
{
|
{
|
||||||
return (@$_POST["q"]?:@$_GET["q"])?:"/";
|
return (@$_POST["q"] ?: @$_GET["q"]) ?: "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -711,7 +736,7 @@ function make_form(string $target, string $method="POST", bool $multipart=false,
|
|||||||
return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$extra_inputs;
|
return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$extra_inputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SHM_FORM(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit="")
|
function SHM_FORM(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit=""): HTMLElement
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
|
|
||||||
@ -736,19 +761,19 @@ function SHM_FORM(string $target, string $method="POST", bool $multipart=false,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SHM_SIMPLE_FORM($target, ...$children)
|
function SHM_SIMPLE_FORM($target, ...$children): HTMLElement
|
||||||
{
|
{
|
||||||
$form = SHM_FORM($target);
|
$form = SHM_FORM($target);
|
||||||
$form->appendChild(emptyHTML(...$children));
|
$form->appendChild(emptyHTML(...$children));
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SHM_SUBMIT(string $text)
|
function SHM_SUBMIT(string $text): HTMLElement
|
||||||
{
|
{
|
||||||
return INPUT(["type"=>"submit", "value"=>$text]);
|
return INPUT(["type"=>"submit", "value"=>$text]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SHM_COMMAND_EXAMPLE(string $ex, string $desc)
|
function SHM_COMMAND_EXAMPLE(string $ex, string $desc): HTMLElement
|
||||||
{
|
{
|
||||||
return DIV(
|
return DIV(
|
||||||
["class"=>"command_example"],
|
["class"=>"command_example"],
|
||||||
@ -757,7 +782,7 @@ function SHM_COMMAND_EXAMPLE(string $ex, string $desc)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot)
|
function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot): HTMLElement
|
||||||
{
|
{
|
||||||
if (is_string($foot)) {
|
if (is_string($foot)) {
|
||||||
$foot = TFOOT(TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>$foot]))));
|
$foot = TFOOT(TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>$foot]))));
|
||||||
@ -777,16 +802,16 @@ function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BYTE_DENOMINATIONS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
const BYTE_DENOMINATIONS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
function human_filesize(int $bytes, $decimals = 2)
|
function human_filesize(int $bytes, $decimals = 2): string
|
||||||
{
|
{
|
||||||
$factor = floor((strlen(strval($bytes)) - 1) / 3);
|
$factor = floor((strlen(strval($bytes)) - 1) / 3);
|
||||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @BYTE_DENOMINATIONS[$factor];
|
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @BYTE_DENOMINATIONS[$factor];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Generates a unique key for the website to prevent unauthorized access.
|
* Generates a unique key for the website to prevent unauthorized access.
|
||||||
*/
|
*/
|
||||||
function generate_key(int $length = 20)
|
function generate_key(int $length = 20): string
|
||||||
{
|
{
|
||||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
$randomString = '';
|
$randomString = '';
|
||||||
@ -797,3 +822,4 @@ function generate_key(int $length = 20)
|
|||||||
|
|
||||||
return $randomString;
|
return $randomString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +1,17 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AdminPageInfo extends ExtensionInfo
|
class AdminPageInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "admin";
|
public const KEY = "admin";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Admin Controls";
|
public string $name = "Admin Controls";
|
||||||
public $url = self::SHIMMIE_URL;
|
public string $url = self::SHIMMIE_URL;
|
||||||
public $authors = self::SHISH_AUTHOR;
|
public array $authors = self::SHISH_AUTHOR;
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Various things to make admins' lives easier";
|
public string $description = "Provides a base for various small admin functions";
|
||||||
public $documentation =
|
public bool $core = true;
|
||||||
"Various moderate-level tools for admins; for advanced, obscure, and possibly dangerous tools see the shimmie2-utils script set
|
public string $visibility = self::VISIBLE_HIDDEN;
|
||||||
<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, and remove any unused tags
|
|
||||||
<p>Database dump:
|
|
||||||
<br>Download the contents of the database in plain text format, useful for backups.
|
|
||||||
<p>Image dump:
|
|
||||||
<br>Download all the images as a .zip file (Requires ZipArchive)";
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php /** @noinspection PhpUnusedPrivateMethodInspection */
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -6,8 +7,7 @@ declare(strict_types=1);
|
|||||||
*/
|
*/
|
||||||
class AdminBuildingEvent extends Event
|
class AdminBuildingEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var Page */
|
public Page $page;
|
||||||
public $page;
|
|
||||||
|
|
||||||
public function __construct(Page $page)
|
public function __construct(Page $page)
|
||||||
{
|
{
|
||||||
@ -18,10 +18,8 @@ class AdminBuildingEvent extends Event
|
|||||||
|
|
||||||
class AdminActionEvent extends Event
|
class AdminActionEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $action;
|
||||||
public $action;
|
public bool $redirect = true;
|
||||||
/** @var bool */
|
|
||||||
public $redirect = true;
|
|
||||||
|
|
||||||
public function __construct(string $action)
|
public function __construct(string $action)
|
||||||
{
|
{
|
||||||
@ -33,11 +31,11 @@ class AdminActionEvent extends Event
|
|||||||
class AdminPage extends Extension
|
class AdminPage extends Extension
|
||||||
{
|
{
|
||||||
/** @var AdminPageTheme */
|
/** @var AdminPageTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $page, $user;
|
global $database, $page, $user;
|
||||||
|
|
||||||
if ($event->page_matches("admin")) {
|
if ($event->page_matches("admin")) {
|
||||||
if (!$user->can(Permissions::MANAGE_ADMINTOOLS)) {
|
if (!$user->can(Permissions::MANAGE_ADMINTOOLS)) {
|
||||||
@ -52,6 +50,7 @@ class AdminPage extends Extension
|
|||||||
if ($user->check_auth_token()) {
|
if ($user->check_auth_token()) {
|
||||||
log_info("admin", "Util: $action");
|
log_info("admin", "Util: $action");
|
||||||
set_time_limit(0);
|
set_time_limit(0);
|
||||||
|
$database->set_timeout(null);
|
||||||
send_event($aae);
|
send_event($aae);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +79,10 @@ class AdminPage extends Extension
|
|||||||
}
|
}
|
||||||
if ($event->cmd == "get-page") {
|
if ($event->cmd == "get-page") {
|
||||||
global $page;
|
global $page;
|
||||||
|
$_SERVER['REQUEST_URI'] = $event->args[0];
|
||||||
if (isset($event->args[1])) {
|
if (isset($event->args[1])) {
|
||||||
parse_str($event->args[1], $_GET);
|
parse_str($event->args[1], $_GET);
|
||||||
|
$_SERVER['REQUEST_URI'] .= "?" . $event->args[1];
|
||||||
}
|
}
|
||||||
send_event(new PageRequestEvent($event->args[0]));
|
send_event(new PageRequestEvent($event->args[0]));
|
||||||
$page->display();
|
$page->display();
|
||||||
@ -103,7 +104,7 @@ class AdminPage extends Extension
|
|||||||
$uid = $event->args[0];
|
$uid = $event->args[0];
|
||||||
$image = Image::by_id_or_hash($uid);
|
$image = Image::by_id_or_hash($uid);
|
||||||
if ($image) {
|
if ($image) {
|
||||||
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
|
send_event(new ThumbnailGenerationEvent($image->hash, $image->get_mime(), true));
|
||||||
} else {
|
} else {
|
||||||
print("No post with ID '$uid'\n");
|
print("No post with ID '$uid'\n");
|
||||||
}
|
}
|
||||||
@ -129,7 +130,6 @@ class AdminPage extends Extension
|
|||||||
public function onAdminBuilding(AdminBuildingEvent $event)
|
public function onAdminBuilding(AdminBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$this->theme->display_page();
|
$this->theme->display_page();
|
||||||
$this->theme->display_form();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
@ -149,46 +149,4 @@ class AdminPage extends Extension
|
|||||||
$event->add_link("Board Admin", make_link("admin"));
|
$event->add_link("Board Admin", make_link("admin"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onAdminAction(AdminActionEvent $event)
|
|
||||||
{
|
|
||||||
$action = $event->action;
|
|
||||||
if (method_exists($this, $action)) {
|
|
||||||
$event->redirect = $this->$action();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function set_tag_case()
|
|
||||||
{
|
|
||||||
global $database;
|
|
||||||
$database->execute(
|
|
||||||
"UPDATE tags SET tag=:tag1 WHERE LOWER(tag) = LOWER(:tag2)",
|
|
||||||
["tag1" => $_POST['tag'], "tag2" => $_POST['tag']]
|
|
||||||
);
|
|
||||||
log_info("admin", "Fixed the case of {$_POST['tag']}", "Fixed case");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function lowercase_all_tags()
|
|
||||||
{
|
|
||||||
global $database;
|
|
||||||
$database->execute("UPDATE tags SET tag=lower(tag)");
|
|
||||||
log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
");
|
|
||||||
$database->Execute("DELETE FROM tags WHERE count=0");
|
|
||||||
log_warning("admin", "Re-counted tags", "Re-counted tags");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class AdminPageTest extends ShimmiePHPUnitTestCase
|
class AdminPageTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testAuth()
|
public function testAuth()
|
||||||
@ -19,59 +21,6 @@ class AdminPageTest extends ShimmiePHPUnitTestCase
|
|||||||
$this->assertEquals("Admin Tools", $page->title);
|
$this->assertEquals("Admin Tools", $page->title);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLowercaseAndSetCase()
|
|
||||||
{
|
|
||||||
// Create a problem
|
|
||||||
$ts = time(); // we need a tag that hasn't been used before
|
|
||||||
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
|
|
||||||
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "TeStCase$ts");
|
|
||||||
|
|
||||||
// Validate problem
|
|
||||||
$page = $this->get_page("post/view/$image_id_1");
|
|
||||||
$this->assertEquals("Image $image_id_1: TeStCase$ts", $page->title);
|
|
||||||
|
|
||||||
// Fix
|
|
||||||
send_event(new AdminActionEvent('lowercase_all_tags'));
|
|
||||||
|
|
||||||
// Validate fix
|
|
||||||
$this->get_page("post/view/$image_id_1");
|
|
||||||
$this->assert_title("Image $image_id_1: testcase$ts");
|
|
||||||
|
|
||||||
// Change
|
|
||||||
$_POST["tag"] = "TestCase$ts";
|
|
||||||
send_event(new AdminActionEvent('set_tag_case'));
|
|
||||||
|
|
||||||
// Validate change
|
|
||||||
$this->get_page("post/view/$image_id_1");
|
|
||||||
$this->assert_title("Image $image_id_1: TestCase$ts");
|
|
||||||
}
|
|
||||||
|
|
||||||
# FIXME: make sure the admin tools actually work
|
|
||||||
public function testRecount()
|
|
||||||
{
|
|
||||||
global $database;
|
|
||||||
|
|
||||||
// Create a problem
|
|
||||||
$ts = time(); // we need a tag that hasn't been used before
|
|
||||||
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
|
|
||||||
$database->execute(
|
|
||||||
"INSERT INTO tags(tag, count) VALUES(:tag, :count)",
|
|
||||||
["tag"=>"tes$ts", "count"=>42]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fix
|
|
||||||
send_event(new AdminActionEvent('recount_tag_use'));
|
|
||||||
|
|
||||||
// Validate fix
|
|
||||||
$this->assertEquals(
|
|
||||||
0,
|
|
||||||
$database->get_one(
|
|
||||||
"SELECT count FROM tags WHERE tag = :tag",
|
|
||||||
["tag"=>"tes$ts"]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCommands()
|
public function testCommands()
|
||||||
{
|
{
|
||||||
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
|
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
use function MicroHTML\INPUT;
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AdminPageTheme extends Themelet
|
class AdminPageTheme extends Themelet
|
||||||
{
|
{
|
||||||
@ -14,41 +15,4 @@ class AdminPageTheme extends Themelet
|
|||||||
$page->set_heading("Admin Tools");
|
$page->set_heading("Admin Tools");
|
||||||
$page->add_block(new NavBlock());
|
$page->add_block(new NavBlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function button(string $name, string $action, bool $protected=false): string
|
|
||||||
{
|
|
||||||
$c_protected = $protected ? " protected" : "";
|
|
||||||
$html = make_form(make_link("admin/$action"), "POST", false, "admin$c_protected");
|
|
||||||
if ($protected) {
|
|
||||||
$html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>";
|
|
||||||
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
|
|
||||||
} else {
|
|
||||||
$html .= "<input type='submit' id='$action' value='$name'>";
|
|
||||||
}
|
|
||||||
$html .= "</form>\n";
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Show a form which links to admin_utils with POST[action] set to one of:
|
|
||||||
* 'lowercase all tags'
|
|
||||||
* 'recount tag use'
|
|
||||||
* etc
|
|
||||||
*/
|
|
||||||
public function display_form()
|
|
||||||
{
|
|
||||||
global $page;
|
|
||||||
|
|
||||||
$html = "";
|
|
||||||
$html .= $this->button("All tags to lowercase", "lowercase_all_tags", true);
|
|
||||||
$html .= $this->button("Recount tag use", "recount_tag_use", false);
|
|
||||||
$page->add_block(new Block("Misc Admin Tools", $html));
|
|
||||||
|
|
||||||
$html = (string)SHM_SIMPLE_FORM(
|
|
||||||
"admin/set_tag_case",
|
|
||||||
INPUT(["type"=>'text', "name"=>'tag', "placeholder"=>'Enter tag with correct case', "class"=>'autocomplete_tags', "autocomplete"=>'off']),
|
|
||||||
SHM_SUBMIT('Set Tag Case'),
|
|
||||||
);
|
|
||||||
$page->add_block(new Block("Set Tag Case", $html));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AliasEditorInfo extends ExtensionInfo
|
class AliasEditorInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "alias_editor";
|
public const KEY = "alias_editor";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Alias Editor";
|
public string $name = "Alias Editor";
|
||||||
public $url = self::SHIMMIE_URL;
|
public string $url = self::SHIMMIE_URL;
|
||||||
public $authors = self::SHISH_AUTHOR;
|
public array $authors = self::SHISH_AUTHOR;
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Edit the alias list";
|
public string $description = "Edit the alias list";
|
||||||
public $documentation = 'The list is visible at <a href="$site/alias/list">/alias/list</a>; only site admins can edit it, other people can view and download it';
|
public ?string $documentation = 'The list is visible at <a href="$site/alias/list">/alias/list</a>; only site admins can edit it, other people can view and download it';
|
||||||
public $core = true;
|
public bool $core = true;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use MicroCRUD\ActionColumn;
|
use MicroCRUD\ActionColumn;
|
||||||
use MicroCRUD\TextColumn;
|
use MicroCRUD\TextColumn;
|
||||||
@ -26,10 +28,8 @@ class AliasTable extends Table
|
|||||||
|
|
||||||
class AddAliasEvent extends Event
|
class AddAliasEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $oldtag;
|
||||||
public $oldtag;
|
public string $newtag;
|
||||||
/** @var string */
|
|
||||||
public $newtag;
|
|
||||||
|
|
||||||
public function __construct(string $oldtag, string $newtag)
|
public function __construct(string $oldtag, string $newtag)
|
||||||
{
|
{
|
||||||
@ -41,7 +41,7 @@ class AddAliasEvent extends Event
|
|||||||
|
|
||||||
class DeleteAliasEvent extends Event
|
class DeleteAliasEvent extends Event
|
||||||
{
|
{
|
||||||
public $oldtag;
|
public string $oldtag;
|
||||||
|
|
||||||
public function __construct(string $oldtag)
|
public function __construct(string $oldtag)
|
||||||
{
|
{
|
||||||
@ -57,7 +57,7 @@ class AddAliasException extends SCoreException
|
|||||||
class AliasEditor extends Extension
|
class AliasEditor extends Extension
|
||||||
{
|
{
|
||||||
/** @var AliasEditorTheme */
|
/** @var AliasEditorTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
@ -96,7 +96,7 @@ class AliasEditor extends Extension
|
|||||||
$this->theme->display_aliases($t->table($t->query()), $t->paginator());
|
$this->theme->display_aliases($t->table($t->query()), $t->paginator());
|
||||||
} elseif ($event->get_arg(0) == "export") {
|
} elseif ($event->get_arg(0) == "export") {
|
||||||
$page->set_mode(PageMode::DATA);
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type(MIME_TYPE_CSV);
|
$page->set_mime(MimeType::CSV);
|
||||||
$page->set_filename("aliases.csv");
|
$page->set_filename("aliases.csv");
|
||||||
$page->set_data($this->get_alias_csv($database));
|
$page->set_data($this->get_alias_csv($database));
|
||||||
} elseif ($event->get_arg(0) == "import") {
|
} elseif ($event->get_arg(0) == "import") {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class AliasEditorTest extends ShimmiePHPUnitTestCase
|
class AliasEditorTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testAliasList()
|
public function testAliasList()
|
||||||
@ -36,7 +38,7 @@ class AliasEditorTest extends ShimmiePHPUnitTestCase
|
|||||||
|
|
||||||
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
|
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
|
||||||
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
|
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
|
||||||
$this->assert_title("Image $image_id: test2");
|
$this->assert_title("Post $image_id: test2");
|
||||||
$this->get_page("post/list/test1/1"); # searching for an alias should find the master tag
|
$this->get_page("post/list/test1/1"); # searching for an alias should find the master tag
|
||||||
$this->assert_response(302);
|
$this->assert_response(302);
|
||||||
$this->get_page("post/list/test2/1"); # check that searching for the main tag still works
|
$this->get_page("post/list/test2/1"); # check that searching for the main tag still works
|
||||||
@ -67,13 +69,13 @@ class AliasEditorTest extends ShimmiePHPUnitTestCase
|
|||||||
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "onetag");
|
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "onetag");
|
||||||
$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases
|
$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases
|
||||||
$this->assert_title("multi tag");
|
$this->assert_title("multi tag");
|
||||||
$this->assert_no_text("No Images Found");
|
$this->assert_no_text("No Posts Found");
|
||||||
$this->get_page("post/list/multi/1");
|
$this->get_page("post/list/multi/1");
|
||||||
$this->assert_title("multi");
|
$this->assert_title("multi");
|
||||||
$this->assert_no_text("No Images Found");
|
$this->assert_no_text("No Posts Found");
|
||||||
$this->get_page("post/list/multi tag/1");
|
$this->get_page("post/list/multi tag/1");
|
||||||
$this->assert_title("multi tag");
|
$this->assert_title("multi tag");
|
||||||
$this->assert_no_text("No Images Found");
|
$this->assert_no_text("No Posts Found");
|
||||||
$this->delete_image($image_id_1);
|
$this->delete_image($image_id_1);
|
||||||
$this->delete_image($image_id_2);
|
$this->delete_image($image_id_2);
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AliasEditorTheme extends Themelet
|
class AliasEditorTheme extends Themelet
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class ApprovalInfo extends ExtensionInfo
|
class ApprovalInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "approval";
|
public const KEY = "approval";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Approval";
|
public string $name = "Approval";
|
||||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||||
public $license = self::LICENSE_WTFPL;
|
public string $license = self::LICENSE_WTFPL;
|
||||||
public $description = "Adds an approval step to the upload/import process.";
|
public string $description = "Adds an approval step to the upload/import process.";
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
abstract class ApprovalConfig
|
abstract class ApprovalConfig
|
||||||
{
|
{
|
||||||
const VERSION = "ext_approval_version";
|
public const VERSION = "ext_approval_version";
|
||||||
const IMAGES = "approve_images";
|
public const IMAGES = "approve_images";
|
||||||
const COMMENTS = "approve_comments";
|
public const COMMENTS = "approve_comments";
|
||||||
}
|
}
|
||||||
|
|
||||||
class Approval extends Extension
|
class Approval extends Extension
|
||||||
{
|
{
|
||||||
/** @var ApprovalTheme */
|
/** @var ApprovalTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onInitExt(InitExtEvent $event)
|
public function onInitExt(InitExtEvent $event)
|
||||||
{
|
{
|
||||||
@ -33,7 +35,7 @@ class Approval extends Extension
|
|||||||
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
|
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
|
||||||
}
|
}
|
||||||
if (empty($image_id)) {
|
if (empty($image_id)) {
|
||||||
throw new SCoreException("Can not approve image: No valid Image ID given.");
|
throw new SCoreException("Can not approve post: No valid Post ID given.");
|
||||||
}
|
}
|
||||||
|
|
||||||
self::approve_image($image_id);
|
self::approve_image($image_id);
|
||||||
@ -48,7 +50,7 @@ class Approval extends Extension
|
|||||||
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
|
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
|
||||||
}
|
}
|
||||||
if (empty($image_id)) {
|
if (empty($image_id)) {
|
||||||
throw new SCoreException("Can not disapprove image: No valid Image ID given.");
|
throw new SCoreException("Can not disapprove image: No valid Post ID given.");
|
||||||
}
|
}
|
||||||
|
|
||||||
self::disapprove_image($image_id);
|
self::disapprove_image($image_id);
|
||||||
@ -77,14 +79,14 @@ class Approval extends Extension
|
|||||||
$approval_action = $_POST["approval_action"];
|
$approval_action = $_POST["approval_action"];
|
||||||
switch ($approval_action) {
|
switch ($approval_action) {
|
||||||
case "approve_all":
|
case "approve_all":
|
||||||
$database->set_timeout(300000); // These updates can take a little bit
|
$database->set_timeout(null); // These updates can take a little bit
|
||||||
$database->execute(
|
$database->execute(
|
||||||
"UPDATE images SET approved = :true, approved_by_id = :approved_by_id WHERE approved = :false",
|
"UPDATE images SET approved = :true, approved_by_id = :approved_by_id WHERE approved = :false",
|
||||||
["approved_by_id"=>$user->id, "true"=>true, "false"=>false]
|
["approved_by_id"=>$user->id, "true"=>true, "false"=>false]
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "disapprove_all":
|
case "disapprove_all":
|
||||||
$database->set_timeout(300000); // These updates can take a little bit
|
$database->set_timeout(null); // These updates can take a little bit
|
||||||
$database->execute(
|
$database->execute(
|
||||||
"UPDATE images SET approved = :false, approved_by_id = NULL WHERE approved = :true",
|
"UPDATE images SET approved = :false, approved_by_id = NULL WHERE approved = :true",
|
||||||
["true"=>true, "false"=>false]
|
["true"=>true, "false"=>false]
|
||||||
@ -99,9 +101,9 @@ class Approval extends Extension
|
|||||||
|
|
||||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $page, $config;
|
global $page;
|
||||||
|
|
||||||
if ($config->get_bool(ApprovalConfig::IMAGES) && $event->image->approved===false && !$user->can(Permissions::APPROVE_IMAGE)) {
|
if (!$this->check_permissions(($event->image))) {
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/list"));
|
$page->set_redirect(make_link("post/list"));
|
||||||
}
|
}
|
||||||
@ -118,16 +120,16 @@ class Approval extends Extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const SEARCH_REGEXP = "/^approved:(yes|no)/";
|
public const SEARCH_REGEXP = "/^approved:(yes|no)/";
|
||||||
public function onSearchTermParse(SearchTermParseEvent $event)
|
public function onSearchTermParse(SearchTermParseEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $database, $config;
|
global $user, $config;
|
||||||
|
|
||||||
if ($config->get_bool(ApprovalConfig::IMAGES)) {
|
if ($config->get_bool(ApprovalConfig::IMAGES)) {
|
||||||
$matches = [];
|
$matches = [];
|
||||||
|
|
||||||
if (is_null($event->term) && $this->no_approval_query($event->context)) {
|
if (is_null($event->term) && $this->no_approval_query($event->context)) {
|
||||||
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_Y ")));
|
$event->add_querylet(new Querylet("approved = :true", ["true"=>true]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($event->term)) {
|
if (is_null($event->term)) {
|
||||||
@ -135,9 +137,9 @@ class Approval extends Extension
|
|||||||
}
|
}
|
||||||
if (preg_match(self::SEARCH_REGEXP, strtolower($event->term), $matches)) {
|
if (preg_match(self::SEARCH_REGEXP, strtolower($event->term), $matches)) {
|
||||||
if ($user->can(Permissions::APPROVE_IMAGE) && $matches[1] == "no") {
|
if ($user->can(Permissions::APPROVE_IMAGE) && $matches[1] == "no") {
|
||||||
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_N ")));
|
$event->add_querylet(new Querylet("approved != :true", ["true"=>true]));
|
||||||
} else {
|
} else {
|
||||||
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_Y ")));
|
$event->add_querylet(new Querylet("approved = :true", ["true"=>true]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,6 +189,26 @@ class Approval extends Extension
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function check_permissions(Image $image): bool
|
||||||
|
{
|
||||||
|
global $user, $config;
|
||||||
|
|
||||||
|
if ($config->get_bool(ApprovalConfig::IMAGES) && $image->approved===false && !$user->can(Permissions::APPROVE_IMAGE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onImageDownloading(ImageDownloadingEvent $event)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Deny images upon insufficient permissions.
|
||||||
|
**/
|
||||||
|
if (!$this->check_permissions($event->image)) {
|
||||||
|
throw new SCoreException("Access denied");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $config;
|
global $user, $config;
|
||||||
@ -241,15 +263,15 @@ class Approval extends Extension
|
|||||||
global $database;
|
global $database;
|
||||||
|
|
||||||
if ($this->get_version(ApprovalConfig::VERSION) < 1) {
|
if ($this->get_version(ApprovalConfig::VERSION) < 1) {
|
||||||
$database->execute($database->scoreql_to_sql(
|
$database->execute("ALTER TABLE images ADD COLUMN approved BOOLEAN NOT NULL DEFAULT FALSE");
|
||||||
"ALTER TABLE images ADD COLUMN approved SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N"
|
$database->execute("ALTER TABLE images ADD COLUMN approved_by_id INTEGER NULL");
|
||||||
));
|
|
||||||
$database->execute(
|
|
||||||
"ALTER TABLE images ADD COLUMN approved_by_id INTEGER NULL"
|
|
||||||
);
|
|
||||||
|
|
||||||
$database->execute("CREATE INDEX images_approved_idx ON images(approved)");
|
$database->execute("CREATE INDEX images_approved_idx ON images(approved)");
|
||||||
$this->set_version(ApprovalConfig::VERSION, 1);
|
$this->set_version(ApprovalConfig::VERSION, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->get_version(ApprovalConfig::VERSION) < 2) {
|
||||||
|
$database->standardise_boolean("images", "approved");
|
||||||
|
$this->set_version(ApprovalConfig::VERSION, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
use function MicroHTML\BR;
|
use function MicroHTML\BR;
|
||||||
use function MicroHTML\BUTTON;
|
use function MicroHTML\BUTTON;
|
||||||
use function MicroHTML\INPUT;
|
use function MicroHTML\INPUT;
|
||||||
|
|
||||||
class ApprovalTheme extends Themelet
|
class ApprovalTheme extends Themelet
|
||||||
{
|
{
|
||||||
public function get_image_admin_html(Image $image)
|
public function get_image_admin_html(Image $image): string
|
||||||
{
|
{
|
||||||
if ($image->approved===true) {
|
if ($image->approved===true) {
|
||||||
$html = SHM_SIMPLE_FORM(
|
$html = SHM_SIMPLE_FORM(
|
||||||
@ -24,26 +26,24 @@ class ApprovalTheme extends Themelet
|
|||||||
return (string)$html;
|
return (string)$html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_help_html(): string
|
||||||
public function get_help_html()
|
|
||||||
{
|
{
|
||||||
return '<p>Search for images that are approved/not approved.</p>
|
return '<p>Search for posts that are approved/not approved.</p>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>approved:yes</pre>
|
<pre>approved:yes</pre>
|
||||||
<p>Returns images that have been approved.</p>
|
<p>Returns posts that have been approved.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>approved:no</pre>
|
<pre>approved:no</pre>
|
||||||
<p>Returns images that have not been approved.</p>
|
<p>Returns posts that have not been approved.</p>
|
||||||
</div>
|
</div>
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function display_admin_block(SetupBuildingEvent $event)
|
public function display_admin_block(SetupBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$sb = new SetupBlock("Approval");
|
$sb = $event->panel->create_new_block("Approval");
|
||||||
$sb->add_bool_option(ApprovalConfig::IMAGES, "Images: ");
|
$sb->add_bool_option(ApprovalConfig::IMAGES, "Posts: ");
|
||||||
$event->panel->add_block($sb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function display_admin_form()
|
public function display_admin_form()
|
||||||
@ -52,9 +52,9 @@ class ApprovalTheme extends Themelet
|
|||||||
|
|
||||||
$html = (string)SHM_SIMPLE_FORM(
|
$html = (string)SHM_SIMPLE_FORM(
|
||||||
"admin/approval",
|
"admin/approval",
|
||||||
BUTTON(["name"=>'approval_action', "value"=>'approve_all'], "Approve All Images"),
|
BUTTON(["name"=>'approval_action', "value"=>'approve_all'], "Approve All Posts"),
|
||||||
BR(),
|
BR(),
|
||||||
BUTTON(["name"=>'approval_action', "value"=>'disapprove_all'], "Disapprove All Images"),
|
BUTTON(["name"=>'approval_action', "value"=>'disapprove_all'], "Disapprove All Posts"),
|
||||||
);
|
);
|
||||||
$page->add_block(new Block("Approval", $html));
|
$page->add_block(new Block("Approval", $html));
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class ArtistsInfo extends ExtensionInfo
|
class ArtistsInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "artists";
|
public const KEY = "artists";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Artists System";
|
public string $name = "Artists System";
|
||||||
public $url = self::SHIMMIE_URL;
|
public string $url = self::SHIMMIE_URL;
|
||||||
public $authors = ["Sein Kraft"=>"mail@seinkraft.info","Alpha"=>"alpha@furries.com.ar"];
|
public array $authors = ["Sein Kraft"=>"mail@seinkraft.info","Alpha"=>"alpha@furries.com.ar"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Simple artists extension";
|
public string $description = "Simple artists extension";
|
||||||
public $beta = true;
|
public bool $beta = true;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AuthorSetEvent extends Event
|
class AuthorSetEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var Image */
|
public Image $image;
|
||||||
public $image;
|
public User $user;
|
||||||
/** @var User */
|
public string $author;
|
||||||
public $user;
|
|
||||||
/** @var string */
|
|
||||||
public $author;
|
|
||||||
|
|
||||||
public function __construct(Image $image, User $user, string $author)
|
public function __construct(Image $image, User $user, string $author)
|
||||||
{
|
{
|
||||||
@ -21,7 +20,7 @@ class AuthorSetEvent extends Event
|
|||||||
class Artists extends Extension
|
class Artists extends Extension
|
||||||
{
|
{
|
||||||
/** @var ArtistsTheme */
|
/** @var ArtistsTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onImageInfoSet(ImageInfoSetEvent $event)
|
public function onImageInfoSet(ImageInfoSetEvent $event)
|
||||||
{
|
{
|
||||||
@ -553,7 +552,7 @@ class Artists extends Extension
|
|||||||
$urlsAsString = $inputs["urls"];
|
$urlsAsString = $inputs["urls"];
|
||||||
$urlsIDsAsString = $inputs["urlsIDs"];
|
$urlsIDsAsString = $inputs["urlsIDs"];
|
||||||
|
|
||||||
if (strpos($name, " ")) {
|
if (str_contains($name, " ")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,7 +682,7 @@ class Artists extends Extension
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function add_artist()
|
private function add_artist(): int
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
$inputs = validate_input([
|
$inputs = validate_input([
|
||||||
@ -695,7 +694,7 @@ class Artists extends Extension
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$name = $inputs["name"];
|
$name = $inputs["name"];
|
||||||
if (strpos($name, " ")) {
|
if (str_contains($name, " ")) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class ArtistsTest extends ShimmiePHPUnitTestCase
|
class ArtistsTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testSearch()
|
public function testSearch()
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class ArtistsTheme extends Themelet
|
class ArtistsTheme extends Themelet
|
||||||
{
|
{
|
||||||
public function get_author_editor_html(string $author): string
|
public function get_author_editor_html(string $author): string
|
||||||
@ -408,7 +410,7 @@ class ArtistsTheme extends Themelet
|
|||||||
'</span>';
|
'</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->add_block(new Block("Artist Images", $artist_images, "main", 20));
|
$page->add_block(new Block("Artist Posts", $artist_images, "main", 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function render_aliases(array $aliases, bool $userIsLogged, bool $userIsAdmin): string
|
private function render_aliases(array $aliases, bool $userIsLogged, bool $userIsAdmin): string
|
||||||
@ -546,12 +548,12 @@ class ArtistsTheme extends Themelet
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_help_html()
|
public function get_help_html(): string
|
||||||
{
|
{
|
||||||
return '<p>Search for images with a particular artist.</p>
|
return '<p>Search for posts with a particular artist.</p>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>artist=leonardo</pre>
|
<pre>artist=leonardo</pre>
|
||||||
<p>Returns images with the artist "leonardo".</p>
|
<p>Returns posts with the artist "leonardo".</p>
|
||||||
</div>
|
</div>
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
abstract class AutoTaggerConfig
|
abstract class AutoTaggerConfig
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AutoTaggerInfo extends ExtensionInfo
|
class AutoTaggerInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "auto_tagger";
|
public const KEY = "auto_tagger";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Auto-Tagger";
|
public string $name = "Auto-Tagger";
|
||||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||||
public $license = self::LICENSE_WTFPL;
|
public string $license = self::LICENSE_WTFPL;
|
||||||
public $description = "Provides several automatic tagging functions";
|
public string $description = "Provides several automatic tagging functions";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
require_once 'config.php';
|
require_once 'config.php';
|
||||||
|
|
||||||
@ -28,10 +30,8 @@ class AutoTaggerTable extends Table
|
|||||||
|
|
||||||
class AddAutoTagEvent extends Event
|
class AddAutoTagEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $tag;
|
||||||
public $tag;
|
public string $additional_tags;
|
||||||
/** @var string */
|
|
||||||
public $additional_tags;
|
|
||||||
|
|
||||||
public function __construct(string $tag, string $additional_tags)
|
public function __construct(string $tag, string $additional_tags)
|
||||||
{
|
{
|
||||||
@ -43,7 +43,7 @@ class AddAutoTagEvent extends Event
|
|||||||
|
|
||||||
class DeleteAutoTagEvent extends Event
|
class DeleteAutoTagEvent extends Event
|
||||||
{
|
{
|
||||||
public $tag;
|
public string $tag;
|
||||||
|
|
||||||
public function __construct(string $tag)
|
public function __construct(string $tag)
|
||||||
{
|
{
|
||||||
@ -63,7 +63,7 @@ class AddAutoTagException extends SCoreException
|
|||||||
class AutoTagger extends Extension
|
class AutoTagger extends Extension
|
||||||
{
|
{
|
||||||
/** @var AutoTaggerTheme */
|
/** @var AutoTaggerTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
@ -102,7 +102,7 @@ class AutoTagger extends Extension
|
|||||||
$this->theme->display_auto_tagtable($t->table($t->query()), $t->paginator());
|
$this->theme->display_auto_tagtable($t->table($t->query()), $t->paginator());
|
||||||
} elseif ($event->get_arg(0) == "export") {
|
} elseif ($event->get_arg(0) == "export") {
|
||||||
$page->set_mode(PageMode::DATA);
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type(MIME_TYPE_CSV);
|
$page->set_mime(MimeType::CSV);
|
||||||
$page->set_filename("auto_tag.csv");
|
$page->set_filename("auto_tag.csv");
|
||||||
$page->set_data($this->get_auto_tag_csv($database));
|
$page->set_data($this->get_auto_tag_csv($database));
|
||||||
} elseif ($event->get_arg(0) == "import") {
|
} elseif ($event->get_arg(0) == "import") {
|
||||||
@ -269,6 +269,7 @@ class AutoTagger extends Extension
|
|||||||
if (!empty($tag_id)) {
|
if (!empty($tag_id)) {
|
||||||
$image_ids = $database->get_col_iterable("SELECT image_id FROM image_tags WHERE tag_id = :tag_id", ["tag_id"=>$tag_id]);
|
$image_ids = $database->get_col_iterable("SELECT image_id FROM image_tags WHERE tag_id = :tag_id", ["tag_id"=>$tag_id]);
|
||||||
foreach ($image_ids as $image_id) {
|
foreach ($image_ids as $image_id) {
|
||||||
|
$image_id = (int) $image_id;
|
||||||
$image = Image::by_id($image_id);
|
$image = Image::by_id($image_id);
|
||||||
$event = new TagSetEvent($image, $image->get_tag_array());
|
$event = new TagSetEvent($image, $image->get_tag_array());
|
||||||
send_event($event);
|
send_event($event);
|
||||||
@ -314,14 +315,10 @@ class AutoTagger extends Extension
|
|||||||
$tags_mixed = array_merge($tags_mixed, $new_tags);
|
$tags_mixed = array_merge($tags_mixed, $new_tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
$results = array_intersect_key(
|
return array_intersect_key(
|
||||||
$tags_mixed,
|
$tags_mixed,
|
||||||
array_unique(array_map('strtolower', $tags_mixed))
|
array_unique(array_map('strtolower', $tags_mixed))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class AutoTaggerTest extends ShimmiePHPUnitTestCase
|
class AutoTaggerTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testAutoTaggerList()
|
public function testAutoTaggerList()
|
||||||
@ -37,14 +39,14 @@ class AutoTaggerTest extends ShimmiePHPUnitTestCase
|
|||||||
|
|
||||||
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
|
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
|
||||||
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
|
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
|
||||||
$this->assert_title("Image $image_id: test1 test2");
|
$this->assert_title("Post $image_id: test1 test2");
|
||||||
$this->delete_image($image_id);
|
$this->delete_image($image_id);
|
||||||
|
|
||||||
send_event(new AddAutoTagEvent("test2", "test3"));
|
send_event(new AddAutoTagEvent("test2", "test3"));
|
||||||
|
|
||||||
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
|
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
|
||||||
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
|
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
|
||||||
$this->assert_title("Image $image_id: test1 test2 test3");
|
$this->assert_title("Post $image_id: test1 test2 test3");
|
||||||
$this->delete_image($image_id);
|
$this->delete_image($image_id);
|
||||||
|
|
||||||
send_event(new DeleteAutoTagEvent("test1"));
|
send_event(new DeleteAutoTagEvent("test1"));
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AutoTaggerTheme extends Themelet
|
class AutoTaggerTheme extends Themelet
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AutoCompleteInfo extends ExtensionInfo
|
class AutoCompleteInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "autocomplete";
|
public const KEY = "autocomplete";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Autocomplete";
|
public string $name = "Autocomplete";
|
||||||
public $authors = ["Daku"=>"admin@codeanimu.net"];
|
public array $authors = ["Daku"=>"admin@codeanimu.net"];
|
||||||
public $description = "Adds autocomplete to search & tagging.";
|
public string $description = "Adds autocomplete to search & tagging.";
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AutoComplete extends Extension
|
class AutoComplete extends Extension
|
||||||
{
|
{
|
||||||
/** @var AutoCompleteTheme */
|
/** @var AutoCompleteTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function get_priority(): int
|
public function get_priority(): int
|
||||||
{
|
{
|
||||||
@ -12,37 +14,49 @@ class AutoComplete extends Extension
|
|||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $cache, $page, $database;
|
global $page;
|
||||||
|
|
||||||
if ($event->page_matches("api/internal/autocomplete")) {
|
if ($event->page_matches("api/internal/autocomplete")) {
|
||||||
if (!isset($_GET["s"])) {
|
$limit = (int)($_GET["limit"] ?? 0);
|
||||||
return;
|
$s = $_GET["s"] ?? "";
|
||||||
}
|
|
||||||
|
$res = $this->complete($s, $limit);
|
||||||
|
|
||||||
$page->set_mode(PageMode::DATA);
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type(MIME_TYPE_JSON);
|
$page->set_mime(MimeType::JSON);
|
||||||
|
$page->set_data(json_encode($res));
|
||||||
$s = strtolower($_GET["s"]);
|
|
||||||
if (
|
|
||||||
$s == '' ||
|
|
||||||
$s[0] == '_' ||
|
|
||||||
$s[0] == '%' ||
|
|
||||||
strlen($s) > 32
|
|
||||||
) {
|
|
||||||
$page->set_data("{}");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//$limit = 0;
|
$this->theme->build_autocomplete($page);
|
||||||
$cache_key = "autocomplete-$s";
|
}
|
||||||
|
|
||||||
|
private function complete(string $search, int $limit): array
|
||||||
|
{
|
||||||
|
global $cache, $database;
|
||||||
|
|
||||||
|
if (!$search) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$search = strtolower($search);
|
||||||
|
if (
|
||||||
|
$search == '' ||
|
||||||
|
$search[0] == '_' ||
|
||||||
|
$search[0] == '%' ||
|
||||||
|
strlen($search) > 32
|
||||||
|
) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache_key = "autocomplete-$search";
|
||||||
$limitSQL = "";
|
$limitSQL = "";
|
||||||
$s = str_replace('_', '\_', $s);
|
$search = str_replace('_', '\_', $search);
|
||||||
$s = str_replace('%', '\%', $s);
|
$search = str_replace('%', '\%', $search);
|
||||||
$SQLarr = ["search"=>"$s%"]; #, "cat_search"=>"%:$s%"];
|
$SQLarr = ["search"=>"$search%"]; #, "cat_search"=>"%:$search%"];
|
||||||
if (isset($_GET["limit"]) && $_GET["limit"] !== 0) {
|
if ($limit !== 0) {
|
||||||
$limitSQL = "LIMIT :limit";
|
$limitSQL = "LIMIT :limit";
|
||||||
$SQLarr['limit'] = $_GET["limit"];
|
$SQLarr['limit'] = $limit;
|
||||||
$cache_key .= "-" . $_GET["limit"];
|
$cache_key .= "-" . $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $cache->get($cache_key);
|
$res = $cache->get($cache_key);
|
||||||
@ -55,15 +69,13 @@ class AutoComplete extends Extension
|
|||||||
-- OR LOWER(tag) LIKE LOWER(:cat_search)
|
-- OR LOWER(tag) LIKE LOWER(:cat_search)
|
||||||
AND count > 0
|
AND count > 0
|
||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
$limitSQL",
|
$limitSQL
|
||||||
|
",
|
||||||
$SQLarr
|
$SQLarr
|
||||||
);
|
);
|
||||||
$cache->set($cache_key, $res, 600);
|
$cache->set($cache_key, $res, 600);
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_data(json_encode($res));
|
return $res;
|
||||||
}
|
|
||||||
|
|
||||||
$this->theme->build_autocomplete($page);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
var metatags = ['order:id', 'order:width', 'order:height', 'order:filesize', 'order:filename'];
|
var metatags = ['order:id', 'order:width', 'order:height', 'order:filesize', 'order:filename', 'order:favorites'];
|
||||||
|
|
||||||
$('[name="search"]').tagit({
|
$('[name="search"]').tagit({
|
||||||
singleFieldDelimiter: ' ',
|
singleFieldDelimiter: ' ',
|
||||||
@ -63,12 +63,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
var keyCode = e.keyCode || e.which;
|
var keyCode = e.keyCode || e.which;
|
||||||
|
|
||||||
//Stop tags containing space.
|
//Stop tags containing space.
|
||||||
if(keyCode == 32) {
|
if(keyCode === 32) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
$('.autocomplete_tags').tagit('createTag', $(this).val());
|
$('.autocomplete_tags').tagit('createTag', $(this).val());
|
||||||
$(this).autocomplete('close');
|
$(this).autocomplete('close');
|
||||||
} else if (keyCode == 9) {
|
} else if (keyCode === 9) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var tag = $('.tagit-autocomplete[style*=\"display: block\"] > li:focus, .tagit-autocomplete[style*=\"display: block\"] > li:first').first();
|
var tag = $('.tagit-autocomplete[style*=\"display: block\"] > li:focus, .tagit-autocomplete[style*=\"display: block\"] > li:first').first();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AutoCompleteTest extends ShimmiePHPUnitTestCase
|
class AutoCompleteTest extends ShimmiePHPUnitTestCase
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class AutoCompleteTheme extends Themelet
|
class AutoCompleteTheme extends Themelet
|
||||||
{
|
{
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BanWordsInfo extends ExtensionInfo
|
class BanWordsInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "ban_words";
|
public const KEY = "ban_words";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Comment Word Ban";
|
public string $name = "Comment Word Ban";
|
||||||
public $url = self::SHIMMIE_URL;
|
public string $url = self::SHIMMIE_URL;
|
||||||
public $authors = self::SHISH_AUTHOR;
|
public array $authors = self::SHISH_AUTHOR;
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $description = "For stopping spam and other comment abuse";
|
public string $description = "For stopping spam and other comment abuse";
|
||||||
public $documentation =
|
public ?string $documentation =
|
||||||
"Allows an administrator to ban certain words
|
"Allows an administrator to ban certain words
|
||||||
from comments. This can be a very simple but effective way
|
from comments. This can be a very simple but effective way
|
||||||
of stopping spam; just add \"viagra\", \"porn\", etc to the
|
of stopping spam; just add \"viagra\", \"porn\", etc to the
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BanWords extends Extension
|
class BanWords extends Extension
|
||||||
{
|
{
|
||||||
@ -55,7 +57,7 @@ xanax
|
|||||||
|
|
||||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$sb = new SetupBlock("Banned Phrases");
|
$sb = $event->panel->create_new_block("Banned Phrases");
|
||||||
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
|
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
|
||||||
$sb->add_longtext_option("banned_words");
|
$sb->add_longtext_option("banned_words");
|
||||||
$failed = [];
|
$failed = [];
|
||||||
@ -69,7 +71,6 @@ xanax
|
|||||||
if ($failed) {
|
if ($failed) {
|
||||||
$sb->add_label("Failed regexes: ".join(", ", $failed));
|
$sb->add_label("Failed regexes: ".join(", ", $failed));
|
||||||
}
|
}
|
||||||
$event->panel->add_block($sb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,7 +88,7 @@ xanax
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// other words are literal
|
// other words are literal
|
||||||
if (strpos($comment, $word) !== false) {
|
if (str_contains($comment, $word)) {
|
||||||
throw $ex;
|
throw $ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class BanWordsTest extends ShimmiePHPUnitTestCase
|
class BanWordsTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function check_blocked($image_id, $words)
|
public function check_blocked($image_id, $words)
|
||||||
@ -8,7 +10,7 @@ class BanWordsTest extends ShimmiePHPUnitTestCase
|
|||||||
send_event(new CommentPostingEvent($image_id, $user, $words));
|
send_event(new CommentPostingEvent($image_id, $user, $words));
|
||||||
$this->fail("Exception not thrown");
|
$this->fail("Exception not thrown");
|
||||||
} catch (CommentPostingException $e) {
|
} catch (CommentPostingException $e) {
|
||||||
$this->assertEquals($e->getMessage(), "Comment contains banned terms");
|
$this->assertEquals("Comment contains banned terms", $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,32 +1,57 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BBCodeInfo extends ExtensionInfo
|
class BBCodeInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "bbcode";
|
public const KEY = "bbcode";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "BBCode";
|
public string $name = "BBCode";
|
||||||
public $url = self::SHIMMIE_URL;
|
public string $url = self::SHIMMIE_URL;
|
||||||
public $authors = self::SHISH_AUTHOR;
|
public array $authors = self::SHISH_AUTHOR;
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $core = true;
|
public bool $core = true;
|
||||||
public $description = "Turns BBCode into HTML";
|
public string $description = "Turns BBCode into HTML";
|
||||||
public $documentation =
|
public ?string $documentation =
|
||||||
" Supported tags:
|
" Basic formatting tags:
|
||||||
<ul>
|
<ul>
|
||||||
<li>[img]url[/img]
|
|
||||||
<li>[url]<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>[/url]
|
|
||||||
<li>[email]<a href=\"mailto:{self::SHISH_EMAIL}\">webmaster@shishnet.org</a>[/email]
|
|
||||||
<li>[b]<b>bold</b>[/b]
|
<li>[b]<b>bold</b>[/b]
|
||||||
<li>[i]<i>italic</i>[/i]
|
<li>[i]<i>italic</i>[/i]
|
||||||
<li>[u]<u>underline</u>[/u]
|
<li>[u]<u>underline</u>[/u]
|
||||||
<li>[s]<s>strikethrough</s>[/s]
|
<li>[s]<s>strikethrough</s>[/s]
|
||||||
<li>[sup]<sup>superscript</sup>[/sup]
|
<li>[sup]<sup>superscript</sup>[/sup]
|
||||||
<li>[sub]<sub>subscript</sub>[/sub]
|
<li>[sub]<sub>subscript</sub>[/sub]
|
||||||
|
<li>[h1]Heading 1[/h1]
|
||||||
|
<li>[h2]Heading 2[/h2]
|
||||||
|
<li>[h3]Heading 3[/h3]
|
||||||
|
<li>[h4]Heading 4[/h4]
|
||||||
|
<li>[align=left|center|right]Aligned Text[/align]
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
Link tags:
|
||||||
|
<ul>
|
||||||
|
<li>[img]url[/img]
|
||||||
|
<li>[url]<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>[/url]
|
||||||
|
<li>[url=<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>]some text[/url]
|
||||||
|
<li>[url]site://ext_doc/bbcode[/url]
|
||||||
|
<li>[url=site://ext_doc/bbcode]Link to BBCode docs[/url]
|
||||||
|
<li>[email]<a href=\"mailto:{self::SHISH_EMAIL}\">webmaster@shishnet.org</a>[/email]
|
||||||
<li>[[wiki article]]
|
<li>[[wiki article]]
|
||||||
<li>[[wiki article|with some text]]
|
<li>[[wiki article|with some text]]
|
||||||
<li>[quote]text[/quote]
|
<li>>>123 (link to post #123)
|
||||||
<li>[quote=Username]text[/quote]
|
<li>[anchor=target]Scroll to #bb-target[/anchor]
|
||||||
<li>>>123 (link to image #123)
|
</ul>
|
||||||
|
<br>
|
||||||
|
More format Tags:
|
||||||
|
<ul>
|
||||||
|
<li>[list]Unordered list[/list]
|
||||||
|
<li>[ul]Unordered list[/ul]
|
||||||
|
<li>[ol]Ordered list[/ol]
|
||||||
|
<li>[li]List Item[/li]
|
||||||
|
<li>[code]<pre>print(\"Hello World!\");</pre>[/code]
|
||||||
|
<li>[spoiler]<span style=\"background-color:#000; color:#000;\">Voldemort is bad</span>[/spoiler]
|
||||||
|
<li>[quote]<blockquote><small>To be or not to be...</small></blockquote>[/quote]
|
||||||
|
<li>[quote=Shakespeare]<blockquote><em>Shakespeare said:</em><br><small>... That is the question</small></blockquote>[/quote]
|
||||||
</ul>";
|
</ul>";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
class BBCode extends FormatterExtension
|
class BBCode extends FormatterExtension
|
||||||
|
@ -1,63 +1,65 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class BBCodeTest extends ShimmiePHPUnitTestCase
|
class BBCodeTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testBasics()
|
public function testBasics()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[b]bold[/b][i]italic[/i]"),
|
"<b>bold</b><i>italic</i>",
|
||||||
"<b>bold</b><i>italic</i>"
|
$this->filter("[b]bold[/b][i]italic[/i]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStacking()
|
public function testStacking()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[b]B[/b][i]I[/i][b]B[/b]"),
|
"<b>B</b><i>I</i><b>B</b>",
|
||||||
"<b>B</b><i>I</i><b>B</b>"
|
$this->filter("[b]B[/b][i]I[/i][b]B[/b]")
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[b]bold[i]bolditalic[/i]bold[/b]"),
|
"<b>bold<i>bolditalic</i>bold</b>",
|
||||||
"<b>bold<i>bolditalic</i>bold</b>"
|
$this->filter("[b]bold[i]bolditalic[/i]bold[/b]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailure()
|
public function testFailure()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[b]bold[i]italic"),
|
"[b]bold[i]italic",
|
||||||
"[b]bold[i]italic"
|
$this->filter("[b]bold[i]italic")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCode()
|
public function testCode()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[code][b]bold[/b][/code]"),
|
"<pre>[b]bold[/b]</pre>",
|
||||||
"<pre>[b]bold[/b]</pre>"
|
$this->filter("[code][b]bold[/b][/code]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNestedList()
|
public function testNestedList()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[list][*]a[list][*]a[*]b[/list][*]b[/list]"),
|
"<ul><li>a<ul><li>a<li>b</ul><li>b</ul>",
|
||||||
"<ul><li>a<ul><li>a<li>b</ul><li>b</ul>"
|
$this->filter("[list][*]a[list][*]a[*]b[/list][*]b[/list]")
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[ul][*]a[ol][*]a[*]b[/ol][*]b[/ul]"),
|
"<ul><li>a<ol><li>a<li>b</ol><li>b</ul>",
|
||||||
"<ul><li>a<ol><li>a<li>b</ol><li>b</ul>"
|
$this->filter("[ul][*]a[ol][*]a[*]b[/ol][*]b[/ul]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSpoiler()
|
public function testSpoiler()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[spoiler]ShishNet[/spoiler]"),
|
"<span style=\"background-color:#000; color:#000;\">ShishNet</span>",
|
||||||
"<span style=\"background-color:#000; color:#000;\">ShishNet</span>"
|
$this->filter("[spoiler]ShishNet[/spoiler]")
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->strip("[spoiler]ShishNet[/spoiler]"),
|
"FuvfuArg",
|
||||||
"FuvfuArg"
|
$this->strip("[spoiler]ShishNet[/spoiler]")
|
||||||
);
|
);
|
||||||
#$this->assertEquals(
|
#$this->assertEquals(
|
||||||
# $this->filter("[spoiler]ShishNet"),
|
# $this->filter("[spoiler]ShishNet"),
|
||||||
@ -67,42 +69,42 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
|
|||||||
public function testURL()
|
public function testURL()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[url]https://shishnet.org[/url]"),
|
"<a href=\"https://shishnet.org\">https://shishnet.org</a>",
|
||||||
"<a href=\"https://shishnet.org\">https://shishnet.org</a>"
|
$this->filter("[url]https://shishnet.org[/url]")
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[url=https://shishnet.org]ShishNet[/url]"),
|
"<a href=\"https://shishnet.org\">ShishNet</a>",
|
||||||
"<a href=\"https://shishnet.org\">ShishNet</a>"
|
$this->filter("[url=https://shishnet.org]ShishNet[/url]")
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[url=javascript:alert(\"owned\")]click to fail[/url]"),
|
"[url=javascript:alert(\"owned\")]click to fail[/url]",
|
||||||
"[url=javascript:alert(\"owned\")]click to fail[/url]"
|
$this->filter("[url=javascript:alert(\"owned\")]click to fail[/url]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testEmailURL()
|
public function testEmailURL()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[email]spam@shishnet.org[/email]"),
|
"<a href=\"mailto:spam@shishnet.org\">spam@shishnet.org</a>",
|
||||||
"<a href=\"mailto:spam@shishnet.org\">spam@shishnet.org</a>"
|
$this->filter("[email]spam@shishnet.org[/email]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAnchor()
|
public function testAnchor()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->filter("[anchor=rules]Rules[/anchor]"),
|
'<span class="anchor">Rules <a class="alink" href="#bb-rules" name="bb-rules" title="link to this anchor"> ¶ </a></span>',
|
||||||
'<span class="anchor">Rules <a class="alink" href="#bb-rules" name="bb-rules" title="link to this anchor"> ¶ </a></span>'
|
$this->filter("[anchor=rules]Rules[/anchor]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function filter($in)
|
private function filter($in): string
|
||||||
{
|
{
|
||||||
$bb = new BBCode();
|
$bb = new BBCode();
|
||||||
return $bb->format($in);
|
return $bb->format($in);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function strip($in)
|
private function strip($in): string
|
||||||
{
|
{
|
||||||
$bb = new BBCode();
|
$bb = new BBCode();
|
||||||
return $bb->strip($in);
|
return $bb->strip($in);
|
||||||
|
15
ext/biography/info.php
Normal file
15
ext/biography/info.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
class BiographyInfo extends ExtensionInfo
|
||||||
|
{
|
||||||
|
public const KEY = "biography";
|
||||||
|
|
||||||
|
public string $key = self::KEY;
|
||||||
|
public string $name = "User Bios";
|
||||||
|
public string $url = self::SHIMMIE_URL;
|
||||||
|
public array $authors = self::SHISH_AUTHOR;
|
||||||
|
public string $license = self::LICENSE_GPLV2;
|
||||||
|
public string $description = "Allow users to write a bit about themselves";
|
||||||
|
}
|
36
ext/biography/main.php
Normal file
36
ext/biography/main.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
class Biography extends Extension
|
||||||
|
{
|
||||||
|
/** @var BiographyTheme */
|
||||||
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
|
public function onUserPageBuilding(UserPageBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $page, $user;
|
||||||
|
$duser = $event->display_user;
|
||||||
|
$duser_config = UserConfig::get_for_user($event->display_user->id);
|
||||||
|
$bio = $duser_config->get_string("biography", "");
|
||||||
|
|
||||||
|
if ($user->id == $duser->id) {
|
||||||
|
$this->theme->display_composer($page, $bio);
|
||||||
|
} else {
|
||||||
|
$this->theme->display_biography($page, $bio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
|
{
|
||||||
|
global $page, $user, $user_config;
|
||||||
|
if ($event->page_matches("biography")) {
|
||||||
|
if ($user->check_auth_token()) {
|
||||||
|
$user_config->set_string("biography", $_POST['biography']);
|
||||||
|
$page->flash("Bio Updated");
|
||||||
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
|
$page->set_redirect(referer_or(make_link()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
ext/biography/test.php
Normal file
20
ext/biography/test.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
class BiographyTest extends ShimmiePHPUnitTestCase
|
||||||
|
{
|
||||||
|
public function testBio()
|
||||||
|
{
|
||||||
|
$this->log_in_as_user();
|
||||||
|
$this->post_page("biography", ["biography"=>"My bio goes here"]);
|
||||||
|
$this->get_page("user/" . self::$user_name);
|
||||||
|
$this->assert_text("My bio goes here");
|
||||||
|
|
||||||
|
$this->log_in_as_admin();
|
||||||
|
$this->get_page("user/" . self::$user_name);
|
||||||
|
$this->assert_text("My bio goes here");
|
||||||
|
|
||||||
|
$this->get_page("user/" . self::$admin_name);
|
||||||
|
$this->assert_no_text("My bio goes here");
|
||||||
|
}
|
||||||
|
}
|
27
ext/biography/theme.php
Normal file
27
ext/biography/theme.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
use function MicroHTML\TEXTAREA;
|
||||||
|
|
||||||
|
class BiographyTheme extends Themelet
|
||||||
|
{
|
||||||
|
public function display_biography(Page $page, string $bio)
|
||||||
|
{
|
||||||
|
$page->add_block(new Block("About Me", format_text($bio), "main", 30, "about-me"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function display_composer(Page $page, string $bio)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
$post_url = make_link("biography");
|
||||||
|
$auth = $user->get_auth_html();
|
||||||
|
|
||||||
|
$html = SHM_SIMPLE_FORM(
|
||||||
|
$post_url,
|
||||||
|
TEXTAREA(["style"=>"width: 100%", "rows"=>"6", "name"=>"biography"], $bio),
|
||||||
|
SHM_SUBMIT("Save")
|
||||||
|
);
|
||||||
|
|
||||||
|
$page->add_block(new Block("About Me", (string)$html, "main", 30));
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,15 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BlocksInfo extends ExtensionInfo
|
class BlocksInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "blocks";
|
public const KEY = "blocks";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Generic Blocks";
|
public string $name = "Generic Blocks";
|
||||||
public $url = self::SHIMMIE_URL;
|
public string $url = self::SHIMMIE_URL;
|
||||||
public $authors = self::SHISH_AUTHOR;
|
public array $authors = self::SHISH_AUTHOR;
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Add HTML to some space (News, Ads, etc)";
|
public string $description = "Add HTML to some space (News, Ads, etc)";
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class Blocks extends Extension
|
class Blocks extends Extension
|
||||||
{
|
{
|
||||||
/** @var BlocksTheme */
|
/** @var BlocksTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class BlocksTest extends ShimmiePHPUnitTestCase
|
class BlocksTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testBlocks()
|
public function testBlocks()
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
use function MicroHTML\TABLE;
|
use function MicroHTML\TABLE;
|
||||||
use function MicroHTML\TR;
|
use function MicroHTML\TR;
|
||||||
use function MicroHTML\TH;
|
use function MicroHTML\TH;
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BlotterInfo extends ExtensionInfo
|
class BlotterInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "blotter";
|
public const KEY = "blotter";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Blotter";
|
public string $name = "Blotter";
|
||||||
public $url = "http://seemslegit.com/";
|
public string $url = "http://seemslegit.com/";
|
||||||
public $authors = ["Zach Hall"=>"zach@sosguy.net"];
|
public array $authors = ["Zach Hall"=>"zach@sosguy.net"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Displays brief updates about whatever you want on every page.
|
public string $description = "Displays brief updates about whatever you want on every page.
|
||||||
Colors and positioning can be configured to match your site's design.
|
Colors and positioning can be configured to match your site's design.
|
||||||
|
|
||||||
Development TODO at https://github.com/zshall/shimmie2/issues";
|
Development TODO at https://github.com/zshall/shimmie2/issues";
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class Blotter extends Extension
|
class Blotter extends Extension
|
||||||
{
|
{
|
||||||
/** @var BlotterTheme */
|
/** @var BlotterTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onInitExt(InitExtEvent $event)
|
public function onInitExt(InitExtEvent $event)
|
||||||
{
|
{
|
||||||
@ -15,41 +17,35 @@ class Blotter extends Extension
|
|||||||
|
|
||||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||||
{
|
{
|
||||||
global $config;
|
global $database;
|
||||||
$version = $config->get_int("blotter_version", 0);
|
|
||||||
/**
|
if ($this->get_version("blotter_version") < 1) {
|
||||||
* 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", "
|
$database->create_table("blotter", "
|
||||||
id SCORE_AIPK,
|
id SCORE_AIPK,
|
||||||
entry_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
entry_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
entry_text TEXT NOT NULL,
|
entry_text TEXT NOT NULL,
|
||||||
important SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N
|
important BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
");
|
");
|
||||||
// Insert sample data:
|
// Insert sample data:
|
||||||
$database->execute(
|
$database->execute(
|
||||||
"INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), :text, :important)",
|
"INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), :text, :important)",
|
||||||
["text"=>"Installed the blotter extension!", "important"=>"Y"]
|
["text"=>"Installed the blotter extension!", "important"=>true]
|
||||||
);
|
);
|
||||||
log_info("blotter", "Installed tables for blotter extension.");
|
log_info("blotter", "Installed tables for blotter extension.");
|
||||||
$config->set_int("blotter_version", 1);
|
$this->set_version("blotter_version", 2);
|
||||||
|
}
|
||||||
|
if ($this->get_version("blotter_version") < 2) {
|
||||||
|
$database->standardise_boolean("blotter", "important");
|
||||||
|
$this->set_version("blotter_version", 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$sb = new SetupBlock("Blotter");
|
$sb = $event->panel->create_new_block("Blotter");
|
||||||
$sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: ");
|
$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_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) ");
|
||||||
$sb->add_choice_option("blotter_position", ["Top of page" => "subheading", "In navigation bar" => "left"], "<br>Position: ");
|
$sb->add_choice_option("blotter_position", ["Top of page" => "subheading", "In navigation bar" => "left"], "<br>Position: ");
|
||||||
$event->panel->add_block($sb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
@ -98,11 +94,7 @@ class Blotter extends Extension
|
|||||||
if ($entry_text == "") {
|
if ($entry_text == "") {
|
||||||
die("No entry message!");
|
die("No entry message!");
|
||||||
}
|
}
|
||||||
if (isset($_POST['important'])) {
|
$important = isset($_POST['important']);
|
||||||
$important = 'Y';
|
|
||||||
} else {
|
|
||||||
$important = 'N';
|
|
||||||
}
|
|
||||||
// Now insert into db:
|
// Now insert into db:
|
||||||
$database->execute(
|
$database->execute(
|
||||||
"INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), :text, :important)",
|
"INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), :text, :important)",
|
||||||
@ -124,7 +116,7 @@ class Blotter extends Extension
|
|||||||
if (!isset($id)) {
|
if (!isset($id)) {
|
||||||
die("No ID!");
|
die("No ID!");
|
||||||
}
|
}
|
||||||
$database->Execute("DELETE FROM blotter WHERE id=:id", ["id"=>$id]);
|
$database->execute("DELETE FROM blotter WHERE id=:id", ["id"=>$id]);
|
||||||
log_info("blotter", "Removed Entry #$id");
|
log_info("blotter", "Removed Entry #$id");
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("blotter/editor"));
|
$page->set_redirect(make_link("blotter/editor"));
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class BlotterTest extends ShimmiePHPUnitTestCase
|
class BlotterTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testDenial()
|
public function testDenial()
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class BlotterTheme extends Themelet
|
class BlotterTheme extends Themelet
|
||||||
{
|
{
|
||||||
public function display_editor($entries)
|
public function display_editor($entries)
|
||||||
@ -20,7 +22,7 @@ class BlotterTheme extends Themelet
|
|||||||
$page->add_block(new Block("Blotter Entries", $html, "main", 10));
|
$page->add_block(new Block("Blotter Entries", $html, "main", 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function display_blotter($entries)
|
public function display_blotter(array $entries): void
|
||||||
{
|
{
|
||||||
global $page, $config;
|
global $page, $config;
|
||||||
$html = $this->get_html_for_blotter($entries);
|
$html = $this->get_html_for_blotter($entries);
|
||||||
@ -28,7 +30,7 @@ class BlotterTheme extends Themelet
|
|||||||
$page->add_block(new Block(null, $html, $position, 20));
|
$page->add_block(new Block(null, $html, $position, 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_html_for_blotter_editor($entries)
|
private function get_html_for_blotter_editor(array $entries): string
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
|
|
||||||
@ -99,7 +101,7 @@ class BlotterTheme extends Themelet
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_html_for_blotter_page($entries)
|
private function get_html_for_blotter_page(array $entries): string
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This one displays a list of all blotter entries.
|
* This one displays a list of all blotter entries.
|
||||||
@ -130,7 +132,7 @@ class BlotterTheme extends Themelet
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_html_for_blotter($entries)
|
private function get_html_for_blotter(array $entries): string
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
$i_color = $config->get_string("blotter_color", "#FF0000");
|
$i_color = $config->get_string("blotter_color", "#FF0000");
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BrowserSearchInfo extends ExtensionInfo
|
class BrowserSearchInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "browser_search";
|
public const KEY = "browser_search";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Browser Search";
|
public string $name = "Browser Search";
|
||||||
public $url = "http://atravelinggeek.com/";
|
public string $url = "http://atravelinggeek.com/";
|
||||||
public $authors = ["ATravelingGeek"=>"atg@atravelinggeek.com"];
|
public array $authors = ["ATravelingGeek"=>"atg@atravelinggeek.com"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $version = "0.1c, October 26, 2007";
|
public ?string $version = "0.1c, October 26, 2007";
|
||||||
public $description = "Allows the user to add a browser 'plugin' to search the site with real-time suggestions";
|
public string $description = "Allows the user to add a browser 'plugin' to search the site with real-time suggestions";
|
||||||
public $documentation =
|
public ?string $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
|
"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
|
||||||
|
|
||||||
Some code (and lots of help) by Artanis (Erik Youngren <artanis.00@gmail.com>) from the 'tagger' extension - Used with permission";
|
Some code (and lots of help) by Artanis (Erik Youngren <artanis.00@gmail.com>) from the 'tagger' extension - Used with permission";
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BrowserSearch extends Extension
|
class BrowserSearch extends Extension
|
||||||
{
|
{
|
||||||
@ -42,7 +44,7 @@ class BrowserSearch extends Extension
|
|||||||
|
|
||||||
// And now to send it to the browser
|
// And now to send it to the browser
|
||||||
$page->set_mode(PageMode::DATA);
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type(MIME_TYPE_XML);
|
$page->set_mime(MimeType::XML);
|
||||||
$page->set_data($xml);
|
$page->set_data($xml);
|
||||||
} elseif ($event->page_matches("browser_search")) {
|
} elseif ($event->page_matches("browser_search")) {
|
||||||
$suggestions = $config->get_string("search_suggestions_results_order");
|
$suggestions = $config->get_string("search_suggestions_results_order");
|
||||||
@ -78,8 +80,7 @@ class BrowserSearch extends Extension
|
|||||||
$sort_by['Tag Count'] = 't';
|
$sort_by['Tag Count'] = 't';
|
||||||
$sort_by['Disabled'] = 'n';
|
$sort_by['Disabled'] = 'n';
|
||||||
|
|
||||||
$sb = new SetupBlock("Browser Search");
|
$sb = $event->panel->create_new_block("Browser Search");
|
||||||
$sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:");
|
$sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:");
|
||||||
$event->panel->add_block($sb);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
class BrowserSearchTest extends ShimmiePHPUnitTestCase
|
class BrowserSearchTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testBasic()
|
public function testBasic()
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkActionsInfo extends ExtensionInfo
|
class BulkActionsInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "bulk_actions";
|
public const KEY = "bulk_actions";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Bulk Actions";
|
public string $name = "Bulk Actions";
|
||||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||||
public $license = self::LICENSE_WTFPL;
|
public string $license = self::LICENSE_WTFPL;
|
||||||
public $description = "Provides query and selection-based bulk action support";
|
public string $description = "Provides query and selection-based bulk action support";
|
||||||
public $documentation = "Provides bulk action section in list view. Allows performing actions against a set of images based on query or manual selection. Based on Mass Tagger by Christian Walde <walde.christian@googlemail.com>, contributions by Shish and Agasa.";
|
public ?string $documentation = "Provides bulk action section in list view. Allows performing actions against a set of posts based on query or manual selection. Based on Mass Tagger by Christian Walde <walde.christian@googlemail.com>, contributions by Shish and Agasa.";
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkActionException extends SCoreException
|
class BulkActionException extends SCoreException
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
class BulkActionBlockBuildingEvent extends Event
|
class BulkActionBlockBuildingEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var array */
|
public array $actions = [];
|
||||||
public $actions = [];
|
public array $search_terms = [];
|
||||||
|
|
||||||
public $search_terms = [];
|
|
||||||
|
|
||||||
public function add_action(String $action, string $button_text, string $access_key = null, String $confirmation_message = "", String $block = "", int $position = 40)
|
public function add_action(String $action, string $button_text, string $access_key = null, String $confirmation_message = "", String $block = "", int $position = 40)
|
||||||
{
|
{
|
||||||
@ -38,12 +38,9 @@ class BulkActionBlockBuildingEvent extends Event
|
|||||||
|
|
||||||
class BulkActionEvent extends Event
|
class BulkActionEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var string */
|
public string $action;
|
||||||
public $action;
|
public Generator $items;
|
||||||
/** @var array */
|
public bool $redirect = true;
|
||||||
public $items;
|
|
||||||
/** @var bool */
|
|
||||||
public $redirect = true;
|
|
||||||
|
|
||||||
public function __construct(String $action, Generator $items)
|
public function __construct(String $action, Generator $items)
|
||||||
{
|
{
|
||||||
@ -56,7 +53,7 @@ class BulkActionEvent extends Event
|
|||||||
class BulkActions extends Extension
|
class BulkActions extends Extension
|
||||||
{
|
{
|
||||||
/** @var BulkActionsTheme */
|
/** @var BulkActionsTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||||
{
|
{
|
||||||
@ -127,8 +124,8 @@ class BulkActions extends Extension
|
|||||||
switch ($event->action) {
|
switch ($event->action) {
|
||||||
case "bulk_delete":
|
case "bulk_delete":
|
||||||
if ($user->can(Permissions::DELETE_IMAGE)) {
|
if ($user->can(Permissions::DELETE_IMAGE)) {
|
||||||
$i = $this->delete_items($event->items);
|
$i = $this->delete_posts($event->items);
|
||||||
$page->flash("Deleted $i items");
|
$page->flash("Deleted $i[0] items, totaling ".human_filesize($i[1]));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "bulk_tag":
|
case "bulk_tag":
|
||||||
@ -193,14 +190,14 @@ class BulkActions extends Extension
|
|||||||
if (is_iterable($items)) {
|
if (is_iterable($items)) {
|
||||||
send_event($bae);
|
send_event($bae);
|
||||||
}
|
}
|
||||||
} catch (BulkActionException $e) {
|
|
||||||
log_error(BulkActionsInfo::KEY, $e->getMessage(), $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($bae->redirect) {
|
if ($bae->redirect) {
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(referer_or(make_link()));
|
$page->set_redirect(referer_or(make_link()));
|
||||||
}
|
}
|
||||||
|
} catch (BulkActionException $e) {
|
||||||
|
log_error(BulkActionsInfo::KEY, $e->getMessage(), $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,25 +224,27 @@ class BulkActions extends Extension
|
|||||||
return $a["position"] - $b["position"];
|
return $a["position"] - $b["position"];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function delete_items(iterable $items): int
|
private function delete_posts(iterable $posts): array
|
||||||
{
|
{
|
||||||
global $page;
|
global $page;
|
||||||
$total = 0;
|
$total = 0;
|
||||||
foreach ($items as $image) {
|
$size = 0;
|
||||||
|
foreach ($posts as $post) {
|
||||||
try {
|
try {
|
||||||
if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) {
|
if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) {
|
||||||
$reason = $_POST['bulk_ban_reason'];
|
$reason = $_POST['bulk_ban_reason'];
|
||||||
if ($reason) {
|
if ($reason) {
|
||||||
send_event(new AddImageHashBanEvent($image->hash, $reason));
|
send_event(new AddImageHashBanEvent($post->hash, $reason));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
send_event(new ImageDeletionEvent($image));
|
send_event(new ImageDeletionEvent($post));
|
||||||
$total++;
|
$total++;
|
||||||
|
$size += $post->filesize;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$page->flash("Error while removing {$image->id}: " . $e->getMessage());
|
$page->flash("Error while removing {$post->id}: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $total;
|
return [$total, $size];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function tag_items(iterable $items, string $tags, bool $replace): int
|
private function tag_items(iterable $items, string $tags, bool $replace): int
|
||||||
@ -255,7 +254,7 @@ class BulkActions extends Extension
|
|||||||
$pos_tag_array = [];
|
$pos_tag_array = [];
|
||||||
$neg_tag_array = [];
|
$neg_tag_array = [];
|
||||||
foreach ($tags as $new_tag) {
|
foreach ($tags as $new_tag) {
|
||||||
if (strpos($new_tag, '-') === 0) {
|
if (str_starts_with($new_tag, '-')) {
|
||||||
$neg_tag_array[] = substr($new_tag, 1);
|
$neg_tag_array[] = substr($new_tag, 1);
|
||||||
} else {
|
} else {
|
||||||
$pos_tag_array[] = $new_tag;
|
$pos_tag_array[] = $new_tag;
|
||||||
|
@ -8,13 +8,13 @@ function validate_selections(form, confirmationMessage) {
|
|||||||
var queryOnly = false;
|
var queryOnly = false;
|
||||||
if(bulk_selector_active) {
|
if(bulk_selector_active) {
|
||||||
var data = get_selected_items();
|
var data = get_selected_items();
|
||||||
if(data.length==0) {
|
if(data.length===0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var query = $(form).find('input[name="bulk_query"]').val();
|
var query = $(form).find('input[name="bulk_query"]').val();
|
||||||
|
|
||||||
if (query == null || query == "") {
|
if (query == null || query === "") {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
queryOnly = true;
|
queryOnly = true;
|
||||||
@ -22,7 +22,7 @@ function validate_selections(form, confirmationMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(confirmationMessage!=null&&confirmationMessage!="") {
|
if(confirmationMessage!=null&&confirmationMessage!=="") {
|
||||||
return confirm(confirmationMessage);
|
return confirm(confirmationMessage);
|
||||||
} else if(queryOnly) {
|
} else if(queryOnly) {
|
||||||
var action = $(form).find('input[name="submit_button"]').val();
|
var action = $(form).find('input[name="submit_button"]').val();
|
||||||
@ -59,7 +59,7 @@ function deactivate_bulk_selector() {
|
|||||||
|
|
||||||
function get_selected_items() {
|
function get_selected_items() {
|
||||||
var data = $('#bulk_selected_ids').val();
|
var data = $('#bulk_selected_ids').val();
|
||||||
if(data==""||data==null) {
|
if(data===""||data==null) {
|
||||||
data = [];
|
data = [];
|
||||||
} else {
|
} else {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
@ -145,7 +145,7 @@ function select_range(start, end) {
|
|||||||
function ( index, block ) {
|
function ( index, block ) {
|
||||||
block = $(block);
|
block = $(block);
|
||||||
var id = block.data("post-id");
|
var id = block.data("post-id");
|
||||||
if(id==start)
|
if(id===start)
|
||||||
selecting = true;
|
selecting = true;
|
||||||
|
|
||||||
if(selecting) {
|
if(selecting) {
|
||||||
@ -153,7 +153,7 @@ function select_range(start, end) {
|
|||||||
data.push(id);
|
data.push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id==end) {
|
if(id===end) {
|
||||||
selecting = false;
|
selecting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkActionsTheme extends Themelet
|
class BulkActionsTheme extends Themelet
|
||||||
{
|
{
|
||||||
@ -8,7 +10,7 @@ class BulkActionsTheme extends Themelet
|
|||||||
<input id='bulk_selector_activate' type='button' onclick='activate_bulk_selector();' value='Activate (M)anual Select' accesskey='m'/>
|
<input id='bulk_selector_activate' type='button' onclick='activate_bulk_selector();' value='Activate (M)anual Select' accesskey='m'/>
|
||||||
<div id='bulk_selector_controls' style='display: none;'>
|
<div id='bulk_selector_controls' style='display: none;'>
|
||||||
<input id='bulk_selector_deactivate' type='button' onclick='deactivate_bulk_selector();' value='Deactivate (M)anual Select' accesskey='m'/>
|
<input id='bulk_selector_deactivate' type='button' onclick='deactivate_bulk_selector();' value='Deactivate (M)anual Select' accesskey='m'/>
|
||||||
Click on images to mark them.
|
Click on posts to mark them.
|
||||||
<br />
|
<br />
|
||||||
<table><tr><td>
|
<table><tr><td>
|
||||||
<input id='bulk_selector_select_all' type='button'
|
<input id='bulk_selector_select_all' type='button'
|
||||||
@ -45,7 +47,7 @@ class BulkActionsTheme extends Themelet
|
|||||||
$page->add_block($block);
|
$page->add_block($block);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render_ban_reason_input()
|
public function render_ban_reason_input(): string
|
||||||
{
|
{
|
||||||
if (class_exists("ImageBan")) {
|
if (class_exists("ImageBan")) {
|
||||||
return "<input type='text' name='bulk_ban_reason' placeholder='Ban reason (leave blank to not ban)' />";
|
return "<input type='text' name='bulk_ban_reason' placeholder='Ban reason (leave blank to not ban)' />";
|
||||||
@ -54,13 +56,13 @@ class BulkActionsTheme extends Themelet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render_tag_input()
|
public function render_tag_input(): string
|
||||||
{
|
{
|
||||||
return "<label><input type='checkbox' style='width:13px;' name='bulk_tags_replace' value='true'/>Replace tags</label>" .
|
return "<label><input type='checkbox' style='width:13px;' name='bulk_tags_replace' value='true'/>Replace tags</label>" .
|
||||||
"<input type='text' name='bulk_tags' required='required' placeholder='Enter tags here' />";
|
"<input type='text' name='bulk_tags' required='required' placeholder='Enter tags here' />";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render_source_input()
|
public function render_source_input(): string
|
||||||
{
|
{
|
||||||
return "<input type='text' name='bulk_source' required='required' placeholder='Enter source here' />";
|
return "<input type='text' name='bulk_source' required='required' placeholder='Enter source here' />";
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkAddInfo extends ExtensionInfo
|
class BulkAddInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "bulk_add";
|
public const KEY = "bulk_add";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Bulk Add";
|
public string $name = "Bulk Add";
|
||||||
public $url = self::SHIMMIE_URL;
|
public string $url = self::SHIMMIE_URL;
|
||||||
public $authors = self::SHISH_AUTHOR;
|
public array $authors = self::SHISH_AUTHOR;
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Bulk add server-side images";
|
public string $description = "Bulk add server-side images";
|
||||||
public $documentation =
|
public ?string $documentation =
|
||||||
"Upload the images into a new directory via ftp or similar, go to
|
"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.
|
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
|
If there are subdirectories, they get used as tags (eg if you
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkAddEvent extends Event
|
class BulkAddEvent extends Event
|
||||||
{
|
{
|
||||||
public $dir;
|
public string $dir;
|
||||||
public $results;
|
public array $results;
|
||||||
|
|
||||||
public function __construct(string $dir)
|
public function __construct(string $dir)
|
||||||
{
|
{
|
||||||
@ -16,7 +18,7 @@ class BulkAddEvent extends Event
|
|||||||
class BulkAdd extends Extension
|
class BulkAdd extends Extension
|
||||||
{
|
{
|
||||||
/** @var BulkAddTheme */
|
/** @var BulkAddTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkAddTest extends ShimmiePHPUnitTestCase
|
class BulkAddTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
@ -6,7 +8,7 @@ class BulkAddTest extends ShimmiePHPUnitTestCase
|
|||||||
{
|
{
|
||||||
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
|
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
|
||||||
$bae = send_event(new BulkAddEvent('asdf'));
|
$bae = send_event(new BulkAddEvent('asdf'));
|
||||||
$this->assertContains(
|
$this->assertContainsEquals(
|
||||||
"Error, asdf is not a readable directory",
|
"Error, asdf is not a readable directory",
|
||||||
$bae->results,
|
$bae->results,
|
||||||
implode("\n", $bae->results)
|
implode("\n", $bae->results)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkAddTheme extends Themelet
|
class BulkAddTheme extends Themelet
|
||||||
{
|
{
|
||||||
private $messages = [];
|
private array $messages = [];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Show a standard page for results to be put into
|
* Show a standard page for results to be put into
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkAddCSVInfo extends ExtensionInfo
|
class BulkAddCSVInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "bulk_add_csv";
|
public const KEY = "bulk_add_csv";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public string $key = self::KEY;
|
||||||
public $name = "Bulk Add CSV";
|
public string $name = "Bulk Add CSV";
|
||||||
public $url = self::SHIMMIE_URL;
|
public string $url = self::SHIMMIE_URL;
|
||||||
public $authors = ["velocity37"=>"velocity37@gmail.com"];
|
public array $authors = ["velocity37"=>"velocity37@gmail.com"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public string $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Bulk add server-side images with metadata from CSV file";
|
public string $description = "Bulk add server-side posts with metadata from CSV file";
|
||||||
public $documentation =
|
public ?string $documentation =
|
||||||
"Modification of \"Bulk Add\" by Shish.<br><br>
|
"Modification of \"Bulk Add\" by Shish.<br><br>
|
||||||
Adds images from a CSV with the five following values: <br>
|
Adds posts from a CSV with the five following values: <br>
|
||||||
\"/path/to/image.jpg\",\"spaced tags\",\"source\",\"rating s/q/e\",\"/path/thumbnail.jpg\" <br>
|
\"/path/to/image.jpg\",\"spaced tags\",\"source\",\"rating s/q/e\",\"/path/thumbnail.jpg\" <br>
|
||||||
<b>e.g.</b> \"/tmp/cat.png\",\"shish oekaki\",\"shimmie.shishnet.org\",\"s\",\"tmp/custom.jpg\" <br><br>
|
<b>e.g.</b> \"/tmp/cat.png\",\"shish oekaki\",\"shimmie.shishnet.org\",\"s\",\"tmp/custom.jpg\" <br><br>
|
||||||
Any value but the first may be omitted, but there must be five values per line.<br>
|
Any value but the first may be omitted, but there must be five values per line.<br>
|
||||||
<b>e.g.</b> \"/why/not/try/bulk_add.jpg\",\"\",\"\",\"\",\"\"<br><br>
|
<b>e.g.</b> \"/why/not/try/bulk_add.jpg\",\"\",\"\",\"\",\"\"<br><br>
|
||||||
Image thumbnails will be displayed at the AR of the full image. Thumbnails that are
|
Post thumbnails will be displayed at the AR of the full post. Thumbnails that are
|
||||||
normally static (e.g. SWF) will be displayed at the board's max thumbnail size<br><br>
|
normally static (e.g. SWF) will be displayed at the board's max thumbnail size<br><br>
|
||||||
Useful for importing tagged images without having to do database manipulation.<br>
|
Useful for importing tagged posts without having to do database manipulation.<br>
|
||||||
<p><b>Note:</b> requires \"Admin Controls\" and optionally \"Image Ratings\" to be enabled<br><br>";
|
<p><b>Note:</b> requires \"Admin Controls\" and optionally \"Post Ratings\" to be enabled<br><br>";
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkAddCSV extends Extension
|
class BulkAddCSV extends Extension
|
||||||
{
|
{
|
||||||
/** @var BulkAddCSVTheme */
|
/** @var BulkAddCSVTheme */
|
||||||
protected $theme;
|
protected ?Themelet $theme;
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
class BulkAddCSVTheme extends Themelet
|
class BulkAddCSVTheme extends Themelet
|
||||||
{
|
{
|
||||||
private $messages = [];
|
private array $messages = [];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Show a standard page for results to be put into
|
* Show a standard page for results to be put into
|
||||||
*/
|
*/
|
||||||
public function display_upload_results(Page $page)
|
public function display_upload_results(Page $page)
|
||||||
{
|
{
|
||||||
$page->set_title("Adding images from csv");
|
$page->set_title("Adding posts from csv");
|
||||||
$page->set_heading("Adding images from csv");
|
$page->set_heading("Adding posts from csv");
|
||||||
$page->add_block(new NavBlock());
|
$page->add_block(new NavBlock());
|
||||||
foreach ($this->messages as $block) {
|
foreach ($this->messages as $block) {
|
||||||
$page->add_block($block);
|
$page->add_block($block);
|
||||||
@ -26,8 +28,8 @@ class BulkAddCSVTheme extends Themelet
|
|||||||
{
|
{
|
||||||
global $page;
|
global $page;
|
||||||
$html = "
|
$html = "
|
||||||
Add images from a csv. Images will be tagged and have their
|
Add posts from a csv. Posts will be tagged and have their
|
||||||
source and rating set (if \"Image Ratings\" is enabled)
|
source and rating set (if \"Post Ratings\" is enabled)
|
||||||
<br>Specify the absolute or relative path to a local .csv file. Check <a href=\"" . make_link("ext_doc/bulk_add_csv") . "\">here</a> for the expected format.
|
<br>Specify the absolute or relative path to a local .csv file. Check <a href=\"" . make_link("ext_doc/bulk_add_csv") . "\">here</a> for the expected format.
|
||||||
|
|
||||||
<p>".make_form(make_link("bulk_add_csv"))."
|
<p>".make_form(make_link("bulk_add_csv"))."
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user