Compare commits
251 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
eb64d68599 | ||
|
6ef9711f23 | ||
|
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 |
21
.github/workflows/test_and_publish.yml
vendored
21
.github/workflows/test_and_publish.yml
vendored
@ -10,10 +10,9 @@ jobs:
|
|||||||
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.3', '7.4', '8.0']
|
||||||
database: ['pgsql', 'mysql', 'sqlite']
|
database: ['pgsql', 'mysql', 'sqlite']
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -21,6 +20,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,11 +62,8 @@ 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: |
|
||||||
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
|
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
|
||||||
@ -73,16 +76,18 @@ jobs:
|
|||||||
export TEST_DSN="sqlite:data/shimmie.sqlite"
|
export TEST_DSN="sqlite:data/shimmie.sqlite"
|
||||||
fi
|
fi
|
||||||
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:
|
publish:
|
||||||
name: Publish
|
name: Publish
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: test
|
needs: test
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Publish to Registry
|
- name: Publish to Registry
|
||||||
|
14
Dockerfile
14
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:testing-slim 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:testing-slim 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:testing-slim 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:testing-slim
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
HEALTHCHECK --interval=5m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1
|
HEALTHCHECK --interval=5m --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
|
||||||
|
@ -25,28 +25,29 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"require" : {
|
"require" : {
|
||||||
"php" : ">=7.3",
|
"php" : "^7.3 | ^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.13",
|
||||||
|
|
||||||
"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/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"
|
||||||
},
|
},
|
||||||
|
|
||||||
"suggest": {
|
"suggest": {
|
||||||
|
1327
composer.lock
generated
1327
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@ class BasePage
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
public $mode = PageMode::PAGE;
|
public $mode = PageMode::PAGE;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $type = "text/html; charset=utf-8";
|
private $mime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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"]);
|
||||||
@ -243,7 +244,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 +298,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 +335,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);
|
||||||
@ -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>
|
||||||
|
@ -53,8 +53,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
|
||||||
|
65
core/command_builder.php
Normal file
65
core/command_builder.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?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 $executable;
|
||||||
|
private $args = [];
|
||||||
|
public $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;
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,14 +33,6 @@ class Database
|
|||||||
*/
|
*/
|
||||||
private $engine = null;
|
private $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
|
||||||
*/
|
*/
|
||||||
@ -53,13 +45,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 +75,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 +97,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 +111,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)) {
|
||||||
@ -169,6 +141,11 @@ class Database
|
|||||||
$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 +230,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 +327,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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,6 @@ abstract class SCORE
|
|||||||
{
|
{
|
||||||
const AIPK = "SCORE_AIPK";
|
const AIPK = "SCORE_AIPK";
|
||||||
const INET = "SCORE_INET";
|
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
|
||||||
@ -13,9 +10,6 @@ abstract class DBEngine
|
|||||||
/** @var null|string */
|
/** @var null|string */
|
||||||
public $name = null;
|
public $name = null;
|
||||||
|
|
||||||
public $BOOL_Y = null;
|
|
||||||
public $BOOL_N = null;
|
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -33,6 +27,8 @@ abstract class DBEngine
|
|||||||
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
|
||||||
@ -40,9 +36,6 @@ class MySQL extends DBEngine
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
public $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)
|
||||||
{
|
{
|
||||||
$db->exec("SET NAMES utf8;");
|
$db->exec("SET NAMES utf8;");
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +61,10 @@ class MySQL extends DBEngine
|
|||||||
// $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];
|
||||||
@ -82,9 +76,6 @@ class PostgreSQL extends DBEngine
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
public $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)
|
||||||
{
|
{
|
||||||
if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
|
if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
|
||||||
@ -101,9 +92,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +106,15 @@ class PostgreSQL extends DBEngine
|
|||||||
$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];
|
||||||
@ -175,10 +172,6 @@ class SQLite extends DBEngine
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
public $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)
|
||||||
{
|
{
|
||||||
ini_set('sqlite.assoc_case', '0');
|
ini_set('sqlite.assoc_case', '0');
|
||||||
@ -199,9 +192,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +219,10 @@ class SQLite extends DBEngine
|
|||||||
// 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];
|
||||||
|
@ -61,7 +61,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(
|
||||||
@ -138,6 +138,7 @@ abstract class ExtensionInfo
|
|||||||
public $license;
|
public $license;
|
||||||
public $version;
|
public $version;
|
||||||
public $dependencies = [];
|
public $dependencies = [];
|
||||||
|
public $conflicts = [];
|
||||||
public $visibility;
|
public $visibility;
|
||||||
public $description;
|
public $description;
|
||||||
public $documentation;
|
public $documentation;
|
||||||
@ -193,6 +194,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 +243,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");
|
||||||
@ -291,11 +299,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 +313,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));
|
||||||
@ -331,10 +339,10 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
} 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 +366,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 +375,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 +394,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 +402,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 +429,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;
|
|
||||||
}
|
|
@ -91,7 +91,7 @@ class ThumbnailGenerationEvent extends Event
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
public $hash;
|
public $hash;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $type;
|
public $mime;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
public $force;
|
public $force;
|
||||||
|
|
||||||
@ -101,11 +101,11 @@ class ThumbnailGenerationEvent extends Event
|
|||||||
/**
|
/**
|
||||||
* 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;
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,6 @@ 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
|
|
||||||
|
|
||||||
/** @var null|int */
|
/** @var null|int */
|
||||||
public $id = null;
|
public $id = null;
|
||||||
|
|
||||||
@ -34,7 +32,10 @@ class Image
|
|||||||
public $filename;
|
public $filename;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $ext;
|
private $ext;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $mime;
|
||||||
|
|
||||||
/** @var string[]|null */
|
/** @var string[]|null */
|
||||||
public $tag_array;
|
public $tag_array;
|
||||||
@ -60,6 +61,9 @@ class Image
|
|||||||
/** @var boolean */
|
/** @var boolean */
|
||||||
public $video = null;
|
public $video = null;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
public $video_codec = null;
|
||||||
|
|
||||||
/** @var boolean */
|
/** @var boolean */
|
||||||
public $image = null;
|
public $image = null;
|
||||||
|
|
||||||
@ -80,6 +84,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);
|
||||||
|
|
||||||
@ -152,12 +160,9 @@ class Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$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);
|
|
||||||
$result = $database->get_all_iterable($querylet->sql, $querylet->variables);
|
$result = $database->get_all_iterable($querylet->sql, $querylet->variables);
|
||||||
|
|
||||||
Image::$order_sql = null;
|
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,11 +228,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 +278,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 +305,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 +320,7 @@ class Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [$tag_conditions, $img_conditions];
|
return [$tag_conditions, $img_conditions, $order];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -344,8 +357,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 +396,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}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,22 +410,22 @@ class Image
|
|||||||
"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
|
now(), :source
|
||||||
)",
|
)",
|
||||||
[
|
[
|
||||||
"owner_id" => $user->id, "owner_ip" => $_SERVER['REMOTE_ADDR'],
|
"owner_id" => $user->id, "owner_ip" => $_SERVER['REMOTE_ADDR'],
|
||||||
"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), "source" => $this->source
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$this->id = $database->get_last_insert_id('images_id_seq');
|
$this->id = $database->get_last_insert_id('images_id_seq');
|
||||||
@ -419,12 +433,13 @@ 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, 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),
|
||||||
"source" => $this->source,
|
"source" => $this->source,
|
||||||
"id" => $this->id,
|
"id" => $this->id,
|
||||||
@ -435,17 +450,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
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -503,7 +519,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 +534,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 +558,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 +596,38 @@ 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;
|
||||||
|
$this->ext = FileExtension::get_for_mime($this->get_mime());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the image's source URL
|
* Get the image's source URL
|
||||||
*/
|
*/
|
||||||
@ -601,7 +648,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 +660,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 +720,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 +782,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 +795,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 +807,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 +833,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;
|
||||||
|
@ -33,11 +33,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 +70,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)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +136,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 +147,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 +155,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 +165,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);
|
||||||
|
}
|
||||||
|
@ -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));
|
||||||
@ -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)");
|
||||||
$db->commit();
|
|
||||||
|
// mysql auto-commits when creating a table, so the transaction
|
||||||
|
// is closed; other databases need to commit
|
||||||
|
if ($db->is_transaction_open()) {
|
||||||
|
$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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
* 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 +124,16 @@ function list_files(string $base, string $_sub_dir=""): array
|
|||||||
|
|
||||||
$files = [];
|
$files = [];
|
||||||
$dir = opendir("$base/$_sub_dir");
|
$dir = opendir("$base/$_sub_dir");
|
||||||
while ($f = readdir($dir)) {
|
if ($dir===false) {
|
||||||
$files[] = $f;
|
throw new SCoreException("Unable to open directory $base/$_sub_dir");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
while ($f = readdir($dir)) {
|
||||||
|
$files[] = $f;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
closedir($dir);
|
||||||
}
|
}
|
||||||
closedir($dir);
|
|
||||||
sort($files);
|
sort($files);
|
||||||
|
|
||||||
foreach ($files as $filename) {
|
foreach ($files as $filename) {
|
||||||
@ -187,8 +190,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 +222,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;
|
||||||
@ -257,7 +260,7 @@ if (!function_exists('mb_strlen')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @noinspection PhpUnhandledExceptionInspection */
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
function getSubclassesOf(string $parent)
|
function get_subclasses_of(string $parent)
|
||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach (get_declared_classes() as $class) {
|
foreach (get_declared_classes() as $class) {
|
||||||
@ -338,17 +341,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 +487,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.
|
||||||
@ -563,17 +556,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 +599,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
|
||||||
|
@ -4,39 +4,6 @@
|
|||||||
* 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 +28,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";
|
||||||
|
}
|
||||||
|
@ -35,7 +35,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 +61,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(); ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,16 +19,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.8.4$_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
|
||||||
|
@ -9,13 +9,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 +24,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 +69,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 +105,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(
|
||||||
|
@ -43,13 +43,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")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -79,7 +79,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 +105,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,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 +163,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 +175,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 +185,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 +195,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,13 +20,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 +39,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";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,11 +252,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 +269,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 +283,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 +291,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 +340,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.
|
||||||
|
@ -15,9 +15,9 @@ class AdminPageInfo extends ExtensionInfo
|
|||||||
<p>Lowercase all tags:
|
<p>Lowercase all tags:
|
||||||
<br>Set all tags to lowercase for consistency
|
<br>Set all tags to lowercase for consistency
|
||||||
<p>Recount tag use:
|
<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
|
<br>If the counts of posts per tag get messed up somehow, this will reset them, and remove any unused tags
|
||||||
<p>Database dump:
|
<p>Database dump:
|
||||||
<br>Download the contents of the database in plain text format, useful for backups.
|
<br>Download the contents of the database in plain text format, useful for backups.
|
||||||
<p>Image dump:
|
<p>Post dump:
|
||||||
<br>Download all the images as a .zip file (Requires ZipArchive)";
|
<br>Download all the posts as a .zip file (Requires ZipArchive)";
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ class AdminPage extends Extension
|
|||||||
|
|
||||||
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 +52,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(300000);
|
||||||
send_event($aae);
|
send_event($aae);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +81,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 +106,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");
|
||||||
}
|
}
|
||||||
@ -180,14 +183,14 @@ class AdminPage extends Extension
|
|||||||
private function recount_tag_use()
|
private function recount_tag_use()
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
$database->Execute("
|
$database->execute("
|
||||||
UPDATE tags
|
UPDATE tags
|
||||||
SET count = COALESCE(
|
SET count = COALESCE(
|
||||||
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
|
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
");
|
");
|
||||||
$database->Execute("DELETE FROM tags WHERE count=0");
|
$database->execute("DELETE FROM tags WHERE count=0");
|
||||||
log_warning("admin", "Re-counted tags", "Re-counted tags");
|
log_warning("admin", "Re-counted tags", "Re-counted tags");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -28,14 +28,14 @@ class AdminPageTest extends ShimmiePHPUnitTestCase
|
|||||||
|
|
||||||
// Validate problem
|
// Validate problem
|
||||||
$page = $this->get_page("post/view/$image_id_1");
|
$page = $this->get_page("post/view/$image_id_1");
|
||||||
$this->assertEquals("Image $image_id_1: TeStCase$ts", $page->title);
|
$this->assertEquals("Post $image_id_1: TeStCase$ts", $page->title);
|
||||||
|
|
||||||
// Fix
|
// Fix
|
||||||
send_event(new AdminActionEvent('lowercase_all_tags'));
|
send_event(new AdminActionEvent('lowercase_all_tags'));
|
||||||
|
|
||||||
// Validate fix
|
// Validate fix
|
||||||
$this->get_page("post/view/$image_id_1");
|
$this->get_page("post/view/$image_id_1");
|
||||||
$this->assert_title("Image $image_id_1: testcase$ts");
|
$this->assert_title("Post $image_id_1: testcase$ts");
|
||||||
|
|
||||||
// Change
|
// Change
|
||||||
$_POST["tag"] = "TestCase$ts";
|
$_POST["tag"] = "TestCase$ts";
|
||||||
@ -43,7 +43,7 @@ class AdminPageTest extends ShimmiePHPUnitTestCase
|
|||||||
|
|
||||||
// Validate change
|
// Validate change
|
||||||
$this->get_page("post/view/$image_id_1");
|
$this->get_page("post/view/$image_id_1");
|
||||||
$this->assert_title("Image $image_id_1: TestCase$ts");
|
$this->assert_title("Post $image_id_1: TestCase$ts");
|
||||||
}
|
}
|
||||||
|
|
||||||
# FIXME: make sure the admin tools actually work
|
# FIXME: make sure the admin tools actually work
|
||||||
|
@ -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") {
|
||||||
|
@ -36,7 +36,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 +67,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);
|
||||||
|
|
||||||
|
@ -33,7 +33,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 +48,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);
|
||||||
@ -99,9 +99,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"));
|
||||||
}
|
}
|
||||||
@ -121,13 +121,13 @@ class Approval extends Extension
|
|||||||
const SEARCH_REGEXP = "/^approved:(yes|no)/";
|
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 +135,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 +187,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 +261,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,14 @@ class ApprovalTheme extends Themelet
|
|||||||
|
|
||||||
public function get_help_html()
|
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>
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ class ApprovalTheme extends Themelet
|
|||||||
public function display_admin_block(SetupBuildingEvent $event)
|
public function display_admin_block(SetupBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$sb = new SetupBlock("Approval");
|
$sb = new SetupBlock("Approval");
|
||||||
$sb->add_bool_option(ApprovalConfig::IMAGES, "Images: ");
|
$sb->add_bool_option(ApprovalConfig::IMAGES, "Posts: ");
|
||||||
$event->panel->add_block($sb);
|
$event->panel->add_block($sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -553,7 +553,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,7 +695,7 @@ class Artists extends Extension
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$name = $inputs["name"];
|
$name = $inputs["name"];
|
||||||
if (strpos($name, " ")) {
|
if (str_contains($name, " ")) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,19 +27,19 @@ class ArtistsTheme extends Themelet
|
|||||||
<input type='submit' name='edit' id='edit' value='New Artist'/>
|
<input type='submit' name='edit' id='edit' value='New Artist'/>
|
||||||
</form>";
|
</form>";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($mode == "editor") {
|
if ($mode == "editor") {
|
||||||
$html = "<form method='post' action='".make_link("artist/new_artist")."'>
|
$html = "<form method='post' action='".make_link("artist/new_artist")."'>
|
||||||
".$user->get_auth_html()."
|
".$user->get_auth_html()."
|
||||||
<input type='submit' name='edit' value='New Artist'/>
|
<input type='submit' name='edit' value='New Artist'/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form method='post' action='".make_link("artist/edit_artist")."'>
|
<form method='post' action='".make_link("artist/edit_artist")."'>
|
||||||
".$user->get_auth_html()."
|
".$user->get_auth_html()."
|
||||||
<input type='submit' name='edit' value='Edit Artist'/>
|
<input type='submit' name='edit' value='Edit Artist'/>
|
||||||
<input type='hidden' name='artist_id' value='".$artistID."'>
|
<input type='hidden' name='artist_id' value='".$artistID."'>
|
||||||
</form>";
|
</form>";
|
||||||
|
|
||||||
if ($is_admin) {
|
if ($is_admin) {
|
||||||
$html .= "<form method='post' action='".make_link("artist/nuke_artist")."'>
|
$html .= "<form method='post' action='".make_link("artist/nuke_artist")."'>
|
||||||
".$user->get_auth_html()."
|
".$user->get_auth_html()."
|
||||||
@ -47,19 +47,19 @@ class ArtistsTheme extends Themelet
|
|||||||
<input type='hidden' name='artist_id' value='".$artistID."'>
|
<input type='hidden' name='artist_id' value='".$artistID."'>
|
||||||
</form>";
|
</form>";
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= "<form method='post' action='".make_link("artist/add_alias")."'>
|
$html .= "<form method='post' action='".make_link("artist/add_alias")."'>
|
||||||
".$user->get_auth_html()."
|
".$user->get_auth_html()."
|
||||||
<input type='submit' name='edit' value='Add Alias'/>
|
<input type='submit' name='edit' value='Add Alias'/>
|
||||||
<input type='hidden' name='artist_id' value='".$artistID."'>
|
<input type='hidden' name='artist_id' value='".$artistID."'>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form method='post' action='".make_link("artist/add_member")."'>
|
<form method='post' action='".make_link("artist/add_member")."'>
|
||||||
".$user->get_auth_html()."
|
".$user->get_auth_html()."
|
||||||
<input type='submit' name='edit' value='Add Member'/>
|
<input type='submit' name='edit' value='Add Member'/>
|
||||||
<input type='hidden' name='artist_id' value='".$artistID."'>
|
<input type='hidden' name='artist_id' value='".$artistID."'>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form method='post' action='".make_link("artist/add_url")."'>
|
<form method='post' action='".make_link("artist/add_url")."'>
|
||||||
".$user->get_auth_html()."
|
".$user->get_auth_html()."
|
||||||
<input type='submit' name='edit' value='Add Url'/>
|
<input type='submit' name='edit' value='Add Url'/>
|
||||||
@ -131,7 +131,7 @@ class ArtistsTheme extends Themelet
|
|||||||
global $page;
|
global $page;
|
||||||
$page->add_block(new Block("Edit artist", $html, "main", 10));
|
$page->add_block(new Block("Edit artist", $html, "main", 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function new_artist_composer()
|
public function new_artist_composer()
|
||||||
{
|
{
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
@ -152,7 +152,7 @@ class ArtistsTheme extends Themelet
|
|||||||
$page->set_heading("Artists");
|
$page->set_heading("Artists");
|
||||||
$page->add_block(new Block("Artists", $html, "main", 10));
|
$page->add_block(new Block("Artists", $html, "main", 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function list_artists($artists, $pageNumber, $totalPages)
|
public function list_artists($artists, $pageNumber, $totalPages)
|
||||||
{
|
{
|
||||||
global $user, $page;
|
global $user, $page;
|
||||||
@ -167,7 +167,7 @@ class ArtistsTheme extends Themelet
|
|||||||
if (!$user->is_anonymous()) {
|
if (!$user->is_anonymous()) {
|
||||||
$html .= "<th colspan='2'>Action</th>";
|
$html .= "<th colspan='2'>Action</th>";
|
||||||
} // space for edit link
|
} // space for edit link
|
||||||
|
|
||||||
$html .= "</tr></thead>";
|
$html .= "</tr></thead>";
|
||||||
|
|
||||||
$deletionLinkActionArray = [
|
$deletionLinkActionArray = [
|
||||||
@ -244,7 +244,7 @@ class ArtistsTheme extends Themelet
|
|||||||
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
|
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
|
||||||
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
|
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
';
|
';
|
||||||
|
|
||||||
global $page;
|
global $page;
|
||||||
@ -354,7 +354,7 @@ class ArtistsTheme extends Themelet
|
|||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>";
|
<th></th>";
|
||||||
|
|
||||||
if ($userIsLogged) {
|
if ($userIsLogged) {
|
||||||
$html .= "<th></th>";
|
$html .= "<th></th>";
|
||||||
}
|
}
|
||||||
@ -402,13 +402,13 @@ class ArtistsTheme extends Themelet
|
|||||||
$artist_images = "";
|
$artist_images = "";
|
||||||
foreach ($images as $image) {
|
foreach ($images as $image) {
|
||||||
$thumb_html = $this->build_thumb_html($image);
|
$thumb_html = $this->build_thumb_html($image);
|
||||||
|
|
||||||
$artist_images .= '<span class="thumb">'.
|
$artist_images .= '<span class="thumb">'.
|
||||||
'<a href="$image_link">'.$thumb_html.'</a>'.
|
'<a href="$image_link">'.$thumb_html.'</a>'.
|
||||||
'</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
|
||||||
@ -548,11 +548,11 @@ class ArtistsTheme extends Themelet
|
|||||||
|
|
||||||
public function get_help_html()
|
public function get_help_html()
|
||||||
{
|
{
|
||||||
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>
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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") {
|
||||||
@ -314,14 +314,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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,14 +37,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"));
|
||||||
|
@ -12,58 +12,78 @@ 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 = $_GET["limit"] ?? 0;
|
||||||
return;
|
$s = $_GET["s"] ?? null;
|
||||||
}
|
|
||||||
|
$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);
|
||||||
|
|
||||||
$s = strtolower($_GET["s"]);
|
|
||||||
if (
|
|
||||||
$s == '' ||
|
|
||||||
$s[0] == '_' ||
|
|
||||||
$s[0] == '%' ||
|
|
||||||
strlen($s) > 32
|
|
||||||
) {
|
|
||||||
$page->set_data("{}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//$limit = 0;
|
|
||||||
$cache_key = "autocomplete-$s";
|
|
||||||
$limitSQL = "";
|
|
||||||
$s = str_replace('_', '\_', $s);
|
|
||||||
$s = str_replace('%', '\%', $s);
|
|
||||||
$SQLarr = ["search"=>"$s%"]; #, "cat_search"=>"%:$s%"];
|
|
||||||
if (isset($_GET["limit"]) && $_GET["limit"] !== 0) {
|
|
||||||
$limitSQL = "LIMIT :limit";
|
|
||||||
$SQLarr['limit'] = $_GET["limit"];
|
|
||||||
$cache_key .= "-" . $_GET["limit"];
|
|
||||||
}
|
|
||||||
|
|
||||||
$res = $cache->get($cache_key);
|
|
||||||
if (!$res) {
|
|
||||||
$res = $database->get_pairs(
|
|
||||||
"
|
|
||||||
SELECT tag, count
|
|
||||||
FROM tags
|
|
||||||
WHERE LOWER(tag) LIKE LOWER(:search)
|
|
||||||
-- OR LOWER(tag) LIKE LOWER(:cat_search)
|
|
||||||
AND count > 0
|
|
||||||
ORDER BY count DESC
|
|
||||||
$limitSQL",
|
|
||||||
$SQLarr
|
|
||||||
);
|
|
||||||
$cache->set($cache_key, $res, 600);
|
|
||||||
}
|
|
||||||
|
|
||||||
$page->set_data(json_encode($res));
|
$page->set_data(json_encode($res));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->theme->build_autocomplete($page);
|
$this->theme->build_autocomplete($page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onApiRequest(ApiRequestEvent $event)
|
||||||
|
{
|
||||||
|
if ($event->method == "autocomplete") {
|
||||||
|
$event->result = $this->complete(
|
||||||
|
$event->params->search ?? "",
|
||||||
|
$event->params->limit ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = "";
|
||||||
|
$search = str_replace('_', '\_', $search);
|
||||||
|
$search = str_replace('%', '\%', $search);
|
||||||
|
$SQLarr = ["search"=>"$search%"]; #, "cat_search"=>"%:$search%"];
|
||||||
|
if ($limit !== 0) {
|
||||||
|
$limitSQL = "LIMIT :limit";
|
||||||
|
$SQLarr['limit'] = $limit;
|
||||||
|
$cache_key .= "-" . $limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = $cache->get($cache_key);
|
||||||
|
if (!$res) {
|
||||||
|
$res = $database->get_pairs(
|
||||||
|
"
|
||||||
|
SELECT tag, count
|
||||||
|
FROM tags
|
||||||
|
WHERE LOWER(tag) LIKE LOWER(:search)
|
||||||
|
-- OR LOWER(tag) LIKE LOWER(:cat_search)
|
||||||
|
AND count > 0
|
||||||
|
ORDER BY count DESC
|
||||||
|
$limitSQL
|
||||||
|
",
|
||||||
|
$SQLarr
|
||||||
|
);
|
||||||
|
$cache->set($cache_key, $res, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,6 @@ class BBCodeInfo extends ExtensionInfo
|
|||||||
<li>[[wiki article|with some text]]
|
<li>[[wiki article|with some text]]
|
||||||
<li>[quote]text[/quote]
|
<li>[quote]text[/quote]
|
||||||
<li>[quote=Username]text[/quote]
|
<li>[quote=Username]text[/quote]
|
||||||
<li>>>123 (link to image #123)
|
<li>>>123 (link to post #123)
|
||||||
</ul>";
|
</ul>";
|
||||||
}
|
}
|
||||||
|
@ -4,60 +4,60 @@ 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,32 +67,32 @@ 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]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,31 +15,26 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,11 +93,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 +115,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"));
|
||||||
|
@ -42,7 +42,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");
|
||||||
|
@ -9,5 +9,5 @@ class BulkActionsInfo extends ExtensionInfo
|
|||||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||||
public $license = self::LICENSE_WTFPL;
|
public $license = self::LICENSE_WTFPL;
|
||||||
public $description = "Provides query and selection-based bulk action support";
|
public $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 $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.";
|
||||||
}
|
}
|
||||||
|
@ -193,14 +193,14 @@ class BulkActions extends Extension
|
|||||||
if (is_iterable($items)) {
|
if (is_iterable($items)) {
|
||||||
send_event($bae);
|
send_event($bae);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($bae->redirect) {
|
||||||
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
|
$page->set_redirect(referer_or(make_link()));
|
||||||
|
}
|
||||||
} catch (BulkActionException $e) {
|
} catch (BulkActionException $e) {
|
||||||
log_error(BulkActionsInfo::KEY, $e->getMessage(), $e->getMessage());
|
log_error(BulkActionsInfo::KEY, $e->getMessage(), $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($bae->redirect) {
|
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
|
||||||
$page->set_redirect(referer_or(make_link()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +255,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,7 +8,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'
|
||||||
|
@ -6,7 +6,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)
|
||||||
|
@ -9,16 +9,16 @@ class BulkAddCSVInfo extends ExtensionInfo
|
|||||||
public $url = self::SHIMMIE_URL;
|
public $url = self::SHIMMIE_URL;
|
||||||
public $authors = ["velocity37"=>"velocity37@gmail.com"];
|
public $authors = ["velocity37"=>"velocity37@gmail.com"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Bulk add server-side images with metadata from CSV file";
|
public $description = "Bulk add server-side posts with metadata from CSV file";
|
||||||
public $documentation =
|
public $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>";
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ class BulkAddCSVTheme extends Themelet
|
|||||||
*/
|
*/
|
||||||
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 +26,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"))."
|
||||||
|
@ -21,7 +21,7 @@ class BulkDownload extends Extension
|
|||||||
|
|
||||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $config;
|
global $user;
|
||||||
|
|
||||||
if ($user->can(Permissions::BULK_DOWNLOAD)) {
|
if ($user->can(Permissions::BULK_DOWNLOAD)) {
|
||||||
$event->add_action(BulkDownload::DOWNLOAD_ACTION_NAME, "Download ZIP");
|
$event->add_action(BulkDownload::DOWNLOAD_ACTION_NAME, "Download ZIP");
|
||||||
|
@ -7,6 +7,7 @@ class BulkExportEvent extends Event
|
|||||||
|
|
||||||
public function __construct(Image $image)
|
public function __construct(Image $image)
|
||||||
{
|
{
|
||||||
|
parent::__construct();
|
||||||
$this->image = $image;
|
$this->image = $image;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,6 +20,7 @@ class BulkImportEvent extends Event
|
|||||||
|
|
||||||
public function __construct(Image $image, $fields)
|
public function __construct(Image $image, $fields)
|
||||||
{
|
{
|
||||||
|
parent::__construct();
|
||||||
$this->image = $image;
|
$this->image = $image;
|
||||||
$this->fields = $fields;
|
$this->fields = $fields;
|
||||||
}
|
}
|
||||||
|
@ -5,30 +5,24 @@ class BulkImportExport extends DataHandlerExtension
|
|||||||
{
|
{
|
||||||
const EXPORT_ACTION_NAME = "bulk_export";
|
const EXPORT_ACTION_NAME = "bulk_export";
|
||||||
const EXPORT_INFO_FILE_NAME = "export.json";
|
const EXPORT_INFO_FILE_NAME = "export.json";
|
||||||
protected $SUPPORTED_MIME = [MIME_TYPE_ZIP];
|
protected $SUPPORTED_MIME = [MimeType::ZIP];
|
||||||
|
|
||||||
|
|
||||||
public function onDataUpload(DataUploadEvent $event)
|
public function onDataUpload(DataUploadEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $database;
|
global $user, $database;
|
||||||
|
|
||||||
if ($this->supported_ext($event->type) &&
|
if ($this->supported_mime($event->mime) &&
|
||||||
$user->can(Permissions::BULK_IMPORT)) {
|
$user->can(Permissions::BULK_IMPORT)) {
|
||||||
$zip = new ZipArchive;
|
$zip = new ZipArchive;
|
||||||
|
|
||||||
if ($zip->open($event->tmpname) === true) {
|
if ($zip->open($event->tmpname) === true) {
|
||||||
$info = $zip->getStream(self::EXPORT_INFO_FILE_NAME);
|
$json_data = $this->get_export_data($zip);
|
||||||
$json_data = [];
|
|
||||||
if ($info !== false) {
|
if (empty($json_data)) {
|
||||||
try {
|
return;
|
||||||
$json_string = stream_get_contents($info);
|
|
||||||
$json_data = json_decode($json_string);
|
|
||||||
} finally {
|
|
||||||
fclose($info);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new SCoreException("Could not get " . self::EXPORT_INFO_FILE_NAME . " from archive");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$total = 0;
|
$total = 0;
|
||||||
$skipped = 0;
|
$skipped = 0;
|
||||||
$failed = 0;
|
$failed = 0;
|
||||||
@ -42,7 +36,7 @@ class BulkImportExport extends DataHandlerExtension
|
|||||||
$image = Image::by_hash($item->hash);
|
$image = Image::by_hash($item->hash);
|
||||||
if ($image!=null) {
|
if ($image!=null) {
|
||||||
$skipped++;
|
$skipped++;
|
||||||
log_info(BulkImportExportInfo::KEY, "Image $item->hash already present, skipping");
|
log_info(BulkImportExportInfo::KEY, "Post $item->hash already present, skipping");
|
||||||
$database->commit();
|
$database->commit();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -106,7 +100,7 @@ class BulkImportExport extends DataHandlerExtension
|
|||||||
|
|
||||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $config;
|
global $user;
|
||||||
|
|
||||||
if ($user->can(Permissions::BULK_EXPORT)) {
|
if ($user->can(Permissions::BULK_EXPORT)) {
|
||||||
$event->add_action(self::EXPORT_ACTION_NAME, "Export");
|
$event->add_action(self::EXPORT_ACTION_NAME, "Export");
|
||||||
@ -169,4 +163,20 @@ class BulkImportExport extends DataHandlerExtension
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function get_export_data(ZipArchive $zip): ?array
|
||||||
|
{
|
||||||
|
$info = $zip->getStream(self::EXPORT_INFO_FILE_NAME);
|
||||||
|
if ($info !== false) {
|
||||||
|
try {
|
||||||
|
$json_string = stream_get_contents($info);
|
||||||
|
$json_data = json_decode($json_string);
|
||||||
|
} finally {
|
||||||
|
fclose($info);
|
||||||
|
}
|
||||||
|
return $json_data;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ class CommentListInfo extends ExtensionInfo
|
|||||||
public const KEY = "comment";
|
public const KEY = "comment";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public $key = self::KEY;
|
||||||
public $name = "Image Comments";
|
public $name = "Post Comments";
|
||||||
public $url = self::SHIMMIE_URL;
|
public $url = self::SHIMMIE_URL;
|
||||||
public $authors = self::SHISH_AUTHOR;
|
public $authors = self::SHISH_AUTHOR;
|
||||||
public $license = self::LICENSE_GPLV2;
|
public $license = self::LICENSE_GPLV2;
|
||||||
|
@ -158,15 +158,15 @@ class CommentList extends Extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->get_version("ext_comments_version") == 1) {
|
if ($this->get_version("ext_comments_version") == 1) {
|
||||||
$database->Execute("CREATE INDEX comments_owner_ip ON comments(owner_ip)");
|
$database->execute("CREATE INDEX comments_owner_ip ON comments(owner_ip)");
|
||||||
$database->Execute("CREATE INDEX comments_posted ON comments(posted)");
|
$database->execute("CREATE INDEX comments_posted ON comments(posted)");
|
||||||
$this->set_version("ext_comments_version", 2);
|
$this->set_version("ext_comments_version", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->get_version("ext_comments_version") == 2) {
|
if ($this->get_version("ext_comments_version") == 2) {
|
||||||
$this->set_version("ext_comments_version", 3);
|
$this->set_version("ext_comments_version", 3);
|
||||||
$database->Execute("ALTER TABLE comments ADD FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE");
|
$database->execute("ALTER TABLE comments ADD FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE");
|
||||||
$database->Execute("ALTER TABLE comments ADD FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT");
|
$database->execute("ALTER TABLE comments ADD FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT");
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: add foreign keys, bump to v3
|
// FIXME: add foreign keys, bump to v3
|
||||||
@ -266,7 +266,7 @@ class CommentList extends Extension
|
|||||||
|
|
||||||
$total_pages = $cache->get("comment_pages");
|
$total_pages = $cache->get("comment_pages");
|
||||||
if (empty($total_pages)) {
|
if (empty($total_pages)) {
|
||||||
$total_pages = (int)($database->get_one("
|
$total_pages = (int)ceil($database->get_one("
|
||||||
SELECT COUNT(c1)
|
SELECT COUNT(c1)
|
||||||
FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
|
FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
|
||||||
") / 10);
|
") / 10);
|
||||||
@ -278,7 +278,7 @@ class CommentList extends Extension
|
|||||||
$threads_per_page = 10;
|
$threads_per_page = 10;
|
||||||
$start = $threads_per_page * $current_page;
|
$start = $threads_per_page * $current_page;
|
||||||
|
|
||||||
$result = $database->Execute("
|
$result = $database->execute("
|
||||||
SELECT image_id,MAX(posted) AS latest
|
SELECT image_id,MAX(posted) AS latest
|
||||||
FROM comments
|
FROM comments
|
||||||
$where
|
$where
|
||||||
@ -370,7 +370,7 @@ class CommentList extends Extension
|
|||||||
public function onCommentDeletion(CommentDeletionEvent $event)
|
public function onCommentDeletion(CommentDeletionEvent $event)
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
$database->Execute("
|
$database->execute("
|
||||||
DELETE FROM comments
|
DELETE FROM comments
|
||||||
WHERE id=:comment_id
|
WHERE id=:comment_id
|
||||||
", ["comment_id"=>$event->comment_id]);
|
", ["comment_id"=>$event->comment_id]);
|
||||||
@ -595,7 +595,7 @@ class CommentList extends Extension
|
|||||||
if ($user->is_anonymous()) {
|
if ($user->is_anonymous()) {
|
||||||
$page->add_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/");
|
$page->add_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/");
|
||||||
}
|
}
|
||||||
$database->Execute(
|
$database->execute(
|
||||||
"INSERT INTO comments(image_id, owner_id, owner_ip, posted, comment) ".
|
"INSERT INTO comments(image_id, owner_id, owner_ip, posted, comment) ".
|
||||||
"VALUES(:image_id, :user_id, :remote_addr, now(), :comment)",
|
"VALUES(:image_id, :user_id, :remote_addr, now(), :comment)",
|
||||||
["image_id"=>$image_id, "user_id"=>$user->id, "remote_addr"=>$_SERVER['REMOTE_ADDR'], "comment"=>$comment]
|
["image_id"=>$image_id, "user_id"=>$user->id, "remote_addr"=>$_SERVER['REMOTE_ADDR'], "comment"=>$comment]
|
||||||
@ -604,7 +604,7 @@ class CommentList extends Extension
|
|||||||
$snippet = substr($comment, 0, 100);
|
$snippet = substr($comment, 0, 100);
|
||||||
$snippet = str_replace("\n", " ", $snippet);
|
$snippet = str_replace("\n", " ", $snippet);
|
||||||
$snippet = str_replace("\r", " ", $snippet);
|
$snippet = str_replace("\r", " ", $snippet);
|
||||||
log_info("comment", "Comment #$cid added to Image #$image_id: $snippet");
|
log_info("comment", "Comment #$cid added to >>$image_id: $snippet");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function comment_checks(int $image_id, User $user, string $comment)
|
private function comment_checks(int $image_id, User $user, string $comment)
|
||||||
|
@ -289,23 +289,23 @@ class CommentListTheme extends Themelet
|
|||||||
|
|
||||||
public function get_help_html()
|
public function get_help_html()
|
||||||
{
|
{
|
||||||
return '<p>Search for images containing a certain number of comments, or comments by a particular individual.</p>
|
return '<p>Search for posts containing a certain number of comments, or comments by a particular individual.</p>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>comments=1</pre>
|
<pre>comments=1</pre>
|
||||||
<p>Returns images with exactly 1 comment.</p>
|
<p>Returns posts with exactly 1 comment.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>comments>0</pre>
|
<pre>comments>0</pre>
|
||||||
<p>Returns images with 1 or more comments. </p>
|
<p>Returns posts with 1 or more comments. </p>
|
||||||
</div>
|
</div>
|
||||||
<p>Can use <, <=, >, >=, or =.</p>
|
<p>Can use <, <=, >, >=, or =.</p>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>commented_by:username</pre>
|
<pre>commented_by:username</pre>
|
||||||
<p>Returns images that have been commented on by "username". </p>
|
<p>Returns posts that have been commented on by "username". </p>
|
||||||
</div>
|
</div>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>commented_by_userno:123</pre>
|
<pre>commented_by_userno:123</pre>
|
||||||
<p>Returns images that have been commented on by user 123. </p>
|
<p>Returns posts that have been commented on by user 123. </p>
|
||||||
</div>
|
</div>
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
|
@ -419,10 +419,10 @@ class CronUploader extends Extension
|
|||||||
if ($corrupt) {
|
if ($corrupt) {
|
||||||
// Move to corrupt dir
|
// Move to corrupt dir
|
||||||
$newDir = join_path($this->get_failed_dir(), $output_subdir, $relativeDir);
|
$newDir = join_path($this->get_failed_dir(), $output_subdir, $relativeDir);
|
||||||
$info = "ERROR: Image was not uploaded. ";
|
$info = "ERROR: Post was not uploaded. ";
|
||||||
} else {
|
} else {
|
||||||
$newDir = join_path($this->get_uploaded_dir(), $output_subdir, $relativeDir);
|
$newDir = join_path($this->get_uploaded_dir(), $output_subdir, $relativeDir);
|
||||||
$info = "Image successfully uploaded. ";
|
$info = "Post successfully uploaded. ";
|
||||||
}
|
}
|
||||||
$newDir = str_replace(DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $newDir);
|
$newDir = str_replace(DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $newDir);
|
||||||
|
|
||||||
@ -434,7 +434,7 @@ class CronUploader extends Extension
|
|||||||
// move file to correct dir
|
// move file to correct dir
|
||||||
rename($path, $newFile);
|
rename($path, $newFile);
|
||||||
|
|
||||||
$this->log_message(SCORE_LOG_INFO, $info . "Image \"$filename\" moved from queue to \"$newDir\".");
|
$this->log_message(SCORE_LOG_INFO, $info . "Post \"$filename\" moved from queue to \"$newDir\".");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -462,11 +462,14 @@ class CronUploader extends Extension
|
|||||||
|
|
||||||
// Generate info message
|
// Generate info message
|
||||||
if ($event->image_id == -1) {
|
if ($event->image_id == -1) {
|
||||||
|
if (array_key_exists("mime", $event->metadata)) {
|
||||||
|
throw new UploadException("File type not recognised (".$event->metadata["mime"]."). Filename: {$filename}");
|
||||||
|
}
|
||||||
throw new UploadException("File type not recognised. Filename: {$filename}");
|
throw new UploadException("File type not recognised. Filename: {$filename}");
|
||||||
} elseif ($event->merged === true) {
|
} elseif ($event->merged === true) {
|
||||||
$infomsg = "Image merged. ID: {$event->image_id} - Filename: {$filename}";
|
$infomsg = "Post merged. ID: {$event->image_id} - Filename: {$filename}";
|
||||||
} else {
|
} else {
|
||||||
$infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename}";
|
$infomsg = "Post uploaded. ID: {$event->image_id} - Filename: {$filename}";
|
||||||
}
|
}
|
||||||
$this->log_message(SCORE_LOG_INFO, $infomsg);
|
$this->log_message(SCORE_LOG_INFO, $infomsg);
|
||||||
|
|
||||||
@ -508,7 +511,7 @@ class CronUploader extends Extension
|
|||||||
$base = $this->get_queue_dir();
|
$base = $this->get_queue_dir();
|
||||||
|
|
||||||
if (!is_dir($base)) {
|
if (!is_dir($base)) {
|
||||||
$this->log_message(SCORE_LOG_WARNING, "Image Queue Directory could not be found at \"$base\".");
|
$this->log_message(SCORE_LOG_WARNING, "Post Queue Directory could not be found at \"$base\".");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,7 +548,7 @@ class CronUploader extends Extension
|
|||||||
global $page;
|
global $page;
|
||||||
|
|
||||||
$page->set_mode(PageMode::MANUAL);
|
$page->set_mode(PageMode::MANUAL);
|
||||||
$page->set_type(MIME_TYPE_TEXT);
|
$page->set_mime(MimeType::TEXT);
|
||||||
$page->send_headers();
|
$page->send_headers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ class CronUploaderTheme extends Themelet
|
|||||||
<li>Create a cron job or something else that can open a url on specified times.
|
<li>Create a cron job or something else that can open a url on specified times.
|
||||||
<br/>cron is a service that runs commands over and over again on a a schedule. You can set up cron (or any similar tool) to run the command above to trigger the import on whatever schedule you desire.
|
<br/>cron is a service that runs commands over and over again on a a schedule. You can set up cron (or any similar tool) to run the command above to trigger the import on whatever schedule you desire.
|
||||||
<br />If you're not sure how to do this, you can give the command to your web host and you can ask them to create the cron job for you.
|
<br />If you're not sure how to do this, you can give the command to your web host and you can ask them to create the cron job for you.
|
||||||
<br />When you create the cron job, you choose when to upload new images.</li>
|
<br />When you create the cron job, you choose when to upload new posts.</li>
|
||||||
</ol>";
|
</ol>";
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class CustomHtmlHeaders extends Extension
|
|||||||
$sitename_in_title = $config->get_string("sitename_in_title");
|
$sitename_in_title = $config->get_string("sitename_in_title");
|
||||||
|
|
||||||
// sitename is already in title (can occur on index & other pages)
|
// sitename is already in title (can occur on index & other pages)
|
||||||
if (strstr($page->title, $site_title)) {
|
if (str_contains($page->title, $site_title)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,25 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use \MicroHTML\HTMLElement;
|
||||||
|
|
||||||
|
function TAGS(...$args)
|
||||||
|
{
|
||||||
|
return new HTMLElement("tags", $args);
|
||||||
|
}
|
||||||
|
function TAG(...$args)
|
||||||
|
{
|
||||||
|
return new HTMLElement("tag", $args);
|
||||||
|
}
|
||||||
|
function POSTS(...$args)
|
||||||
|
{
|
||||||
|
return new HTMLElement("posts", $args);
|
||||||
|
}
|
||||||
|
function POST(...$args)
|
||||||
|
{
|
||||||
|
return new HTMLElement("post", $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DanbooruApi extends Extension
|
class DanbooruApi extends Extension
|
||||||
{
|
{
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
@ -10,14 +30,14 @@ class DanbooruApi extends Extension
|
|||||||
|
|
||||||
if ($event->page_matches("api/danbooru/add_post") || $event->page_matches("api/danbooru/post/create.xml")) {
|
if ($event->page_matches("api/danbooru/add_post") || $event->page_matches("api/danbooru/post/create.xml")) {
|
||||||
// No XML data is returned from this function
|
// No XML data is returned from this function
|
||||||
$page->set_type(MIME_TYPE_TEXT);
|
$page->set_mime(MimeType::TEXT);
|
||||||
$this->api_add_post();
|
$this->api_add_post();
|
||||||
} elseif ($event->page_matches("api/danbooru/find_posts") || $event->page_matches("api/danbooru/post/index.xml")) {
|
} elseif ($event->page_matches("api/danbooru/find_posts") || $event->page_matches("api/danbooru/post/index.xml")) {
|
||||||
$page->set_type(MIME_TYPE_XML_APPLICATION);
|
$page->set_mime(MimeType::XML_APPLICATION);
|
||||||
$page->set_data($this->api_find_posts());
|
$page->set_data((string)$this->api_find_posts());
|
||||||
} elseif ($event->page_matches("api/danbooru/find_tags")) {
|
} elseif ($event->page_matches("api/danbooru/find_tags")) {
|
||||||
$page->set_type(MIME_TYPE_XML_APPLICATION);
|
$page->set_mime(MimeType::XML_APPLICATION);
|
||||||
$page->set_data($this->api_find_tags());
|
$page->set_data((string)$this->api_find_tags());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
|
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
|
||||||
@ -37,7 +57,7 @@ class DanbooruApi extends Extension
|
|||||||
* Authenticates a user based on the contents of the login and password parameters
|
* Authenticates a user based on the contents of the login and password parameters
|
||||||
* or makes them anonymous. Does not set any cookies or anything permanent.
|
* or makes them anonymous. Does not set any cookies or anything permanent.
|
||||||
*/
|
*/
|
||||||
private function authenticate_user()
|
private function authenticate_user(): void
|
||||||
{
|
{
|
||||||
global $config, $user;
|
global $config, $user;
|
||||||
|
|
||||||
@ -66,7 +86,7 @@ class DanbooruApi extends Extension
|
|||||||
* - tags: any typical tag query. See Tag#parse_query for details.
|
* - tags: any typical tag query. See Tag#parse_query for details.
|
||||||
* - after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh
|
* - after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh
|
||||||
*/
|
*/
|
||||||
private function api_find_tags(): string
|
private function api_find_tags(): HTMLElement
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
$results = [];
|
$results = [];
|
||||||
@ -110,16 +130,15 @@ class DanbooruApi extends Extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tag results collected, build XML output
|
// Tag results collected, build XML output
|
||||||
$xml = "<tags>\n";
|
$xml = TAGS();
|
||||||
foreach ($results as $tag) {
|
foreach ($results as $tag) {
|
||||||
$xml .= xml_tag("tag", [
|
$xml->appendChild(TAG([
|
||||||
"type" => "0",
|
"type" => "0",
|
||||||
"counts" => $tag[0],
|
"counts" => $tag[0],
|
||||||
"name" => $tag[1],
|
"name" => $tag[1],
|
||||||
"id" => $tag[2],
|
"id" => $tag[2],
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
$xml .= "</tags>";
|
|
||||||
return $xml;
|
return $xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +156,7 @@ class DanbooruApi extends Extension
|
|||||||
*
|
*
|
||||||
* #return string
|
* #return string
|
||||||
*/
|
*/
|
||||||
private function api_find_posts()
|
private function api_find_posts(): HTMLElement
|
||||||
{
|
{
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
@ -175,7 +194,7 @@ class DanbooruApi extends Extension
|
|||||||
|
|
||||||
// Now we have the array $results filled with Image objects
|
// Now we have the array $results filled with Image objects
|
||||||
// Let's display them
|
// Let's display them
|
||||||
$xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n";
|
$xml = POSTS(["count"=>$count, "offset"=>$start]);
|
||||||
foreach ($results as $img) {
|
foreach ($results as $img) {
|
||||||
// Sanity check to see if $img is really an image object
|
// Sanity check to see if $img is really an image object
|
||||||
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
|
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
|
||||||
@ -185,7 +204,7 @@ class DanbooruApi extends Extension
|
|||||||
$taglist = $img->get_tag_list();
|
$taglist = $img->get_tag_list();
|
||||||
$owner = $img->get_owner();
|
$owner = $img->get_owner();
|
||||||
$previewsize = get_thumbnail_size($img->width, $img->height);
|
$previewsize = get_thumbnail_size($img->width, $img->height);
|
||||||
$xml .= xml_tag("post", [
|
$xml->appendChild(TAG([
|
||||||
"id" => $img->id,
|
"id" => $img->id,
|
||||||
"md5" => $img->hash,
|
"md5" => $img->hash,
|
||||||
"file_name" => $img->filename,
|
"file_name" => $img->filename,
|
||||||
@ -202,9 +221,8 @@ class DanbooruApi extends Extension
|
|||||||
"source" => $img->source,
|
"source" => $img->source,
|
||||||
"score" => 0,
|
"score" => 0,
|
||||||
"author" => $owner->name
|
"author" => $owner->name
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
$xml .= "</posts>";
|
|
||||||
return $xml;
|
return $xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +253,7 @@ class DanbooruApi extends Extension
|
|||||||
* Get:
|
* Get:
|
||||||
* - Redirected to the newly uploaded post.
|
* - Redirected to the newly uploaded post.
|
||||||
*/
|
*/
|
||||||
private function api_add_post()
|
private function api_add_post(): void
|
||||||
{
|
{
|
||||||
global $user, $page;
|
global $user, $page;
|
||||||
$danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path
|
$danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path
|
||||||
@ -271,8 +289,8 @@ class DanbooruApi extends Extension
|
|||||||
}
|
}
|
||||||
} elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided
|
} elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided
|
||||||
$source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
|
$source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
|
||||||
$file = tempnam("/tmp", "shimmie_transload");
|
$file = tempnam(sys_get_temp_dir(), "shimmie_transload");
|
||||||
$ok = transload($source, $file);
|
$ok = fetch_url($source, $file);
|
||||||
if (!$ok) {
|
if (!$ok) {
|
||||||
$page->set_code(409);
|
$page->set_code(409);
|
||||||
$page->add_http_header("X-Danbooru-Errors: fopen read error");
|
$page->add_http_header("X-Danbooru-Errors: fopen read error");
|
||||||
|
@ -8,12 +8,12 @@ class DanbooruApiTest extends ShimmiePHPUnitTestCase
|
|||||||
$image_id = $this->post_image("tests/bedroom_workshop.jpg", "data");
|
$image_id = $this->post_image("tests/bedroom_workshop.jpg", "data");
|
||||||
|
|
||||||
$this->get_page("api/danbooru/find_posts");
|
$this->get_page("api/danbooru/find_posts");
|
||||||
$this->get_page("api/danbooru/find_posts?id=$image_id");
|
$this->get_page("api/danbooru/find_posts", ["id"=>$image_id]);
|
||||||
$this->get_page("api/danbooru/find_posts?md5=17fc89f372ed3636e28bd25cc7f3bac1");
|
$this->get_page("api/danbooru/find_posts", ["md5"=>"17fc89f372ed3636e28bd25cc7f3bac1"]);
|
||||||
|
|
||||||
$this->get_page("api/danbooru/find_tags");
|
$this->get_page("api/danbooru/find_tags");
|
||||||
$this->get_page("api/danbooru/find_tags?id=1");
|
$this->get_page("api/danbooru/find_tags", ["id"=>1]);
|
||||||
$this->get_page("api/danbooru/find_tags?name=data");
|
$this->get_page("api/danbooru/find_tags", ["name"=>"data"]);
|
||||||
|
|
||||||
$page = $this->get_page("api/danbooru/post/show/$image_id");
|
$page = $this->get_page("api/danbooru/post/show/$image_id");
|
||||||
$this->assertEquals(302, $page->code);
|
$this->assertEquals(302, $page->code);
|
||||||
|
17
ext/download/events.php
Normal file
17
ext/download/events.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ImageDownloadingEvent extends Event
|
||||||
|
{
|
||||||
|
public $image;
|
||||||
|
public $mime;
|
||||||
|
public $path;
|
||||||
|
public $file_modified = false;
|
||||||
|
|
||||||
|
public function __construct(Image $image, String $path, string $mime)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->image = $image;
|
||||||
|
$this->path = $path;
|
||||||
|
$this->mime = $mime;
|
||||||
|
}
|
||||||
|
}
|
14
ext/download/info.php
Normal file
14
ext/download/info.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class DownloadInfo extends ExtensionInfo
|
||||||
|
{
|
||||||
|
public const KEY = "download";
|
||||||
|
|
||||||
|
public $key = self::KEY;
|
||||||
|
public $name = "Download";
|
||||||
|
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||||
|
public $license = self::LICENSE_WTFPL;
|
||||||
|
public $description = "System-wide download functions";
|
||||||
|
public $core = true;
|
||||||
|
public $visibility = self::VISIBLE_HIDDEN;
|
||||||
|
}
|
26
ext/download/main.php
Normal file
26
ext/download/main.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "events.php";
|
||||||
|
|
||||||
|
class Download extends Extension
|
||||||
|
{
|
||||||
|
public function get_priority(): int
|
||||||
|
{
|
||||||
|
// Set near the end to give everything else a chance to process
|
||||||
|
return 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function onImageDownloading(ImageDownloadingEvent $event)
|
||||||
|
{
|
||||||
|
global $page;
|
||||||
|
|
||||||
|
$page->set_mime($event->mime);
|
||||||
|
|
||||||
|
$page->set_mode(PageMode::FILE);
|
||||||
|
|
||||||
|
$page->set_file($event->path, $event->file_modified);
|
||||||
|
|
||||||
|
$event->stop_processing = true;
|
||||||
|
}
|
||||||
|
}
|
13
ext/eokm/info.php
Normal file
13
ext/eokm/info.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
class EokmInfo extends ExtensionInfo
|
||||||
|
{
|
||||||
|
public const KEY = "eokm";
|
||||||
|
|
||||||
|
public $key = self::KEY;
|
||||||
|
public $name = "EOKM Filter";
|
||||||
|
public $url = self::SHIMMIE_URL;
|
||||||
|
public $authors = self::SHISH_AUTHOR;
|
||||||
|
public $license = self::LICENSE_GPLV2;
|
||||||
|
public $description = "Check uploads against the EOKM blocklist";
|
||||||
|
}
|
50
ext/eokm/main.php
Normal file
50
ext/eokm/main.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
class Eokm extends Extension
|
||||||
|
{
|
||||||
|
public function get_priority(): int
|
||||||
|
{
|
||||||
|
return 40;
|
||||||
|
} // early, to veto ImageUploadEvent
|
||||||
|
|
||||||
|
public function onImageAddition(ImageAdditionEvent $event)
|
||||||
|
{
|
||||||
|
global $config;
|
||||||
|
$username = $config->get_string("eokm_username");
|
||||||
|
$password = $config->get_string("eokm_password");
|
||||||
|
|
||||||
|
if ($username && $password) {
|
||||||
|
$ch = curl_init("https://api.eokmhashdb.nl/v1/check/md5");
|
||||||
|
// curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/xml', $additionalHeaders));
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($ch, CURLOPT_USERPWD, $username . ":" . $password);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $event->image->hash);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
$return = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($return == "false") {
|
||||||
|
// all ok
|
||||||
|
} elseif ($return == "true") {
|
||||||
|
log_warning("eokm", "User tried to upload banned image {$event->image->hash}");
|
||||||
|
throw new UploadException("Post banned");
|
||||||
|
} else {
|
||||||
|
log_warning("eokm", "Unexpected return from EOKM: $return");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||||
|
{
|
||||||
|
$sb = new SetupBlock("EOKM Filter");
|
||||||
|
|
||||||
|
$sb->start_table();
|
||||||
|
$sb->add_text_option("eokm_username", "Username", true);
|
||||||
|
$sb->add_text_option("eokm_password", "Password", true);
|
||||||
|
$sb->end_table();
|
||||||
|
|
||||||
|
$event->panel->add_block($sb);
|
||||||
|
}
|
||||||
|
}
|
27
ext/eokm/test.php
Normal file
27
ext/eokm/test.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
class EokmTest extends ShimmiePHPUnitTestCase
|
||||||
|
{
|
||||||
|
public function testPass()
|
||||||
|
{
|
||||||
|
// no EOKM login details set, so be a no-op
|
||||||
|
$this->log_in_as_user();
|
||||||
|
$this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
|
||||||
|
$this->assert_no_text("Image too large");
|
||||||
|
$this->assert_no_text("Image too small");
|
||||||
|
$this->assert_no_text("ratio");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function testFail()
|
||||||
|
{
|
||||||
|
$this->log_in_as_user();
|
||||||
|
try {
|
||||||
|
$this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
|
||||||
|
$this->assertTrue(false, "Invalid-size image was allowed");
|
||||||
|
} catch (UploadException $e) {
|
||||||
|
$this->assertEquals("Image too small", $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
@ -36,10 +36,10 @@ class ET extends Extension
|
|||||||
public function onCommand(CommandEvent $event)
|
public function onCommand(CommandEvent $event)
|
||||||
{
|
{
|
||||||
if ($event->cmd == "help") {
|
if ($event->cmd == "help") {
|
||||||
print "\tshimmie-info\n";
|
print "\tinfo\n";
|
||||||
print "\t\tList a bunch of info\n\n";
|
print "\t\tList a bunch of info\n\n";
|
||||||
}
|
}
|
||||||
if ($event->cmd == "shimmie-info") {
|
if ($event->cmd == "info") {
|
||||||
print($this->to_yaml($this->get_info()));
|
print($this->to_yaml($this->get_info()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ class ET extends Extension
|
|||||||
"extensions" => [
|
"extensions" => [
|
||||||
"core" => $core_exts,
|
"core" => $core_exts,
|
||||||
"extra" => $extra_exts,
|
"extra" => $extra_exts,
|
||||||
"handled_extensions" => DataHandlerExtension::get_all_supported_exts(),
|
"handled_mimes" => DataHandlerExtension::get_all_supported_mimes(),
|
||||||
],
|
],
|
||||||
"stats" => [
|
"stats" => [
|
||||||
'images' => (int)$database->get_one("SELECT COUNT(*) FROM images"),
|
'images' => (int)$database->get_one("SELECT COUNT(*) FROM images"),
|
||||||
@ -94,7 +94,7 @@ class ET extends Extension
|
|||||||
"width" => $config->get_int(ImageConfig::THUMB_WIDTH),
|
"width" => $config->get_int(ImageConfig::THUMB_WIDTH),
|
||||||
"height" => $config->get_int(ImageConfig::THUMB_HEIGHT),
|
"height" => $config->get_int(ImageConfig::THUMB_HEIGHT),
|
||||||
"scaling" => $config->get_int(ImageConfig::THUMB_SCALING),
|
"scaling" => $config->get_int(ImageConfig::THUMB_SCALING),
|
||||||
"type" => $config->get_string(ImageConfig::THUMB_TYPE),
|
"mime" => $config->get_string(ImageConfig::THUMB_MIME),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -35,17 +35,16 @@ class ETServer extends Extension
|
|||||||
|
|
||||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||||
{
|
{
|
||||||
global $config, $database;
|
global $database;
|
||||||
|
|
||||||
// shortcut to latest
|
// shortcut to latest
|
||||||
if ($config->get_int("et_server_version") < 1) {
|
if ($this->get_version("et_server_version") < 1) {
|
||||||
$database->create_table("registration", "
|
$database->create_table("registration", "
|
||||||
id SCORE_AIPK,
|
id SCORE_AIPK,
|
||||||
responded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
responded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
data TEXT NOT NULL,
|
data TEXT NOT NULL,
|
||||||
");
|
");
|
||||||
$config->set_int("et_server_version", 1);
|
$this->set_version("et_server_version", 1);
|
||||||
log_info("et_server", "extension installed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,5 @@ class FavoritesInfo extends ExtensionInfo
|
|||||||
"Gives users a \"favorite this image\" button that they can press
|
"Gives users a \"favorite this image\" button that they can press
|
||||||
<p>Favorites for a user can then be retrieved by searching for \"favorited_by=UserName\"
|
<p>Favorites for a user can then be retrieved by searching for \"favorited_by=UserName\"
|
||||||
<p>Popular images can be searched for by eg. \"favorites>5\"
|
<p>Popular images can be searched for by eg. \"favorites>5\"
|
||||||
<p>Favorite info can be added to an image's filename or tooltip using the \$favorites placeholder";
|
<p>Favorite info can be added to a post's filename or tooltip using the \$favorites placeholder";
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ class Favorites extends Extension
|
|||||||
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
||||||
$h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old));
|
$h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old));
|
||||||
$favorites_link = make_link("post/list/favorited_by={$event->display_user->name}/1");
|
$favorites_link = make_link("post/list/favorited_by={$event->display_user->name}/1");
|
||||||
$event->add_stats("<a href='$favorites_link'>Images favorited</a>: $i_favorites_count, $h_favorites_rate per day");
|
$event->add_stats("<a href='$favorites_link'>Posts favorited</a>: $i_favorites_count, $h_favorites_rate per day");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onImageInfoSet(ImageInfoSetEvent $event)
|
public function onImageInfoSet(ImageInfoSetEvent $event)
|
||||||
@ -202,8 +202,8 @@ class Favorites extends Extension
|
|||||||
global $database;
|
global $database;
|
||||||
|
|
||||||
if ($this->get_version("ext_favorites_version") < 1) {
|
if ($this->get_version("ext_favorites_version") < 1) {
|
||||||
$database->Execute("ALTER TABLE images ADD COLUMN favorites INTEGER NOT NULL DEFAULT 0");
|
$database->execute("ALTER TABLE images ADD COLUMN favorites INTEGER NOT NULL DEFAULT 0");
|
||||||
$database->Execute("CREATE INDEX images__favorites ON images(favorites)");
|
$database->execute("CREATE INDEX images__favorites ON images(favorites)");
|
||||||
$database->create_table("user_favorites", "
|
$database->create_table("user_favorites", "
|
||||||
image_id INTEGER NOT NULL,
|
image_id INTEGER NOT NULL,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
@ -218,12 +218,12 @@ class Favorites extends Extension
|
|||||||
|
|
||||||
if ($this->get_version("ext_favorites_version") < 2) {
|
if ($this->get_version("ext_favorites_version") < 2) {
|
||||||
log_info("favorites", "Cleaning user favourites");
|
log_info("favorites", "Cleaning user favourites");
|
||||||
$database->Execute("DELETE FROM user_favorites WHERE user_id NOT IN (SELECT id FROM users)");
|
$database->execute("DELETE FROM user_favorites WHERE user_id NOT IN (SELECT id FROM users)");
|
||||||
$database->Execute("DELETE FROM user_favorites WHERE image_id NOT IN (SELECT id FROM images)");
|
$database->execute("DELETE FROM user_favorites WHERE image_id NOT IN (SELECT id FROM images)");
|
||||||
|
|
||||||
log_info("favorites", "Adding foreign keys to user favourites");
|
log_info("favorites", "Adding foreign keys to user favourites");
|
||||||
$database->Execute("ALTER TABLE user_favorites ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;");
|
$database->execute("ALTER TABLE user_favorites ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;");
|
||||||
$database->Execute("ALTER TABLE user_favorites ADD FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;");
|
$database->execute("ALTER TABLE user_favorites ADD FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;");
|
||||||
$this->set_version("ext_favorites_version", 2);
|
$this->set_version("ext_favorites_version", 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,18 +233,18 @@ class Favorites extends Extension
|
|||||||
global $database;
|
global $database;
|
||||||
if ($do_set) {
|
if ($do_set) {
|
||||||
if (!$database->get_row("select 1 from user_favorites where image_id=:image_id and user_id=:user_id", ["image_id"=>$image_id, "user_id"=>$user_id])) {
|
if (!$database->get_row("select 1 from user_favorites where image_id=:image_id and user_id=:user_id", ["image_id"=>$image_id, "user_id"=>$user_id])) {
|
||||||
$database->Execute(
|
$database->execute(
|
||||||
"INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(:image_id, :user_id, NOW())",
|
"INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(:image_id, :user_id, NOW())",
|
||||||
["image_id"=>$image_id, "user_id"=>$user_id]
|
["image_id"=>$image_id, "user_id"=>$user_id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$database->Execute(
|
$database->execute(
|
||||||
"DELETE FROM user_favorites WHERE image_id = :image_id AND user_id = :user_id",
|
"DELETE FROM user_favorites WHERE image_id = :image_id AND user_id = :user_id",
|
||||||
["image_id"=>$image_id, "user_id"=>$user_id]
|
["image_id"=>$image_id, "user_id"=>$user_id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$database->Execute(
|
$database->execute(
|
||||||
"UPDATE images SET favorites=(SELECT COUNT(*) FROM user_favorites WHERE image_id=:image_id) WHERE id=:user_id",
|
"UPDATE images SET favorites=(SELECT COUNT(*) FROM user_favorites WHERE image_id=:image_id) WHERE id=:user_id",
|
||||||
["image_id"=>$image_id, "user_id"=>$user_id]
|
["image_id"=>$image_id, "user_id"=>$user_id]
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,7 @@ class FavoritesTest extends ShimmiePHPUnitTestCase
|
|||||||
|
|
||||||
# No favourites
|
# No favourites
|
||||||
$this->get_page("post/view/$image_id");
|
$this->get_page("post/view/$image_id");
|
||||||
$this->assert_title("Image $image_id: test");
|
$this->assert_title("Post $image_id: test");
|
||||||
$this->assert_no_text("Favorited By");
|
$this->assert_no_text("Favorited By");
|
||||||
|
|
||||||
# Add a favourite
|
# Add a favourite
|
||||||
@ -17,7 +17,7 @@ class FavoritesTest extends ShimmiePHPUnitTestCase
|
|||||||
|
|
||||||
# Favourite shown on page
|
# Favourite shown on page
|
||||||
$this->get_page("post/view/$image_id");
|
$this->get_page("post/view/$image_id");
|
||||||
$this->assert_title("Image $image_id: test");
|
$this->assert_title("Post $image_id: test");
|
||||||
$this->assert_text("Favorited By");
|
$this->assert_text("Favorited By");
|
||||||
|
|
||||||
# Favourite shown on index
|
# Favourite shown on index
|
||||||
@ -26,14 +26,14 @@ class FavoritesTest extends ShimmiePHPUnitTestCase
|
|||||||
|
|
||||||
# Favourite shown on user page
|
# Favourite shown on user page
|
||||||
$this->get_page("user/test");
|
$this->get_page("user/test");
|
||||||
$this->assert_text("Images favorited</a>: 1");
|
$this->assert_text("Posts favorited</a>: 1");
|
||||||
|
|
||||||
# Delete a favourite
|
# Delete a favourite
|
||||||
send_event(new FavoriteSetEvent($image_id, $user, false));
|
send_event(new FavoriteSetEvent($image_id, $user, false));
|
||||||
|
|
||||||
# No favourites
|
# No favourites
|
||||||
$this->get_page("post/view/$image_id");
|
$this->get_page("post/view/$image_id");
|
||||||
$this->assert_title("Image $image_id: test");
|
$this->assert_title("Post $image_id: test");
|
||||||
$this->assert_no_text("Favorited By");
|
$this->assert_no_text("Favorited By");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,23 +34,23 @@ class FavoritesTheme extends Themelet
|
|||||||
|
|
||||||
public function get_help_html()
|
public function get_help_html()
|
||||||
{
|
{
|
||||||
return '<p>Search for images that have been favorited a certain number of times, or favorited by a particular individual.</p>
|
return '<p>Search for posts that have been favorited a certain number of times, or favorited by a particular individual.</p>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>favorites=1</pre>
|
<pre>favorites=1</pre>
|
||||||
<p>Returns images that have been favorited once.</p>
|
<p>Returns posts that have been favorited once.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>favorites>0</pre>
|
<pre>favorites>0</pre>
|
||||||
<p>Returns images that have been favorited 1 or more times</p>
|
<p>Returns posts that have been favorited 1 or more times</p>
|
||||||
</div>
|
</div>
|
||||||
<p>Can use <, <=, >, >=, or =.</p>
|
<p>Can use <, <=, >, >=, or =.</p>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>favorited_by:username</pre>
|
<pre>favorited_by:username</pre>
|
||||||
<p>Returns images that have been favorited by "username". </p>
|
<p>Returns posts that have been favorited by "username". </p>
|
||||||
</div>
|
</div>
|
||||||
<div class="command_example">
|
<div class="command_example">
|
||||||
<pre>favorited_by_userno:123</pre>
|
<pre>favorited_by_userno:123</pre>
|
||||||
<p>Returns images that have been favorited by user 123. </p>
|
<p>Returns posts that have been favorited by user 123. </p>
|
||||||
</div>
|
</div>
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
|
@ -5,21 +5,21 @@ class FeaturedInfo extends ExtensionInfo
|
|||||||
public const KEY = "featured";
|
public const KEY = "featured";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public $key = self::KEY;
|
||||||
public $name = "Featured Image";
|
public $name = "Featured Post";
|
||||||
public $url = self::SHIMMIE_URL;
|
public $url = self::SHIMMIE_URL;
|
||||||
public $authors = self::SHISH_AUTHOR;
|
public $authors = self::SHISH_AUTHOR;
|
||||||
public $license = self::LICENSE_GPLV2;
|
public $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Bring a specific image to the users' attentions";
|
public $description = "Bring a specific image to the users' attentions";
|
||||||
public $documentation =
|
public $documentation =
|
||||||
"Once enabled, a new \"feature this\" button will appear next
|
"Once enabled, a new \"feature this\" button will appear next
|
||||||
to the other image control buttons (delete, rotate, etc).
|
to the other post control buttons (delete, rotate, etc).
|
||||||
Clicking it will set the image as the site's current feature,
|
Clicking it will set the image as the site's current feature,
|
||||||
which will be shown in the side bar of the post list.
|
which will be shown in the side bar of the post list.
|
||||||
<p><b>Viewing a featured image</b>
|
<p><b>Viewing a featured post</b>
|
||||||
<br>Visit <code>/featured_image/view</code>
|
<br>Visit <code>/featured_image/view</code>
|
||||||
<p><b>Downloading a featured image</b>
|
<p><b>Downloading a featured post</b>
|
||||||
<br>Link to <code>/featured_image/download</code>. This will give
|
<br>Link to <code>/featured_image/download</code>. This will give
|
||||||
the raw data for an image (no HTML). This is useful so that you
|
the raw data for a post (no HTML). This is useful so that you
|
||||||
can set your desktop wallpaper to be the download URL, refreshed
|
can set your desktop wallpaper to be the download URL, refreshed
|
||||||
every couple of hours.";
|
every couple of hours.";
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class Featured extends Extension
|
|||||||
$id = int_escape($_POST['image_id']);
|
$id = int_escape($_POST['image_id']);
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
$config->set_int("featured_id", $id);
|
$config->set_int("featured_id", $id);
|
||||||
log_info("featured", "Featured image set to $id", "Featured image set");
|
log_info("featured", "Featured post set to >>$id", "Featured post set");
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/$id"));
|
$page->set_redirect(make_link("post/view/$id"));
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ class Featured extends Extension
|
|||||||
$image = Image::by_id($config->get_int("featured_id"));
|
$image = Image::by_id($config->get_int("featured_id"));
|
||||||
if (!is_null($image)) {
|
if (!is_null($image)) {
|
||||||
$page->set_mode(PageMode::DATA);
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type($image->get_mime_type());
|
$page->set_mime($image->get_mime());
|
||||||
$page->set_data(file_get_contents($image->get_image_filename()));
|
$page->set_data(file_get_contents($image->get_image_filename()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class FeaturedTest extends ShimmiePHPUnitTestCase
|
|||||||
$config->set_int("featured_id", $image_id);
|
$config->set_int("featured_id", $image_id);
|
||||||
|
|
||||||
$this->get_page("post/list");
|
$this->get_page("post/list");
|
||||||
$this->assert_text("Featured Image");
|
$this->assert_text("Featured Post");
|
||||||
|
|
||||||
# FIXME: test changing from one feature to another
|
# FIXME: test changing from one feature to another
|
||||||
|
|
||||||
@ -31,6 +31,6 @@ class FeaturedTest extends ShimmiePHPUnitTestCase
|
|||||||
// after deletion, there should be no feature
|
// after deletion, there should be no feature
|
||||||
$this->delete_image($image_id);
|
$this->delete_image($image_id);
|
||||||
$this->get_page("post/list");
|
$this->get_page("post/list");
|
||||||
$this->assert_no_text("Featured Image");
|
$this->assert_no_text("Featured Post");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ class FeaturedTheme extends Themelet
|
|||||||
{
|
{
|
||||||
public function display_featured(Page $page, Image $image): void
|
public function display_featured(Page $page, Image $image): void
|
||||||
{
|
{
|
||||||
$page->add_block(new Block("Featured Image", $this->build_featured_html($image), "left", 3));
|
$page->add_block(new Block("Featured Post", $this->build_featured_html($image), "left", 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_buttons_html(int $image_id): string
|
public function get_buttons_html(int $image_id): string
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class ForumInfo extends ExtensionInfo
|
class ForumInfo extends ExtensionInfo
|
||||||
{
|
{
|
||||||
public const KEY = "dorum";
|
public const KEY = "forum";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public $key = self::KEY;
|
||||||
public $name = "Forum";
|
public $name = "Forum";
|
||||||
|
@ -18,10 +18,10 @@ class Forum extends Extension
|
|||||||
|
|
||||||
// shortcut to latest
|
// shortcut to latest
|
||||||
|
|
||||||
if ($config->get_int("forum_version") < 1) {
|
if ($this->get_version("forum_version") < 1) {
|
||||||
$database->create_table("forum_threads", "
|
$database->create_table("forum_threads", "
|
||||||
id SCORE_AIPK,
|
id SCORE_AIPK,
|
||||||
sticky SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N,
|
sticky BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
title VARCHAR(255) NOT NULL,
|
title VARCHAR(255) NOT NULL,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
@ -41,19 +41,22 @@ class Forum extends Extension
|
|||||||
");
|
");
|
||||||
$database->execute("CREATE INDEX forum_posts_date_idx ON forum_posts(date)", []);
|
$database->execute("CREATE INDEX forum_posts_date_idx ON forum_posts(date)", []);
|
||||||
|
|
||||||
$config->set_int("forum_version", 2);
|
|
||||||
$config->set_int("forumTitleSubString", 25);
|
$config->set_int("forumTitleSubString", 25);
|
||||||
$config->set_int("forumThreadsPerPage", 15);
|
$config->set_int("forumThreadsPerPage", 15);
|
||||||
$config->set_int("forumPostsPerPage", 15);
|
$config->set_int("forumPostsPerPage", 15);
|
||||||
|
|
||||||
$config->set_int("forumMaxCharsPerPost", 512);
|
$config->set_int("forumMaxCharsPerPost", 512);
|
||||||
|
|
||||||
log_info("forum", "extension installed");
|
$this->set_version("forum_version", 3);
|
||||||
}
|
}
|
||||||
if ($config->get_int("forum_version") < 2) {
|
if ($this->get_version("forum_version") < 2) {
|
||||||
$database->execute("ALTER TABLE forum_threads ADD FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT");
|
$database->execute("ALTER TABLE forum_threads ADD FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT");
|
||||||
$database->execute("ALTER TABLE forum_posts ADD FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT");
|
$database->execute("ALTER TABLE forum_posts ADD FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT");
|
||||||
$config->set_int("forum_version", 2);
|
$this->set_version("forum_version", 2);
|
||||||
|
}
|
||||||
|
if ($this->get_version("forum_version") < 3) {
|
||||||
|
$database->standardise_boolean("forum_threads", "sticky");
|
||||||
|
$this->set_version("forum_version", 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +87,10 @@ class Forum extends Extension
|
|||||||
$event->add_stats("Forum posts: $posts_count, $posts_rate per day");
|
$event->add_stats("Forum posts: $posts_count, $posts_rate per day");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageNavBuilding(PageNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
$event->add_nav_link("forum", new Link('forum/index'), "Forum");
|
||||||
|
}
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
@ -302,11 +309,7 @@ class Forum extends Extension
|
|||||||
private function save_new_thread(User $user)
|
private function save_new_thread(User $user)
|
||||||
{
|
{
|
||||||
$title = html_escape($_POST["title"]);
|
$title = html_escape($_POST["title"]);
|
||||||
$sticky = !empty($_POST["sticky"]) ? html_escape($_POST["sticky"]) : "N";
|
$sticky = !empty($_POST["sticky"]);
|
||||||
|
|
||||||
if ($sticky == "") {
|
|
||||||
$sticky = "N";
|
|
||||||
}
|
|
||||||
|
|
||||||
global $database;
|
global $database;
|
||||||
$database->execute(
|
$database->execute(
|
||||||
|
@ -12,7 +12,7 @@ class ForumTheme extends Themelet
|
|||||||
$page->set_title(html_escape("Forum"));
|
$page->set_title(html_escape("Forum"));
|
||||||
$page->set_heading(html_escape("Forum"));
|
$page->set_heading(html_escape("Forum"));
|
||||||
$page->add_block(new Block("Forum", $html, "main", 10));
|
$page->add_block(new Block("Forum", $html, "main", 10));
|
||||||
|
|
||||||
$this->display_paginator($page, "forum/index", null, $pageNumber, $totalPages);
|
$this->display_paginator($page, "forum/index", null, $pageNumber, $totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class ForumTheme extends Themelet
|
|||||||
$max_characters = $config->get_int('forumMaxCharsPerPost');
|
$max_characters = $config->get_int('forumMaxCharsPerPost');
|
||||||
$html = make_form(make_link("forum/create"));
|
$html = make_form(make_link("forum/create"));
|
||||||
|
|
||||||
|
|
||||||
if (!is_null($threadTitle)) {
|
if (!is_null($threadTitle)) {
|
||||||
$threadTitle = html_escape($threadTitle);
|
$threadTitle = html_escape($threadTitle);
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ class ForumTheme extends Themelet
|
|||||||
if (!is_null($threadText)) {
|
if (!is_null($threadText)) {
|
||||||
$threadText = html_escape($threadText);
|
$threadText = html_escape($threadText);
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= "
|
$html .= "
|
||||||
<table style='width: 500px;'>
|
<table style='width: 500px;'>
|
||||||
<tr><td>Title:</td><td><input type='text' name='title' value='$threadTitle'></td></tr>
|
<tr><td>Title:</td><td><input type='text' name='title' value='$threadTitle'></td></tr>
|
||||||
@ -51,25 +51,25 @@ class ForumTheme extends Themelet
|
|||||||
$page->set_heading(html_escape($blockTitle));
|
$page->set_heading(html_escape($blockTitle));
|
||||||
$page->add_block(new Block($blockTitle, $html, "main", 120));
|
$page->add_block(new Block($blockTitle, $html, "main", 120));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function display_new_post_composer(Page $page, $threadID)
|
public function display_new_post_composer(Page $page, $threadID)
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$max_characters = $config->get_int('forumMaxCharsPerPost');
|
$max_characters = $config->get_int('forumMaxCharsPerPost');
|
||||||
|
|
||||||
$html = make_form(make_link("forum/answer"));
|
$html = make_form(make_link("forum/answer"));
|
||||||
|
|
||||||
$html .= '<input type="hidden" name="threadID" value="'.$threadID.'" />';
|
$html .= '<input type="hidden" name="threadID" value="'.$threadID.'" />';
|
||||||
|
|
||||||
$html .= "
|
$html .= "
|
||||||
<table style='width: 500px;'>
|
<table style='width: 500px;'>
|
||||||
<tr><td>Message:</td><td><textarea id='message' name='message' ></textarea>
|
<tr><td>Message:</td><td><textarea id='message' name='message' ></textarea>
|
||||||
<tr><td></td><td><small>Max characters alowed: $max_characters.</small></td></tr>
|
<tr><td></td><td><small>Max characters alowed: $max_characters.</small></td></tr>
|
||||||
</td></tr>";
|
</td></tr>";
|
||||||
|
|
||||||
$html .= "<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
|
$html .= "<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
@ -84,9 +84,9 @@ class ForumTheme extends Themelet
|
|||||||
public function display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber, $totalPages)
|
public function display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber, $totalPages)
|
||||||
{
|
{
|
||||||
global $config, $page/*, $user*/;
|
global $config, $page/*, $user*/;
|
||||||
|
|
||||||
$posts_per_page = $config->get_int('forumPostsPerPage');
|
$posts_per_page = $config->get_int('forumPostsPerPage');
|
||||||
|
|
||||||
$current_post = 0;
|
$current_post = 0;
|
||||||
|
|
||||||
$html =
|
$html =
|
||||||
@ -96,7 +96,7 @@ class ForumTheme extends Themelet
|
|||||||
"<th id=threadHeadUser>User</th>".
|
"<th id=threadHeadUser>User</th>".
|
||||||
"<th>Message</th>".
|
"<th>Message</th>".
|
||||||
"</tr></thead>";
|
"</tr></thead>";
|
||||||
|
|
||||||
foreach ($posts as $post) {
|
foreach ($posts as $post) {
|
||||||
$current_post++;
|
$current_post++;
|
||||||
$message = $post["message"];
|
$message = $post["message"];
|
||||||
@ -104,29 +104,29 @@ class ForumTheme extends Themelet
|
|||||||
$tfe = new TextFormattingEvent($message);
|
$tfe = new TextFormattingEvent($message);
|
||||||
send_event($tfe);
|
send_event($tfe);
|
||||||
$message = $tfe->formatted;
|
$message = $tfe->formatted;
|
||||||
|
|
||||||
$message = str_replace('\n\r', '<br>', $message);
|
$message = str_replace('\n\r', '<br>', $message);
|
||||||
$message = str_replace('\r\n', '<br>', $message);
|
$message = str_replace('\r\n', '<br>', $message);
|
||||||
$message = str_replace('\n', '<br>', $message);
|
$message = str_replace('\n', '<br>', $message);
|
||||||
$message = str_replace('\r', '<br>', $message);
|
$message = str_replace('\r', '<br>', $message);
|
||||||
|
|
||||||
$message = stripslashes($message);
|
$message = stripslashes($message);
|
||||||
|
|
||||||
$userLink = "<a href='".make_link("user/".$post["user_name"]."")."'>".$post["user_name"]."</a>";
|
$userLink = "<a href='".make_link("user/".$post["user_name"]."")."'>".$post["user_name"]."</a>";
|
||||||
|
|
||||||
$poster = User::by_name($post["user_name"]);
|
$poster = User::by_name($post["user_name"]);
|
||||||
$gravatar = $poster->get_avatar_html();
|
$gravatar = $poster->get_avatar_html();
|
||||||
|
|
||||||
$rank = "<sup class='user_rank'>{$post["user_class"]}</sup>";
|
$rank = "<sup class='user_rank'>{$post["user_class"]}</sup>";
|
||||||
|
|
||||||
$postID = $post['id'];
|
$postID = $post['id'];
|
||||||
|
|
||||||
//if($user->can(Permissions::FORUM_ADMIN)){
|
//if($user->can(Permissions::FORUM_ADMIN)){
|
||||||
//$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>";
|
//$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>";
|
||||||
//} else {
|
//} else {
|
||||||
//$delete_link = "";
|
//$delete_link = "";
|
||||||
//}
|
//}
|
||||||
|
|
||||||
if ($showAdminOptions) {
|
if ($showAdminOptions) {
|
||||||
$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>";
|
$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>";
|
||||||
} else {
|
} else {
|
||||||
@ -152,17 +152,17 @@ class ForumTheme extends Themelet
|
|||||||
<td class='forumSubmessage'></td>
|
<td class='forumSubmessage'></td>
|
||||||
</tr>";
|
</tr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= "</tbody></table>";
|
$html .= "</tbody></table>";
|
||||||
|
|
||||||
$this->display_paginator($page, "forum/view/".$threadID, null, $pageNumber, $totalPages);
|
$this->display_paginator($page, "forum/view/".$threadID, null, $pageNumber, $totalPages);
|
||||||
|
|
||||||
$page->set_title(html_escape($threadTitle));
|
$page->set_title(html_escape($threadTitle));
|
||||||
$page->set_heading(html_escape($threadTitle));
|
$page->set_heading(html_escape($threadTitle));
|
||||||
$page->add_block(new Block($threadTitle, $html, "main", 20));
|
$page->add_block(new Block($threadTitle, $html, "main", 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function add_actions_block(Page $page, $threadID)
|
public function add_actions_block(Page $page, $threadID)
|
||||||
{
|
{
|
||||||
@ -192,29 +192,29 @@ class ForumTheme extends Themelet
|
|||||||
$current_post = 0;
|
$current_post = 0;
|
||||||
foreach ($threads as $thread) {
|
foreach ($threads as $thread) {
|
||||||
$oe = ($current_post++ % 2 == 0) ? "even" : "odd";
|
$oe = ($current_post++ % 2 == 0) ? "even" : "odd";
|
||||||
|
|
||||||
global $config;
|
global $config;
|
||||||
$titleSubString = $config->get_int('forumTitleSubString');
|
$titleSubString = $config->get_int('forumTitleSubString');
|
||||||
|
|
||||||
if ($titleSubString < strlen($thread["title"])) {
|
if ($titleSubString < strlen($thread["title"])) {
|
||||||
$title = substr($thread["title"], 0, $titleSubString);
|
$title = substr($thread["title"], 0, $titleSubString);
|
||||||
$title = $title."...";
|
$title = $title."...";
|
||||||
} else {
|
} else {
|
||||||
$title = $thread["title"];
|
$title = $thread["title"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($thread["sticky"] == "Y") {
|
if (bool_escape($thread["sticky"])) {
|
||||||
$sticky = "Sticky: ";
|
$sticky = "Sticky: ";
|
||||||
} else {
|
} else {
|
||||||
$sticky = "";
|
$sticky = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= "<tr class='$oe'>".
|
$html .= "<tr class='$oe'>".
|
||||||
'<td class="left">'.$sticky.'<a href="'.make_link("forum/view/".$thread["id"]).'">'.$title."</a></td>".
|
'<td class="left">'.$sticky.'<a href="'.make_link("forum/view/".$thread["id"]).'">'.$title."</a></td>".
|
||||||
'<td><a href="'.make_link("user/".$thread["user_name"]).'">'.$thread["user_name"]."</a></td>".
|
'<td><a href="'.make_link("user/".$thread["user_name"]).'">'.$thread["user_name"]."</a></td>".
|
||||||
"<td>".autodate($thread["uptodate"])."</td>".
|
"<td>".autodate($thread["uptodate"])."</td>".
|
||||||
"<td>".$thread["response_count"]."</td>";
|
"<td>".$thread["response_count"]."</td>";
|
||||||
|
|
||||||
if ($showAdminOptions) {
|
if ($showAdminOptions) {
|
||||||
$html .= '<td><a href="'.make_link("forum/nuke/".$thread["id"]).'" title="Delete '.$title.'">Delete</a></td>';
|
$html .= '<td><a href="'.make_link("forum/nuke/".$thread["id"]).'" title="Delete '.$title.'">Delete</a></td>';
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class ArchiveFileHandler extends DataHandlerExtension
|
class ArchiveFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
protected $SUPPORTED_MIME = [MIME_TYPE_ZIP];
|
protected $SUPPORTED_MIME = [MimeType::ZIP];
|
||||||
|
|
||||||
public function onInitExt(InitExtEvent $event)
|
public function onInitExt(InitExtEvent $event)
|
||||||
{
|
{
|
||||||
@ -21,7 +21,7 @@ class ArchiveFileHandler extends DataHandlerExtension
|
|||||||
|
|
||||||
public function onDataUpload(DataUploadEvent $event)
|
public function onDataUpload(DataUploadEvent $event)
|
||||||
{
|
{
|
||||||
if ($this->supported_ext($event->type)) {
|
if ($this->supported_mime($event->mime)) {
|
||||||
global $config, $page;
|
global $config, $page;
|
||||||
$tmp = sys_get_temp_dir();
|
$tmp = sys_get_temp_dir();
|
||||||
$tmpdir = "$tmp/shimmie-archive-{$event->hash}";
|
$tmpdir = "$tmp/shimmie-archive-{$event->hash}";
|
||||||
@ -29,12 +29,17 @@ class ArchiveFileHandler extends DataHandlerExtension
|
|||||||
$cmd = str_replace('%f', $event->tmpname, $cmd);
|
$cmd = str_replace('%f', $event->tmpname, $cmd);
|
||||||
$cmd = str_replace('%d', $tmpdir, $cmd);
|
$cmd = str_replace('%d', $tmpdir, $cmd);
|
||||||
exec($cmd);
|
exec($cmd);
|
||||||
$results = add_dir($tmpdir);
|
if (file_exists($tmpdir)) {
|
||||||
if (count($results) > 0) {
|
try {
|
||||||
$page->flash("Adding files" . implode("\n", $results));
|
$results = add_dir($tmpdir);
|
||||||
|
if (count($results) > 0) {
|
||||||
|
$page->flash("Adding files" . implode("\n", $results));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
deltree($tmpdir);
|
||||||
|
}
|
||||||
|
$event->image_id = -2; // default -1 = upload wasn't handled
|
||||||
}
|
}
|
||||||
deltree($tmpdir);
|
|
||||||
$event->image_id = -2; // default -1 = upload wasn't handled
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class CBZFileHandler extends DataHandlerExtension
|
class CBZFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
public $SUPPORTED_MIME = [MIME_TYPE_COMIC_ZIP];
|
protected $SUPPORTED_MIME = [MimeType::COMIC_ZIP];
|
||||||
|
|
||||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||||
{
|
{
|
||||||
@ -20,14 +20,14 @@ class CBZFileHandler extends DataHandlerExtension
|
|||||||
unlink($tmp);
|
unlink($tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function create_thumb(string $hash, string $type): bool
|
protected function create_thumb(string $hash, string $mime): bool
|
||||||
{
|
{
|
||||||
$cover = $this->get_representative_image(warehouse_path(Image::IMAGE_DIR, $hash));
|
$cover = $this->get_representative_image(warehouse_path(Image::IMAGE_DIR, $hash));
|
||||||
create_scaled_image(
|
create_scaled_image(
|
||||||
$cover,
|
$cover,
|
||||||
warehouse_path(Image::THUMBNAIL_DIR, $hash),
|
warehouse_path(Image::THUMBNAIL_DIR, $hash),
|
||||||
get_thumbnail_max_size_scaled(),
|
get_thumbnail_max_size_scaled(),
|
||||||
get_extension(get_mime($cover)),
|
MimeType::get_for_file($cover),
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
@ -55,7 +55,7 @@ class CBZFileHandler extends DataHandlerExtension
|
|||||||
sort($names);
|
sort($names);
|
||||||
$cover = $names[0];
|
$cover = $names[0];
|
||||||
foreach ($names as $name) {
|
foreach ($names as $name) {
|
||||||
if (strpos(strtolower($name), "cover") !== false) {
|
if (str_contains(strtolower($name), "cover")) {
|
||||||
$cover = $name;
|
$cover = $name;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class FlashFileHandler extends DataHandlerExtension
|
class FlashFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
protected $SUPPORTED_MIME = [MIME_TYPE_FLASH];
|
protected $SUPPORTED_MIME = [MimeType::FLASH];
|
||||||
|
|
||||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||||
{
|
{
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
class IcoFileHandler extends DataHandlerExtension
|
class IcoFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
protected $SUPPORTED_MIME = [MIME_TYPE_ICO, MIME_TYPE_ANI];
|
protected $SUPPORTED_MIME = [MimeType::ICO, MimeType::ANI, MimeType::WIN_BITMAP, MimeType::ICO_OSX];
|
||||||
|
|
||||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||||
{
|
{
|
||||||
$event->image->lossless = true;
|
$event->image->lossless = true;
|
||||||
$event->image->video = false;
|
$event->image->video = false;
|
||||||
$event->image->audio = false;
|
$event->image->audio = false;
|
||||||
$event->image->image = ($event->ext!="ani");
|
$event->image->image = ($event->mime!= MimeType::ANI);
|
||||||
|
|
||||||
$fp = fopen($event->file_name, "r");
|
$fp = fopen($event->file_name, "r");
|
||||||
try {
|
try {
|
||||||
@ -25,10 +25,10 @@ class IcoFileHandler extends DataHandlerExtension
|
|||||||
$event->image->height = $height == 0 ? 256 : $height;
|
$event->image->height = $height == 0 ? 256 : $height;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function create_thumb(string $hash, string $type): bool
|
protected function create_thumb(string $hash, string $mime): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
create_image_thumb($hash, $type, MediaEngine::IMAGICK);
|
create_image_thumb($hash, $mime, MediaEngine::IMAGICK);
|
||||||
return true;
|
return true;
|
||||||
} catch (MediaException $e) {
|
} catch (MediaException $e) {
|
||||||
log_warning("handle_ico", "Could not generate thumbnail. " . $e->getMessage());
|
log_warning("handle_ico", "Could not generate thumbnail. " . $e->getMessage());
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
// TODO: Add support for generating an icon from embedded cover art
|
||||||
|
// TODO: MORE AUDIO FORMATS
|
||||||
|
|
||||||
class MP3FileHandler extends DataHandlerExtension
|
class MP3FileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
protected $SUPPORTED_MIME = [MIME_TYPE_MP3];
|
protected $SUPPORTED_MIME = [MimeType::MP3];
|
||||||
|
|
||||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||||
{
|
{
|
||||||
@ -23,6 +26,6 @@ class MP3FileHandler extends DataHandlerExtension
|
|||||||
|
|
||||||
protected function check_contents(string $tmpname): bool
|
protected function check_contents(string $tmpname): bool
|
||||||
{
|
{
|
||||||
return get_mime($tmpname) === MIME_TYPE_MP3;
|
return MimeType::get_for_file($tmpname) === MimeType::MP3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,18 @@
|
|||||||
|
|
||||||
class PixelFileHandler extends DataHandlerExtension
|
class PixelFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
protected $SUPPORTED_MIME = [MIME_TYPE_JPEG, MIME_TYPE_GIF, MIME_TYPE_PNG, MIME_TYPE_WEBP];
|
protected $SUPPORTED_MIME = [MimeType::JPEG, MimeType::GIF, MimeType::PNG, MimeType::WEBP];
|
||||||
|
|
||||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||||
{
|
{
|
||||||
if (in_array($event->ext, Media::LOSSLESS_FORMATS)) {
|
$event->image->lossless = Media::is_lossless($event->file_name, $event->mime);
|
||||||
$event->image->lossless = true;
|
|
||||||
} elseif ($event->ext==EXTENSION_WEBP) {
|
|
||||||
$event->image->lossless = Media::is_lossless_webp($event->file_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($event->image->lossless==null) {
|
|
||||||
$event->image->lossless = false;
|
|
||||||
}
|
|
||||||
$event->image->audio = false;
|
$event->image->audio = false;
|
||||||
switch ($event->ext) {
|
switch ($event->mime) {
|
||||||
case EXTENSION_GIF:
|
case MimeType::GIF:
|
||||||
$event->image->video = Media::is_animated_gif($event->file_name);
|
$event->image->video = MimeType::is_animated_gif($event->file_name);
|
||||||
break;
|
break;
|
||||||
case EXTENSION_WEBP:
|
case MimeType::WEBP:
|
||||||
$event->image->video = Media::is_animated_webp($event->file_name);
|
$event->image->video = MimeType::is_animated_webp($event->file_name);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$event->image->video = false;
|
$event->image->video = false;
|
||||||
|
@ -3,7 +3,7 @@ use enshrined\svgSanitize\Sanitizer;
|
|||||||
|
|
||||||
class SVGFileHandler extends DataHandlerExtension
|
class SVGFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
protected $SUPPORTED_MIME = [MIME_TYPE_SVG];
|
protected $SUPPORTED_MIME = [MimeType::SVG];
|
||||||
|
|
||||||
/** @var SVGFileHandlerTheme */
|
/** @var SVGFileHandlerTheme */
|
||||||
protected $theme;
|
protected $theme;
|
||||||
@ -16,7 +16,7 @@ class SVGFileHandler extends DataHandlerExtension
|
|||||||
$image = Image::by_id($id);
|
$image = Image::by_id($id);
|
||||||
$hash = $image->hash;
|
$hash = $image->hash;
|
||||||
|
|
||||||
$page->set_type(MIME_TYPE_SVG);
|
$page->set_mime(MimeType::SVG);
|
||||||
$page->set_mode(PageMode::DATA);
|
$page->set_mode(PageMode::DATA);
|
||||||
|
|
||||||
$sanitizer = new Sanitizer();
|
$sanitizer = new Sanitizer();
|
||||||
@ -67,7 +67,7 @@ class SVGFileHandler extends DataHandlerExtension
|
|||||||
|
|
||||||
protected function check_contents(string $file): bool
|
protected function check_contents(string $file): bool
|
||||||
{
|
{
|
||||||
if (get_mime($file)!==MIME_TYPE_SVG) {
|
if (MimeType::get_for_file($file)!==MimeType::SVG) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,20 +4,21 @@ abstract class VideoFileHandlerConfig
|
|||||||
{
|
{
|
||||||
public const PLAYBACK_AUTOPLAY = "video_playback_autoplay";
|
public const PLAYBACK_AUTOPLAY = "video_playback_autoplay";
|
||||||
public const PLAYBACK_LOOP = "video_playback_loop";
|
public const PLAYBACK_LOOP = "video_playback_loop";
|
||||||
|
public const PLAYBACK_MUTE = "video_playback_mute";
|
||||||
public const ENABLED_FORMATS = "video_enabled_formats";
|
public const ENABLED_FORMATS = "video_enabled_formats";
|
||||||
}
|
}
|
||||||
|
|
||||||
class VideoFileHandler extends DataHandlerExtension
|
class VideoFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
public const SUPPORTED_MIME = [
|
public const SUPPORTED_MIME = [
|
||||||
MIME_TYPE_ASF,
|
MimeType::ASF,
|
||||||
MIME_TYPE_AVI,
|
MimeType::AVI,
|
||||||
MIME_TYPE_FLASH_VIDEO,
|
MimeType::FLASH_VIDEO,
|
||||||
MIME_TYPE_MKV,
|
MimeType::MKV,
|
||||||
MIME_TYPE_MP4_VIDEO,
|
MimeType::MP4_VIDEO,
|
||||||
MIME_TYPE_OGG_VIDEO,
|
MimeType::OGG_VIDEO,
|
||||||
MIME_TYPE_QUICKTIME,
|
MimeType::QUICKTIME,
|
||||||
MIME_TYPE_WEBM,
|
MimeType::WEBM,
|
||||||
];
|
];
|
||||||
protected $SUPPORTED_MIME = self::SUPPORTED_MIME;
|
protected $SUPPORTED_MIME = self::SUPPORTED_MIME;
|
||||||
|
|
||||||
@ -27,17 +28,18 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
|
|
||||||
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, true);
|
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, true);
|
||||||
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_LOOP, true);
|
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_LOOP, true);
|
||||||
|
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_MUTE, false);
|
||||||
$config->set_default_array(
|
$config->set_default_array(
|
||||||
VideoFileHandlerConfig::ENABLED_FORMATS,
|
VideoFileHandlerConfig::ENABLED_FORMATS,
|
||||||
[MIME_TYPE_FLASH_VIDEO, MIME_TYPE_MP4_VIDEO, MIME_TYPE_OGG_VIDEO, MIME_TYPE_WEBM]
|
[MimeType::FLASH_VIDEO, MimeType::MP4_VIDEO, MimeType::OGG_VIDEO, MimeType::WEBM]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_options(): array
|
private function get_options(): array
|
||||||
{
|
{
|
||||||
$output = [];
|
$output = [];
|
||||||
foreach ($this->SUPPORTED_MIME as $format) {
|
foreach ($this->SUPPORTED_MIME as $mime) {
|
||||||
$output[MIME_TYPE_MAP[$format][MIME_TYPE_MAP_NAME]] = $format;
|
$output[MimeMap::get_name_for_mime($mime)] = $mime;
|
||||||
}
|
}
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
@ -45,11 +47,12 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$sb = new SetupBlock("Video Options");
|
$sb = new SetupBlock("Video Options");
|
||||||
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, "Autoplay: ");
|
$sb->start_table();
|
||||||
$sb->add_label("<br>");
|
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, "Autoplay", true);
|
||||||
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_LOOP, "Loop: ");
|
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_LOOP, "Loop", true);
|
||||||
$sb->add_label("<br>Enabled Formats:");
|
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_MUTE, "Mute", true);
|
||||||
$sb->add_multichoice_option(VideoFileHandlerConfig::ENABLED_FORMATS, $this->get_options());
|
$sb->add_multichoice_option(VideoFileHandlerConfig::ENABLED_FORMATS, $this->get_options(), "Enabled Formats", true);
|
||||||
|
$sb->end_table();
|
||||||
$event->panel->add_block($sb);
|
$event->panel->add_block($sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +67,7 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
if (array_key_exists("streams", $data)) {
|
if (array_key_exists("streams", $data)) {
|
||||||
$video = false;
|
$video = false;
|
||||||
$audio = true;
|
$audio = true;
|
||||||
|
$video_codec = null;
|
||||||
$streams = $data["streams"];
|
$streams = $data["streams"];
|
||||||
if (is_array($streams)) {
|
if (is_array($streams)) {
|
||||||
foreach ($streams as $stream) {
|
foreach ($streams as $stream) {
|
||||||
@ -76,6 +80,7 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
break;
|
break;
|
||||||
case "video":
|
case "video":
|
||||||
$video = true;
|
$video = true;
|
||||||
|
$video_codec = $stream["codec_name"];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +95,14 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$event->image->video = $video;
|
$event->image->video = $video;
|
||||||
|
$event->image->video_codec = $video_codec;
|
||||||
$event->image->audio = $audio;
|
$event->image->audio = $audio;
|
||||||
|
if ($event->image->get_mime()==MimeType::MKV &&
|
||||||
|
VideoContainers::is_video_codec_supported(VideoContainers::WEBM, $event->image->video_codec)) {
|
||||||
|
// WEBMs are MKVs with the VP9 or VP8 codec
|
||||||
|
// For browser-friendliness, we'll just change the mime type
|
||||||
|
$event->image->set_mime(MimeType::WEBM);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (array_key_exists("format", $data)&& is_array($data["format"])) {
|
if (array_key_exists("format", $data)&& is_array($data["format"])) {
|
||||||
@ -105,17 +117,13 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function supported_ext(string $ext): bool
|
protected function supported_mime(string $mime): bool
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS);
|
$enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS);
|
||||||
foreach ($enabled_formats as $format) {
|
|
||||||
if (in_array($ext, MIME_TYPE_MAP[$format][MIME_TYPE_MAP_EXT])) {
|
return MimeType::matches_array($mime, $enabled_formats, true);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function create_thumb(string $hash, string $type): bool
|
protected function create_thumb(string $hash, string $type): bool
|
||||||
@ -128,13 +136,11 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
if (file_exists($tmpname)) {
|
if (file_exists($tmpname)) {
|
||||||
$mime = get_mime($tmpname);
|
$mime = MimeType::get_for_file($tmpname);
|
||||||
|
|
||||||
$enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS);
|
$enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS);
|
||||||
foreach ($enabled_formats as $format) {
|
if (MimeType::matches_array($mime, $enabled_formats)) {
|
||||||
if (in_array($mime, MIME_TYPE_MAP[$format][MIME_TYPE_MAP_MIME])) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -7,10 +7,11 @@ class VideoFileHandlerTheme extends Themelet
|
|||||||
global $config;
|
global $config;
|
||||||
$ilink = $image->get_image_link();
|
$ilink = $image->get_image_link();
|
||||||
$thumb_url = make_http($image->get_thumb_link()); //used as fallback image
|
$thumb_url = make_http($image->get_thumb_link()); //used as fallback image
|
||||||
$ext = strtolower($image->get_ext());
|
$mime = strtolower($image->get_mime());
|
||||||
$full_url = make_http($ilink);
|
$full_url = make_http($ilink);
|
||||||
$autoplay = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY);
|
$autoplay = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY);
|
||||||
$loop = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_LOOP);
|
$loop = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_LOOP);
|
||||||
|
$mute = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_MUTE);
|
||||||
$player = make_link('vendor/bower-asset/mediaelement/build/flashmediaelement.swf');
|
$player = make_link('vendor/bower-asset/mediaelement/build/flashmediaelement.swf');
|
||||||
|
|
||||||
$width="auto";
|
$width="auto";
|
||||||
@ -25,11 +26,10 @@ class VideoFileHandlerTheme extends Themelet
|
|||||||
$html = "Video not playing? <a href='$ilink'>Click here</a> to download the file.<br/>";
|
$html = "Video not playing? <a href='$ilink'>Click here</a> to download the file.<br/>";
|
||||||
|
|
||||||
//Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
//Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
||||||
$mime = get_mime_for_extension($ext);
|
|
||||||
|
|
||||||
if (in_array($mime, VideoFileHandler::SUPPORTED_MIME)) {
|
if (MimeType::matches_array($mime, VideoFileHandler::SUPPORTED_MIME)) {
|
||||||
//FLV isn't supported by <video>, but it should always fallback to the flash-based method.
|
//FLV isn't supported by <video>, but it should always fallback to the flash-based method.
|
||||||
if ($mime == MIME_TYPE_WEBM) {
|
if ($mime == MimeType::WEBM) {
|
||||||
//Several browsers still lack WebM support sadly: https://caniuse.com/#feat=webm
|
//Several browsers still lack WebM support sadly: https://caniuse.com/#feat=webm
|
||||||
$html .= "<!--[if IE]><p>To view webm files with IE, please <a href='https://tools.google.com/dlpage/webmmf/' target='_blank'>download this plugin</a>.</p><![endif]-->";
|
$html .= "<!--[if IE]><p>To view webm files with IE, please <a href='https://tools.google.com/dlpage/webmmf/' target='_blank'>download this plugin</a>.</p><![endif]-->";
|
||||||
}
|
}
|
||||||
@ -50,16 +50,17 @@ class VideoFileHandlerTheme extends Themelet
|
|||||||
<img alt='thumb' src=\"{$thumb_url}\" />
|
<img alt='thumb' src=\"{$thumb_url}\" />
|
||||||
</object>";
|
</object>";
|
||||||
|
|
||||||
if ($mime == MIME_TYPE_FLASH_VIDEO) {
|
if ($mime == MimeType::FLASH_VIDEO) {
|
||||||
//FLV doesn't support <video>.
|
//FLV doesn't support <video>.
|
||||||
$html .= $html_fallback;
|
$html .= $html_fallback;
|
||||||
} else {
|
} else {
|
||||||
$autoplay = ($autoplay ? ' autoplay' : '');
|
$autoplay = ($autoplay ? ' autoplay' : '');
|
||||||
$loop = ($loop ? ' loop' : '');
|
$loop = ($loop ? ' loop' : '');
|
||||||
|
$mute = ($mute ? ' muted' : '');
|
||||||
|
|
||||||
$html .= "
|
$html .= "
|
||||||
<video controls class='shm-main-image' id='main_image' alt='main image' poster='$thumb_url' {$autoplay} {$loop}
|
<video controls class='shm-main-image' id='main_image' alt='main image' poster='$thumb_url' {$autoplay} {$loop} {$mute}
|
||||||
style='height: $height; width: $width; max-width: 100%'>
|
style='height: $height; width: $width; max-width: 100%; object-fit: contain; background-color: black;'>
|
||||||
<source src='{$ilink}' type='{$mime}'>
|
<source src='{$ilink}' type='{$mime}'>
|
||||||
|
|
||||||
<!-- If browser doesn't support filetype, fallback to flash -->
|
<!-- If browser doesn't support filetype, fallback to flash -->
|
||||||
|
@ -9,7 +9,7 @@ class HomeInfo extends ExtensionInfo
|
|||||||
public $authors =["Bzchan"=>"bzchan@animemahou.com"];
|
public $authors =["Bzchan"=>"bzchan@animemahou.com"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public $license = self::LICENSE_GPLV2;
|
||||||
public $visibility = self::VISIBLE_ADMIN;
|
public $visibility = self::VISIBLE_ADMIN;
|
||||||
public $description = "Displays a front page with logo, search box and image count";
|
public $description = "Displays a front page with logo, search box and post count";
|
||||||
public $documentation =
|
public $documentation =
|
||||||
"Once enabled, the page will show up at the URL \"home\", so if you want
|
"Once enabled, the page will show up at the URL \"home\", so if you want
|
||||||
this to be the front page of your site, you should go to \"Board Config\"
|
this to be the front page of your site, you should go to \"Board Config\"
|
||||||
|
@ -55,21 +55,21 @@ class Home extends Extension
|
|||||||
$length = strlen($strtotal);
|
$length = strlen($strtotal);
|
||||||
for ($n=0; $n<$length; $n++) {
|
for ($n=0; $n<$length; $n++) {
|
||||||
$cur = $strtotal[$n];
|
$cur = $strtotal[$n];
|
||||||
$counter_text .= " <img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' /> ";
|
$counter_text .= "<img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' />";
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the homelinks and process them
|
// get the homelinks and process them
|
||||||
if (strlen($config->get_string('home_links', '')) > 0) {
|
if (strlen($config->get_string('home_links', '')) > 0) {
|
||||||
$main_links = $config->get_string('home_links');
|
$main_links = $config->get_string('home_links');
|
||||||
} else {
|
} else {
|
||||||
$main_links = '[url=site://post/list]Posts[/url] [url=site://comment/list]Comments[/url] [url=site://tags]Tags[/url]';
|
$main_links = '[url=site://post/list]Posts[/url][url=site://comment/list]Comments[/url][url=site://tags]Tags[/url]';
|
||||||
if (class_exists("Pools")) {
|
if (class_exists("Pools")) {
|
||||||
$main_links .= ' [url=site://pool]Pools[/url]';
|
$main_links .= '[url=site://pool]Pools[/url]';
|
||||||
}
|
}
|
||||||
if (class_exists("Wiki")) {
|
if (class_exists("Wiki")) {
|
||||||
$main_links .= ' [url=site://wiki]Wiki[/url]';
|
$main_links .= '[url=site://wiki]Wiki[/url]';
|
||||||
}
|
}
|
||||||
$main_links .= ' [url=site://ext_doc]Documentation[/url]';
|
$main_links .= '[url=site://ext_doc]Documentation[/url]';
|
||||||
}
|
}
|
||||||
$main_links = format_text($main_links);
|
$main_links = format_text($main_links);
|
||||||
$main_text = $config->get_string('home_text', '');
|
$main_text = $config->get_string('home_text', '');
|
||||||
|
@ -2,21 +2,29 @@
|
|||||||
|
|
||||||
abstract class ImageConfig
|
abstract class ImageConfig
|
||||||
{
|
{
|
||||||
|
const VERSION = 'ext_image_version';
|
||||||
|
|
||||||
const THUMB_ENGINE = 'thumb_engine';
|
const THUMB_ENGINE = 'thumb_engine';
|
||||||
const THUMB_WIDTH = 'thumb_width';
|
const THUMB_WIDTH = 'thumb_width';
|
||||||
const THUMB_HEIGHT = 'thumb_height';
|
const THUMB_HEIGHT = 'thumb_height';
|
||||||
const THUMB_SCALING = 'thumb_scaling';
|
const THUMB_SCALING = 'thumb_scaling';
|
||||||
const THUMB_QUALITY = 'thumb_quality';
|
const THUMB_QUALITY = 'thumb_quality';
|
||||||
const THUMB_TYPE = 'thumb_type';
|
const THUMB_MIME = 'thumb_mime';
|
||||||
const THUMB_FIT = 'thumb_fit';
|
const THUMB_FIT = 'thumb_fit';
|
||||||
|
const THUMB_ALPHA_COLOR ='thumb_alpha_color';
|
||||||
|
|
||||||
const SHOW_META = 'image_show_meta';
|
const SHOW_META = 'image_show_meta';
|
||||||
const ILINK = 'image_ilink';
|
const ILINK = 'image_ilink';
|
||||||
const TLINK = 'image_tlink';
|
const TLINK = 'image_tlink';
|
||||||
const TIP = 'image_tip';
|
const TIP = 'image_tip';
|
||||||
|
const INFO = 'image_info';
|
||||||
const EXPIRES = 'image_expires';
|
const EXPIRES = 'image_expires';
|
||||||
const UPLOAD_COLLISION_HANDLER = 'upload_collision_handler';
|
const UPLOAD_COLLISION_HANDLER = 'upload_collision_handler';
|
||||||
|
|
||||||
const COLLISION_MERGE = 'merge';
|
const COLLISION_MERGE = 'merge';
|
||||||
const COLLISION_ERROR = 'error';
|
const COLLISION_ERROR = 'error';
|
||||||
|
|
||||||
|
const ON_DELETE = 'image_on_delete';
|
||||||
|
const ON_DELETE_NEXT = 'next';
|
||||||
|
const ON_DELETE_LIST = 'list';
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ class ImageIOInfo extends ExtensionInfo
|
|||||||
public const KEY = "image";
|
public const KEY = "image";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public $key = self::KEY;
|
||||||
public $name = "Image Manager";
|
public $name = "Post Manager";
|
||||||
public $url = self::SHIMMIE_URL;
|
public $url = self::SHIMMIE_URL;
|
||||||
public $authors = [self::SHISH_NAME=> self::SHISH_EMAIL, "jgen"=>"jgen.tech@gmail.com"];
|
public $authors = [self::SHISH_NAME=> self::SHISH_EMAIL, "jgen"=>"jgen.tech@gmail.com"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public $license = self::LICENSE_GPLV2;
|
||||||
|
@ -10,30 +10,39 @@ class ImageIO extends Extension
|
|||||||
/** @var ImageIOTheme */
|
/** @var ImageIOTheme */
|
||||||
protected $theme;
|
protected $theme;
|
||||||
|
|
||||||
const COLLISION_OPTIONS = ['Error'=>ImageConfig::COLLISION_ERROR, 'Merge'=>ImageConfig::COLLISION_MERGE];
|
const COLLISION_OPTIONS = [
|
||||||
|
'Error'=>ImageConfig::COLLISION_ERROR,
|
||||||
|
'Merge'=>ImageConfig::COLLISION_MERGE
|
||||||
|
];
|
||||||
|
|
||||||
|
const ON_DELETE_OPTIONS = [
|
||||||
|
'Return to post list'=>ImageConfig::ON_DELETE_LIST,
|
||||||
|
'Go to next post'=>ImageConfig::ON_DELETE_NEXT
|
||||||
|
];
|
||||||
|
|
||||||
const EXIF_READ_FUNCTION = "exif_read_data";
|
const EXIF_READ_FUNCTION = "exif_read_data";
|
||||||
|
|
||||||
|
|
||||||
const THUMBNAIL_ENGINES = [
|
const THUMBNAIL_ENGINES = [
|
||||||
'Built-in GD' => MediaEngine::GD,
|
'Built-in GD' => MediaEngine::GD,
|
||||||
'ImageMagick' => MediaEngine::IMAGICK
|
'ImageMagick' => MediaEngine::IMAGICK
|
||||||
];
|
];
|
||||||
|
|
||||||
const THUMBNAIL_TYPES = [
|
const THUMBNAIL_TYPES = [
|
||||||
'JPEG' => EXTENSION_JPG,
|
'JPEG' => MimeType::JPEG,
|
||||||
'WEBP (Not IE/Safari compatible)' => EXTENSION_WEBP
|
'WEBP (Not IE/Safari compatible)' => MimeType::WEBP
|
||||||
];
|
];
|
||||||
|
|
||||||
public function onInitExt(InitExtEvent $event)
|
public function onInitExt(InitExtEvent $event)
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
$config->set_default_string(ImageConfig::THUMB_ENGINE, MediaEngine::GD);
|
||||||
$config->set_default_int(ImageConfig::THUMB_WIDTH, 192);
|
$config->set_default_int(ImageConfig::THUMB_WIDTH, 192);
|
||||||
$config->set_default_int(ImageConfig::THUMB_HEIGHT, 192);
|
$config->set_default_int(ImageConfig::THUMB_HEIGHT, 192);
|
||||||
$config->set_default_int(ImageConfig::THUMB_SCALING, 100);
|
$config->set_default_int(ImageConfig::THUMB_SCALING, 100);
|
||||||
$config->set_default_int(ImageConfig::THUMB_QUALITY, 75);
|
$config->set_default_int(ImageConfig::THUMB_QUALITY, 75);
|
||||||
$config->set_default_string(ImageConfig::THUMB_TYPE, EXTENSION_JPG);
|
$config->set_default_string(ImageConfig::THUMB_MIME, MimeType::JPEG);
|
||||||
$config->set_default_string(ImageConfig::THUMB_FIT, Media::RESIZE_TYPE_FIT);
|
$config->set_default_string(ImageConfig::THUMB_FIT, Media::RESIZE_TYPE_FIT);
|
||||||
|
$config->set_default_string(ImageConfig::THUMB_ALPHA_COLOR, Media::DEFAULT_ALPHA_CONVERSION_COLOR);
|
||||||
|
|
||||||
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||||
$config->set_default_bool(ImageConfig::SHOW_META, false);
|
$config->set_default_bool(ImageConfig::SHOW_META, false);
|
||||||
@ -45,16 +54,41 @@ class ImageIO extends Extension
|
|||||||
$config->set_default_int(ImageConfig::EXPIRES, (60*60*24*31)); // defaults to one month
|
$config->set_default_int(ImageConfig::EXPIRES, (60*60*24*31)); // defaults to one month
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||||
|
{
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
if ($this->get_version(ImageConfig::VERSION) < 1) {
|
||||||
|
switch ($config->get_string("thumb_type")) {
|
||||||
|
case FileExtension::WEBP:
|
||||||
|
$config->set_string(ImageConfig::THUMB_MIME, MimeType::WEBP);
|
||||||
|
break;
|
||||||
|
case FileExtension::JPEG:
|
||||||
|
$config->set_string(ImageConfig::THUMB_MIME, MimeType::JPEG);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$config->set_string("thumb_type", null);
|
||||||
|
|
||||||
|
$this->set_version(ImageConfig::VERSION, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
|
global $config;
|
||||||
if ($event->page_matches("image/delete")) {
|
if ($event->page_matches("image/delete")) {
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
if ($user->can(Permissions::DELETE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
|
if ($user->can(Permissions::DELETE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
|
||||||
$image = Image::by_id(int_escape($_POST['image_id']));
|
$image = Image::by_id(int_escape($_POST['image_id']));
|
||||||
if ($image) {
|
if ($image) {
|
||||||
send_event(new ImageDeletionEvent($image));
|
send_event(new ImageDeletionEvent($image));
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
|
||||||
$page->set_redirect(referer_or(make_link("post/list"), ['post/view']));
|
if ($config->get_string(ImageConfig::ON_DELETE)===ImageConfig::ON_DELETE_NEXT) {
|
||||||
|
redirect_to_next_image($image);
|
||||||
|
} else {
|
||||||
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
|
$page->set_redirect(referer_or(make_link("post/list"), ['post/view']));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($event->page_matches("image/replace")) {
|
} elseif ($event->page_matches("image/replace")) {
|
||||||
@ -66,7 +100,7 @@ class ImageIO extends Extension
|
|||||||
$page->set_redirect(make_link('upload/replace/'.$image->id));
|
$page->set_redirect(make_link('upload/replace/'.$image->id));
|
||||||
} else {
|
} else {
|
||||||
/* Invalid image ID */
|
/* Invalid image ID */
|
||||||
throw new ImageReplaceException("Image to replace does not exist.");
|
throw new ImageReplaceException("Post to replace does not exist.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($event->page_matches("image")) {
|
} elseif ($event->page_matches("image")) {
|
||||||
@ -124,7 +158,7 @@ class ImageIO extends Extension
|
|||||||
$event->image = Image::by_id($existing->id);
|
$event->image = Image::by_id($existing->id);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
$error = "Image <a href='".make_link("post/view/{$existing->id}")."'>{$existing->id}</a> ".
|
$error = "Post <a href='".make_link("post/view/{$existing->id}")."'>{$existing->id}</a> ".
|
||||||
"already has hash {$image->hash}:<p>".$this->theme->build_thumb_html($existing);
|
"already has hash {$image->hash}:<p>".$this->theme->build_thumb_html($existing);
|
||||||
throw new ImageAdditionException($error);
|
throw new ImageAdditionException($error);
|
||||||
}
|
}
|
||||||
@ -133,7 +167,7 @@ class ImageIO extends Extension
|
|||||||
// actually insert the info
|
// actually insert the info
|
||||||
$image->save_to_db();
|
$image->save_to_db();
|
||||||
|
|
||||||
log_info("image", "Uploaded Image #{$image->id} ({$image->hash})");
|
log_info("image", "Uploaded >>{$image->id} ({$image->hash})");
|
||||||
|
|
||||||
# at this point in time, the image's tags haven't really been set,
|
# at this point in time, the image's tags haven't really been set,
|
||||||
# and so, having $image->tag_array set to something is a lie (but
|
# and so, having $image->tag_array set to something is a lie (but
|
||||||
@ -145,7 +179,7 @@ class ImageIO extends Extension
|
|||||||
send_event(new TagSetEvent($image, $tags_to_set));
|
send_event(new TagSetEvent($image, $tags_to_set));
|
||||||
|
|
||||||
if ($image->source !== null) {
|
if ($image->source !== null) {
|
||||||
log_info("core-image", "Source for Image #{$image->id} set to: {$image->source}");
|
log_info("core-image", "Source for >>{$image->id} set to: {$image->source}");
|
||||||
}
|
}
|
||||||
} catch (ImageAdditionException $e) {
|
} catch (ImageAdditionException $e) {
|
||||||
throw new UploadException($e->error);
|
throw new UploadException($e->error);
|
||||||
@ -163,16 +197,20 @@ class ImageIO extends Extension
|
|||||||
$id = $event->id;
|
$id = $event->id;
|
||||||
$image = $event->image;
|
$image = $event->image;
|
||||||
|
|
||||||
|
$image->set_mime(
|
||||||
|
MimeType::get_for_file($image->get_image_filename())
|
||||||
|
);
|
||||||
|
|
||||||
/* Check to make sure the image exists. */
|
/* Check to make sure the image exists. */
|
||||||
$existing = Image::by_id($id);
|
$existing = Image::by_id($id);
|
||||||
|
|
||||||
if (is_null($existing)) {
|
if (is_null($existing)) {
|
||||||
throw new ImageReplaceException("Image to replace does not exist!");
|
throw new ImageReplaceException("Post to replace does not exist!");
|
||||||
}
|
}
|
||||||
|
|
||||||
$duplicate = Image::by_hash($image->hash);
|
$duplicate = Image::by_hash($image->hash);
|
||||||
if (!is_null($duplicate) && $duplicate->id!=$id) {
|
if (!is_null($duplicate) && $duplicate->id!=$id) {
|
||||||
$error = "Image <a href='" . make_link("post/view/{$duplicate->id}") . "'>{$duplicate->id}</a> " .
|
$error = "Post <a href='" . make_link("post/view/{$duplicate->id}") . "'>{$duplicate->id}</a> " .
|
||||||
"already has hash {$image->hash}:<p>" . $this->theme->build_thumb_html($duplicate);
|
"already has hash {$image->hash}:<p>" . $this->theme->build_thumb_html($duplicate);
|
||||||
throw new ImageReplaceException($error);
|
throw new ImageReplaceException($error);
|
||||||
}
|
}
|
||||||
@ -196,9 +234,9 @@ class ImageIO extends Extension
|
|||||||
$existing->remove_image_only(); // Actually delete the old image file from disk
|
$existing->remove_image_only(); // Actually delete the old image file from disk
|
||||||
|
|
||||||
/* Generate new thumbnail */
|
/* Generate new thumbnail */
|
||||||
send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->ext)));
|
send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->get_mime())));
|
||||||
|
|
||||||
log_info("image", "Replaced Image #{$id} with ({$image->hash})");
|
log_info("image", "Replaced >>{$id} with ({$image->hash})");
|
||||||
} catch (ImageReplaceException $e) {
|
} catch (ImageReplaceException $e) {
|
||||||
throw new UploadException($e->error);
|
throw new UploadException($e->error);
|
||||||
}
|
}
|
||||||
@ -211,21 +249,23 @@ class ImageIO extends Extension
|
|||||||
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
||||||
$h_image_rate = sprintf("%.1f", ($i_image_count / $i_days_old));
|
$h_image_rate = sprintf("%.1f", ($i_image_count / $i_days_old));
|
||||||
$images_link = make_link("post/list/user=$u_name/1");
|
$images_link = make_link("post/list/user=$u_name/1");
|
||||||
$event->add_stats("<a href='$images_link'>Images uploaded</a>: $i_image_count, $h_image_rate per day");
|
$event->add_stats("<a href='$images_link'>Posts uploaded</a>: $i_image_count, $h_image_rate per day");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$sb = new SetupBlock("Image Options");
|
$sb = new SetupBlock("Post Options");
|
||||||
$sb->start_table();
|
$sb->start_table();
|
||||||
$sb->position = 30;
|
$sb->position = 30;
|
||||||
// advanced only
|
// advanced only
|
||||||
//$sb->add_text_option(ImageConfig::ILINK, "Image link: ");
|
//$sb->add_text_option(ImageConfig::ILINK, "Image link: ");
|
||||||
//$sb->add_text_option(ImageConfig::TLINK, "<br>Thumbnail link: ");
|
//$sb->add_text_option(ImageConfig::TLINK, "<br>Thumbnail link: ");
|
||||||
$sb->add_text_option(ImageConfig::TIP, "Image tooltip", true);
|
$sb->add_text_option(ImageConfig::TIP, "Post tooltip", true);
|
||||||
|
$sb->add_text_option(ImageConfig::INFO, "Post info", true);
|
||||||
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "Upload collision handler", true);
|
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "Upload collision handler", true);
|
||||||
|
$sb->add_choice_option(ImageConfig::ON_DELETE, self::ON_DELETE_OPTIONS, "On Delete", true);
|
||||||
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||||
$sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true);
|
$sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true);
|
||||||
}
|
}
|
||||||
@ -235,7 +275,7 @@ class ImageIO extends Extension
|
|||||||
$sb = new SetupBlock("Thumbnailing");
|
$sb = new SetupBlock("Thumbnailing");
|
||||||
$sb->start_table();
|
$sb->start_table();
|
||||||
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine", true);
|
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine", true);
|
||||||
$sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype", true);
|
$sb->add_choice_option(ImageConfig::THUMB_MIME, self::THUMBNAIL_TYPES, "Filetype", true);
|
||||||
|
|
||||||
$sb->add_int_option(ImageConfig::THUMB_WIDTH, "Max Width", true);
|
$sb->add_int_option(ImageConfig::THUMB_WIDTH, "Max Width", true);
|
||||||
$sb->add_int_option(ImageConfig::THUMB_HEIGHT, "Max Height", true);
|
$sb->add_int_option(ImageConfig::THUMB_HEIGHT, "Max Height", true);
|
||||||
@ -249,6 +289,9 @@ class ImageIO extends Extension
|
|||||||
|
|
||||||
$sb->add_int_option(ImageConfig::THUMB_QUALITY, "Quality", true);
|
$sb->add_int_option(ImageConfig::THUMB_QUALITY, "Quality", true);
|
||||||
$sb->add_int_option(ImageConfig::THUMB_SCALING, "High-DPI Scale %", true);
|
$sb->add_int_option(ImageConfig::THUMB_SCALING, "High-DPI Scale %", true);
|
||||||
|
if ($config->get_string(ImageConfig::THUMB_MIME)===MimeType::JPEG) {
|
||||||
|
$sb->add_color_option(ImageConfig::THUMB_ALPHA_COLOR, "Alpha Conversion Color", true);
|
||||||
|
}
|
||||||
|
|
||||||
$sb->end_table();
|
$sb->end_table();
|
||||||
|
|
||||||
@ -258,7 +301,7 @@ class ImageIO extends Extension
|
|||||||
public function onParseLinkTemplate(ParseLinkTemplateEvent $event)
|
public function onParseLinkTemplate(ParseLinkTemplateEvent $event)
|
||||||
{
|
{
|
||||||
$fname = $event->image->get_filename();
|
$fname = $event->image->get_filename();
|
||||||
$base_fname = strpos($fname, '.') ? substr($fname, 0, strrpos($fname, '.')) : $fname;
|
$base_fname = str_contains($fname, '.') ? substr($fname, 0, strrpos($fname, '.')) : $fname;
|
||||||
|
|
||||||
$event->replace('$id', (string)$event->image->id);
|
$event->replace('$id', (string)$event->image->id);
|
||||||
$event->replace('$hash_ab', substr($event->image->hash, 0, 2));
|
$event->replace('$hash_ab', substr($event->image->hash, 0, 2));
|
||||||
@ -266,31 +309,31 @@ class ImageIO extends Extension
|
|||||||
$event->replace('$hash', $event->image->hash);
|
$event->replace('$hash', $event->image->hash);
|
||||||
$event->replace('$filesize', to_shorthand_int($event->image->filesize));
|
$event->replace('$filesize', to_shorthand_int($event->image->filesize));
|
||||||
$event->replace('$filename', $base_fname);
|
$event->replace('$filename', $base_fname);
|
||||||
|
$event->replace('$ext', $event->image->get_ext());
|
||||||
$event->replace('$date', autodate($event->image->posted, false));
|
$event->replace('$date', autodate($event->image->posted, false));
|
||||||
|
$event->replace("\\n", "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function send_file(int $image_id, string $type)
|
private function send_file(int $image_id, string $type)
|
||||||
{
|
{
|
||||||
global $config;
|
global $config, $page;
|
||||||
$image = Image::by_id($image_id);
|
|
||||||
|
|
||||||
global $page;
|
$image = Image::by_id($image_id);
|
||||||
if (!is_null($image)) {
|
if (!is_null($image)) {
|
||||||
if ($type == "thumb") {
|
if ($type == "thumb") {
|
||||||
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
|
$mime = $config->get_string(ImageConfig::THUMB_MIME);
|
||||||
$page->set_type(get_mime_for_extension($ext));
|
|
||||||
|
|
||||||
$file = $image->get_thumb_filename();
|
$file = $image->get_thumb_filename();
|
||||||
} else {
|
} else {
|
||||||
$page->set_type($image->get_mime_type());
|
$mime = $image->get_mime();
|
||||||
$file = $image->get_image_filename();
|
$file = $image->get_image_filename();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file_exists($file)) {
|
if (!file_exists($file)) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$page->set_mime($mime);
|
||||||
|
|
||||||
|
|
||||||
if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) {
|
if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) {
|
||||||
$if_modified_since = preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]);
|
$if_modified_since = preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]);
|
||||||
@ -319,12 +362,14 @@ class ImageIO extends Extension
|
|||||||
}
|
}
|
||||||
$page->add_http_header('Expires: ' . $expires);
|
$page->add_http_header('Expires: ' . $expires);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
send_event(new ImageDownloadingEvent($image, $file, $mime));
|
||||||
} else {
|
} else {
|
||||||
$page->set_title("Not Found");
|
$page->set_title("Not Found");
|
||||||
$page->set_heading("Not Found");
|
$page->set_heading("Not Found");
|
||||||
$page->add_block(new Block("Navigation", "<a href='" . make_link() . "'>Index</a>", "left", 0));
|
$page->add_block(new Block("Navigation", "<a href='" . make_link() . "'>Index</a>", "left", 0));
|
||||||
$page->add_block(new Block(
|
$page->add_block(new Block(
|
||||||
"Image not in database",
|
"Post not in database",
|
||||||
"The requested image was not found in the database"
|
"The requested image was not found in the database"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ class ImageBanInfo extends ExtensionInfo
|
|||||||
public const KEY = "image_hash_ban";
|
public const KEY = "image_hash_ban";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public $key = self::KEY;
|
||||||
public $name = "Image Hash Ban";
|
public $name = "Post Hash Ban";
|
||||||
public $url = "http://atravelinggeek.com/";
|
public $url = "http://atravelinggeek.com/";
|
||||||
public $authors = ["ATravelingGeek"=>"atg@atravelinggeek.com"];
|
public $authors = ["ATravelingGeek"=>"atg@atravelinggeek.com"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public $license = self::LICENSE_GPLV2;
|
||||||
|
@ -78,7 +78,7 @@ class ImageBan extends Extension
|
|||||||
$row = $database->get_row("SELECT * FROM image_bans WHERE hash = :hash", ["hash"=>$event->hash]);
|
$row = $database->get_row("SELECT * FROM image_bans WHERE hash = :hash", ["hash"=>$event->hash]);
|
||||||
if ($row) {
|
if ($row) {
|
||||||
log_info("image_hash_ban", "Attempted to upload a blocked image ({$event->hash} - {$row['reason']})");
|
log_info("image_hash_ban", "Attempted to upload a blocked image ({$event->hash} - {$row['reason']})");
|
||||||
throw new UploadException("Image ".html_escape($row["hash"])." has been banned, reason: ".format_text($row["reason"]));
|
throw new UploadException("Post ".html_escape($row["hash"])." has been banned, reason: ".format_text($row["reason"]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,11 +97,11 @@ class ImageBan extends Extension
|
|||||||
|
|
||||||
if ($hash) {
|
if ($hash) {
|
||||||
send_event(new AddImageHashBanEvent($hash, $reason));
|
send_event(new AddImageHashBanEvent($hash, $reason));
|
||||||
$page->flash("Image ban added");
|
$page->flash("Post ban added");
|
||||||
|
|
||||||
if ($image) {
|
if ($image) {
|
||||||
send_event(new ImageDeletionEvent($image));
|
send_event(new ImageDeletionEvent($image));
|
||||||
$page->flash("Image deleted");
|
$page->flash("Post deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
@ -111,7 +111,7 @@ class ImageBan extends Extension
|
|||||||
$user->ensure_authed();
|
$user->ensure_authed();
|
||||||
$input = validate_input(["d_hash"=>"string"]);
|
$input = validate_input(["d_hash"=>"string"]);
|
||||||
send_event(new RemoveImageHashBanEvent($input['d_hash']));
|
send_event(new RemoveImageHashBanEvent($input['d_hash']));
|
||||||
$page->flash("Image ban removed");
|
$page->flash("Post ban removed");
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(referer_or(make_link()));
|
$page->set_redirect(referer_or(make_link()));
|
||||||
} elseif ($event->get_arg(0) == "list") {
|
} elseif ($event->get_arg(0) == "list") {
|
||||||
@ -129,7 +129,7 @@ class ImageBan extends Extension
|
|||||||
global $user;
|
global $user;
|
||||||
if ($event->parent==="system") {
|
if ($event->parent==="system") {
|
||||||
if ($user->can(Permissions::BAN_IMAGE)) {
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
$event->add_nav_link("image_bans", new Link('image_hash_ban/list/1'), "Image Bans", NavLink::is_active(["image_hash_ban"]));
|
$event->add_nav_link("image_bans", new Link('image_hash_ban/list/1'), "Post Bans", NavLink::is_active(["image_hash_ban"]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ class ImageBan extends Extension
|
|||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can(Permissions::BAN_IMAGE)) {
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
$event->add_link("Image Bans", make_link("image_hash_ban/list/1"));
|
$event->add_link("Post Bans", make_link("image_hash_ban/list/1"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,10 +8,10 @@ class ImageBanTheme extends Themelet
|
|||||||
*/
|
*/
|
||||||
public function display_bans(Page $page, $table, $paginator)
|
public function display_bans(Page $page, $table, $paginator)
|
||||||
{
|
{
|
||||||
$page->set_title("Image Bans");
|
$page->set_title("Post Bans");
|
||||||
$page->set_heading("Image Bans");
|
$page->set_heading("Post Bans");
|
||||||
$page->add_block(new NavBlock());
|
$page->add_block(new NavBlock());
|
||||||
$page->add_block(new Block("Edit Image Bans", $table . $paginator));
|
$page->add_block(new Block("Edit Post Bans", $table . $paginator));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -24,7 +24,7 @@ class ImageBanTheme extends Themelet
|
|||||||
INPUT(["type"=>'hidden', "name"=>'c_hash', "value"=>$image->hash]),
|
INPUT(["type"=>'hidden', "name"=>'c_hash', "value"=>$image->hash]),
|
||||||
INPUT(["type"=>'hidden', "name"=>'c_image_id', "value"=>$image->id]),
|
INPUT(["type"=>'hidden', "name"=>'c_image_id', "value"=>$image->id]),
|
||||||
INPUT(["type"=>'text', "name"=>'c_reason']),
|
INPUT(["type"=>'text', "name"=>'c_reason']),
|
||||||
INPUT(["type"=>'submit', "value"=>'Ban Hash and Delete Image']),
|
INPUT(["type"=>'submit', "value"=>'Ban Hash and Delete Post']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,13 @@ class ImageViewCounterInfo extends ExtensionInfo
|
|||||||
public const KEY = "image_view_counter";
|
public const KEY = "image_view_counter";
|
||||||
|
|
||||||
public $key = self::KEY;
|
public $key = self::KEY;
|
||||||
public $name = "Image View Counter";
|
public $name = "Post View Counter";
|
||||||
public $url = "http://www.drudexsoftware.com/";
|
public $url = "http://www.drudexsoftware.com/";
|
||||||
public $authors = ["Drudex Software"=>"support@drudexsoftware.com"];
|
public $authors = ["Drudex Software"=>"support@drudexsoftware.com"];
|
||||||
public $license = self::LICENSE_GPLV2;
|
public $license = self::LICENSE_GPLV2;
|
||||||
public $description = "Tracks & displays how many times an image is viewed";
|
public $description = "Tracks & displays how many times a post is viewed";
|
||||||
public $documentation =
|
public $documentation =
|
||||||
"Whenever anyone views an image, a view will be added to that image.
|
"Whenever anyone views a post, a view will be added to that image.
|
||||||
This extension will also track any username & the IP address.
|
This extension will also track any username & the IP address.
|
||||||
This is done to prevent duplicate views.
|
This is done to prevent duplicate views.
|
||||||
A person can only count as a view again 1 hour after viewing the image initially.";
|
A person can only count as a view again 1 hour after viewing the image initially.";
|
||||||
|
@ -2,66 +2,31 @@
|
|||||||
|
|
||||||
class ImageViewCounter extends Extension
|
class ImageViewCounter extends Extension
|
||||||
{
|
{
|
||||||
|
protected $theme;
|
||||||
private $view_interval = 3600; # allows views to be added each hour
|
private $view_interval = 3600; # allows views to be added each hour
|
||||||
|
|
||||||
# Add Setup Block with options for view counter
|
|
||||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
|
||||||
{
|
|
||||||
$sb = new SetupBlock("Image View Counter");
|
|
||||||
$sb->add_bool_option("image_viewcounter_adminonly", "Display view counter only to admin");
|
|
||||||
|
|
||||||
$event->panel->add_block($sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Adds view to database if needed
|
|
||||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||||
{
|
|
||||||
$imgid = $event->image->id; // determines image id
|
|
||||||
$this->addview($imgid); // adds a view
|
|
||||||
}
|
|
||||||
|
|
||||||
# display views to user or admin below image if allowed
|
|
||||||
public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event)
|
|
||||||
{
|
|
||||||
global $user, $config;
|
|
||||||
|
|
||||||
$adminonly = $config->get_bool("image_viewcounter_adminonly"); // todo
|
|
||||||
if ($adminonly == false || ($adminonly && $user->can(Permissions::SEE_IMAGE_VIEW_COUNTS))) {
|
|
||||||
$event->add_part(
|
|
||||||
"<tr><th>Views:</th><td>".
|
|
||||||
$this->get_view_count($event->image->id) .
|
|
||||||
"</tr>",
|
|
||||||
38
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Installs DB table
|
|
||||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
|
||||||
{
|
|
||||||
global $database, $config;
|
|
||||||
|
|
||||||
// if the sql table doesn't exist yet, create it
|
|
||||||
if ($config->get_bool("image_viewcounter_installed") == false) { //todo
|
|
||||||
$database->create_table("image_views", "
|
|
||||||
id SCORE_AIPK,
|
|
||||||
image_id INTEGER NOT NULL,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
timestamp INTEGER NOT NULL,
|
|
||||||
ipaddress SCORE_INET NOT NULL");
|
|
||||||
$config->set_bool("image_viewcounter_installed", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a view to the item if needed
|
|
||||||
*/
|
|
||||||
private function addview(int $imgid)
|
|
||||||
{
|
{
|
||||||
global $database, $user;
|
global $database, $user;
|
||||||
|
|
||||||
|
$imgid = $event->image->id;
|
||||||
|
|
||||||
|
// counts views from current IP in the last hour
|
||||||
|
$recent_from_ip = (int)$database->get_one(
|
||||||
|
"
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM image_views
|
||||||
|
WHERE ipaddress=:ipaddress AND timestamp >:lasthour AND image_id =:image_id
|
||||||
|
",
|
||||||
|
[
|
||||||
|
"ipaddress" => $_SERVER['REMOTE_ADDR'],
|
||||||
|
"lasthour" => time() - $this->view_interval,
|
||||||
|
"image_id" => $imgid
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
// don't add view if person already viewed recently
|
// don't add view if person already viewed recently
|
||||||
if ($this->can_add_view($imgid) === false) {
|
if ($recent_from_ip > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,54 +45,64 @@ class ImageViewCounter extends Extension
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event)
|
||||||
* Returns true if this IP hasn't recently viewed this image
|
|
||||||
*/
|
|
||||||
private function can_add_view(int $imgid)
|
|
||||||
{
|
{
|
||||||
global $database;
|
global $user, $database;
|
||||||
|
|
||||||
// counts views from current IP in the last hour
|
if ($user->can(Permissions::SEE_IMAGE_VIEW_COUNTS)) {
|
||||||
$recent_from_ip = (int)$database->get_one(
|
|
||||||
"
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM image_views
|
|
||||||
WHERE ipaddress=:ipaddress AND timestamp >:lasthour AND image_id =:image_id
|
|
||||||
",
|
|
||||||
[
|
|
||||||
"ipaddress" => $_SERVER['REMOTE_ADDR'],
|
|
||||||
"lasthour" => time() - $this->view_interval,
|
|
||||||
"image_id" => $imgid
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// if no views were found with the set criteria, return true
|
|
||||||
if ($recent_from_ip == 0) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the int of the view count from the given image id
|
|
||||||
*/
|
|
||||||
private function get_view_count(int $imgid = 0)
|
|
||||||
{
|
|
||||||
global $database;
|
|
||||||
|
|
||||||
if ($imgid == 0) { // return view count of all images
|
|
||||||
$view_count = (int)$database->get_one(
|
|
||||||
"SELECT COUNT(*) FROM image_views"
|
|
||||||
);
|
|
||||||
} else { // return view count of specified image
|
|
||||||
$view_count = (int)$database->get_one(
|
$view_count = (int)$database->get_one(
|
||||||
"SELECT COUNT(*) FROM image_views WHERE image_id =:image_id",
|
"SELECT COUNT(*) FROM image_views WHERE image_id =:image_id",
|
||||||
["image_id" => $imgid]
|
["image_id" => $event->image->id]
|
||||||
|
);
|
||||||
|
|
||||||
|
$event->add_part(
|
||||||
|
"<tr><th>Views:</th><td>$view_count</td></tr>",
|
||||||
|
38
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// returns the count as int
|
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||||
return $view_count;
|
{
|
||||||
|
global $database, $config;
|
||||||
|
|
||||||
|
if ($config->get_bool("image_viewcounter_installed") == false) { //todo
|
||||||
|
$database->create_table("image_views", "
|
||||||
|
id SCORE_AIPK,
|
||||||
|
image_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
timestamp INTEGER NOT NULL,
|
||||||
|
ipaddress SCORE_INET NOT NULL");
|
||||||
|
$config->set_bool("image_viewcounter_installed", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
|
{
|
||||||
|
global $database;
|
||||||
|
|
||||||
|
if ($event->page_matches("popular_images")) {
|
||||||
|
$sql = "
|
||||||
|
SELECT image_id, count(*) AS total_views
|
||||||
|
FROM image_views, images
|
||||||
|
WHERE image_views.image_id = image_views.image_id
|
||||||
|
AND image_views.image_id = images.id
|
||||||
|
GROUP BY image_views.image_id
|
||||||
|
ORDER BY total_views DESC
|
||||||
|
";
|
||||||
|
$result = $database->get_col($sql);
|
||||||
|
$images = [];
|
||||||
|
foreach ($result as $id) {
|
||||||
|
$images[] = Image::by_id(intval($id));
|
||||||
|
}
|
||||||
|
$this->theme->view_popular($images);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if ($event->parent=="posts") {
|
||||||
|
$event->add_nav_link("sort_by_visits", new Link('popular_images'), "Popular Posts");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
ext/image_view_counter/theme.php
Normal file
24
ext/image_view_counter/theme.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
class ImageViewCounterTheme extends Themelet
|
||||||
|
{
|
||||||
|
public function view_popular($images)
|
||||||
|
{
|
||||||
|
global $page, $config;
|
||||||
|
$pop_images = "";
|
||||||
|
foreach ($images as $image) {
|
||||||
|
$pop_images .= $this->build_thumb_html($image) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$nav_html = "<a href=".make_link().">Index</a>";
|
||||||
|
|
||||||
|
$page->set_heading($config->get_string(SetupConfig::TITLE));
|
||||||
|
$page->add_block(new Block("Navigation", $nav_html, "left", 10));
|
||||||
|
$page->add_block(new Block(null, $pop_images, "main", 30));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_help_html()
|
||||||
|
{
|
||||||
|
return '<p>Search for posts that have received views by users.</p>';
|
||||||
|
}
|
||||||
|
}
|
@ -6,30 +6,25 @@
|
|||||||
*/
|
*/
|
||||||
class SearchTermParseEvent extends Event
|
class SearchTermParseEvent extends Event
|
||||||
{
|
{
|
||||||
/** @var null|string */
|
/** @var int */
|
||||||
|
public $id = 0;
|
||||||
|
/** @var null|string */
|
||||||
public $term = null;
|
public $term = null;
|
||||||
/** @var string[] */
|
/** @var string[] */
|
||||||
public $context = [];
|
public $context = [];
|
||||||
/** @var Querylet[] */
|
/** @var Querylet[] */
|
||||||
public $querylets = [];
|
public $querylets = [];
|
||||||
|
/** @var null|string */
|
||||||
|
public $order = null;
|
||||||
|
|
||||||
public function __construct(string $term=null, array $context=[])
|
public function __construct(int $id, string $term=null, array $context=[])
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
$this->id = $id;
|
||||||
$this->term = $term;
|
$this->term = $term;
|
||||||
$this->context = $context;
|
$this->context = $context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function is_querylet_set(): bool
|
|
||||||
{
|
|
||||||
return (count($this->querylets) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get_querylets(): array
|
|
||||||
{
|
|
||||||
return $this->querylets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function add_querylet(Querylet $q)
|
public function add_querylet(Querylet $q)
|
||||||
{
|
{
|
||||||
$this->querylets[] = $q;
|
$this->querylets[] = $q;
|
||||||
|
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