Compare commits
128 Commits
Author | SHA1 | Date | |
---|---|---|---|
6631e2cb6f | |||
|
7040b1b8e5 | ||
|
c1068f1b2b | ||
|
bb5614c5ef | ||
|
81417a5031 | ||
|
2197b15012 | ||
|
eecd35d175 | ||
|
1c216e8d51 | ||
|
04987ea70e | ||
|
844ec8b53e | ||
|
72de50aa71 | ||
|
ac63992efa | ||
|
7c32b1f7a8 | ||
|
919a3039c4 | ||
|
ea34d9b756 | ||
|
30f62c2ff8 | ||
|
9b9f1d0341 | ||
|
835c3b68a1 | ||
|
b937ad6255 | ||
|
8e976fb812 | ||
|
dd08b936e3 | ||
|
1fdd5bf575 | ||
|
6d16c52367 | ||
|
587735a866 | ||
|
a2fe0725f5 | ||
|
73660b376e | ||
|
d243867b18 | ||
|
fac2067069 | ||
|
2f313b704a | ||
|
8fe7038e73 | ||
|
c171e98591 | ||
|
edc8e5aa43 | ||
|
f6923af8ab | ||
|
18cd74f57d | ||
|
4d69e7ce34 | ||
|
04cde74226 | ||
|
b2405166b3 | ||
|
4d0b90921d | ||
|
05d4a3a592 | ||
|
b3fb923cd1 | ||
|
ad1e52bf05 | ||
|
549ec593bb | ||
|
ed8a9fca52 | ||
|
ec290d8676 | ||
|
5446f29141 | ||
|
86f7a06ed0 | ||
|
e1aefb78ab | ||
|
12c331cbd2 | ||
|
8b407e3df3 | ||
|
bccb206369 | ||
|
2bb5f349f9 | ||
|
007e07e507 | ||
|
63b2601e67 | ||
|
16c58e266b | ||
|
6145ecc6f8 | ||
|
830915adf2 | ||
|
06bd4589da | ||
|
1e76fb239e | ||
|
f7c6b662cd | ||
|
00060c34c2 | ||
|
10d46395d7 | ||
|
72645af9a4 | ||
|
2cae6cd273 | ||
|
0b2e36303d | ||
|
b0cb46abca | ||
|
78710166a1 | ||
|
c146a9f53d | ||
|
f6112d26a2 | ||
|
b04b5af190 | ||
|
02d42a01b4 | ||
|
0039aafe94 | ||
|
1d389f0156 | ||
|
69cb67fe24 | ||
|
5ea26a80cc | ||
|
126c629a1a | ||
|
ab4b745310 | ||
|
f47e35e4e5 | ||
|
fd359fb08c | ||
|
866b77ab19 | ||
|
b60e8ac5b4 | ||
|
70acc6015b | ||
|
a3a129df5f | ||
|
02675609b4 | ||
|
85662575c5 | ||
|
c16e3fd939 | ||
|
5ea7cc5b36 | ||
|
efde5e1edf | ||
|
7b9c9dc208 | ||
|
bc3de6a52a | ||
|
d85f4d3799 | ||
|
880a702b42 | ||
|
b5f0bc7621 | ||
|
599043baa5 | ||
|
36a2125e90 | ||
|
1f50f14672 | ||
|
b0c5043892 | ||
|
861ee946a9 | ||
|
237f8148f3 | ||
|
17f3b44212 | ||
|
511a82f2ba | ||
|
2d0b107adb | ||
|
ecbf4f52a0 | ||
|
06e5b02874 | ||
|
f819993685 | ||
|
1b10d8583e | ||
|
e362f3bad2 | ||
|
5f5b858175 | ||
|
168cf99188 | ||
|
134fd7d919 | ||
|
ea637132bd | ||
|
566c92b780 | ||
|
4e4deed889 | ||
|
d4e05d947a | ||
|
25248c089d | ||
|
0094d5c1a8 | ||
|
f0db4f9a02 | ||
|
03806d0420 | ||
|
c3b67f346b | ||
|
edee8e7427 | ||
|
c794e457b1 | ||
|
1753cbd72b | ||
|
8b1b4d257e | ||
|
ea0e83abc9 | ||
|
bf4280461f | ||
|
d0b00a72ce | ||
|
d14819387e | ||
|
040bffa4f6 | ||
|
3f26013b28 |
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -4,27 +4,25 @@ about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
**Server Software**
|
||||
(You can get all these stats from `http://<your site>/system_info`)
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
- Shimmie version:
|
||||
- Database: [mysql, postgres, ...]
|
||||
- Web server: [apache, nginx, ...]
|
||||
|
||||
**Client Software (please complete the following information)**
|
||||
- Device: [e.g. iphone, windows desktop]
|
||||
- Browser: [e.g. chrome, safari]
|
||||
|
||||
**What steps trigger this bug**
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
**What did you expect to happen?**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
**What actually happened?**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Server Software, if you're the server admin (please complete the following information):**
|
||||
- Shimmie version
|
||||
- Database [mysql, postgres, ...]
|
||||
- Web server [apache, nginx, ...]
|
||||
|
||||
**Client Software (please complete the following information):**
|
||||
- Device [e.g. iphone, windows desktop]
|
||||
- Browser [e.g. chrome, safari]
|
||||
|
62
.github/workflows/release.yml
vendored
Normal file
62
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Get version from tag
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
|
||||
|
||||
- name: Test version in sys_config
|
||||
run: grep ${{ steps.get_version.outputs.VERSION }} core/sys_config.php
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
composer install --no-dev
|
||||
cd ..
|
||||
tar cvzf shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz shimmie2
|
||||
zip -r shimmie2-${{ steps.get_version.outputs.VERSION }}.zip shimmie2
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Shimmie ${{ steps.get_version.outputs.VERSION }}
|
||||
body: Automated release from tags
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Zip
|
||||
id: upload-release-asset-zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../shimmie2-${{ steps.get_version.outputs.VERSION }}.zip
|
||||
asset_name: shimmie2-${{ steps.get_version.outputs.VERSION }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Tar
|
||||
id: upload-release-asset-tar
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz
|
||||
asset_name: shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz
|
||||
asset_content_type: application/gzip
|
2
.github/workflows/test_and_publish.yml
vendored
2
.github/workflows/test_and_publish.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p data/config
|
||||
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
|
||||
sudo apt update && sudo apt-get install -y postgresql postgresql-client ;
|
||||
sudo systemctl start postgresql ;
|
||||
psql --version ;
|
||||
sudo -u postgres psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres ;
|
||||
sudo -u postgres psql -c "CREATE USER shimmie WITH PASSWORD 'shimmie';" -U postgres ;
|
||||
|
@ -17,8 +17,7 @@
|
||||
# rather than link to images/ha/hash and have an ugly filename,
|
||||
# we link to images/hash/tags.ext; mod_rewrite splits things so
|
||||
# that shimmie sees hash and the user sees tags.ext
|
||||
RewriteRule ^_images/([0-9a-f]{2})([0-9a-f]{30}).*$ data/images/$1/$1$2 [L]
|
||||
RewriteRule ^_thumbs/([0-9a-f]{2})([0-9a-f]{30}).*$ data/thumbs/$1/$1$2 [L]
|
||||
RewriteRule ^_(images|thumbs)/([0-9a-f]{2})([0-9a-f]{30}).*$ data/$1/$2/$2$3 [L]
|
||||
|
||||
# any requests for files which don't physically exist should be handled by index.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
|
24
Dockerfile
24
Dockerfile
@ -1,7 +1,17 @@
|
||||
# "Build" shimmie (composer install - done in its own stage so that we don't
|
||||
# need to include all the composer fluff in the final image)
|
||||
FROM debian:stable-slim
|
||||
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 imagemagick
|
||||
FROM debian:stable-slim AS app
|
||||
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
|
||||
COPY composer.json composer.lock /app/
|
||||
WORKDIR /app
|
||||
RUN composer install --no-dev
|
||||
COPY . /app/
|
||||
|
||||
# Tests in their own image. Really we should inherit from app and then
|
||||
# `composer install` phpunit on top of that; but for some reason
|
||||
# `composer install --no-dev && composer install` doesn't install dev
|
||||
FROM debian:stable-slim AS tests
|
||||
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
|
||||
COPY composer.json composer.lock /app/
|
||||
WORKDIR /app
|
||||
RUN composer install
|
||||
@ -15,7 +25,7 @@ RUN [ $RUN_TESTS = false ] || (\
|
||||
echo '=== Cleaning ===' && rm -rf data)
|
||||
|
||||
# Build su-exec so that our final image can be nicer
|
||||
FROM debian:stable-slim
|
||||
FROM debian:stable-slim AS suexec
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc libc-dev curl
|
||||
RUN curl -k -o /usr/local/bin/su-exec.c https://raw.githubusercontent.com/ncopa/su-exec/master/su-exec.c; \
|
||||
gcc -Wall /usr/local/bin/su-exec.c -o/usr/local/bin/su-exec; \
|
||||
@ -29,11 +39,11 @@ HEALTHCHECK --interval=5m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ ||
|
||||
ENV UID=1000 \
|
||||
GID=1000
|
||||
RUN apt update && apt install -y curl \
|
||||
php7.3-cli php7.3-gd php7.3-pgsql php7.3-mysql php7.3-sqlite3 php7.3-zip php7.3-dom php7.3-mbstring php-xdebug \
|
||||
composer imagemagick vim zip unzip && \
|
||||
php7.3-cli php7.3-gd php7.3-pgsql php7.3-mysql php7.3-sqlite3 php7.3-zip php7.3-dom php7.3-mbstring \
|
||||
imagemagick zip unzip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=0 /app /app
|
||||
COPY --from=1 /usr/local/bin/su-exec /usr/local/bin/su-exec
|
||||
COPY --from=app /app /app
|
||||
COPY --from=suexec /usr/local/bin/su-exec /usr/local/bin/su-exec
|
||||
|
||||
WORKDIR /app
|
||||
CMD ["/bin/sh", "/app/tests/docker-init.sh"]
|
||||
|
17
README.md
17
README.md
@ -17,24 +17,25 @@
|
||||
|
||||
# Documentation
|
||||
|
||||
* [Install straight on disk](https://github.com/shish/shimmie2/tree/master/docs/INSTALL.md)
|
||||
* [Install in docker container](https://github.com/shish/shimmie2/tree/master/docs/DOCKER.md)
|
||||
* [Upgrade process](https://github.com/shish/shimmie2/tree/master/docs/UPGRADE.md)
|
||||
* [Advanced config](https://github.com/shish/shimmie2/tree/master/docs/CONFIG.md)
|
||||
* [Developer notes](https://github.com/shish/shimmie2/tree/master/docs/DEV.md)
|
||||
* [High-performance notes](https://github.com/shish/shimmie2/tree/master/docs/SPEED.md)
|
||||
* [Install straight on disk](https://github.com/shish/shimmie2/wiki/Install)
|
||||
* [Install in docker container](https://github.com/shish/shimmie2/wiki/Docker)
|
||||
* [Upgrade process](https://github.com/shish/shimmie2/wiki/Upgrade)
|
||||
* [Basic settings](https://github.com/shish/shimmie2/wiki/Settings)
|
||||
* [Advanced config](https://github.com/shish/shimmie2/wiki/Advanced-Config)
|
||||
* [Developer notes](https://github.com/shish/shimmie2/wiki/Development-Info)
|
||||
* [High-performance notes](https://github.com/shish/shimmie2/wiki/Performance)
|
||||
|
||||
|
||||
# Contact
|
||||
|
||||
Email: webmaster at shishnet.org
|
||||
|
||||
Issue/Bug tracker: http://github.com/shish/shimmie2/issues
|
||||
Issue/Bug tracker: https://github.com/shish/shimmie2/issues
|
||||
|
||||
|
||||
# Licence
|
||||
|
||||
All code is released under the [GNU GPL Version 2](http://www.gnu.org/licenses/gpl-2.0.html) unless mentioned otherwise.
|
||||
All code is released under the [GNU GPL Version 2](https://www.gnu.org/licenses/gpl-2.0.html) unless mentioned otherwise.
|
||||
|
||||
If you give shimmie to someone else, you have to give them the source (which
|
||||
should be easy, as PHP is an interpreted language...). If you want to add
|
||||
|
@ -6,10 +6,6 @@
|
||||
"minimum-stability" : "dev",
|
||||
|
||||
"repositories" : [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
},
|
||||
{
|
||||
"type" : "package",
|
||||
"package" : {
|
||||
@ -25,30 +21,29 @@
|
||||
],
|
||||
|
||||
"require" : {
|
||||
"php" : ">=7.3",
|
||||
"php" : "^7.3",
|
||||
"ext-pdo": "*",
|
||||
"ext-json": "*",
|
||||
"ext-fileinfo": "*",
|
||||
|
||||
"flexihash/flexihash" : "^2.0.0",
|
||||
"ifixit/php-akismet" : "1.*",
|
||||
"google/recaptcha" : "~1.1",
|
||||
"dapphp/securimage" : "3.6.*",
|
||||
"shish/eventtracer-php" : "dev-master",
|
||||
"shish/ffsphp" : "0.0.*",
|
||||
"shish/microcrud" : "dev-master",
|
||||
"shish/microhtml" : "^1.0.0",
|
||||
"shish/eventtracer-php" : "^2.0.0",
|
||||
"shish/ffsphp" : "^1.0.0",
|
||||
"shish/microcrud" : "^2.0.0",
|
||||
"shish/microhtml" : "^2.0.0",
|
||||
"enshrined/svg-sanitize" : "0.13.*",
|
||||
|
||||
"bower-asset/jquery" : "1.12.*",
|
||||
"bower-asset/jquery-timeago" : "1.5.*",
|
||||
"bower-asset/tablesorter" : "dev-master",
|
||||
"bower-asset/mediaelement" : "2.21.*",
|
||||
"bower-asset/js-cookie" : "2.1.*"
|
||||
},
|
||||
|
||||
"require-dev" : {
|
||||
"phpunit/phpunit" : "8.*"
|
||||
},
|
||||
},
|
||||
|
||||
"suggest": {
|
||||
"ext-memcache": "memcache caching",
|
||||
@ -59,9 +54,14 @@
|
||||
"ext-curl": "some extensions",
|
||||
"ext-ctype": "some extensions",
|
||||
"ext-json": "some extensions",
|
||||
"ext-zip": "self-updater extension",
|
||||
"ext-zip": "self-updater extension, bulk import/export",
|
||||
"ext-zlib": "anti-spam",
|
||||
"ext-xml": "some extensions",
|
||||
"ext-gd": "GD-based thumbnailing"
|
||||
}
|
||||
},"replace": {
|
||||
"bower-asset/jquery": ">=1.11.0",
|
||||
"bower-asset/inputmask": ">=3.2.0",
|
||||
"bower-asset/punycode": ">=1.3.0",
|
||||
"bower-asset/yii2-pjax": ">=2.0.0"
|
||||
}
|
||||
}
|
||||
|
1655
composer.lock
generated
1655
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ abstract class PageMode
|
||||
const DATA = 'data';
|
||||
const PAGE = 'page';
|
||||
const FILE = 'file';
|
||||
const MANUAL = 'manual';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,16 +239,13 @@ class BasePage
|
||||
|
||||
// ==============================================
|
||||
|
||||
/**
|
||||
* Display the page according to the mode and data given.
|
||||
*/
|
||||
public function display(): void
|
||||
public function send_headers(): void
|
||||
{
|
||||
header("HTTP/1.0 {$this->code} Shimmie");
|
||||
header("Content-type: " . $this->type);
|
||||
header("X-Powered-By: Shimmie-" . VERSION);
|
||||
|
||||
if (!headers_sent()) {
|
||||
header("HTTP/1.0 {$this->code} Shimmie");
|
||||
header("Content-type: " . $this->type);
|
||||
header("X-Powered-By: Shimmie-" . VERSION);
|
||||
|
||||
foreach ($this->http_headers as $head) {
|
||||
header($head);
|
||||
}
|
||||
@ -257,8 +255,20 @@ class BasePage
|
||||
} else {
|
||||
print "Error: Headers have already been sent to the client.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the page according to the mode and data given.
|
||||
*/
|
||||
public function display(): void
|
||||
{
|
||||
if ($this->mode!=PageMode::MANUAL) {
|
||||
$this->send_headers();
|
||||
}
|
||||
|
||||
switch ($this->mode) {
|
||||
case PageMode::MANUAL:
|
||||
break;
|
||||
case PageMode::PAGE:
|
||||
usort($this->blocks, "blockcmp");
|
||||
$this->add_auto_html_headers();
|
||||
@ -394,9 +404,7 @@ class BasePage
|
||||
$js_latest = $config_latest;
|
||||
$js_files = array_merge(
|
||||
[
|
||||
"vendor/bower-asset/jquery/dist/jquery.min.js",
|
||||
"vendor/bower-asset/jquery-timeago/jquery.timeago.js",
|
||||
"vendor/bower-asset/tablesorter/jquery.tablesorter.min.js",
|
||||
"vendor/bower-asset/js-cookie/src/js.cookie.js",
|
||||
"ext/static_files/modernizr-3.3.1.custom.js",
|
||||
],
|
||||
|
@ -53,7 +53,7 @@ class BaseThemelet
|
||||
$h_tip = html_escape($image->get_tooltip());
|
||||
$h_tags = html_escape(strtolower($image->get_tag_list()));
|
||||
|
||||
$extArr = array_flip(['swf', 'svg', 'mp3']); //List of thumbless filetypes
|
||||
$extArr = array_flip([EXTENSION_FLASH, EXTENSION_SVG, EXTENSION_MP3]); //List of thumbless filetypes
|
||||
if (!isset($extArr[$image->ext])) {
|
||||
$tsize = get_thumbnail_size($image->width, $image->height);
|
||||
} else {
|
||||
|
@ -60,7 +60,7 @@ class Database
|
||||
$this->connect_engine();
|
||||
$this->engine->init($this->db);
|
||||
|
||||
$this->beginTransaction();
|
||||
$this->begin_transaction();
|
||||
}
|
||||
|
||||
private function connect_engine(): void
|
||||
@ -78,11 +78,14 @@ class Database
|
||||
} elseif ($db_proto === DatabaseDriver::SQLITE) {
|
||||
$this->engine = new SQLite();
|
||||
} else {
|
||||
die('Unknown PDO driver: '.$db_proto);
|
||||
die_nicely(
|
||||
'Unknown PDO driver: '.$db_proto,
|
||||
"Please check that this is a valid driver, installing the PHP modules if needed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function beginTransaction(): void
|
||||
public function begin_transaction(): void
|
||||
{
|
||||
if ($this->transaction === false) {
|
||||
$this->db->beginTransaction();
|
||||
@ -90,9 +93,14 @@ class Database
|
||||
}
|
||||
}
|
||||
|
||||
public function is_transaction_open(): bool
|
||||
{
|
||||
return !is_null($this->db) && $this->transaction === true;
|
||||
}
|
||||
|
||||
public function commit(): bool
|
||||
{
|
||||
if (!is_null($this->db) && $this->transaction === true) {
|
||||
if ($this->is_transaction_open()) {
|
||||
$this->transaction = false;
|
||||
return $this->db->commit();
|
||||
} else {
|
||||
@ -102,7 +110,7 @@ class Database
|
||||
|
||||
public function rollback(): bool
|
||||
{
|
||||
if (!is_null($this->db) && $this->transaction === true) {
|
||||
if ($this->is_transaction_open()) {
|
||||
$this->transaction = false;
|
||||
return $this->db->rollback();
|
||||
} else {
|
||||
@ -139,6 +147,11 @@ class Database
|
||||
return $this->engine->name;
|
||||
}
|
||||
|
||||
public function get_version(): string
|
||||
{
|
||||
return $this->engine->get_version($this->db);
|
||||
}
|
||||
|
||||
private function count_time(string $method, float $start, string $query, ?array $args): void
|
||||
{
|
||||
global $_tracer, $tracer_enabled;
|
||||
|
@ -31,6 +31,8 @@ abstract class DBEngine
|
||||
}
|
||||
|
||||
abstract public function set_timeout(PDO $db, int $time);
|
||||
|
||||
abstract public function get_version(PDO $db): string;
|
||||
}
|
||||
|
||||
class MySQL extends DBEngine
|
||||
@ -68,12 +70,15 @@ class MySQL extends DBEngine
|
||||
// These only apply to read-only queries, which appears to be the best we can to mysql-wise
|
||||
// $db->exec("SET SESSION MAX_EXECUTION_TIME=".$time.";");
|
||||
}
|
||||
|
||||
public function get_version(PDO $db): string
|
||||
{
|
||||
return $db->query('select version()')->fetch()[0];
|
||||
}
|
||||
}
|
||||
|
||||
class PostgreSQL extends DBEngine
|
||||
{
|
||||
|
||||
|
||||
/** @var string */
|
||||
public $name = DatabaseDriver::PGSQL;
|
||||
|
||||
@ -87,7 +92,9 @@ class PostgreSQL extends DBEngine
|
||||
} else {
|
||||
$db->exec("SET application_name TO 'shimmie [local]';");
|
||||
}
|
||||
$this->set_timeout($db, DATABASE_TIMEOUT);
|
||||
if (defined("DATABASE_TIMEOUT")) {
|
||||
$this->set_timeout($db, DATABASE_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
public function scoreql_to_sql(string $data): string
|
||||
@ -110,6 +117,11 @@ class PostgreSQL extends DBEngine
|
||||
{
|
||||
$db->exec("SET statement_timeout TO ".$time.";");
|
||||
}
|
||||
|
||||
public function get_version(PDO $db): string
|
||||
{
|
||||
return $db->query('select version()')->fetch()[0];
|
||||
}
|
||||
}
|
||||
|
||||
// shimmie functions for export to sqlite
|
||||
@ -216,4 +228,9 @@ class SQLite extends DBEngine
|
||||
{
|
||||
// There doesn't seem to be such a thing for SQLite, so it does nothing
|
||||
}
|
||||
|
||||
public function get_version(PDO $db): string
|
||||
{
|
||||
return $db->query('select sqlite_version()')->fetch()[0];
|
||||
}
|
||||
}
|
||||
|
@ -108,21 +108,26 @@ class PageRequestEvent extends Event
|
||||
if ($offset >= 0 && $offset < $this->arg_count) {
|
||||
return $this->args[$offset];
|
||||
} else {
|
||||
throw new SCoreException("Requested an invalid argument #$n");
|
||||
$nm1 = $this->arg_count - 1;
|
||||
throw new SCoreException("Requested an invalid page argument {$offset} / {$nm1}");
|
||||
}
|
||||
}
|
||||
|
||||
public function try_page_num(int $n): int
|
||||
/**
|
||||
* If page arg $n is set, then treat that as a 1-indexed page number
|
||||
* and return a 0-indexed page number less than $max; else return 0
|
||||
*/
|
||||
public function try_page_num(int $n, ?int $max=null): int
|
||||
{
|
||||
if ($this->count_args() > $n) {
|
||||
$i = $this->get_arg($n);
|
||||
if (is_numeric($i) && int_escape($i) > 0) {
|
||||
return int_escape($i);
|
||||
return page_number($i, $max);
|
||||
} else {
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Also loads the theme object into $this->theme if available
|
||||
*
|
||||
* The original concept came from Artanis's Extension extension
|
||||
* --> http://github.com/Artanis/simple-extension/tree/master
|
||||
* --> https://github.com/Artanis/simple-extension/tree/master
|
||||
* Then re-implemented by Shish after he broke the forum and couldn't
|
||||
* find the thread where the original was posted >_<
|
||||
*/
|
||||
@ -115,7 +115,7 @@ abstract class ExtensionInfo
|
||||
// Every credit you get costs us RAM. It stops now.
|
||||
public const SHISH_NAME = "Shish";
|
||||
public const SHISH_EMAIL = "webmaster@shishnet.org";
|
||||
public const SHIMMIE_URL = "http://code.shishnet.org/shimmie2/";
|
||||
public const SHIMMIE_URL = "https://code.shishnet.org/shimmie2/";
|
||||
public const SHISH_AUTHOR = [self::SHISH_NAME=>self::SHISH_EMAIL];
|
||||
|
||||
public const LICENSE_GPLV2 = "GPLv2";
|
||||
@ -275,7 +275,7 @@ abstract class FormatterExtension extends Extension
|
||||
*/
|
||||
abstract class DataHandlerExtension extends Extension
|
||||
{
|
||||
protected $SUPPORTED_EXT = [];
|
||||
protected $SUPPORTED_MIME = [];
|
||||
|
||||
protected function move_upload_to_archive(DataUploadEvent $event)
|
||||
{
|
||||
@ -298,14 +298,11 @@ abstract class DataHandlerExtension extends Extension
|
||||
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
|
||||
|
||||
/* Check if we are replacing an image */
|
||||
if (array_key_exists('replace', $event->metadata) && isset($event->metadata['replace'])) {
|
||||
if (!is_null($event->replace_id)) {
|
||||
/* hax: This seems like such a dirty way to do this.. */
|
||||
|
||||
/* Validate things */
|
||||
$image_id = int_escape($event->metadata['replace']);
|
||||
|
||||
/* Check to make sure the image exists. */
|
||||
$existing = Image::by_id($image_id);
|
||||
$existing = Image::by_id($event->replace_id);
|
||||
|
||||
if (is_null($existing)) {
|
||||
throw new UploadException("Image to replace does not exist!");
|
||||
@ -320,19 +317,25 @@ abstract class DataHandlerExtension extends Extension
|
||||
if (is_null($image)) {
|
||||
throw new UploadException("Data handler failed to create image object from data");
|
||||
}
|
||||
if (empty($image->ext)) {
|
||||
throw new UploadException("Unable to determine extension for ". $event->tmpname);
|
||||
}
|
||||
try {
|
||||
send_event(new MediaCheckPropertiesEvent($image));
|
||||
} catch (MediaException $e) {
|
||||
throw new UploadException("Unable to scan media properties: ".$e->getMessage());
|
||||
}
|
||||
|
||||
send_event(new ImageReplaceEvent($image_id, $image));
|
||||
$event->image_id = $image_id;
|
||||
send_event(new ImageReplaceEvent($event->replace_id, $image));
|
||||
$event->image_id = $event->replace_id;
|
||||
} else {
|
||||
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata);
|
||||
if (is_null($image)) {
|
||||
throw new UploadException("Data handler failed to create image object from data");
|
||||
}
|
||||
if (empty($image->ext)) {
|
||||
throw new UploadException("Unable to determine extension for ". $event->tmpname);
|
||||
}
|
||||
try {
|
||||
send_event(new MediaCheckPropertiesEvent($image));
|
||||
} catch (MediaException $e) {
|
||||
@ -406,10 +409,12 @@ abstract class DataHandlerExtension extends Extension
|
||||
$image->hash = $metadata['hash'];
|
||||
$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(getMimeType($filename));
|
||||
} else {
|
||||
$image->ext = get_extension_for_file($filename);
|
||||
}
|
||||
if (empty($image->ext)) {
|
||||
$image->ext = (($pos = strpos($metadata['extension'], '?')) !== false) ? substr($metadata['extension'], 0, $pos) : $metadata['extension'];
|
||||
}
|
||||
|
||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
@ -422,15 +427,20 @@ abstract class DataHandlerExtension extends Extension
|
||||
|
||||
protected function supported_ext(string $ext): bool
|
||||
{
|
||||
return in_array(strtolower($ext), $this->SUPPORTED_EXT);
|
||||
return in_array(get_mime_for_extension($ext), $this->SUPPORTED_MIME);
|
||||
}
|
||||
|
||||
public static function get_all_supported_exts(): array
|
||||
{
|
||||
$arr = [];
|
||||
foreach (getSubclassesOf("DataHandlerExtension") as $handler) {
|
||||
$arr = array_merge($arr, (new $handler())->SUPPORTED_EXT);
|
||||
$handler = (new $handler());
|
||||
|
||||
foreach ($handler->SUPPORTED_MIME as $mime) {
|
||||
$arr = array_merge($arr, get_all_extension_for_mime($mime));
|
||||
}
|
||||
}
|
||||
$arr = array_unique($arr);
|
||||
return $arr;
|
||||
}
|
||||
}
|
||||
|
448
core/filetypes.php
Normal file
448
core/filetypes.php
Normal file
@ -0,0 +1,448 @@
|
||||
<?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;
|
||||
}
|
@ -213,6 +213,27 @@ class Image
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts consecutive days of image uploads
|
||||
*/
|
||||
public static function count_upload_streak(): int
|
||||
{
|
||||
$now = date_create();
|
||||
$last_date = $now;
|
||||
foreach (self::find_images_iterable() as $img) {
|
||||
$next_date = date_create($img->posted);
|
||||
if (date_diff($next_date, $last_date)->days > 0) {
|
||||
break;
|
||||
}
|
||||
$last_date = $next_date;
|
||||
}
|
||||
if ($last_date === $now) {
|
||||
return 0;
|
||||
}
|
||||
$diff_d = ($now->getTimestamp() - $last_date->getTimestamp()) / 86400;
|
||||
return (int)ceil($diff_d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of image results for a given search
|
||||
*
|
||||
@ -570,7 +591,7 @@ class Image
|
||||
*/
|
||||
public function get_mime_type(): string
|
||||
{
|
||||
return getMimeType($this->get_image_filename(), $this->get_ext());
|
||||
return get_mime($this->get_image_filename(), $this->get_ext());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,7 +39,7 @@ function add_dir(string $base): array
|
||||
* @param string $tags
|
||||
* @throws UploadException
|
||||
*/
|
||||
function add_image(string $tmpname, string $filename, string $tags): void
|
||||
function add_image(string $tmpname, string $filename, string $tags): int
|
||||
{
|
||||
assert(file_exists($tmpname));
|
||||
|
||||
@ -52,7 +52,11 @@ function add_image(string $tmpname, string $filename, string $tags): void
|
||||
|
||||
$metadata['tags'] = Tag::explode($tags);
|
||||
$metadata['source'] = null;
|
||||
send_event(new DataUploadEvent($tmpname, $metadata));
|
||||
|
||||
$due = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($due);
|
||||
|
||||
return $due->image_id;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,6 +73,12 @@ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_sca
|
||||
{
|
||||
global $config;
|
||||
|
||||
$fit = $config->get_string(ImageConfig::THUMB_FIT);
|
||||
|
||||
if (in_array($fit, [Media::RESIZE_TYPE_FILL, Media::RESIZE_TYPE_STRETCH, Media::RESIZE_TYPE_FIT_BLUR])) {
|
||||
return [$config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_HEIGHT)];
|
||||
}
|
||||
|
||||
if ($orig_width === 0) {
|
||||
$orig_width = 192;
|
||||
}
|
||||
@ -128,21 +138,35 @@ function get_thumbnail_max_size_scaled(): array
|
||||
|
||||
function create_image_thumb(string $hash, string $type, string $engine = null)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$inname = warehouse_path(Image::IMAGE_DIR, $hash);
|
||||
$outname = warehouse_path(Image::THUMBNAIL_DIR, $hash);
|
||||
$tsize = get_thumbnail_max_size_scaled();
|
||||
create_scaled_image($inname, $outname, $tsize, $type, $engine);
|
||||
create_scaled_image(
|
||||
$inname,
|
||||
$outname,
|
||||
$tsize,
|
||||
$type,
|
||||
$engine,
|
||||
$config->get_string(ImageConfig::THUMB_FIT)
|
||||
);
|
||||
}
|
||||
|
||||
function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine)
|
||||
|
||||
|
||||
function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine = null, ?string $resize_type = null)
|
||||
{
|
||||
global $config;
|
||||
if (empty($engine)) {
|
||||
$engine = $config->get_string(ImageConfig::THUMB_ENGINE);
|
||||
}
|
||||
if (empty($resize_type)) {
|
||||
$resize_type = $config->get_string(ImageConfig::THUMB_FIT);
|
||||
}
|
||||
|
||||
$output_format = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||
if ($output_format=="webp") {
|
||||
if ($output_format==EXTENSION_WEBP) {
|
||||
$output_format = Media::WEBP_LOSSY;
|
||||
}
|
||||
|
||||
@ -153,10 +177,10 @@ function create_scaled_image(string $inname, string $outname, array $tsize, stri
|
||||
$outname,
|
||||
$tsize[0],
|
||||
$tsize[1],
|
||||
false,
|
||||
$resize_type,
|
||||
$output_format,
|
||||
$config->get_int(ImageConfig::THUMB_QUALITY),
|
||||
true,
|
||||
$config->get_bool('thumb_upscale', false)
|
||||
true
|
||||
));
|
||||
}
|
||||
|
@ -116,8 +116,6 @@ class Tag
|
||||
|
||||
for ($i = 0; $i < count($tags1); $i++) {
|
||||
if ($tags1[$i]!==$tags2[$i]) {
|
||||
var_dump($tags1);
|
||||
var_dump($tags2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@
|
||||
* @package Shimmie
|
||||
* @copyright Copyright (c) 2007-2015, Shish et al.
|
||||
* @author Shish [webmaster at shishnet.org], jgen [jeffgenovy at gmail.com]
|
||||
* @link http://code.shishnet.org/shimmie2/
|
||||
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
|
||||
* @link https://code.shishnet.org/shimmie2/
|
||||
* @license https://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
|
||||
*
|
||||
* Initialise the database, check that folder
|
||||
* permissions are set properly.
|
||||
@ -20,23 +20,12 @@ function install()
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
if (is_readable("data/config/shimmie.conf.php")) {
|
||||
exit_with_page(
|
||||
die_nicely(
|
||||
"Shimmie is already installed.",
|
||||
"data/config/shimmie.conf.php exists, how did you get here?"
|
||||
);
|
||||
}
|
||||
|
||||
if (!file_exists("vendor/")) {
|
||||
exit_with_page("Install Error", "
|
||||
<p>Shimmie is unable to find the composer vendor directory.</p>
|
||||
<p>Have you followed the composer setup instructions found in the
|
||||
<a href=\"https://github.com/shish/shimmie2#installation-development\">README</a>?</p>
|
||||
<p>If you are not intending to do any development with Shimmie,
|
||||
it is highly recommend you use one of the pre-packaged releases
|
||||
found on <a href=\"https://github.com/shish/shimmie2/releases\">Github</a> instead.</p>
|
||||
");
|
||||
}
|
||||
|
||||
// Pull in necessary files
|
||||
require_once "vendor/autoload.php";
|
||||
global $_tracer;
|
||||
@ -80,7 +69,7 @@ function do_install($dsn)
|
||||
create_tables(new Database($dsn));
|
||||
write_config($dsn);
|
||||
} catch (InstallerException $e) {
|
||||
exit_with_page($e->title, $e->body, $e->code);
|
||||
die_nicely($e->title, $e->body, $e->code);
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,7 +117,7 @@ function ask_questions()
|
||||
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
|
||||
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
|
||||
|
||||
exit_with_page(
|
||||
die_nicely(
|
||||
"Install Options",
|
||||
<<<EOD
|
||||
$warn_msg
|
||||
@ -315,7 +304,7 @@ function write_config($dsn)
|
||||
|
||||
if (file_put_contents("data/config/shimmie.conf.php", $file_content, LOCK_EX)) {
|
||||
header("Location: index.php?flash=Installation%20complete");
|
||||
exit_with_page(
|
||||
die_nicely(
|
||||
"Installation Successful",
|
||||
"<p>If you aren't redirected, <a href=\"index.php\">click here to Continue</a>."
|
||||
);
|
||||
@ -335,25 +324,3 @@ function write_config($dsn)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function exit_with_page($title, $body, $code=0)
|
||||
{
|
||||
print("<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>Shimmie Installer</title>
|
||||
<link rel=\"shortcut icon\" href=\"ext/static_files/static/favicon.ico\">
|
||||
<link rel=\"stylesheet\" href=\"ext/static_files/style.css\" type=\"text/css\">
|
||||
</head>
|
||||
<body>
|
||||
<div id=\"installer\">
|
||||
<h1>Shimmie Installer</h1>
|
||||
<h3>$title</h3>
|
||||
<div class=\"container\">
|
||||
$body
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>");
|
||||
exit($code);
|
||||
}
|
||||
|
@ -10,6 +10,15 @@ define("SCORE_LOG_INFO", 20);
|
||||
define("SCORE_LOG_DEBUG", 10);
|
||||
define("SCORE_LOG_NOTSET", 0);
|
||||
|
||||
const LOGGING_LEVEL_NAMES = [
|
||||
SCORE_LOG_NOTSET=>"Not Set",
|
||||
SCORE_LOG_DEBUG=>"Debug",
|
||||
SCORE_LOG_INFO=>"Info",
|
||||
SCORE_LOG_WARNING=>"Warning",
|
||||
SCORE_LOG_ERROR=>"Error",
|
||||
SCORE_LOG_CRITICAL=>"Critical",
|
||||
];
|
||||
|
||||
/**
|
||||
* A shorthand way to send a LogEvent
|
||||
*
|
||||
|
@ -18,6 +18,7 @@ abstract class Permissions
|
||||
public const BAN_IP = "ban_ip";
|
||||
|
||||
public const CREATE_USER = "create_user";
|
||||
public const CREATE_OTHER_USER = "create_other_user";
|
||||
public const EDIT_USER_NAME = "edit_user_name";
|
||||
public const EDIT_USER_PASSWORD = "edit_user_password";
|
||||
public const EDIT_USER_INFO = "edit_user_info"; # email address, etc
|
||||
@ -95,4 +96,11 @@ abstract class Permissions
|
||||
public const CRON_ADMIN = "cron_admin";
|
||||
public const APPROVE_IMAGE = "approve_image";
|
||||
public const APPROVE_COMMENT = "approve_comment";
|
||||
|
||||
public const SET_PRIVATE_IMAGE = "set_private_image";
|
||||
public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images";
|
||||
|
||||
public const BULK_IMPORT = "bulk_import";
|
||||
public const BULK_EXPORT = "bulk_export";
|
||||
public const BULK_DOWNLOAD = "bulk_download";
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
* Things which should be in the core API *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
require_once "filetypes.php";
|
||||
|
||||
|
||||
/**
|
||||
* Return the unique elements of an array, case insensitively
|
||||
*/
|
||||
@ -27,7 +30,7 @@ function array_iunique(array $array): array
|
||||
/**
|
||||
* Figure out if an IP is in a specified range
|
||||
*
|
||||
* from http://uk.php.net/network
|
||||
* from https://uk.php.net/network
|
||||
*/
|
||||
function ip_in_range(string $IP, string $CIDR): bool
|
||||
{
|
||||
@ -86,7 +89,7 @@ function deltree(string $f): void
|
||||
/**
|
||||
* Copy an entire file hierarchy
|
||||
*
|
||||
* from a comment on http://uk.php.net/copy
|
||||
* from a comment on https://uk.php.net/copy
|
||||
*/
|
||||
function full_copy(string $source, string $target): void
|
||||
{
|
||||
@ -150,6 +153,14 @@ function list_files(string $base, string $_sub_dir=""): array
|
||||
return $file_list;
|
||||
}
|
||||
|
||||
function flush_output(): void
|
||||
{
|
||||
if (!defined("UNITTEST")) {
|
||||
@ob_flush();
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
function stream_file(string $file, int $start, int $end): void
|
||||
{
|
||||
$fp = fopen($file, 'r');
|
||||
@ -162,10 +173,7 @@ function stream_file(string $file, int $start, int $end): void
|
||||
$buffer = $end - $p + 1;
|
||||
}
|
||||
echo fread($fp, $buffer);
|
||||
if (!defined("UNITTEST")) {
|
||||
@ob_flush();
|
||||
}
|
||||
flush();
|
||||
flush_output();
|
||||
|
||||
// After flush, we can tell if the client browser has disconnected.
|
||||
// This means we can start sending a large file, and if we detect they disappeared
|
||||
@ -248,99 +256,6 @@ if (!function_exists('mb_strlen')) {
|
||||
}
|
||||
}
|
||||
|
||||
const MIME_TYPE_MAP = [
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'ico' => 'image/x-icon',
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'flv' => 'video/x-flv',
|
||||
'svg' => 'image/svg+xml',
|
||||
'pdf' => 'application/pdf',
|
||||
'zip' => 'application/zip',
|
||||
'gz' => 'application/x-gzip',
|
||||
'tar' => 'application/x-tar',
|
||||
'bz' => 'application/x-bzip',
|
||||
'bz2' => 'application/x-bzip2',
|
||||
'txt' => 'text/plain',
|
||||
'asc' => 'text/plain',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'css' => 'text/css',
|
||||
'js' => 'text/javascript',
|
||||
'xml' => 'text/xml',
|
||||
'xsl' => 'application/xsl+xml',
|
||||
'ogg' => 'application/ogg',
|
||||
'mp3' => 'audio/mpeg',
|
||||
'wav' => 'audio/x-wav',
|
||||
'avi' => 'video/x-msvideo',
|
||||
'mpg' => 'video/mpeg',
|
||||
'mpeg' => 'video/mpeg',
|
||||
'mov' => 'video/quicktime',
|
||||
'php' => 'text/x-php',
|
||||
'mp4' => 'video/mp4',
|
||||
'ogv' => 'video/ogg',
|
||||
'webm' => 'video/webm',
|
||||
'webp' => 'image/webp',
|
||||
'bmp' =>'image/x-ms-bmp',
|
||||
'psd' => 'image/vnd.adobe.photoshop',
|
||||
'mkv' => 'video/x-matroska'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get MIME type for file
|
||||
*
|
||||
* The contents of this function are taken from the __getMimeType() function
|
||||
* from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Schönknecht
|
||||
* and released under the 'Simplified BSD License'.
|
||||
*/
|
||||
function getMimeType(string $file, string $ext=""): string
|
||||
{
|
||||
// Static extension lookup
|
||||
$ext = strtolower($ext);
|
||||
|
||||
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
||||
return MIME_TYPE_MAP[$ext];
|
||||
}
|
||||
|
||||
$type = false;
|
||||
// Fileinfo documentation says fileinfo_open() will use the
|
||||
// MAGIC env var for the magic file
|
||||
if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
|
||||
($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) {
|
||||
if (($type = finfo_file($finfo, $file)) !== false) {
|
||||
// Remove the charset and grab the last content-type
|
||||
$type = explode(' ', str_replace('; charset=', ';charset=', $type));
|
||||
$type = array_pop($type);
|
||||
$type = explode(';', $type);
|
||||
$type = trim(array_shift($type));
|
||||
}
|
||||
finfo_close($finfo);
|
||||
|
||||
// If anyone is still using mime_content_type()
|
||||
} elseif (function_exists('mime_content_type')) {
|
||||
$type = trim(mime_content_type($file));
|
||||
}
|
||||
|
||||
if ($type !== false && strlen($type) > 0) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
function get_extension(?string $mime_type): ?string
|
||||
{
|
||||
if (empty($mime_type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ext = array_search($mime_type, MIME_TYPE_MAP);
|
||||
return ($ext ? $ext : null);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
function getSubclassesOf(string $parent)
|
||||
{
|
||||
@ -380,7 +295,7 @@ function zglob(string $pattern): array
|
||||
/**
|
||||
* Figure out the path to the shimmie install directory.
|
||||
*
|
||||
* eg if shimmie is visible at http://foo.com/gallery, this
|
||||
* eg if shimmie is visible at https://foo.com/gallery, this
|
||||
* function should return /gallery
|
||||
*
|
||||
* PHP really, really sucks.
|
||||
@ -406,6 +321,23 @@ function get_base_href(): string
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* The opposite of the standard library's parse_url
|
||||
*/
|
||||
function unparse_url($parsed_url)
|
||||
{
|
||||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
|
||||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
|
||||
$pass = ($user || $pass) ? "$pass@" : '';
|
||||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||||
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
|
||||
$fragment = !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||||
return "$scheme$user$pass$host$port$path$query$fragment";
|
||||
}
|
||||
|
||||
function startsWith(string $haystack, string $needle): bool
|
||||
{
|
||||
$length = strlen($needle);
|
||||
@ -478,7 +410,7 @@ function bool_escape($input): bool
|
||||
Sometimes, I don't like PHP -- this, is one of those times...
|
||||
"a boolean FALSE is not considered a valid boolean value by this function."
|
||||
Yay for Got'chas!
|
||||
http://php.net/manual/en/filter.filters.validate.php
|
||||
https://php.net/manual/en/filter.filters.validate.php
|
||||
*/
|
||||
if (is_bool($input)) {
|
||||
return $input;
|
||||
@ -511,6 +443,24 @@ function no_escape(string $input): string
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a 1-indexed numeric-ish thing, return a zero-indexed
|
||||
* number between 0 and $max
|
||||
*/
|
||||
function page_number(string $input, ?int $max=null): int
|
||||
{
|
||||
if (!is_numeric($input)) {
|
||||
$pageNumber = 0;
|
||||
} elseif ($input <= 0) {
|
||||
$pageNumber = 0;
|
||||
} elseif (!is_null($max) && $input >= $max) {
|
||||
$pageNumber = $max - 1;
|
||||
} else {
|
||||
$pageNumber = $input - 1;
|
||||
}
|
||||
return $pageNumber;
|
||||
}
|
||||
|
||||
function clamp(?int $val, ?int $min=null, ?int $max=null): int
|
||||
{
|
||||
if (!is_numeric($val) || (!is_null($min) && $val < $min)) {
|
||||
@ -570,11 +520,14 @@ function truncate(string $string, int $limit, string $break=" ", string $pad="..
|
||||
*/
|
||||
function parse_shorthand_int(string $limit): int
|
||||
{
|
||||
if (preg_match('/^([\d\.]+)([gmk])?b?$/i', (string)$limit, $m)) {
|
||||
if (preg_match('/^([\d\.]+)([tgmk])?b?$/i', (string)$limit, $m)) {
|
||||
$value = $m[1];
|
||||
if (isset($m[2])) {
|
||||
switch (strtolower($m[2])) {
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case 't': $value *= 1024; // fall through
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
case 'g': $value *= 1024; // fall through
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
@ -598,7 +551,9 @@ function to_shorthand_int(int $int): string
|
||||
{
|
||||
assert($int >= 0);
|
||||
|
||||
if ($int >= pow(1024, 3)) {
|
||||
if ($int >= pow(1024, 4)) {
|
||||
return sprintf("%.1fTB", $int / pow(1024, 4));
|
||||
} elseif ($int >= pow(1024, 3)) {
|
||||
return sprintf("%.1fGB", $int / pow(1024, 3));
|
||||
} elseif ($int >= pow(1024, 2)) {
|
||||
return sprintf("%.1fMB", $int / pow(1024, 2));
|
||||
|
63
core/sanitize_php.php
Normal file
63
core/sanitize_php.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* A small number of PHP-sanity things (eg don't silently ignore errors) to
|
||||
* be included right at the very start of index.php and tests/bootstrap.php
|
||||
*/
|
||||
|
||||
$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)
|
||||
{
|
||||
print("<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>Shimmie</title>
|
||||
<link rel=\"shortcut icon\" href=\"ext/static_files/static/favicon.ico\">
|
||||
<link rel=\"stylesheet\" href=\"ext/static_files/style.css\" type=\"text/css\">
|
||||
</head>
|
||||
<body>
|
||||
<div id=\"installer\">
|
||||
<h1>Shimmie</h1>
|
||||
<h3>$title</h3>
|
||||
<div class=\"container\">
|
||||
$body
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>");
|
||||
if ($code != 0) {
|
||||
http_response_code(500);
|
||||
}
|
||||
exit($code);
|
||||
}
|
@ -101,7 +101,7 @@ function send_event(Event $event): Event
|
||||
if ($tracer_enabled) {
|
||||
$_tracer->begin(get_class($event));
|
||||
}
|
||||
// SHIT: http://bugs.php.net/bug.php?id=35106
|
||||
// SHIT: https://bugs.php.net/bug.php?id=35106
|
||||
$my_event_listeners = $_shm_event_listeners[get_class($event)];
|
||||
ksort($my_event_listeners);
|
||||
|
||||
|
@ -18,6 +18,7 @@ function _d(string $name, $value): void
|
||||
define($name, $value);
|
||||
}
|
||||
}
|
||||
$_g = file_exists(".git") ? '+' : '';
|
||||
_d("DATABASE_DSN", null); // string PDO database connection details
|
||||
_d("DATABASE_TIMEOUT", 10000);// int Time to wait for each statement to complete
|
||||
_d("CACHE_DSN", null); // string cache connection details
|
||||
@ -25,7 +26,7 @@ _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("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("VERSION", '2.8.0'); // string shimmie version
|
||||
_d("VERSION", "2.8.4$_g"); // string shimmie version
|
||||
_d("TIMEZONE", null); // string timezone
|
||||
_d("EXTRA_EXTS", ""); // string optional extra extensions
|
||||
_d("BASE_HREF", null); // string force a specific base URL (default is auto-detect)
|
||||
|
18
core/tests/init.test.php
Normal file
18
core/tests/init.test.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class TestInit extends TestCase
|
||||
{
|
||||
public function testInitExt()
|
||||
{
|
||||
send_event(new InitExtEvent());
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testDatabaseUpgrade()
|
||||
{
|
||||
send_event(new DatabaseUpgradeEvent());
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
@ -8,15 +8,35 @@ class UrlsTest extends TestCase
|
||||
{
|
||||
public function test_make_link()
|
||||
{
|
||||
// basic
|
||||
$this->assertEquals(
|
||||
"/test/foo",
|
||||
make_link("foo")
|
||||
);
|
||||
|
||||
// remove leading slash from path
|
||||
$this->assertEquals(
|
||||
"/test/foo",
|
||||
make_link("/foo")
|
||||
);
|
||||
|
||||
// query
|
||||
$this->assertEquals(
|
||||
"/test/foo?a=1&b=2",
|
||||
make_link("foo", "a=1&b=2")
|
||||
);
|
||||
|
||||
// hash
|
||||
$this->assertEquals(
|
||||
"/test/foo#cake",
|
||||
make_link("foo", null, "cake")
|
||||
);
|
||||
|
||||
// query + hash
|
||||
$this->assertEquals(
|
||||
"/test/foo?a=1&b=2#cake",
|
||||
make_link("foo", "a=1&b=2", "cake")
|
||||
);
|
||||
}
|
||||
|
||||
public function test_make_http()
|
||||
@ -35,8 +55,47 @@ class UrlsTest extends TestCase
|
||||
|
||||
// absolute
|
||||
$this->assertEquals(
|
||||
"http://foo.com",
|
||||
make_http("http://foo.com")
|
||||
"https://foo.com",
|
||||
make_http("https://foo.com")
|
||||
);
|
||||
}
|
||||
|
||||
public function test_modify_url()
|
||||
{
|
||||
$this->assertEquals(
|
||||
"/foo/bar?a=3&b=2",
|
||||
modify_url("/foo/bar?a=1&b=2", ["a"=>"3"])
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
"https://blah.com/foo/bar?b=2",
|
||||
modify_url("https://blah.com/foo/bar?a=1&b=2", ["a"=>null])
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
"/foo/bar",
|
||||
modify_url("/foo/bar?a=1&b=2", ["a"=>null, "b"=>null])
|
||||
);
|
||||
}
|
||||
|
||||
public function test_referer_or()
|
||||
{
|
||||
unset($_SERVER['HTTP_REFERER']);
|
||||
$this->assertEquals(
|
||||
"foo",
|
||||
referer_or("foo")
|
||||
);
|
||||
|
||||
$_SERVER['HTTP_REFERER'] = "cake";
|
||||
$this->assertEquals(
|
||||
"cake",
|
||||
referer_or("foo")
|
||||
);
|
||||
|
||||
$_SERVER['HTTP_REFERER'] = "cake";
|
||||
$this->assertEquals(
|
||||
"foo",
|
||||
referer_or("foo", ["cake"])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
<?php declare(strict_types=1);
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* HTML Generation *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
class Link
|
||||
{
|
||||
@ -26,32 +23,27 @@ class Link
|
||||
*
|
||||
* eg make_link("post/list") becomes "/v2/index.php?q=post/list"
|
||||
*/
|
||||
function make_link(?string $page=null, ?string $query=null): string
|
||||
function make_link(?string $page=null, ?string $query=null, ?string $fragment=null): string
|
||||
{
|
||||
global $config;
|
||||
|
||||
if (is_null($page)) {
|
||||
$page = $config->get_string(SetupConfig::MAIN_PAGE);
|
||||
}
|
||||
$page = trim($page, "/");
|
||||
|
||||
$parts = [];
|
||||
$install_dir = get_base_href();
|
||||
if (SPEED_HAX || $config->get_bool('nice_urls', false)) {
|
||||
$base = $install_dir;
|
||||
$parts['path'] = "$install_dir/$page";
|
||||
} else {
|
||||
$base = "$install_dir/index.php?q=";
|
||||
$parts['path'] = "$install_dir/index.php";
|
||||
$query = empty($query) ? "q=$page" : "q=$page&$query";
|
||||
}
|
||||
$parts['query'] = $query; // http_build_query($query);
|
||||
$parts['fragment'] = $fragment; // http_build_query($hash);
|
||||
|
||||
if (is_null($query)) {
|
||||
return str_replace("//", "/", $base.'/'.$page);
|
||||
} else {
|
||||
if (strpos($base, "?")) {
|
||||
return $base .'/'. $page .'&'. $query;
|
||||
} elseif (strpos($query, "#") === 0) {
|
||||
return $base .'/'. $page . $query;
|
||||
} else {
|
||||
return $base .'/'. $page .'?'. $query;
|
||||
}
|
||||
}
|
||||
return unparse_url($parts);
|
||||
}
|
||||
|
||||
|
||||
@ -60,43 +52,28 @@ function make_link(?string $page=null, ?string $query=null): string
|
||||
*/
|
||||
function modify_current_url(array $changes): string
|
||||
{
|
||||
return modify_url($_SERVER['QUERY_STRING'], $changes);
|
||||
return modify_url($_SERVER['REQUEST_URI'], $changes);
|
||||
}
|
||||
|
||||
function modify_url(string $url, array $changes): string
|
||||
{
|
||||
// SHIT: PHP is officially the worst web API ever because it does not
|
||||
// have a built-in function to do this.
|
||||
$parts = parse_url($url);
|
||||
|
||||
// SHIT: parse_str is magically retarded; not only is it a useless name, it also
|
||||
// didn't return the parsed array, preferring to overwrite global variables with
|
||||
// whatever data the user supplied. Thankfully, 4.0.3 added an extra option to
|
||||
// give it an array to use...
|
||||
$params = [];
|
||||
parse_str($url, $params);
|
||||
|
||||
if (isset($changes['q'])) {
|
||||
$base = $changes['q'];
|
||||
unset($changes['q']);
|
||||
} else {
|
||||
$base = _get_query();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $params);
|
||||
}
|
||||
|
||||
if (isset($params['q'])) {
|
||||
unset($params['q']);
|
||||
}
|
||||
|
||||
foreach ($changes as $k => $v) {
|
||||
if (is_null($v) and isset($params[$k])) {
|
||||
unset($params[$k]);
|
||||
}
|
||||
$params[$k] = $v;
|
||||
}
|
||||
$parts['query'] = http_build_query($params);
|
||||
|
||||
return make_link($base, http_build_query($params));
|
||||
return unparse_url($parts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Turn a relative link into an absolute one, including hostname
|
||||
*/
|
||||
@ -116,3 +93,22 @@ function make_http(string $link): string
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* If HTTP_REFERER is set, and not blacklisted, then return it
|
||||
* Else return a default $dest
|
||||
*/
|
||||
function referer_or(string $dest, ?array $blacklist=null): string
|
||||
{
|
||||
if (empty($_SERVER['HTTP_REFERER'])) {
|
||||
return $dest;
|
||||
}
|
||||
if ($blacklist) {
|
||||
foreach ($blacklist as $b) {
|
||||
if (strstr($_SERVER['HTTP_REFERER'], $b)) {
|
||||
return $dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $_SERVER['HTTP_REFERER'];
|
||||
}
|
||||
|
@ -247,6 +247,9 @@ class User
|
||||
|
||||
public function check_auth_token(): bool
|
||||
{
|
||||
if (defined("UNITTEST")) {
|
||||
return true;
|
||||
}
|
||||
return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token());
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,8 @@ new UserClass("user", "base", [
|
||||
Permissions::EDIT_FAVOURITES => true,
|
||||
Permissions::SEND_PM => true,
|
||||
Permissions::READ_PM => true,
|
||||
Permissions::SET_PRIVATE_IMAGE => true,
|
||||
Permissions::BULK_DOWNLOAD => true,
|
||||
]);
|
||||
|
||||
new UserClass("hellbanned", "user", [
|
||||
@ -118,6 +120,7 @@ new UserClass("admin", "base", [
|
||||
Permissions::BAN_IP => true,
|
||||
|
||||
Permissions::CREATE_USER => true,
|
||||
Permissions::CREATE_OTHER_USER => true,
|
||||
Permissions::EDIT_USER_NAME => true,
|
||||
Permissions::EDIT_USER_PASSWORD => true,
|
||||
Permissions::EDIT_USER_INFO => true,
|
||||
@ -196,6 +199,13 @@ new UserClass("admin", "base", [
|
||||
|
||||
Permissions::APPROVE_IMAGE => true,
|
||||
Permissions::APPROVE_COMMENT => true,
|
||||
|
||||
Permissions::BULK_IMPORT =>true,
|
||||
Permissions::BULK_EXPORT =>true,
|
||||
Permissions::BULK_DOWNLOAD => true,
|
||||
|
||||
Permissions::SET_PRIVATE_IMAGE => true,
|
||||
Permissions::SET_OTHERS_PRIVATE_IMAGES => true,
|
||||
]);
|
||||
|
||||
@include_once "data/config/user-classes.conf.php";
|
||||
|
110
core/util.php
110
core/util.php
@ -104,7 +104,7 @@ function get_memory_limit(): int
|
||||
Get PHP's configured memory limit.
|
||||
Note that this is set to -1 for NO memory limit.
|
||||
|
||||
http://ca2.php.net/manual/en/ini.core.php#ini.memory-limit
|
||||
https://ca2.php.net/manual/en/ini.core.php#ini.memory-limit
|
||||
*/
|
||||
$memory = parse_shorthand_int(ini_get("memory_limit"));
|
||||
|
||||
@ -393,6 +393,67 @@ function get_dir_contents(string $dir): array
|
||||
);
|
||||
}
|
||||
|
||||
function remove_empty_dirs(string $dir): bool
|
||||
{
|
||||
assert(!empty($dir));
|
||||
|
||||
$result = true;
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$items = array_diff(
|
||||
scandir(
|
||||
$dir
|
||||
),
|
||||
['..', '.']
|
||||
);
|
||||
foreach ($items as $item) {
|
||||
$path = join_path($dir, $item);
|
||||
if (is_dir($path)) {
|
||||
$result = $result && remove_empty_dirs($path);
|
||||
} else {
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
if ($result===true) {
|
||||
$result = $result && rmdir($dir);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
function get_files_recursively(string $dir): array
|
||||
{
|
||||
assert(!empty($dir));
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$things = array_diff(
|
||||
scandir(
|
||||
$dir
|
||||
),
|
||||
['..', '.']
|
||||
);
|
||||
|
||||
$output = [];
|
||||
|
||||
|
||||
foreach ($things as $thing) {
|
||||
$path = join_path($dir, $thing);
|
||||
if (is_file($path)) {
|
||||
$output[] = $path;
|
||||
} else {
|
||||
$output = array_merge($output, get_files_recursively($path));
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns amount of files & total size of dir.
|
||||
*/
|
||||
@ -493,31 +554,18 @@ function _load_theme_files()
|
||||
require_all(_get_themelet_files(get_theme()));
|
||||
}
|
||||
|
||||
function _sanitise_environment(): void
|
||||
function _set_up_shimmie_environment(): void
|
||||
{
|
||||
global $tracer_enabled;
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
if (file_exists("images") && !file_exists("data/images")) {
|
||||
die("As of Shimmie 2.7 images and thumbs should be moved to data/images and data/thumbs");
|
||||
die_nicely("Upgrade error", "As of Shimmie 2.7 images and thumbs should be moved to data/images and data/thumbs");
|
||||
}
|
||||
|
||||
if (TIMEZONE) {
|
||||
date_default_timezone_set(TIMEZONE);
|
||||
}
|
||||
|
||||
# ini_set('zend.assertions', '1'); // generate assertions
|
||||
ini_set('assert.exception', '1'); // throw exceptions when failed
|
||||
if (DEBUG) {
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
@ -526,16 +574,6 @@ date and you should plan on moving elsewhere.
|
||||
// so to prevent running out of memory during complex operations code that uses it should
|
||||
// check if tracer output is enabled before making use of it.
|
||||
$tracer_enabled = constant('TRACE_FILE')!==null;
|
||||
|
||||
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>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -598,6 +636,7 @@ function _fatal_error(Exception $e): void
|
||||
<p><b>Message:</b> '.html_escape($message).'
|
||||
'.$q.'
|
||||
<p><b>Version:</b> '.$version.' (on '.$phpver.')
|
||||
<p><b>Stack Trace:</b></p><pre>'.$e->getTraceAsString().'</pre>
|
||||
</body>
|
||||
</html>
|
||||
';
|
||||
@ -643,7 +682,7 @@ function show_ip(string $ip, string $ban_reason): string
|
||||
global $user;
|
||||
$u_reason = url_escape($ban_reason);
|
||||
$u_end = url_escape("+1 week");
|
||||
$ban = $user->can(Permissions::BAN_IP) ? ", <a href='".make_link("ip_ban/list", "c_ip=$ip&c_reason=$u_reason&c_expires=$u_end#create")."'>Ban</a>" : "";
|
||||
$ban = $user->can(Permissions::BAN_IP) ? ", <a href='".make_link("ip_ban/list", "c_ip=$ip&c_reason=$u_reason&c_expires=$u_end", "create")."'>Ban</a>" : "";
|
||||
$ip = $user->can(Permissions::VIEW_IP) ? $ip.$ban : "";
|
||||
return $ip;
|
||||
}
|
||||
@ -677,7 +716,7 @@ function SHM_FORM(string $target, string $method="POST", bool $multipart=false,
|
||||
global $user;
|
||||
|
||||
$attrs = [
|
||||
"action"=>$target,
|
||||
"action"=>make_link($target),
|
||||
"method"=>$method
|
||||
];
|
||||
|
||||
@ -743,3 +782,18 @@ function human_filesize(int $bytes, $decimals = 2)
|
||||
$factor = floor((strlen(strval($bytes)) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @BYTE_DENOMINATIONS[$factor];
|
||||
}
|
||||
|
||||
/*
|
||||
* Generates a unique key for the website to prevent unauthorized access.
|
||||
*/
|
||||
function generate_key(int $length = 20)
|
||||
{
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$randomString = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters [rand(0, strlen($characters) - 1)];
|
||||
}
|
||||
|
||||
return $randomString;
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
# Custom Configuration
|
||||
|
||||
Various aspects of Shimmie can be configured to suit your site specific needs
|
||||
via the file `data/config/shimmie.conf.php` (created after installation).
|
||||
|
||||
Take a look at `core/sys_config.php` for the available options that can
|
||||
be used.
|
||||
|
||||
|
||||
# Custom User Classes
|
||||
|
||||
User classes can be added to or altered by placing them in
|
||||
`data/config/user-classes.conf.php`.
|
||||
|
||||
For example, one can override the default anonymous "allow nothing"
|
||||
permissions like so:
|
||||
|
||||
```php
|
||||
new UserClass("anonymous", "base", [
|
||||
Permissions::CREATE_COMMENT => True,
|
||||
Permissions::EDIT_IMAGE_TAG => True,
|
||||
Permissions::EDIT_IMAGE_SOURCE => True,
|
||||
Permissions::CREATE_IMAGE_REPORT => True,
|
||||
]);
|
||||
```
|
||||
|
||||
For a moderator class, being a regular user who can delete images and comments:
|
||||
|
||||
```php
|
||||
new UserClass("moderator", "user", [
|
||||
Permissions::DELETE_IMAGE => True,
|
||||
Permissions::DELETE_COMMENT => True,
|
||||
]);
|
||||
```
|
||||
|
||||
For a list of permissions, see `core/permissions.php`
|
139
docs/DEV.md
139
docs/DEV.md
@ -1,139 +0,0 @@
|
||||
# Development Info
|
||||
|
||||
## Themes
|
||||
|
||||
Theme customisation is done by creating files in `themes/<theme name>`.
|
||||
|
||||
The general idea with Shimmie theming is that each `Extension` will add a
|
||||
set of `Block`s to the `Page`, then the `Page` is in charge of deciding
|
||||
how they should be laid out, what they should look like, etc.
|
||||
|
||||
The overall layout is controlled by `page.class.php`, where the `render()`
|
||||
function will take a look at all of the separate `Block`s and turn them
|
||||
into the final rendered HTML.
|
||||
|
||||
Individual `Extension`s will render their content by calling functions
|
||||
in `ext/<extension name>/theme.php` - for example the code in
|
||||
`ext/comment/main.php` will display a list of comments by calling
|
||||
`display_comment_list()` from `ext/comment/theme.php`.
|
||||
|
||||
If a theme wants to customise how the comment list is rendered, it would
|
||||
do so by creating an override file in `themes/<theme name>/comment.theme.php`
|
||||
with contents like:
|
||||
|
||||
```
|
||||
class CustomCommentTheme extends CommentTheme {
|
||||
public function display_comment_list(
|
||||
array $images,
|
||||
int $page_number,
|
||||
int $total_pages,
|
||||
bool $can_post
|
||||
) {
|
||||
[... render the comment list however you like here ...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Events and Extensions
|
||||
|
||||
An event is a little blob of data saying "something happened", possibly
|
||||
"something happened, here's the specific data". Events are sent with the
|
||||
`send_event()` function. Since events can store data, they can be used to
|
||||
return data to the extension which sent them, for example:
|
||||
|
||||
```
|
||||
$tfe = send_event(new TextFormattingEvent($original_text));
|
||||
$formatted_text = $tfe->formatted;
|
||||
```
|
||||
|
||||
An extension is something which is capable of reacting to events.
|
||||
|
||||
|
||||
### Useful Variables
|
||||
|
||||
There are a few global variables which are pretty essential to most extensions:
|
||||
|
||||
* $config -- some variety of Config subclass
|
||||
* $database -- a Database object used to get raw SQL access
|
||||
* $page -- a Page to holds all the loose bits of extension output
|
||||
* $user -- the currently logged in User
|
||||
* $cache -- an optional cache for fast key / value lookups (eg Memcache)
|
||||
|
||||
Each of these can be imported at the start of a function with eg "global $page, $user;"
|
||||
|
||||
|
||||
### The Hello World Extension
|
||||
|
||||
Here's a simple extension which listens for `PageRequestEvent`s, and each time
|
||||
it sees one, it sends out a `HelloEvent`.
|
||||
|
||||
```
|
||||
// ext/hello/main.php
|
||||
public class HelloEvent extends Event {
|
||||
public function __construct($username) {
|
||||
$this->username = $username;
|
||||
}
|
||||
}
|
||||
|
||||
public class Hello extends Extension {
|
||||
public function onPageRequest(PageRequestEvent $event) { // Every time a page request is sent
|
||||
global $user; // Look at the global "currently logged in user" object
|
||||
send_event(new HelloEvent($user->name)); // Broadcast a signal saying hello to that user
|
||||
}
|
||||
public function onHello(HelloEvent $event) { // When the "Hello" signal is recieved
|
||||
$this->theme->display_hello($event->username); // Display a message on the web page
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
// ext/hello/theme.php
|
||||
public class HelloTheme extends Themelet {
|
||||
public function display_hello($username) {
|
||||
global $page;
|
||||
$h_user = html_escape($username); // Escape the data before adding it to the page
|
||||
$block = new Block("Hello!", "Hello there $h_user"); // HTML-safe variables start with "h_"
|
||||
$page->add_block($block); // Add the block to the page
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
// themes/mytheme/hello.theme.php
|
||||
public class CustomHelloTheme extends HelloTheme { // CustomHelloTheme overrides HelloTheme
|
||||
public function display_hello($username) { // the display_hello() function is customised
|
||||
global $page;
|
||||
$h_user = html_escape($username);
|
||||
$page->add_block(new Block(
|
||||
"Hello!",
|
||||
"Hello there $h_user, look at my snazzy custom theme!"
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Cookies
|
||||
|
||||
ui-\* cookies are for the client-side scripts only; in some configurations
|
||||
(eg with varnish cache) they will be stripped before they reach the server
|
||||
|
||||
shm-\* CSS classes are for javascript to hook into; if you're customising
|
||||
themes, be careful with these, and avoid styling them, eg:
|
||||
|
||||
- shm-thumb = outermost element of a thumbnail
|
||||
* data-tags
|
||||
* data-post-id
|
||||
- shm-toggler = click this to toggle elements that match the selector
|
||||
* data-toggle-sel
|
||||
- shm-unlocker = click this to unlock elements that match the selector
|
||||
* data-unlock-sel
|
||||
- shm-clink = a link to a comment, flash the target element when clicked
|
||||
* data-clink-sel
|
||||
|
||||
|
||||
## Fin
|
||||
|
||||
Please tell me if those docs are lacking in any way, so that they can be
|
||||
improved for the next person who uses them
|
@ -1,20 +0,0 @@
|
||||
# Docker
|
||||
|
||||
If you just want to run shimmie inside docker, there's a pre-built image
|
||||
in dockerhub - `shish2k/shimmie2` - which can be used like:
|
||||
```
|
||||
docker run -p 8000 -v /my/hard/drive:/app/data shish2k/shimmie2
|
||||
```
|
||||
|
||||
There are various options settable with environment variables:
|
||||
- `UID` / `GID` - which user ID to run as (default 1000/1000)
|
||||
- `INSTALL_DSN` - specify a data source to install into, to skip the installer screen, eg
|
||||
`-e INSTALL_DSN="pgsql:user=shimmie;password=6y5erdfg;host=127.0.0.1;dbname=shimmie"`
|
||||
|
||||
|
||||
## Build custom image
|
||||
|
||||
If you want to build your own image from source:
|
||||
```
|
||||
docker build -t shimmie2 .
|
||||
```
|
@ -1,27 +0,0 @@
|
||||
# Requirements
|
||||
|
||||
- These are generally based on "whatever is in Debian Stable", because that's
|
||||
conservative without being TOO painfully out of date, and is a nice target
|
||||
for the unit test Docker build.
|
||||
- A database: PostgreSQL 11+ / MariaDB 10.3+ / SQLite 3.27+
|
||||
- [Stable PHP](https://en.wikipedia.org/wiki/PHP#Release_history) (7.3+ as of writing)
|
||||
- GD or ImageMagick
|
||||
|
||||
# Get the Code
|
||||
|
||||
Two main options:
|
||||
|
||||
1. Via Git (allows easiest updates via `git pull`):
|
||||
* `git clone https://github.com/shish/shimmie2`
|
||||
* Install [Composer](https://getcomposer.org/). (If you don't already have it)
|
||||
* Run `composer install` in the shimmie folder.
|
||||
2. Via Stable Release:
|
||||
* Download the latest release under [Releases](https://github.com/shish/shimmie2/releases).
|
||||
|
||||
# Install
|
||||
|
||||
1. Create a blank database
|
||||
2. Visit the install folder with a web browser
|
||||
3. Enter the location of the database
|
||||
4. Click "install". Hopefully you'll end up at the welcome screen; if
|
||||
not, you should be given instructions on how to fix any errors~
|
@ -1,65 +0,0 @@
|
||||
Notes for any sites which require extra performance
|
||||
===================================================
|
||||
|
||||
Image Serving
|
||||
-------------
|
||||
Firstly, make sure your webserver is configured properly and nice URLs are
|
||||
enabled, so that images will be served straight from disk by the webserver
|
||||
instead of via PHP. If you're serving images via PHP, then your site might
|
||||
melt under the load of 5 concurrent users...
|
||||
|
||||
Add a Cache
|
||||
-----------
|
||||
eg installing memcached, then setting
|
||||
`define("CACHE_DSN", "memcache://127.0.0.1:11211")` - a bunch of stuff will
|
||||
get served from the high-speed cache instead of the SQL database.
|
||||
|
||||
`SPEED_HAX`
|
||||
-----------
|
||||
Setting this to true will make a bunch of changes which reduce the correctness
|
||||
of the software and increase admin workload for the sake of speed. You almost
|
||||
certainly don't want to set this, but if you do (eg you're trying to run a
|
||||
site with 10,000 concurrent users on a single server), it can be a huge help.
|
||||
|
||||
Notable behaviour changes:
|
||||
|
||||
- Database schema upgrades are no longer automatic; you'll need to run
|
||||
`php index.php db-upgrade` from the CLI each time you update the code.
|
||||
- Mapping from Events to Extensions is cached - you'll need to delete
|
||||
`data/cache/shm_event_listeners.php` after each code change, and after
|
||||
enabling or disabling any extensions.
|
||||
- Tag lists (eg alphabetic, popularity, map) are cached and you'll need
|
||||
to delete them manually when you feel like it
|
||||
- Anonymous users can only search for 3 tags at once
|
||||
- We only show the first 500 pages of results for any query, except for
|
||||
the most simple (no tags, or one positive tag)
|
||||
- We only ever show the first 5,000 results for complex queries
|
||||
- Only comments from the past 24 hours show up in /comment/list
|
||||
- Web crawlers are blocked from creating too many nonsense searches
|
||||
- The first 10 pages in the index get extra caching
|
||||
- RSS is limited to 10 pages
|
||||
- HTML for thumbnails is cached
|
||||
|
||||
`WH_SPLITS`
|
||||
-----------
|
||||
Store files as `images/ab/cd/...` instead of `images/ab/...`, which can
|
||||
reduce filesystem load when you have millions of images.
|
||||
|
||||
Multiple Image Servers
|
||||
----------------------
|
||||
Image links don't have to be `/images/$hash.$ext` on the local server, they
|
||||
can be full URLs, and include weighted random parts, eg:
|
||||
|
||||
`https://{fred=3,leo=1}.mysite.com/images/$hash.$ext` - the software will then
|
||||
use consistent hashing to map 75% of the files to `fred.mysite.com` and 25% to
|
||||
`leo.mysite.com` - then you can install Varnish or Squid or something as a
|
||||
caching reverse-proxy.
|
||||
|
||||
Profiling
|
||||
---------
|
||||
`define()`'ing `TRACE_FILE` to a filename and `TRACE_THRESHOLD` to a number
|
||||
of seconds will result in JSON event traces being dumped into that file
|
||||
whenever a page takes longer than the threshold to load. These traces can
|
||||
then be loaded into the chrome trace viewer (chrome://tracing/) and you'll
|
||||
get a breakdown of page performance by extension, event, database, and cache
|
||||
queries.
|
@ -1,9 +0,0 @@
|
||||
# Upgrade from earlier versions
|
||||
|
||||
I very much recommend going via each major release in turn (eg, 2.0.6
|
||||
-> 2.1.3 -> 2.2.4 -> 2.3.0 rather than 2.0.6 -> 2.3.0).
|
||||
|
||||
While the basic database and file formats haven't changed *completely*, it's
|
||||
different enough to be a pain.
|
||||
|
||||
|
@ -96,7 +96,7 @@ class AliasEditor extends Extension
|
||||
$this->theme->display_aliases($t->table($t->query()), $t->paginator());
|
||||
} elseif ($event->get_arg(0) == "export") {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/csv");
|
||||
$page->set_type(MIME_TYPE_CSV);
|
||||
$page->set_filename("aliases.csv");
|
||||
$page->set_data($this->get_alias_csv($database));
|
||||
} elseif ($event->get_arg(0) == "import") {
|
||||
|
@ -52,6 +52,10 @@ class DeleteAutoTagEvent extends Event
|
||||
}
|
||||
}
|
||||
|
||||
class AutoTaggerException extends SCoreException
|
||||
{
|
||||
}
|
||||
|
||||
class AddAutoTagException extends SCoreException
|
||||
{
|
||||
}
|
||||
@ -98,7 +102,7 @@ class AutoTagger extends Extension
|
||||
$this->theme->display_auto_tagtable($t->table($t->query()), $t->paginator());
|
||||
} elseif ($event->get_arg(0) == "export") {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/csv");
|
||||
$page->set_type(MIME_TYPE_CSV);
|
||||
$page->set_filename("auto_tag.csv");
|
||||
$page->set_data($this->get_auto_tag_csv($database));
|
||||
} elseif ($event->get_arg(0) == "import") {
|
||||
|
@ -20,7 +20,7 @@ class AutoComplete extends Extension
|
||||
}
|
||||
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("application/json");
|
||||
$page->set_type(MIME_TYPE_JSON);
|
||||
|
||||
$s = strtolower($_GET["s"]);
|
||||
if (
|
||||
|
@ -15,7 +15,7 @@ class BBCodeInfo extends ExtensionInfo
|
||||
" Supported tags:
|
||||
<ul>
|
||||
<li>[img]url[/img]
|
||||
<li>[url]<a href=\"{self::SHIMMIE_URL}\">http://code.shishnet.org/</a>[/url]
|
||||
<li>[url]<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>[/url]
|
||||
<li>[email]<a href=\"mailto:{self::SHISH_EMAIL}\">webmaster@shishnet.org</a>[/email]
|
||||
<li>[b]<b>bold</b>[/b]
|
||||
<li>[i]<i>italic</i>[/i]
|
||||
|
@ -67,12 +67,12 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
|
||||
public function testURL()
|
||||
{
|
||||
$this->assertEquals(
|
||||
$this->filter("[url]http://shishnet.org[/url]"),
|
||||
"<a href=\"http://shishnet.org\">http://shishnet.org</a>"
|
||||
$this->filter("[url]https://shishnet.org[/url]"),
|
||||
"<a href=\"https://shishnet.org\">https://shishnet.org</a>"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$this->filter("[url=http://shishnet.org]ShishNet[/url]"),
|
||||
"<a href=\"http://shishnet.org\">ShishNet</a>"
|
||||
$this->filter("[url=https://shishnet.org]ShishNet[/url]"),
|
||||
"<a href=\"https://shishnet.org\">ShishNet</a>"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$this->filter("[url=javascript:alert(\"owned\")]click to fail[/url]"),
|
||||
@ -107,4 +107,32 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
|
||||
$bb = new BBCode();
|
||||
return $bb->strip($in);
|
||||
}
|
||||
|
||||
public function testSiteLinks()
|
||||
{
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="" href="/test/post/view/123">>>123</a>',
|
||||
$this->filter(">>123")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="#c456" href="/test/post/view/123#c456">>>123#c456</a>',
|
||||
$this->filter(">>123#c456")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="" href="/test/foo/bar">foo/bar</a>',
|
||||
$this->filter("[url]site://foo/bar[/url]")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="#c123" href="/test/foo/bar#c123">foo/bar#c123</a>',
|
||||
$this->filter("[url]site://foo/bar#c123[/url]")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="" href="/test/foo/bar">look at my post</a>',
|
||||
$this->filter("[url=site://foo/bar]look at my post[/url]")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="#c123" href="/test/foo/bar#c123">look at my comment</a>',
|
||||
$this->filter("[url=site://foo/bar#c123]look at my comment[/url]")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -12,5 +12,5 @@ class BlotterInfo extends ExtensionInfo
|
||||
public $description = "Displays brief updates about whatever you want on every page.
|
||||
Colors and positioning can be configured to match your site's design.
|
||||
|
||||
Development TODO at http://github.com/zshall/shimmie2/issues";
|
||||
Development TODO at https://github.com/zshall/shimmie2/issues";
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class BrowserSearch extends Extension
|
||||
|
||||
// And now to send it to the browser
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/xml");
|
||||
$page->set_type(MIME_TYPE_XML);
|
||||
$page->set_data($xml);
|
||||
} elseif ($event->page_matches("browser_search")) {
|
||||
$suggestions = $config->get_string("search_suggestions_results_order");
|
||||
|
@ -1,5 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
class BulkActionException extends SCoreException
|
||||
{
|
||||
}
|
||||
class BulkActionBlockBuildingEvent extends Event
|
||||
{
|
||||
/** @var array */
|
||||
@ -39,6 +42,8 @@ class BulkActionEvent extends Event
|
||||
public $action;
|
||||
/** @var array */
|
||||
public $items;
|
||||
/** @var bool */
|
||||
public $redirect = true;
|
||||
|
||||
public function __construct(String $action, Generator $items)
|
||||
{
|
||||
@ -164,28 +169,38 @@ class BulkActions extends Extension
|
||||
|
||||
$action = $_POST['bulk_action'];
|
||||
|
||||
$items = null;
|
||||
if (isset($_POST['bulk_selected_ids']) && $_POST['bulk_selected_ids'] != "") {
|
||||
$data = json_decode($_POST['bulk_selected_ids']);
|
||||
if (is_array($data)&&!empty($data)) {
|
||||
$items = $this->yield_items($data);
|
||||
try {
|
||||
$items = null;
|
||||
if (isset($_POST['bulk_selected_ids']) && !empty($_POST['bulk_selected_ids'])) {
|
||||
$data = json_decode($_POST['bulk_selected_ids']);
|
||||
if (empty($data)) {
|
||||
throw new BulkActionException("No ids specified in bulk_selected_ids");
|
||||
}
|
||||
if (is_array($data) && !empty($data)) {
|
||||
$items = $this->yield_items($data);
|
||||
}
|
||||
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||
$query = $_POST['bulk_query'];
|
||||
if ($query != null && $query != "") {
|
||||
$items = $this->yield_search_results($query);
|
||||
}
|
||||
} else {
|
||||
throw new BulkActionException("No ids selected and no query present, cannot perform bulk operation on entire collection");
|
||||
}
|
||||
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||
$query = $_POST['bulk_query'];
|
||||
if ($query != null && $query != "") {
|
||||
$items = $this->yield_search_results($query);
|
||||
|
||||
$bae = new BulkActionEvent($action, $items);
|
||||
|
||||
if (is_iterable($items)) {
|
||||
send_event($bae);
|
||||
}
|
||||
} catch (BulkActionException $e) {
|
||||
log_error(BulkActionsInfo::KEY, $e->getMessage(), $e->getMessage());
|
||||
}
|
||||
|
||||
if (is_iterable($items)) {
|
||||
send_event(new BulkActionEvent($action, $items));
|
||||
if ($bae->redirect) {
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
}
|
||||
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
$_SERVER['HTTP_REFERER'] = make_link();
|
||||
}
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ function deactivate_bulk_selector() {
|
||||
set_selected_items([]);
|
||||
$('#bulk_selector_controls').hide();
|
||||
$('#bulk_selector_activate').show();
|
||||
$('input[name="bulk_selected_ids"]').val("");
|
||||
bulk_selector_active = false;
|
||||
}
|
||||
|
||||
@ -94,7 +95,6 @@ function deselect_item(id) {
|
||||
|
||||
function toggle_selection( id ) {
|
||||
var data = get_selected_items();
|
||||
console.log(id);
|
||||
if(data.includes(id)) {
|
||||
data.splice(data.indexOf(id),1);
|
||||
set_selected_items(data);
|
||||
|
14
ext/bulk_download/info.php
Normal file
14
ext/bulk_download/info.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
|
||||
class BulkDownloadInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "bulk_download";
|
||||
|
||||
public $key = self::KEY;
|
||||
public $name = "Bulk Download";
|
||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||
public $license = self::LICENSE_WTFPL;
|
||||
public $description = "Allows bulk downloading images.";
|
||||
public $dependencies = [BulkActionsInfo::KEY];
|
||||
}
|
78
ext/bulk_download/main.php
Normal file
78
ext/bulk_download/main.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
class BulkDownloadConfig
|
||||
{
|
||||
public const SIZE_LIMIT = "bulk_download_size_limit";
|
||||
}
|
||||
class BulkDownloadException extends BulkActionException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
class BulkDownload extends Extension
|
||||
{
|
||||
private const DOWNLOAD_ACTION_NAME = "bulk_download";
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
global $config;
|
||||
$config->set_default_int(BulkDownloadConfig::SIZE_LIMIT, parse_shorthand_int('100MB'));
|
||||
}
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
|
||||
if ($user->can(Permissions::BULK_DOWNLOAD)) {
|
||||
$event->add_action(BulkDownload::DOWNLOAD_ACTION_NAME, "Download ZIP");
|
||||
}
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$sb = new SetupBlock("Bulk Download");
|
||||
|
||||
$sb->start_table();
|
||||
$sb->add_shorthand_int_option(BulkDownloadConfig::SIZE_LIMIT, "Size Limit", true);
|
||||
$sb->end_table();
|
||||
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $user, $page, $config;
|
||||
|
||||
if ($user->can(Permissions::BULK_DOWNLOAD)&&
|
||||
($event->action == BulkDownload::DOWNLOAD_ACTION_NAME)) {
|
||||
$download_filename = $user->name . '-' . date('YmdHis') . '.zip';
|
||||
$zip_filename = tempnam(sys_get_temp_dir(), "shimmie_bulk_download");
|
||||
$zip = new ZipArchive;
|
||||
$size_total = 0;
|
||||
$max_size = $config->get_int(BulkDownloadConfig::SIZE_LIMIT);
|
||||
|
||||
if ($zip->open($zip_filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
|
||||
foreach ($event->items as $image) {
|
||||
$img_loc = warehouse_path(Image::IMAGE_DIR, $image->hash, false);
|
||||
$size_total += filesize($img_loc);
|
||||
if ($size_total>$max_size) {
|
||||
throw new BulkDownloadException("Bulk download limited to ".human_filesize($max_size));
|
||||
}
|
||||
|
||||
|
||||
$filename = urldecode($image->get_nice_image_name());
|
||||
$filename = str_replace(":", ";", $filename);
|
||||
$zip->addFile($img_loc, $filename);
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
$page->set_mode(PageMode::FILE);
|
||||
$page->set_file($zip_filename, true);
|
||||
$page->set_filename($download_filename);
|
||||
|
||||
$event->redirect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
ext/bulk_import_export/events.php
Normal file
25
ext/bulk_import_export/events.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
class BulkExportEvent extends Event
|
||||
{
|
||||
public $image;
|
||||
public $fields = [];
|
||||
|
||||
public function __construct(Image $image)
|
||||
{
|
||||
$this->image = $image;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BulkImportEvent extends Event
|
||||
{
|
||||
public $image;
|
||||
public $fields = [];
|
||||
|
||||
public function __construct(Image $image, $fields)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->fields = $fields;
|
||||
}
|
||||
}
|
15
ext/bulk_import_export/info.php
Normal file
15
ext/bulk_import_export/info.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
include_once "events.php";
|
||||
|
||||
class BulkImportExportInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "bulk_import_export";
|
||||
|
||||
public $key = self::KEY;
|
||||
public $name = "Bulk Import/Export";
|
||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||
public $license = self::LICENSE_WTFPL;
|
||||
public $description = "Allows bulk exporting/importing of images and associated data.";
|
||||
public $dependencies = [BulkActionsInfo::KEY];
|
||||
}
|
172
ext/bulk_import_export/main.php
Normal file
172
ext/bulk_import_export/main.php
Normal file
@ -0,0 +1,172 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
|
||||
class BulkImportExport extends DataHandlerExtension
|
||||
{
|
||||
const EXPORT_ACTION_NAME = "bulk_export";
|
||||
const EXPORT_INFO_FILE_NAME = "export.json";
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_ZIP];
|
||||
|
||||
|
||||
public function onDataUpload(DataUploadEvent $event)
|
||||
{
|
||||
global $user, $database;
|
||||
|
||||
if ($this->supported_ext($event->type) &&
|
||||
$user->can(Permissions::BULK_IMPORT)) {
|
||||
$zip = new ZipArchive;
|
||||
|
||||
if ($zip->open($event->tmpname) === true) {
|
||||
$info = $zip->getStream(self::EXPORT_INFO_FILE_NAME);
|
||||
$json_data = [];
|
||||
if ($info !== false) {
|
||||
try {
|
||||
$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;
|
||||
$skipped = 0;
|
||||
$failed = 0;
|
||||
|
||||
$database->commit();
|
||||
|
||||
while (!empty($json_data)) {
|
||||
$item = array_pop($json_data);
|
||||
$database->begin_transaction();
|
||||
try {
|
||||
$image = Image::by_hash($item->hash);
|
||||
if ($image!=null) {
|
||||
$skipped++;
|
||||
log_info(BulkImportExportInfo::KEY, "Image $item->hash already present, skipping");
|
||||
$database->commit();
|
||||
continue;
|
||||
}
|
||||
|
||||
$tmpfile = tempnam(sys_get_temp_dir(), "shimmie_bulk_import");
|
||||
$stream = $zip->getStream($item->hash);
|
||||
if ($zip === false) {
|
||||
throw new SCoreException("Could not import " . $item->hash . ": File not in zip");
|
||||
}
|
||||
|
||||
file_put_contents($tmpfile, $stream);
|
||||
|
||||
$id = add_image($tmpfile, $item->filename, Tag::implode($item->tags));
|
||||
|
||||
if ($id==-1) {
|
||||
throw new SCoreException("Unable to import file $item->hash");
|
||||
}
|
||||
|
||||
$image = Image::by_id($id);
|
||||
|
||||
if ($image==null) {
|
||||
throw new SCoreException("Unable to import file $item->hash");
|
||||
}
|
||||
|
||||
if ($item->source!=null) {
|
||||
$image->set_source($item->source);
|
||||
}
|
||||
send_event(new BulkImportEvent($image, $item));
|
||||
|
||||
$database->commit();
|
||||
$total++;
|
||||
} catch (Exception $ex) {
|
||||
$failed++;
|
||||
try {
|
||||
$database->rollBack();
|
||||
} catch (Exception $ex2) {
|
||||
log_error(BulkImportExportInfo::KEY, "Could not roll back transaction: " . $ex2->getMessage(), "Could not import " . $item->hash . ": " . $ex->getMessage());
|
||||
}
|
||||
log_error(BulkImportExportInfo::KEY, "Could not import " . $item->hash . ": " . $ex->getMessage(), "Could not import " . $item->hash . ": " . $ex->getMessage());
|
||||
continue;
|
||||
} finally {
|
||||
if (!empty($tmpfile) && is_file($tmpfile)) {
|
||||
unlink($tmpfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
$event->image_id = -2; // default -1 = upload wasn't handled
|
||||
|
||||
log_info(
|
||||
BulkImportExportInfo::KEY,
|
||||
"Imported $total items, skipped $skipped, $failed failed",
|
||||
"Imported $total items, skipped $skipped, $failed failed"
|
||||
);
|
||||
} else {
|
||||
throw new SCoreException("Could not open zip archive");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
|
||||
if ($user->can(Permissions::BULK_EXPORT)) {
|
||||
$event->add_action(self::EXPORT_ACTION_NAME, "Export");
|
||||
}
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $user, $page;
|
||||
|
||||
if ($user->can(Permissions::BULK_EXPORT) &&
|
||||
($event->action == self::EXPORT_ACTION_NAME)) {
|
||||
$download_filename = $user->name . '-' . date('YmdHis') . '.zip';
|
||||
$zip_filename = tempnam(sys_get_temp_dir(), "shimmie_bulk_export");
|
||||
$zip = new ZipArchive;
|
||||
|
||||
$json_data = [];
|
||||
|
||||
if ($zip->open($zip_filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
|
||||
foreach ($event->items as $image) {
|
||||
$img_loc = warehouse_path(Image::IMAGE_DIR, $image->hash, false);
|
||||
|
||||
$export_event = new BulkExportEvent($image);
|
||||
send_event($export_event);
|
||||
$data = $export_event->fields;
|
||||
$data["hash"] = $image->hash;
|
||||
$data["tags"] = $image->get_tag_array();
|
||||
$data["filename"] = $image->filename;
|
||||
$data["source"] = $image->source;
|
||||
|
||||
array_push($json_data, $data);
|
||||
|
||||
$zip->addFile($img_loc, $image->hash);
|
||||
}
|
||||
|
||||
$json_data = json_encode($json_data, JSON_PRETTY_PRINT);
|
||||
$zip->addFromString(self::EXPORT_INFO_FILE_NAME, $json_data);
|
||||
|
||||
$zip->close();
|
||||
|
||||
$page->set_mode(PageMode::FILE);
|
||||
$page->set_file($zip_filename, true);
|
||||
$page->set_filename($download_filename);
|
||||
|
||||
$event->redirect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// we don't actually do anything, just accept one upload and spawn several
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function check_contents(string $tmpname): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function create_thumb(string $hash, string $type): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -210,7 +210,7 @@ class CommentList extends Extension
|
||||
$cpe = new CommentPostingEvent(int_escape($_POST['image_id']), $user, $_POST['comment']);
|
||||
send_event($cpe);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
|
||||
$page->set_redirect(make_link("post/view/$i_iid", null, "comment_on_$i_iid"));
|
||||
} catch (CommentPostingException $ex) {
|
||||
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
|
||||
}
|
||||
@ -226,11 +226,7 @@ class CommentList extends Extension
|
||||
send_event(new CommentDeletionEvent(int_escape($event->get_arg(1))));
|
||||
$page->flash("Deleted comment");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
if (!empty($_SERVER['HTTP_REFERER'])) {
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
} else {
|
||||
$page->set_redirect(make_link("post/view/" . $event->get_arg(2)));
|
||||
}
|
||||
$page->set_redirect(referer_or(make_link("post/view/" . $event->get_arg(2))));
|
||||
}
|
||||
} else {
|
||||
$this->theme->display_permission_denied();
|
||||
@ -264,8 +260,51 @@ class CommentList extends Extension
|
||||
|
||||
private function onPageRequest_list(PageRequestEvent $event)
|
||||
{
|
||||
$page_num = $event->try_page_num(1);
|
||||
$this->build_page($page_num);
|
||||
global $cache, $database, $user;
|
||||
|
||||
$where = SPEED_HAX ? "WHERE posted > now() - interval '24 hours'" : "";
|
||||
|
||||
$total_pages = $cache->get("comment_pages");
|
||||
if (empty($total_pages)) {
|
||||
$total_pages = (int)($database->get_one("
|
||||
SELECT COUNT(c1)
|
||||
FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
|
||||
") / 10);
|
||||
$cache->set("comment_pages", $total_pages, 600);
|
||||
}
|
||||
$total_pages = max($total_pages, 1);
|
||||
|
||||
$current_page = $event->try_page_num(1, $total_pages);
|
||||
$threads_per_page = 10;
|
||||
$start = $threads_per_page * $current_page;
|
||||
|
||||
$result = $database->Execute("
|
||||
SELECT image_id,MAX(posted) AS latest
|
||||
FROM comments
|
||||
$where
|
||||
GROUP BY image_id
|
||||
ORDER BY latest DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
", ["limit"=>$threads_per_page, "offset"=>$start]);
|
||||
|
||||
$user_ratings = Extension::is_enabled(RatingsInfo::KEY) ? Ratings::get_user_class_privs($user) : "";
|
||||
|
||||
$images = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$image = Image::by_id((int)$row["image_id"]);
|
||||
if (
|
||||
Extension::is_enabled(RatingsInfo::KEY) && !is_null($image) &&
|
||||
!in_array($image->rating, $user_ratings)
|
||||
) {
|
||||
$image = null; // this is "clever", I may live to regret it
|
||||
}
|
||||
if (!is_null($image)) {
|
||||
$comments = $this->get_comments($image->id);
|
||||
$images[] = [$image, $comments];
|
||||
}
|
||||
}
|
||||
|
||||
$this->theme->display_comment_list($images, $current_page+1, $total_pages, $user->can(Permissions::CREATE_COMMENT));
|
||||
}
|
||||
|
||||
private function onPageRequest_beta_search(PageRequestEvent $event)
|
||||
@ -276,9 +315,8 @@ class CommentList extends Extension
|
||||
$i_comment_count = Comment::count_comments_by_user($duser);
|
||||
$com_per_page = 50;
|
||||
$total_pages = (int)ceil($i_comment_count / $com_per_page);
|
||||
$page_num = clamp($page_num, 1, $total_pages);
|
||||
$comments = $this->get_user_comments($duser->id, $com_per_page, ($page_num - 1) * $com_per_page);
|
||||
$this->theme->display_all_user_comments($comments, $page_num, $total_pages, $duser);
|
||||
$comments = $this->get_user_comments($duser->id, $com_per_page, $page_num * $com_per_page);
|
||||
$this->theme->display_all_user_comments($comments, $page_num+1, $total_pages, $duser);
|
||||
}
|
||||
|
||||
public function onAdminBuilding(AdminBuildingEvent $event)
|
||||
@ -389,56 +427,6 @@ class CommentList extends Extension
|
||||
}
|
||||
}
|
||||
|
||||
private function build_page(int $current_page)
|
||||
{
|
||||
global $cache, $database, $user;
|
||||
|
||||
$where = SPEED_HAX ? "WHERE posted > now() - interval '24 hours'" : "";
|
||||
|
||||
$total_pages = $cache->get("comment_pages");
|
||||
if (empty($total_pages)) {
|
||||
$total_pages = (int)($database->get_one("
|
||||
SELECT COUNT(c1)
|
||||
FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
|
||||
") / 10);
|
||||
$cache->set("comment_pages", $total_pages, 600);
|
||||
}
|
||||
$total_pages = max($total_pages, 1);
|
||||
|
||||
$current_page = clamp($current_page, 1, $total_pages);
|
||||
|
||||
$threads_per_page = 10;
|
||||
$start = $threads_per_page * ($current_page - 1);
|
||||
|
||||
$result = $database->Execute("
|
||||
SELECT image_id,MAX(posted) AS latest
|
||||
FROM comments
|
||||
$where
|
||||
GROUP BY image_id
|
||||
ORDER BY latest DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
", ["limit"=>$threads_per_page, "offset"=>$start]);
|
||||
|
||||
$user_ratings = Extension::is_enabled(RatingsInfo::KEY) ? Ratings::get_user_class_privs($user) : "";
|
||||
|
||||
$images = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$image = Image::by_id((int)$row["image_id"]);
|
||||
if (
|
||||
Extension::is_enabled(RatingsInfo::KEY) && !is_null($image) &&
|
||||
!in_array($image->rating, $user_ratings)
|
||||
) {
|
||||
$image = null; // this is "clever", I may live to regret it
|
||||
}
|
||||
if (!is_null($image)) {
|
||||
$comments = $this->get_comments($image->id);
|
||||
$images[] = [$image, $comments];
|
||||
}
|
||||
}
|
||||
|
||||
$this->theme->display_comment_list($images, $current_page, $total_pages, $user->can(Permissions::CREATE_COMMENT));
|
||||
}
|
||||
|
||||
/**
|
||||
* #return Comment[]
|
||||
*/
|
||||
@ -563,18 +551,9 @@ class CommentList extends Extension
|
||||
'website' => '',
|
||||
'body' => $text,
|
||||
'permalink' => '',
|
||||
];
|
||||
|
||||
# akismet breaks if there's no referrer in the environment; so if there
|
||||
# isn't, supply one manually
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
$comment['referrer'] = 'none';
|
||||
log_warning("comment", "User '{$user->name}' commented with no referrer: $text");
|
||||
}
|
||||
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||
$comment['user_agent'] = 'none';
|
||||
log_warning("comment", "User '{$user->name}' commented with no user-agent: $text");
|
||||
}
|
||||
'referrer' => $_SERVER['HTTP_REFERER'] ?? 'none',
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'none',
|
||||
];
|
||||
|
||||
$akismet = new Akismet(
|
||||
$_SERVER['SERVER_NAME'],
|
||||
|
@ -234,7 +234,7 @@ class CommentListTheme extends Themelet
|
||||
$html = "
|
||||
<div class=\"comment $hb\">
|
||||
$h_userlink: $h_comment
|
||||
<a href=\"".make_link("post/view/$i_image_id#c$i_comment_id")."\">>>></a>
|
||||
<a href=\"".make_link("post/view/$i_image_id", null, "c$i_comment_id")."\">>>></a>
|
||||
</div>
|
||||
";
|
||||
} else {
|
||||
|
@ -3,22 +3,25 @@
|
||||
|
||||
abstract class CronUploaderConfig
|
||||
{
|
||||
const DEFAULT_PATH = "cron_uploader";
|
||||
public const DEFAULT_PATH = "cron_uploader";
|
||||
|
||||
const KEY = "cron_uploader_key";
|
||||
const COUNT = "cron_uploader_count";
|
||||
const DIR = "cron_uploader_dir";
|
||||
const USER = "cron_uploader_user";
|
||||
public const KEY = "cron_uploader_key";
|
||||
public const DIR = "cron_uploader_dir";
|
||||
public const USER = "cron_uploader_user";
|
||||
public const STOP_ON_ERROR = "cron_uploader_stop_on_error";
|
||||
public const INCLUDE_ALL_LOGS = "cron_uploader_include_all_logs";
|
||||
public const LOG_LEVEL = "cron_uploader_log_level";
|
||||
|
||||
public static function set_defaults(): void
|
||||
{
|
||||
global $config;
|
||||
$config->set_default_int(self::COUNT, 1);
|
||||
$config->set_default_string(self::DIR, data_path(self::DEFAULT_PATH));
|
||||
|
||||
$config->set_default_bool(self::INCLUDE_ALL_LOGS, false);
|
||||
$config->set_default_bool(self::STOP_ON_ERROR, false);
|
||||
$config->set_default_int(self::LOG_LEVEL, SCORE_LOG_INFO);
|
||||
$upload_key = $config->get_string(self::KEY, "");
|
||||
if (empty($upload_key)) {
|
||||
$upload_key = self::generate_key();
|
||||
$upload_key = generate_key();
|
||||
|
||||
$config->set_string(self::KEY, $upload_key);
|
||||
}
|
||||
@ -48,18 +51,6 @@ abstract class CronUploaderConfig
|
||||
$config->set_string(self::KEY, $value);
|
||||
}
|
||||
|
||||
public static function get_count(): int
|
||||
{
|
||||
global $config;
|
||||
return $config->get_int(self::COUNT);
|
||||
}
|
||||
|
||||
public static function set_count(int $value): void
|
||||
{
|
||||
global $config;
|
||||
$config->set_int(self::COUNT, $value);
|
||||
}
|
||||
|
||||
public static function get_dir(): string
|
||||
{
|
||||
global $config;
|
||||
@ -76,21 +67,4 @@ abstract class CronUploaderConfig
|
||||
global $config;
|
||||
$config->set_string(self::DIR, $value);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generates a unique key for the website to prevent unauthorized access.
|
||||
*/
|
||||
private static function generate_key()
|
||||
{
|
||||
$length = 20;
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$randomString = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters [rand(0, strlen($characters) - 1)];
|
||||
}
|
||||
|
||||
return $randomString;
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class CronUploader extends Extension
|
||||
const UPLOADED_DIR = "uploaded";
|
||||
const FAILED_DIR = "failed_to_upload";
|
||||
|
||||
public $output_buffer = [];
|
||||
private static $IMPORT_RUNNING = false;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
@ -57,10 +57,18 @@ class CronUploader extends Extension
|
||||
|
||||
$sb = new SetupBlock("Cron Uploader");
|
||||
$sb->start_table();
|
||||
$sb->add_int_option(CronUploaderConfig::COUNT, "Upload per run", true);
|
||||
$sb->add_text_option(CronUploaderConfig::DIR, "Root dir", true);
|
||||
$sb->add_text_option(CronUploaderConfig::KEY, "Key", true);
|
||||
$sb->add_choice_option(CronUploaderConfig::USER, $users, "User", true);
|
||||
$sb->add_bool_option(CronUploaderConfig::STOP_ON_ERROR, "Stop On Error", true);
|
||||
$sb->add_choice_option(CronUploaderConfig::LOG_LEVEL, [
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_INFO] => SCORE_LOG_INFO,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_WARNING] => SCORE_LOG_WARNING,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_ERROR] => SCORE_LOG_ERROR,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_CRITICAL] => SCORE_LOG_CRITICAL,
|
||||
], "Output Log Level: ", true);
|
||||
$sb->add_bool_option(CronUploaderConfig::INCLUDE_ALL_LOGS, "Include All Logs", true);
|
||||
$sb->end_table();
|
||||
$sb->add_label("<a href='$documentation_link'>Read the documentation</a> for cron setup instructions.");
|
||||
|
||||
@ -108,6 +116,24 @@ class CronUploader extends Extension
|
||||
}
|
||||
}
|
||||
|
||||
public function onLog(LogEvent $event)
|
||||
{
|
||||
global $config;
|
||||
$all = $config->get_bool(CronUploaderConfig::INCLUDE_ALL_LOGS);
|
||||
if (self::$IMPORT_RUNNING &&
|
||||
$event->priority >= $config->get_int(CronUploaderConfig::LOG_LEVEL) &&
|
||||
($event->section==self::NAME || $all)
|
||||
) {
|
||||
$output = "[" . date('Y-m-d H:i:s') . "] " . ($all ? '['. $event->section .'] ' :'') . "[" . LOGGING_LEVEL_NAMES[$event->priority] . "] " . $event->message ;
|
||||
|
||||
echo $output . "\r\n";
|
||||
flush_output();
|
||||
|
||||
$log_path = $this->get_log_file();
|
||||
file_put_contents($log_path, $output);
|
||||
}
|
||||
}
|
||||
|
||||
private function restage_folder(string $folder)
|
||||
{
|
||||
global $page;
|
||||
@ -123,33 +149,46 @@ class CronUploader extends Extension
|
||||
|
||||
$this->prep_root_dir();
|
||||
|
||||
$results = get_dir_contents($queue_dir);
|
||||
|
||||
if (count($results) > 0) {
|
||||
$page->flash("Queue folder must be empty to re-stage");
|
||||
return;
|
||||
}
|
||||
|
||||
$results = get_dir_contents($stage_dir);
|
||||
$results = get_files_recursively($stage_dir);
|
||||
|
||||
if (count($results) == 0) {
|
||||
if (rmdir($stage_dir)===false) {
|
||||
if (remove_empty_dirs($stage_dir)===false) {
|
||||
$page->flash("Nothing to stage from $folder, cannot remove folder");
|
||||
} else {
|
||||
$page->flash("Nothing to stage from $folder, removing folder");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($results as $result) {
|
||||
$original_path = join_path($stage_dir, $result);
|
||||
$new_path = join_path($queue_dir, $result);
|
||||
$new_path = join_path($queue_dir, substr($result, strlen($stage_dir)));
|
||||
|
||||
rename($original_path, $new_path);
|
||||
if (file_exists($new_path)) {
|
||||
$page->flash("File already exists in queue folder: " .$result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$page->flash("Re-staged $folder to queue");
|
||||
rmdir($stage_dir);
|
||||
$success = true;
|
||||
foreach ($results as $result) {
|
||||
$new_path = join_path($queue_dir, substr($result, strlen($stage_dir)));
|
||||
|
||||
$dir = dirname($new_path);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0775, true);
|
||||
}
|
||||
|
||||
if (rename($result, $new_path)===false) {
|
||||
$page->flash("Could not move file: " .$result);
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($success===true) {
|
||||
$page->flash("Re-staged $folder to queue");
|
||||
if (remove_empty_dirs($stage_dir)===false) {
|
||||
$page->flash("Could not remove $folder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function clear_folder($folder)
|
||||
@ -265,7 +304,11 @@ class CronUploader extends Extension
|
||||
*/
|
||||
public function process_upload(string $key, ?int $upload_count = null): bool
|
||||
{
|
||||
global $database;
|
||||
global $database, $config, $_shm_load_start;
|
||||
|
||||
$max_time = intval(ini_get('max_execution_time'))*.8;
|
||||
|
||||
$this->set_headers();
|
||||
|
||||
if ($key!=CronUploaderConfig::get_key()) {
|
||||
throw new SCoreException("Cron upload key incorrect");
|
||||
@ -287,24 +330,12 @@ class CronUploader extends Extension
|
||||
throw new SCoreException("Cron upload process is already running");
|
||||
}
|
||||
|
||||
self::$IMPORT_RUNNING = true;
|
||||
try {
|
||||
//set_time_limit(0);
|
||||
|
||||
// Gets amount of imgs to upload
|
||||
if ($upload_count == null) {
|
||||
$upload_count = CronUploaderConfig::get_count();
|
||||
}
|
||||
|
||||
$output_subdir = date('Ymd-His', time());
|
||||
$image_queue = $this->generate_image_queue(CronUploaderConfig::get_dir(), $upload_count);
|
||||
|
||||
|
||||
// Throw exception if there's nothing in the queue
|
||||
if (count($image_queue) == 0) {
|
||||
$this->log_message(SCORE_LOG_WARNING, "Your queue is empty so nothing could be uploaded.");
|
||||
$this->handle_log();
|
||||
return false;
|
||||
}
|
||||
$image_queue = $this->generate_image_queue(CronUploaderConfig::get_dir());
|
||||
|
||||
// Randomize Images
|
||||
//shuffle($this->image_queue);
|
||||
@ -314,14 +345,18 @@ class CronUploader extends Extension
|
||||
$failed = 0;
|
||||
|
||||
// Upload the file(s)
|
||||
for ($i = 0; $i < $upload_count && sizeof($image_queue) > 0; $i++) {
|
||||
$img = array_pop($image_queue);
|
||||
|
||||
foreach ($image_queue as $img) {
|
||||
$execution_time = microtime(true) - $_shm_load_start;
|
||||
if ($execution_time>$max_time) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
$database->beginTransaction();
|
||||
$database->begin_transaction();
|
||||
$this->log_message(SCORE_LOG_INFO, "Adding file: {$img[0]} - tags: {$img[2]}");
|
||||
$result = $this->add_image($img[0], $img[1], $img[2]);
|
||||
$database->commit();
|
||||
if ($database->is_transaction_open()) {
|
||||
$database->commit();
|
||||
}
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
|
||||
if ($result->merged) {
|
||||
$merged++;
|
||||
@ -330,28 +365,37 @@ class CronUploader extends Extension
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
try {
|
||||
$database->rollback();
|
||||
if ($database->is_transaction_open()) {
|
||||
$database->rollback();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
$failed++;
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
|
||||
$this->log_message(SCORE_LOG_ERROR, "(" . gettype($e) . ") " . $e->getMessage());
|
||||
$this->log_message(SCORE_LOG_ERROR, $e->getTraceAsString());
|
||||
if ($config->get_bool(CronUploaderConfig::STOP_ON_ERROR)) {
|
||||
break;
|
||||
} else {
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Throw exception if there's nothing in the queue
|
||||
if ($merged+$failed+$added === 0) {
|
||||
$this->log_message(SCORE_LOG_WARNING, "Your queue is empty so nothing could be uploaded.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->log_message(SCORE_LOG_INFO, "Items added: $added");
|
||||
$this->log_message(SCORE_LOG_INFO, "Items merged: $merged");
|
||||
$this->log_message(SCORE_LOG_INFO, "Items failed: $failed");
|
||||
|
||||
|
||||
// Display upload log
|
||||
$this->handle_log();
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
self::$IMPORT_RUNNING = false;
|
||||
flock($lockfile, LOCK_UN);
|
||||
fclose($lockfile);
|
||||
}
|
||||
@ -359,7 +403,13 @@ class CronUploader extends Extension
|
||||
|
||||
private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false)
|
||||
{
|
||||
$relativeDir = dirname(substr($path, strlen(CronUploaderConfig::get_dir()) + 7));
|
||||
$rootDir = CronUploaderConfig::get_dir();
|
||||
$rootLength = strlen($rootDir);
|
||||
if ($rootDir[$rootLength-1]=="/"||$rootDir[$rootLength-1]=="\\") {
|
||||
$rootLength--;
|
||||
}
|
||||
|
||||
$relativeDir = dirname(substr($path, $rootLength + 7));
|
||||
|
||||
if ($relativeDir==".") {
|
||||
$relativeDir = "";
|
||||
@ -405,7 +455,7 @@ class CronUploader extends Extension
|
||||
if (array_key_exists('extension', $pathinfo)) {
|
||||
$metadata ['extension'] = $pathinfo ['extension'];
|
||||
}
|
||||
$metadata ['tags'] = $tagArray; // doesn't work when not logged in here, handled below
|
||||
$metadata ['tags'] = $tagArray;
|
||||
$metadata ['source'] = null;
|
||||
$event = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($event);
|
||||
@ -420,34 +470,46 @@ class CronUploader extends Extension
|
||||
}
|
||||
$this->log_message(SCORE_LOG_INFO, $infomsg);
|
||||
|
||||
// Set tags
|
||||
$img = Image::by_id($event->image_id);
|
||||
$img->set_tags(array_merge($tagArray, $img->get_tag_array()));
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
private const PARTIAL_DOWNLOAD_EXTENSIONS = ['crdownload','part'];
|
||||
private const SKIPPABLE_FILES = ['.ds_store','thumbs.db'];
|
||||
private const SKIPPABLE_DIRECTORIES = ['__macosx'];
|
||||
|
||||
private function is_skippable_file(string $path)
|
||||
private function is_skippable_dir(string $path)
|
||||
{
|
||||
$info = pathinfo($path);
|
||||
|
||||
if (in_array(strtolower($info['extension']), self::PARTIAL_DOWNLOAD_EXTENSIONS)) {
|
||||
if (array_key_exists("basename", $info) && in_array(strtolower($info['basename']), self::SKIPPABLE_DIRECTORIES)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function generate_image_queue(string $root_dir, ?int $limit = null): array
|
||||
private function is_skippable_file(string $path)
|
||||
{
|
||||
$info = pathinfo($path);
|
||||
|
||||
if (array_key_exists("basename", $info) && in_array(strtolower($info['basename']), self::SKIPPABLE_FILES)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (array_key_exists("extension", $info) && in_array(strtolower($info['extension']), self::PARTIAL_DOWNLOAD_EXTENSIONS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function generate_image_queue(string $root_dir, ?int $limit = null): Generator
|
||||
{
|
||||
$base = $this->get_queue_dir();
|
||||
$output = [];
|
||||
|
||||
if (!is_dir($base)) {
|
||||
$this->log_message(SCORE_LOG_WARNING, "Image Queue Directory could not be found at \"$base\".");
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
|
||||
$ite = new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS);
|
||||
@ -458,31 +520,19 @@ class CronUploader extends Extension
|
||||
$relativePath = substr($fullpath, strlen($base));
|
||||
$tags = path_to_tags($relativePath);
|
||||
|
||||
$img = [
|
||||
yield [
|
||||
0 => $fullpath,
|
||||
1 => $pathinfo ["basename"],
|
||||
2 => $tags
|
||||
];
|
||||
$output[] = $img;
|
||||
if (!empty($limit) && count($output) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
private function log_message(int $severity, string $message): void
|
||||
{
|
||||
log_msg(self::NAME, $severity, $message);
|
||||
|
||||
$time = "[" . date('Y-m-d H:i:s') . "]";
|
||||
$this->output_buffer[] = $time . " " . $message;
|
||||
|
||||
$log_path = $this->get_log_file();
|
||||
|
||||
file_put_contents($log_path, $time . " " . $message);
|
||||
}
|
||||
|
||||
private function get_log_file(): string
|
||||
@ -490,16 +540,12 @@ class CronUploader extends Extension
|
||||
return join_path(CronUploaderConfig::get_dir(), "uploads.log");
|
||||
}
|
||||
|
||||
/**
|
||||
* This is run at the end to display & save the log.
|
||||
*/
|
||||
private function handle_log()
|
||||
private function set_headers(): void
|
||||
{
|
||||
global $page;
|
||||
|
||||
// Display message
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/plain");
|
||||
$page->set_data(implode("\r\n", $this->output_buffer));
|
||||
$page->set_mode(PageMode::MANUAL);
|
||||
$page->set_type(MIME_TYPE_TEXT);
|
||||
$page->send_headers();
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,9 @@ class CronUploaderTheme extends Themelet
|
||||
<br />When you create the cron job, you choose when to upload new images.</li>
|
||||
</ol>";
|
||||
|
||||
|
||||
$max_time = intval(ini_get('max_execution_time'))*.8;
|
||||
|
||||
$usage_html = "Upload your images you want to be uploaded to the queue directory using your FTP client or other means.
|
||||
<br />(<b>{$queue_dirinfo['path']}</b>)
|
||||
<ol>
|
||||
@ -71,7 +74,7 @@ class CronUploaderTheme extends Themelet
|
||||
|
||||
<ul>
|
||||
<li>If an import is already running, another cannot start until it is done.</li>
|
||||
<li>Each time it runs it will import up to ".CronUploaderConfig::get_count()." file(s). This is controlled from <a href='".make_link("setup")."'>Board Config</a>.</li>
|
||||
<li>Each time it runs it will import for up to ".number_format($max_time)." seconds. This is controlled by the PHP max execution time.</li>
|
||||
<li>Uploaded images will be moved to the 'uploaded' directory into a subfolder named after the time the import started. It's recommended that you remove everything out of this directory from time to time. If you have admin controls enabled, this can be done from <a href='".make_link("admin")."'>Board Admin</a>.</li>
|
||||
<li>If you enable the db logging extension, you can view the log output on this screen. Otherwise the log will be written to a file at ".CronUploaderConfig::get_dir().DIRECTORY_SEPARATOR."uploads.log</li>
|
||||
</ul>
|
||||
@ -107,7 +110,7 @@ class CronUploaderTheme extends Themelet
|
||||
|
||||
$html .= make_form(make_link("admin/cron_uploader_restage"));
|
||||
$html .= "<table class='form'>";
|
||||
$html .= "<tr><th>Failed dir</th><td><select name='failed_dir' required='required'><option></option>";
|
||||
$html .= "<tr><th>Failed dir</th><td><select name='failed_dir' required='required'>";
|
||||
|
||||
foreach ($failed_dirs as $dir) {
|
||||
$html .= "<option value='$dir'>$dir</option>";
|
||||
|
@ -10,20 +10,20 @@ class DanbooruApi extends Extension
|
||||
|
||||
if ($event->page_matches("api/danbooru/add_post") || $event->page_matches("api/danbooru/post/create.xml")) {
|
||||
// No XML data is returned from this function
|
||||
$page->set_type("text/plain");
|
||||
$page->set_type(MIME_TYPE_TEXT);
|
||||
$this->api_add_post();
|
||||
} elseif ($event->page_matches("api/danbooru/find_posts") || $event->page_matches("api/danbooru/post/index.xml")) {
|
||||
$page->set_type("application/xml");
|
||||
$page->set_type(MIME_TYPE_XML_APPLICATION);
|
||||
$page->set_data($this->api_find_posts());
|
||||
} elseif ($event->page_matches("api/danbooru/find_tags")) {
|
||||
$page->set_type("application/xml");
|
||||
$page->set_type(MIME_TYPE_XML_APPLICATION);
|
||||
$page->set_data($this->api_find_tags());
|
||||
}
|
||||
|
||||
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
|
||||
// Shimmie view page
|
||||
// Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123
|
||||
// This redirects that to http://shimmie/post/view/123
|
||||
// Example: danbooruup says the url is https://shimmie/api/danbooru/post/show/123
|
||||
// This redirects that to https://shimmie/post/view/123
|
||||
elseif ($event->page_matches("api/danbooru/post/show")) {
|
||||
$fixedlocation = make_link("post/view/" . $event->get_arg(0));
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
|
@ -9,6 +9,7 @@ class ETInfo extends ExtensionInfo
|
||||
public $url = self::SHIMMIE_URL;
|
||||
public $authors = self::SHISH_AUTHOR;
|
||||
public $license = self::LICENSE_GPLV2;
|
||||
public $core = true;
|
||||
public $description = "Show various bits of system information";
|
||||
public $documentation =
|
||||
"Knowing the information that this extension shows can be very useful for debugging. There's also an option to send
|
||||
|
124
ext/et/main.php
124
ext/et/main.php
@ -10,7 +10,7 @@ class ET extends Extension
|
||||
global $user;
|
||||
if ($event->page_matches("system_info")) {
|
||||
if ($user->can(Permissions::VIEW_SYSINTO)) {
|
||||
$this->theme->display_info_page($this->get_info());
|
||||
$this->theme->display_info_page($this->to_yaml($this->get_info()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,20 +29,18 @@ class ET extends Extension
|
||||
{
|
||||
global $user;
|
||||
if ($user->can(Permissions::VIEW_SYSINTO)) {
|
||||
$event->add_link("System Info", make_link("system_info"));
|
||||
$event->add_link("System Info", make_link("system_info"), 99);
|
||||
}
|
||||
}
|
||||
|
||||
public function onCommand(CommandEvent $event)
|
||||
{
|
||||
if ($event->cmd == "help") {
|
||||
print "\tget-info\n";
|
||||
print "\tshimmie-info\n";
|
||||
print "\t\tList a bunch of info\n\n";
|
||||
}
|
||||
if ($event->cmd == "info") {
|
||||
foreach ($this->get_info() as $k => $v) {
|
||||
print("$k = $v\n");
|
||||
}
|
||||
if ($event->cmd == "shimmie-info") {
|
||||
print($this->to_yaml($this->get_info()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,51 +51,81 @@ class ET extends Extension
|
||||
{
|
||||
global $config, $database;
|
||||
|
||||
$info = [];
|
||||
$info['site_title'] = $config->get_string(SetupConfig::TITLE);
|
||||
$info['site_theme'] = $config->get_string(SetupConfig::THEME);
|
||||
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
|
||||
|
||||
$info['sys_shimmie'] = VERSION;
|
||||
$info['sys_schema'] = $config->get_int("db_version");
|
||||
$info['sys_php'] = phpversion();
|
||||
$info['sys_db'] = $database->get_driver_name();
|
||||
$info['sys_os'] = php_uname();
|
||||
$info['sys_disk'] = to_shorthand_int((int)disk_total_space("./") - (int)disk_free_space("./")) . " / " .
|
||||
to_shorthand_int((int)disk_total_space("./"));
|
||||
$info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown';
|
||||
|
||||
$info[MediaConfig::FFMPEG_PATH] = $config->get_string(MediaConfig::FFMPEG_PATH);
|
||||
$info[MediaConfig::CONVERT_PATH] = $config->get_string(MediaConfig::CONVERT_PATH);
|
||||
$info[MediaConfig::MEM_LIMIT] = $config->get_int(MediaConfig::MEM_LIMIT);
|
||||
|
||||
$info[ImageConfig::THUMB_ENGINE] = $config->get_string(ImageConfig::THUMB_ENGINE);
|
||||
$info[ImageConfig::THUMB_QUALITY] = $config->get_int(ImageConfig::THUMB_QUALITY);
|
||||
$info[ImageConfig::THUMB_WIDTH] = $config->get_int(ImageConfig::THUMB_WIDTH);
|
||||
$info[ImageConfig::THUMB_HEIGHT] = $config->get_int(ImageConfig::THUMB_HEIGHT);
|
||||
$info[ImageConfig::THUMB_SCALING] = $config->get_int(ImageConfig::THUMB_SCALING);
|
||||
$info[ImageConfig::THUMB_TYPE] = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||
|
||||
$info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images");
|
||||
$info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments");
|
||||
$info['stat_users'] = $database->get_one("SELECT COUNT(*) FROM users");
|
||||
$info['stat_tags'] = $database->get_one("SELECT COUNT(*) FROM tags");
|
||||
$info['stat_image_tags'] = $database->get_one("SELECT COUNT(*) FROM image_tags");
|
||||
|
||||
$els = [];
|
||||
foreach (getSubclassesOf("Extension") as $class) {
|
||||
$els[] = $class;
|
||||
$core_exts = ExtensionInfo::get_core_extensions();
|
||||
$extra_exts = [];
|
||||
foreach (ExtensionInfo::get_all() as $info) {
|
||||
if ($info->is_enabled() && !in_array($info->key, $core_exts)) {
|
||||
$extra_exts[] = $info->key;
|
||||
}
|
||||
}
|
||||
$info['sys_extensions'] = join(', ', $els);
|
||||
|
||||
$info['handled_extensions'] = join(', ', DataHandlerExtension::get_all_supported_exts());
|
||||
$info = [
|
||||
"about" => [
|
||||
'title' => $config->get_string(SetupConfig::TITLE),
|
||||
'theme' => $config->get_string(SetupConfig::THEME),
|
||||
'url' => make_http(make_link("/")),
|
||||
],
|
||||
"versions" => [
|
||||
'shimmie' => VERSION,
|
||||
'schema' => $config->get_int("db_version"),
|
||||
'php' => phpversion(),
|
||||
'db' => $database->get_driver_name() . " " . $database->get_version(),
|
||||
'os' => php_uname(),
|
||||
'server' => $_SERVER["SERVER_SOFTWARE"] ?? 'unknown',
|
||||
],
|
||||
"extensions" => [
|
||||
"core" => $core_exts,
|
||||
"extra" => $extra_exts,
|
||||
"handled_extensions" => DataHandlerExtension::get_all_supported_exts(),
|
||||
],
|
||||
"stats" => [
|
||||
'images' => (int)$database->get_one("SELECT COUNT(*) FROM images"),
|
||||
'comments' => (int)$database->get_one("SELECT COUNT(*) FROM comments"),
|
||||
'users' => (int)$database->get_one("SELECT COUNT(*) FROM users"),
|
||||
],
|
||||
"media" => [
|
||||
"memory_limit" => to_shorthand_int($config->get_int(MediaConfig::MEM_LIMIT)),
|
||||
"disk_use" => to_shorthand_int((int)disk_total_space("./") - (int)disk_free_space("./")),
|
||||
"disk_total" => to_shorthand_int((int)disk_total_space("./")),
|
||||
],
|
||||
"thumbnails" => [
|
||||
"engine" => $config->get_string(ImageConfig::THUMB_ENGINE),
|
||||
"quality" => $config->get_int(ImageConfig::THUMB_QUALITY),
|
||||
"width" => $config->get_int(ImageConfig::THUMB_WIDTH),
|
||||
"height" => $config->get_int(ImageConfig::THUMB_HEIGHT),
|
||||
"scaling" => $config->get_int(ImageConfig::THUMB_SCALING),
|
||||
"type" => $config->get_string(ImageConfig::THUMB_TYPE),
|
||||
],
|
||||
];
|
||||
|
||||
//$cfs = array();
|
||||
//foreach($database->get_all("SELECT name, value FROM config") as $pair) {
|
||||
// $cfs[] = $pair['name']."=".$pair['value'];
|
||||
//}
|
||||
//$info[''] = "Config: ".join(", ", $cfs);
|
||||
if (file_exists(".git")) {
|
||||
try {
|
||||
$commitHash = trim(exec('git log --pretty="%h" -n1 HEAD'));
|
||||
$commitBranch= trim(exec('git rev-parse --abbrev-ref HEAD'));
|
||||
$commitOrigin= trim(exec('git config --get remote.origin.url'));
|
||||
$commitOrigin= preg_replace("#//.*@#", "//xxx@", $commitOrigin);
|
||||
$info['git'] = [
|
||||
'commit' => $commitHash,
|
||||
'branch' => $commitBranch,
|
||||
'origin' => $commitOrigin,
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
private function to_yaml($info)
|
||||
{
|
||||
$data = "";
|
||||
foreach ($info as $title => $section) {
|
||||
$data .= "$title:\n";
|
||||
foreach ($section as $k => $v) {
|
||||
$data .= " $k: " . json_encode($v, JSON_UNESCAPED_SLASHES) . "\n";
|
||||
}
|
||||
$data .= "\n";
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use function MicroHTML\FORM;
|
||||
use function MicroHTML\INPUT;
|
||||
use function MicroHTML\P;
|
||||
use function MicroHTML\TEXTAREA;
|
||||
|
||||
class ETTheme extends Themelet
|
||||
{
|
||||
/*
|
||||
@ -7,61 +12,32 @@ class ETTheme extends Themelet
|
||||
*
|
||||
* $info = an array of ($name => $value)
|
||||
*/
|
||||
public function display_info_page($info)
|
||||
public function display_info_page($yaml)
|
||||
{
|
||||
global $page;
|
||||
|
||||
$page->set_title("System Info");
|
||||
$page->set_heading("System Info");
|
||||
$page->add_block(new NavBlock());
|
||||
$page->add_block(new Block("Information:", $this->build_data_form($info)));
|
||||
$page->add_block(new Block("Information:", $this->build_data_form($yaml)));
|
||||
}
|
||||
|
||||
protected function build_data_form($info)
|
||||
protected function build_data_form($yaml)
|
||||
{
|
||||
$data = <<<EOD
|
||||
Optional:
|
||||
Site title: {$info['site_title']}
|
||||
Theme: {$info['site_theme']}
|
||||
Genre: [describe your site here]
|
||||
URL: {$info['site_url']}
|
||||
|
||||
System stats:
|
||||
Shimmie: {$info['sys_shimmie']}
|
||||
Schema: {$info['sys_schema']}
|
||||
PHP: {$info['sys_php']}
|
||||
OS: {$info['sys_os']}
|
||||
Database: {$info['sys_db']}
|
||||
Server: {$info['sys_server']}
|
||||
Disk use: {$info['sys_disk']}
|
||||
|
||||
Media System:
|
||||
Memory Limit: {$info[MediaConfig::MEM_LIMIT]}
|
||||
|
||||
Thumbnail Generation:
|
||||
Engine: {$info[ImageConfig::THUMB_ENGINE]}
|
||||
Type: {$info[ImageConfig::THUMB_TYPE]}
|
||||
Quality: {$info[ImageConfig::THUMB_QUALITY]}
|
||||
Width: {$info[ImageConfig::THUMB_WIDTH]}
|
||||
Height: {$info[ImageConfig::THUMB_HEIGHT]}
|
||||
Scaling: {$info[ImageConfig::THUMB_SCALING]}
|
||||
|
||||
Shimmie stats:
|
||||
Images: {$info['stat_images']}
|
||||
Comments: {$info['stat_comments']}
|
||||
Users: {$info['stat_users']}
|
||||
Tags: {$info['stat_tags']}
|
||||
Applications: {$info['stat_image_tags']}
|
||||
Extensions: {$info['sys_extensions']}
|
||||
EOD;
|
||||
return <<<EOD
|
||||
<form action='https://shimmie.shishnet.org/register.php' method='POST'>
|
||||
<input type='hidden' name='registration_api' value='1'>
|
||||
<textarea name='data' rows='20' cols='80'>$data</textarea>
|
||||
<br><input type='submit' value='Click to send to Shish'>
|
||||
<br>Your stats are useful so that I know which combinations
|
||||
of web servers / databases / etc I need to support.
|
||||
</form>
|
||||
EOD;
|
||||
return (string)FORM(
|
||||
["action"=>"https://shimmie.shishnet.org/register.php", "method"=>"POST"],
|
||||
INPUT(["type"=>"hidden", "name"=>"registration_api", "value"=>"2"]),
|
||||
P(
|
||||
"Your stats are useful so that I know which combinations of ".
|
||||
"web servers / databases / etc I need to support :)"
|
||||
),
|
||||
P(TEXTAREA(
|
||||
["name"=>'data', "style"=>"width: 100%; height: 20em;"],
|
||||
$yaml
|
||||
)),
|
||||
P(INPUT(
|
||||
["type"=>'submit', "value"=>'Click to send to Shish', "style"=>"width: 100%; padding: 1em;"]
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ class ExtManagerTheme extends Themelet
|
||||
$form = SHM_SIMPLE_FORM(
|
||||
"ext_manager/set",
|
||||
TABLE(
|
||||
["id"=>'extensions', "class"=>'zebra sortable'],
|
||||
["id"=>'extensions', "class"=>'zebra'],
|
||||
THEAD(TR(
|
||||
$editable ? TH("Enabled") : null,
|
||||
TH("Name"),
|
||||
|
@ -1,5 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
use function MicroHTML\INPUT;
|
||||
use function MicroHTML\DIV;
|
||||
use function MicroHTML\A;
|
||||
use function MicroHTML\IMG;
|
||||
|
||||
class FeaturedTheme extends Themelet
|
||||
{
|
||||
@ -19,16 +22,21 @@ class FeaturedTheme extends Themelet
|
||||
|
||||
public function build_featured_html(Image $image, ?string $query=null): string
|
||||
{
|
||||
$i_id = $image->id;
|
||||
$h_view_link = make_link("post/view/$i_id", $query);
|
||||
$h_thumb_link = $image->get_thumb_link();
|
||||
$h_tip = html_escape($image->get_tooltip());
|
||||
$tsize = get_thumbnail_size($image->width, $image->height);
|
||||
|
||||
return "
|
||||
<a href='$h_view_link'>
|
||||
<img id='thumb_{$i_id}' title='{$h_tip}' alt='{$h_tip}' class='highlighted' style='height: {$tsize[1]}px; width: {$tsize[0]}px;' src='{$h_thumb_link}'>
|
||||
</a>
|
||||
";
|
||||
return (string)DIV(
|
||||
["style"=>"text-align: center;"],
|
||||
A(
|
||||
["href"=>make_link("post/view/{$image->id}", $query)],
|
||||
IMG([
|
||||
"id"=>"thumb_rand_{$image->id}",
|
||||
"title"=>$image->get_tooltip(),
|
||||
"alt"=>$image->get_tooltip(),
|
||||
"class"=>'highlighted',
|
||||
"style"=>"max-height: {$tsize[1]}px; max-width: 100%;",
|
||||
"src"=>$image->get_thumb_link()
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -249,19 +249,10 @@ class Forum extends Extension
|
||||
{
|
||||
global $config, $database;
|
||||
$threadsPerPage = $config->get_int('forumThreadsPerPage', 15);
|
||||
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_threads") / $threadsPerPage);
|
||||
$totalPages = (int)ceil($database->get_one("SELECT COUNT(*) FROM forum_threads") / $threadsPerPage);
|
||||
|
||||
if ($event->count_args() >= 2) {
|
||||
$pageNumber = $event->get_arg(1);
|
||||
if (!is_numeric($pageNumber)) {
|
||||
$pageNumber = 0;
|
||||
} elseif ($pageNumber <= 0) {
|
||||
$pageNumber = 0;
|
||||
} elseif ($pageNumber >= $totalPages) {
|
||||
$pageNumber = $totalPages - 1;
|
||||
} else {
|
||||
$pageNumber--;
|
||||
}
|
||||
$pageNumber = page_number($event->get_arg(1), $totalPages);
|
||||
} else {
|
||||
$pageNumber = 0;
|
||||
}
|
||||
@ -286,20 +277,11 @@ class Forum extends Extension
|
||||
global $config, $database;
|
||||
$threadID = int_escape($event->get_arg(1));
|
||||
$postsPerPage = $config->get_int('forumPostsPerPage', 15);
|
||||
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_posts WHERE thread_id = :id", ['id'=>$threadID]) / $postsPerPage);
|
||||
$totalPages = (int)ceil($database->get_one("SELECT COUNT(*) FROM forum_posts WHERE thread_id = :id", ['id'=>$threadID]) / $postsPerPage);
|
||||
$threadTitle = $this->get_thread_title($threadID);
|
||||
|
||||
if ($event->count_args() >= 3) {
|
||||
$pageNumber = $event->get_arg(2);
|
||||
if (!is_numeric($pageNumber)) {
|
||||
$pageNumber = 0;
|
||||
} elseif ($pageNumber <= 0) {
|
||||
$pageNumber = 0;
|
||||
} elseif ($pageNumber >= $totalPages) {
|
||||
$pageNumber = $totalPages - 1;
|
||||
} else {
|
||||
$pageNumber--;
|
||||
}
|
||||
$pageNumber = page_number($event->get_arg(2), $totalPages);
|
||||
} else {
|
||||
$pageNumber = 0;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class FourOhFourInfo extends ExtensionInfo
|
||||
public $url = self::SHIMMIE_URL;
|
||||
public $authors = self::SHISH_AUTHOR;
|
||||
public $license = self::LICENSE_GPLV2;
|
||||
public $visibility = self::VISIBLE_ADMIN;
|
||||
public $visibility = self::VISIBLE_HIDDEN;
|
||||
public $description = "If no other extension puts anything onto the page, show 404";
|
||||
public $core = true;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class ArchiveFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["zip"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_ZIP];
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class CBZFileHandler extends DataHandlerExtension
|
||||
{
|
||||
public $SUPPORTED_EXT = ["cbz"];
|
||||
public $SUPPORTED_MIME = [MIME_TYPE_COMIC_ZIP];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
@ -27,7 +27,7 @@ class CBZFileHandler extends DataHandlerExtension
|
||||
$cover,
|
||||
warehouse_path(Image::THUMBNAIL_DIR, $hash),
|
||||
get_thumbnail_max_size_scaled(),
|
||||
get_extension(getMimeType($cover)),
|
||||
get_extension(get_mime($cover)),
|
||||
null
|
||||
);
|
||||
return true;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class FlashFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["swf"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_FLASH];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class IcoFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["ico", "ani", "cur"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_ICO, MIME_TYPE_ANI];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class MP3FileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["mp3"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_MP3];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
@ -23,6 +23,6 @@ class MP3FileHandler extends DataHandlerExtension
|
||||
|
||||
protected function check_contents(string $tmpname): bool
|
||||
{
|
||||
return getMimeType($tmpname) == 'audio/mpeg';
|
||||
return get_mime($tmpname) === MIME_TYPE_MP3;
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
class PixelFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["jpg", "jpeg", "gif", "png", "webp"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_JPEG, MIME_TYPE_GIF, MIME_TYPE_PNG, MIME_TYPE_WEBP];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
if (in_array($event->ext, Media::LOSSLESS_FORMATS)) {
|
||||
$event->image->lossless = true;
|
||||
} elseif ($event->ext=="webp") {
|
||||
} elseif ($event->ext==EXTENSION_WEBP) {
|
||||
$event->image->lossless = Media::is_lossless_webp($event->file_name);
|
||||
}
|
||||
|
||||
@ -17,10 +17,10 @@ class PixelFileHandler extends DataHandlerExtension
|
||||
}
|
||||
$event->image->audio = false;
|
||||
switch ($event->ext) {
|
||||
case "gif":
|
||||
case EXTENSION_GIF:
|
||||
$event->image->video = Media::is_animated_gif($event->file_name);
|
||||
break;
|
||||
case "webp":
|
||||
case EXTENSION_WEBP:
|
||||
$event->image->video = Media::is_animated_webp($event->file_name);
|
||||
break;
|
||||
default:
|
||||
|
@ -9,7 +9,7 @@ class PixelFileHandlerTheme extends Themelet
|
||||
$u_ilink = $image->get_image_link();
|
||||
if ($config->get_bool(ImageConfig::SHOW_META) && function_exists(ImageIO::EXIF_READ_FUNCTION)) {
|
||||
# FIXME: only read from jpegs?
|
||||
$exif = @exif_read_data($image->get_image_filename(), 0, true);
|
||||
$exif = @exif_read_data($image->get_image_filename(), "IFD0", true);
|
||||
if ($exif) {
|
||||
$head = "";
|
||||
foreach ($exif as $key => $section) {
|
||||
|
@ -3,7 +3,7 @@ use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class SVGFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["svg"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_SVG];
|
||||
|
||||
/** @var SVGFileHandlerTheme */
|
||||
protected $theme;
|
||||
@ -16,7 +16,7 @@ class SVGFileHandler extends DataHandlerExtension
|
||||
$image = Image::by_id($id);
|
||||
$hash = $image->hash;
|
||||
|
||||
$page->set_type("image/svg+xml");
|
||||
$page->set_type(MIME_TYPE_SVG);
|
||||
$page->set_mode(PageMode::DATA);
|
||||
|
||||
$sanitizer = new Sanitizer();
|
||||
@ -67,7 +67,7 @@ class SVGFileHandler extends DataHandlerExtension
|
||||
|
||||
protected function check_contents(string $file): bool
|
||||
{
|
||||
if (getMimeType($file)!="image/svg+xml") {
|
||||
if (get_mime($file)!==MIME_TYPE_SVG) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,30 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
abstract class VideoFileHandlerConfig
|
||||
{
|
||||
public const PLAYBACK_AUTOPLAY = "video_playback_autoplay";
|
||||
public const PLAYBACK_LOOP = "video_playback_loop";
|
||||
public const ENABLED_FORMATS = "video_enabled_formats";
|
||||
}
|
||||
|
||||
class VideoFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_MIME = [
|
||||
'video/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/flv',
|
||||
'video/x-flv'
|
||||
public const SUPPORTED_MIME = [
|
||||
MIME_TYPE_ASF,
|
||||
MIME_TYPE_AVI,
|
||||
MIME_TYPE_FLASH_VIDEO,
|
||||
MIME_TYPE_MKV,
|
||||
MIME_TYPE_MP4_VIDEO,
|
||||
MIME_TYPE_OGG_VIDEO,
|
||||
MIME_TYPE_QUICKTIME,
|
||||
MIME_TYPE_WEBM,
|
||||
];
|
||||
protected $SUPPORTED_EXT = ["flv", "mp4", "m4v", "ogv", "webm"];
|
||||
protected $SUPPORTED_MIME = self::SUPPORTED_MIME;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$config->set_default_bool('video_playback_autoplay', true);
|
||||
$config->set_default_bool('video_playback_loop', true);
|
||||
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, true);
|
||||
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_LOOP, true);
|
||||
$config->set_default_array(
|
||||
VideoFileHandlerConfig::ENABLED_FORMATS,
|
||||
[MIME_TYPE_FLASH_VIDEO, MIME_TYPE_MP4_VIDEO, MIME_TYPE_OGG_VIDEO, MIME_TYPE_WEBM]
|
||||
);
|
||||
}
|
||||
|
||||
private function get_options(): array
|
||||
{
|
||||
$output = [];
|
||||
foreach ($this->SUPPORTED_MIME as $format) {
|
||||
$output[MIME_TYPE_MAP[$format][MIME_TYPE_MAP_NAME]] = $format;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$sb = new SetupBlock("Video Options");
|
||||
$sb->add_bool_option("video_playback_autoplay", "Autoplay: ");
|
||||
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, "Autoplay: ");
|
||||
$sb->add_label("<br>");
|
||||
$sb->add_bool_option("video_playback_loop", "Loop: ");
|
||||
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_LOOP, "Loop: ");
|
||||
$sb->add_label("<br>Enabled Formats:");
|
||||
$sb->add_multichoice_option(VideoFileHandlerConfig::ENABLED_FORMATS, $this->get_options());
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
@ -80,6 +105,19 @@ class VideoFileHandler extends DataHandlerExtension
|
||||
}
|
||||
}
|
||||
|
||||
protected function supported_ext(string $ext): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
$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 true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function create_thumb(string $hash, string $type): bool
|
||||
{
|
||||
return Media::create_thumbnail_ffmpeg($hash);
|
||||
@ -87,6 +125,18 @@ class VideoFileHandler extends DataHandlerExtension
|
||||
|
||||
protected function check_contents(string $tmpname): bool
|
||||
{
|
||||
return in_array(getMimeType($tmpname), $this->SUPPORTED_MIME);
|
||||
global $config;
|
||||
|
||||
if (file_exists($tmpname)) {
|
||||
$mime = get_mime($tmpname);
|
||||
|
||||
$enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS);
|
||||
foreach ($enabled_formats as $format) {
|
||||
if (in_array($mime, MIME_TYPE_MAP[$format][MIME_TYPE_MAP_MIME])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -9,18 +9,28 @@ class VideoFileHandlerTheme extends Themelet
|
||||
$thumb_url = make_http($image->get_thumb_link()); //used as fallback image
|
||||
$ext = strtolower($image->get_ext());
|
||||
$full_url = make_http($ilink);
|
||||
$autoplay = $config->get_bool("video_playback_autoplay");
|
||||
$loop = $config->get_bool("video_playback_loop");
|
||||
$autoplay = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY);
|
||||
$loop = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_LOOP);
|
||||
$player = make_link('vendor/bower-asset/mediaelement/build/flashmediaelement.swf');
|
||||
|
||||
$width="auto";
|
||||
if ($image->width>1) {
|
||||
$width = $image->width."px";
|
||||
}
|
||||
$height="auto";
|
||||
if ($image->height>1) {
|
||||
$height = $image->height."px";
|
||||
}
|
||||
|
||||
$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
|
||||
$supportedExts = ['mp4' => 'video/mp4', 'm4v' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm', 'flv' => 'video/flv'];
|
||||
if (array_key_exists($ext, $supportedExts)) {
|
||||
$mime = get_mime_for_extension($ext);
|
||||
|
||||
if (in_array($mime, VideoFileHandler::SUPPORTED_MIME)) {
|
||||
//FLV isn't supported by <video>, but it should always fallback to the flash-based method.
|
||||
if ($ext == "webm") {
|
||||
//Several browsers still lack WebM support sadly: http://caniuse.com/#feat=webm
|
||||
if ($mime == MIME_TYPE_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]-->";
|
||||
}
|
||||
|
||||
@ -40,7 +50,7 @@ class VideoFileHandlerTheme extends Themelet
|
||||
<img alt='thumb' src=\"{$thumb_url}\" />
|
||||
</object>";
|
||||
|
||||
if ($ext == "flv") {
|
||||
if ($mime == MIME_TYPE_FLASH_VIDEO) {
|
||||
//FLV doesn't support <video>.
|
||||
$html .= $html_fallback;
|
||||
} else {
|
||||
@ -49,8 +59,8 @@ class VideoFileHandlerTheme extends Themelet
|
||||
|
||||
$html .= "
|
||||
<video controls class='shm-main-image' id='main_image' alt='main image' poster='$thumb_url' {$autoplay} {$loop}
|
||||
style='max-width: 100%'>
|
||||
<source src='{$ilink}' type='{$supportedExts[$ext]}'>
|
||||
style='height: $height; width: $width; max-width: 100%'>
|
||||
<source src='{$ilink}' type='{$mime}'>
|
||||
|
||||
<!-- If browser doesn't support filetype, fallback to flash -->
|
||||
{$html_fallback}
|
||||
@ -60,7 +70,7 @@ class VideoFileHandlerTheme extends Themelet
|
||||
}
|
||||
} else {
|
||||
//This should never happen, but just in case let's have a fallback..
|
||||
$html = "Video type '$ext' not recognised";
|
||||
$html = "Video type '$mime' not recognised";
|
||||
}
|
||||
$page->add_block(new Block("Video", $html, "main", 10));
|
||||
}
|
||||
|
@ -9,5 +9,6 @@ class HelpPagesInfo extends ExtensionInfo
|
||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||
public $license = self::LICENSE_WTFPL;
|
||||
public $description = "Provides documentation screens";
|
||||
public $visibility = self::VISIBLE_HIDDEN;
|
||||
public $core = true;
|
||||
}
|
||||
|
16
ext/help_pages/test.php
Normal file
16
ext/help_pages/test.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
class HelpPagesTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function test_list()
|
||||
{
|
||||
send_event(new HelpPageListBuildingEvent());
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function test_page()
|
||||
{
|
||||
send_event(new HelpPageBuildingEvent("test"));
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
BIN
ext/home/counters/femcounter/0.png
Normal file
BIN
ext/home/counters/femcounter/0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
BIN
ext/home/counters/femcounter/1.png
Normal file
BIN
ext/home/counters/femcounter/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
ext/home/counters/femcounter/2.png
Normal file
BIN
ext/home/counters/femcounter/2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
BIN
ext/home/counters/femcounter/3.png
Normal file
BIN
ext/home/counters/femcounter/3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
BIN
ext/home/counters/femcounter/4.png
Normal file
BIN
ext/home/counters/femcounter/4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -5,6 +5,22 @@ class Home extends Extension
|
||||
/** @var HomeTheme */
|
||||
protected $theme;
|
||||
|
||||
private $femDimensions = array(
|
||||
array(23, 64),
|
||||
array(14, 86),
|
||||
array(31, 63),
|
||||
array(37, 100),
|
||||
array(24, 90)
|
||||
);
|
||||
|
||||
private $femTags = array(
|
||||
"Hatsune_Miku",
|
||||
"Monika",
|
||||
"Violet_Parr",
|
||||
"Keith_Kogane",
|
||||
"Rin_Kagamine",
|
||||
);
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $config, $page;
|
||||
@ -34,6 +50,27 @@ class Home extends Extension
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
private function addCountToBlankImage($charId, $digit)
|
||||
{
|
||||
$font = realpath('ext/home/vga.ttf');
|
||||
$file = "ext/home/counters/femcounter/$charId.png";
|
||||
$img = imagecreatefrompng($file);
|
||||
$black = imagecolorallocate($img, 0, 0, 0);
|
||||
$x = $this->femDimensions[$charId][0];
|
||||
$y = $this->femDimensions[$charId][1];
|
||||
imagettftext($img, 20, 0, $x, $y + 20, $black, $font, $digit);
|
||||
imagetruecolortopalette($img, true, 16);
|
||||
imagesavealpha($img, true);
|
||||
imagecolortransparent($img, imagecolorat($img, 0, 0));
|
||||
ob_start();
|
||||
imagegif($img);
|
||||
$image_data = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$data = base64_encode($image_data);
|
||||
imagedestroy($img);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
private function get_body()
|
||||
{
|
||||
@ -48,14 +85,23 @@ class Home extends Extension
|
||||
$counter_dir = $config->get_string('home_counter', 'default');
|
||||
|
||||
$total = Image::count_images();
|
||||
$streak = Image::count_upload_streak();
|
||||
$strtotal = "$total";
|
||||
$num_comma = number_format($total);
|
||||
$streak_comma = number_format($streak);
|
||||
|
||||
$counter_text = "";
|
||||
$length = strlen($strtotal);
|
||||
for ($n=0; $n<$length; $n++) {
|
||||
$cur = $strtotal[$n];
|
||||
$counter_text .= " <img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' /> ";
|
||||
if ($counter_dir === 'femcounter') {
|
||||
$charId = $n % count($this->femTags);
|
||||
$base64url = $this->addCountToBlankImage($charId, $cur);
|
||||
$tag = $this->femTags[$charId];
|
||||
$counter_text .= " <a href='$base_href/post/list/$tag/1'><img alt='$cur' title='$tag' src='data:image/gif;base64,$base64url' /></a> ";
|
||||
} else {
|
||||
$counter_text .= " <img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' /> ";
|
||||
}
|
||||
}
|
||||
|
||||
// get the homelinks and process them
|
||||
@ -74,6 +120,6 @@ class Home extends Extension
|
||||
$main_links = format_text($main_links);
|
||||
$main_text = $config->get_string('home_text', '');
|
||||
|
||||
return $this->theme->build_body($sitename, $main_links, $main_text, $contact_link, $num_comma, $counter_text);
|
||||
return $this->theme->build_body($sitename, $main_links, $main_text, $contact_link, $num_comma, $counter_text, $streak_comma);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ div#front-page div#links a {margin: 0 0.5em;}
|
||||
div#front-page li {list-style-type: none; margin: 0;}
|
||||
@media (max-width: 800px) {
|
||||
div#front-page h1 {font-size: 3em; margin-top: 0.5em; margin-bottom: 0.5em;}
|
||||
#counter {display: none;}
|
||||
/*#counter {display: none;}*/
|
||||
}
|
||||
|
||||
div#front-page > #search > form { margin: 0 auto; }
|
||||
|
@ -50,7 +50,7 @@ EOD
|
||||
<div class='space' id='foot'>
|
||||
<small><small>
|
||||
$contact_link Serving $num_comma posts –
|
||||
Running <a href='http://code.shishnet.org/shimmie2/'>Shimmie2</a>
|
||||
Running <a href='https://code.shishnet.org/shimmie2/'>Shimmie2</a>
|
||||
</small></small>
|
||||
</div>
|
||||
</div>";
|
||||
|
BIN
ext/home/vga.ttf
Normal file
BIN
ext/home/vga.ttf
Normal file
Binary file not shown.
@ -8,6 +8,7 @@ abstract class ImageConfig
|
||||
const THUMB_SCALING = 'thumb_scaling';
|
||||
const THUMB_QUALITY = 'thumb_quality';
|
||||
const THUMB_TYPE = 'thumb_type';
|
||||
const THUMB_FIT = 'thumb_fit';
|
||||
|
||||
const SHOW_META = 'image_show_meta';
|
||||
const ILINK = 'image_ilink';
|
||||
|
@ -10,6 +10,6 @@ class ImageIOInfo extends ExtensionInfo
|
||||
public $authors = [self::SHISH_NAME=> self::SHISH_EMAIL, "jgen"=>"jgen.tech@gmail.com"];
|
||||
public $license = self::LICENSE_GPLV2;
|
||||
public $description = "Handle the image database";
|
||||
public $visibility = self::VISIBLE_ADMIN;
|
||||
public $visibility = self::VISIBLE_HIDDEN;
|
||||
public $core = true;
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ class ImageIO extends Extension
|
||||
];
|
||||
|
||||
const THUMBNAIL_TYPES = [
|
||||
'JPEG' => "jpg",
|
||||
'WEBP (Not IE/Safari compatible)' => "webp"
|
||||
'JPEG' => EXTENSION_JPG,
|
||||
'WEBP (Not IE/Safari compatible)' => EXTENSION_WEBP
|
||||
];
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
@ -32,7 +32,8 @@ class ImageIO extends Extension
|
||||
$config->set_default_int(ImageConfig::THUMB_HEIGHT, 192);
|
||||
$config->set_default_int(ImageConfig::THUMB_SCALING, 100);
|
||||
$config->set_default_int(ImageConfig::THUMB_QUALITY, 75);
|
||||
$config->set_default_string(ImageConfig::THUMB_TYPE, 'jpg');
|
||||
$config->set_default_string(ImageConfig::THUMB_TYPE, EXTENSION_JPG);
|
||||
$config->set_default_string(ImageConfig::THUMB_FIT, Media::RESIZE_TYPE_FIT);
|
||||
|
||||
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||
$config->set_default_bool(ImageConfig::SHOW_META, false);
|
||||
@ -53,17 +54,13 @@ class ImageIO extends Extension
|
||||
if ($image) {
|
||||
send_event(new ImageDeletionEvent($image));
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
if (isset($_SERVER['HTTP_REFERER']) && !strstr($_SERVER['HTTP_REFERER'], 'post/view')) {
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
} else {
|
||||
$page->set_redirect(make_link("post/list"));
|
||||
}
|
||||
$page->set_redirect(referer_or(make_link("post/list"), ['post/view']));
|
||||
}
|
||||
}
|
||||
} elseif ($event->page_matches("image/replace")) {
|
||||
global $page, $user;
|
||||
if ($user->can(Permissions::REPLACE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
|
||||
$image = Image::by_id($_POST['image_id']);
|
||||
$image = Image::by_id(int_escape($_POST['image_id']));
|
||||
if ($image) {
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link('upload/replace/'.$image->id));
|
||||
@ -219,35 +216,41 @@ class ImageIO extends Extension
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$sb = new SetupBlock("Image Options");
|
||||
$sb->start_table();
|
||||
$sb->position = 30;
|
||||
// advanced only
|
||||
//$sb->add_text_option(ImageConfig::ILINK, "Image link: ");
|
||||
//$sb->add_text_option(ImageConfig::TLINK, "<br>Thumbnail link: ");
|
||||
$sb->add_text_option(ImageConfig::TIP, "Image tooltip: ");
|
||||
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "<br>Upload collision handler: ");
|
||||
$sb->add_text_option(ImageConfig::TIP, "Image tooltip", true);
|
||||
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "Upload collision handler", true);
|
||||
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||
$sb->add_bool_option(ImageConfig::SHOW_META, "<br>Show metadata: ");
|
||||
$sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true);
|
||||
}
|
||||
|
||||
$sb->end_table();
|
||||
$event->panel->add_block($sb);
|
||||
|
||||
$sb = new SetupBlock("Thumbnailing");
|
||||
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine: ");
|
||||
$sb->add_label("<br>");
|
||||
$sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype: ");
|
||||
$sb->start_table();
|
||||
$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_label("<br>Size ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_WIDTH);
|
||||
$sb->add_label(" x ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_HEIGHT);
|
||||
$sb->add_label(" px at ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_QUALITY);
|
||||
$sb->add_label(" % quality ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_WIDTH, "Max Width", true);
|
||||
$sb->add_int_option(ImageConfig::THUMB_HEIGHT, "Max Height", true);
|
||||
|
||||
$sb->add_label("<br>High-DPI scaling ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_SCALING);
|
||||
$sb->add_label("%");
|
||||
$options = [];
|
||||
foreach (MediaEngine::RESIZE_TYPE_SUPPORT[$config->get_string(ImageConfig::THUMB_ENGINE)] as $type) {
|
||||
$options[$type] = $type;
|
||||
}
|
||||
|
||||
$sb->add_choice_option(ImageConfig::THUMB_FIT, $options, "Fit", true);
|
||||
|
||||
$sb->add_int_option(ImageConfig::THUMB_QUALITY, "Quality", true);
|
||||
$sb->add_int_option(ImageConfig::THUMB_SCALING, "High-DPI Scale %", true);
|
||||
|
||||
$sb->end_table();
|
||||
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
@ -275,11 +278,7 @@ class ImageIO extends Extension
|
||||
if (!is_null($image)) {
|
||||
if ($type == "thumb") {
|
||||
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
||||
$page->set_type(MIME_TYPE_MAP[$ext]);
|
||||
} else {
|
||||
$page->set_type("image/jpeg");
|
||||
}
|
||||
$page->set_type(get_mime_for_extension($ext));
|
||||
|
||||
$file = $image->get_thumb_filename();
|
||||
} else {
|
||||
@ -287,6 +286,12 @@ class ImageIO extends Extension
|
||||
$file = $image->get_image_filename();
|
||||
}
|
||||
|
||||
if (!file_exists($file)) {
|
||||
http_response_code(404);
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) {
|
||||
$if_modified_since = preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]);
|
||||
} else {
|
||||
|
@ -20,4 +20,23 @@ class ImageIOTest extends ShimmiePHPUnitTestCase
|
||||
$page = $this->get_page("thumb/$image_id/moo.jpg");
|
||||
$this->assertEquals(200, $page->code);
|
||||
}
|
||||
|
||||
public function testDeleteRequest()
|
||||
{
|
||||
$this->log_in_as_admin();
|
||||
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
|
||||
$_POST['image_id'] = "$image_id";
|
||||
send_event(new PageRequestEvent("image/delete"));
|
||||
$this->assertTrue(true); // FIXME: assert image was deleted?
|
||||
}
|
||||
|
||||
public function testReplaceRequest()
|
||||
{
|
||||
global $page;
|
||||
$this->log_in_as_admin();
|
||||
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
|
||||
$_POST['image_id'] = "$image_id";
|
||||
send_event(new PageRequestEvent("image/replace"));
|
||||
$this->assertEquals("redirect", $page->mode);
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ class ImageBan extends Extension
|
||||
}
|
||||
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
}
|
||||
} elseif ($event->get_arg(0) == "remove") {
|
||||
$user->ensure_authed();
|
||||
@ -113,7 +113,7 @@ class ImageBan extends Extension
|
||||
send_event(new RemoveImageHashBanEvent($input['d_hash']));
|
||||
$page->flash("Image ban removed");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
} elseif ($event->get_arg(0) == "list") {
|
||||
$t = new HashBanTable($database->raw_db());
|
||||
$t->token = $user->get_auth_token();
|
||||
|
@ -3,6 +3,13 @@ class ImageBanTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
private $hash = "feb01bab5698a11dd87416724c7a89e3";
|
||||
|
||||
public function testPages()
|
||||
{
|
||||
$this->log_in_as_admin();
|
||||
$page = $this->get_page("image_hash_ban/list");
|
||||
$this->assertEquals(200, $page->code);
|
||||
}
|
||||
|
||||
public function testBan()
|
||||
{
|
||||
$this->log_in_as_admin();
|
||||
|
@ -245,11 +245,18 @@ class Index extends Extension
|
||||
Image::$order_sql = "images.$ord $sort";
|
||||
$event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag
|
||||
} elseif (preg_match("/^order[=|:]random[_]([0-9]{1,4})$/i", $event->term, $matches)) {
|
||||
//order[=|:]random requires a seed to avoid duplicates
|
||||
//since the tag can't be changed during the parseevent, we instead generate the seed during submit using js
|
||||
// requires a seed to avoid duplicates
|
||||
// since the tag can't be changed during the parseevent, we instead generate the seed during submit using js
|
||||
$seed = $matches[1];
|
||||
Image::$order_sql = "RAND($seed)";
|
||||
$event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag
|
||||
} elseif (preg_match("/^order[=|:]dailyshuffle$/i", $event->term, $matches)) {
|
||||
// will use today's date as seed, thus allowing for a dynamic randomized list without outside intervention.
|
||||
// This way the list will change every day, giving a more dynamic feel to the imageboard.
|
||||
// recommended to change homepage to "post/list/order:dailyshuffle/1"
|
||||
$seed = date("Ymd");
|
||||
Image::$order_sql = "RAND($seed)";
|
||||
$event->add_querylet(new Querylet("1=1"));
|
||||
}
|
||||
|
||||
$this->stpen++;
|
||||
|
@ -190,4 +190,17 @@ class IndexTest extends ShimmiePHPUnitTestCase
|
||||
// negative tag alone, should work
|
||||
$this->assert_search_results(["-pbx"], [$image_ids[1]]);
|
||||
}
|
||||
|
||||
// This isn't really an index thing, we just want to test this from
|
||||
// SOMEWHERE because the default theme doesn't use them.
|
||||
public function test_nav()
|
||||
{
|
||||
send_event(new UserLoginEvent(User::by_name(self::$user_name)));
|
||||
send_event(new PageNavBuildingEvent());
|
||||
// just a few common parents
|
||||
foreach (["help", "posts", "system", "user"] as $parent) {
|
||||
send_event(new PageSubNavBuildingEvent($parent));
|
||||
}
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class LinkImageTheme extends Themelet
|
||||
<table><tr>
|
||||
|
||||
<td><fieldset>
|
||||
<legend><a href='http://en.wikipedia.org/wiki/Bbcode' target='_blank'>BBCode</a></legend>
|
||||
<legend><a href='https://en.wikipedia.org/wiki/Bbcode' target='_blank'>BBCode</a></legend>
|
||||
<table>
|
||||
".
|
||||
$this->link_code("Link", $this->url($post_link, $text_link, "ubb"), "ubb_text-link").
|
||||
@ -26,7 +26,7 @@ class LinkImageTheme extends Themelet
|
||||
</fieldset></td>
|
||||
|
||||
<td><fieldset>
|
||||
<legend><a href='http://en.wikipedia.org/wiki/Html' target='_blank'>HTML</a></legend>
|
||||
<legend><a href='https://en.wikipedia.org/wiki/Html' target='_blank'>HTML</a></legend>
|
||||
<table>
|
||||
".
|
||||
$this->link_code("Link", $this->url($post_link, $text_link, "html"), "html_text-link").
|
||||
|
@ -234,11 +234,11 @@ class LogDatabase extends Extension
|
||||
{
|
||||
$sb = new SetupBlock("Logging (Database)");
|
||||
$sb->add_choice_option("log_db_priority", [
|
||||
"Debug" => SCORE_LOG_DEBUG,
|
||||
"Info" => SCORE_LOG_INFO,
|
||||
"Warning" => SCORE_LOG_WARNING,
|
||||
"Error" => SCORE_LOG_ERROR,
|
||||
"Critical" => SCORE_LOG_CRITICAL,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_INFO] => SCORE_LOG_INFO,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_WARNING] => SCORE_LOG_WARNING,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_ERROR] => SCORE_LOG_ERROR,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_CRITICAL] => SCORE_LOG_CRITICAL,
|
||||
], "Debug Level: ");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ class MediaResizeEvent extends Event
|
||||
public $target_height;
|
||||
public $target_quality;
|
||||
public $minimize;
|
||||
public $ignore_aspect_ratio;
|
||||
public $allow_upscale;
|
||||
public $resize_type;
|
||||
|
||||
public function __construct(
|
||||
String $engine,
|
||||
@ -21,7 +21,7 @@ class MediaResizeEvent extends Event
|
||||
string $output_path,
|
||||
int $target_width,
|
||||
int $target_height,
|
||||
bool $ignore_aspect_ratio = false,
|
||||
string $resize_type = Media::RESIZE_TYPE_FIT,
|
||||
string $target_format = null,
|
||||
int $target_quality = 80,
|
||||
bool $minimize = false,
|
||||
@ -38,8 +38,8 @@ class MediaResizeEvent extends Event
|
||||
$this->target_format = $target_format;
|
||||
$this->target_quality = $target_quality;
|
||||
$this->minimize = $minimize;
|
||||
$this->ignore_aspect_ratio = $ignore_aspect_ratio;
|
||||
$this->allow_upscale = $allow_upscale;
|
||||
$this->resize_type = $resize_type;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,28 +26,32 @@ class Media extends Extension
|
||||
|
||||
const LOSSLESS_FORMATS = [
|
||||
self::WEBP_LOSSLESS,
|
||||
"png",
|
||||
"psd",
|
||||
"bmp",
|
||||
"ico",
|
||||
"cur",
|
||||
"ani",
|
||||
"gif"
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_PSD,
|
||||
EXTENSION_BMP,
|
||||
EXTENSION_ICO,
|
||||
EXTENSION_CUR,
|
||||
EXTENSION_ANI,
|
||||
EXTENSION_GIF
|
||||
|
||||
];
|
||||
|
||||
const ALPHA_FORMATS = [
|
||||
self::WEBP_LOSSLESS,
|
||||
self::WEBP_LOSSY,
|
||||
"webp",
|
||||
"png",
|
||||
EXTENSION_WEBP,
|
||||
EXTENSION_PNG,
|
||||
];
|
||||
|
||||
const FORMAT_ALIASES = [
|
||||
"tif" => "tiff",
|
||||
"jpeg" => "jpg",
|
||||
EXTENSION_TIF => EXTENSION_TIFF,
|
||||
EXTENSION_JPEG => EXTENSION_JPG,
|
||||
];
|
||||
|
||||
const RESIZE_TYPE_FIT = "Fit";
|
||||
const RESIZE_TYPE_FIT_BLUR = "Fit Blur";
|
||||
const RESIZE_TYPE_FILL = "Fill";
|
||||
const RESIZE_TYPE_STRETCH = "Stretch";
|
||||
|
||||
//RIFF####WEBPVP8?..............ANIM
|
||||
private const WEBP_ANIMATION_HEADER =
|
||||
@ -118,7 +122,7 @@ class Media extends Extension
|
||||
$sb->add_text_option(MediaConfig::FFMPEG_PATH, "ffmpeg", true);
|
||||
$sb->add_text_option(MediaConfig::FFPROBE_PATH, "ffprobe", true);
|
||||
|
||||
$sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "Mem limit: ", true);
|
||||
$sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "Mem limit", true);
|
||||
$sb->end_table();
|
||||
|
||||
$event->panel->add_block($sb);
|
||||
@ -206,6 +210,10 @@ class Media extends Extension
|
||||
*/
|
||||
public function onMediaResize(MediaResizeEvent $event)
|
||||
{
|
||||
if (!in_array($event->resize_type, MediaEngine::RESIZE_TYPE_SUPPORT[MediaEngine::IMAGICK])) {
|
||||
throw new MediaException("Resize type $event->resize_type not supported by selected media engine $event->engine");
|
||||
}
|
||||
|
||||
switch ($event->engine) {
|
||||
case MediaEngine::GD:
|
||||
$info = getimagesize($event->input_path);
|
||||
@ -220,7 +228,7 @@ class Media extends Extension
|
||||
$event->target_height,
|
||||
$event->output_path,
|
||||
$event->target_format,
|
||||
$event->ignore_aspect_ratio,
|
||||
$event->resize_type,
|
||||
$event->target_quality,
|
||||
$event->allow_upscale
|
||||
);
|
||||
@ -236,7 +244,7 @@ class Media extends Extension
|
||||
$event->target_height,
|
||||
$event->output_path,
|
||||
$event->target_format,
|
||||
$event->ignore_aspect_ratio,
|
||||
$event->resize_type,
|
||||
$event->target_quality,
|
||||
$event->minimize,
|
||||
$event->allow_upscale
|
||||
@ -321,7 +329,7 @@ class Media extends Extension
|
||||
* We need to consider the size that we are GOING TO instead.
|
||||
*
|
||||
* The factor of 2.5 is simply a rough guideline.
|
||||
* http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize
|
||||
* https://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize
|
||||
*
|
||||
* @param array $info The output of getimagesize() for the source file in question.
|
||||
* @return int The number of bytes an image resize operation is estimated to use.
|
||||
@ -356,6 +364,7 @@ class Media extends Extension
|
||||
}
|
||||
|
||||
$inname = warehouse_path(Image::IMAGE_DIR, $hash);
|
||||
$tmpname = tempnam("/tmp", "shimmie_ffmpeg_thumb");
|
||||
$outname = warehouse_path(Image::THUMBNAIL_DIR, $hash);
|
||||
|
||||
$orig_size = self::video_size($inname);
|
||||
@ -363,7 +372,7 @@ class Media extends Extension
|
||||
|
||||
$codec = "mjpeg";
|
||||
$quality = $config->get_int(ImageConfig::THUMB_QUALITY);
|
||||
if ($config->get_string(ImageConfig::THUMB_TYPE) == "webp") {
|
||||
if ($config->get_string(ImageConfig::THUMB_TYPE) == EXTENSION_WEBP) {
|
||||
$codec = "libwebp";
|
||||
} else {
|
||||
// mjpeg quality ranges from 2-31, with 2 being the best quality.
|
||||
@ -376,12 +385,11 @@ class Media extends Extension
|
||||
$args = [
|
||||
escapeshellarg($ffmpeg),
|
||||
"-y", "-i", escapeshellarg($inname),
|
||||
"-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}",
|
||||
"-vf", "thumbnail",
|
||||
"-f", "image2",
|
||||
"-vframes", "1",
|
||||
"-c:v", $codec,
|
||||
"-q:v", $quality,
|
||||
escapeshellarg($outname),
|
||||
"-c:v", "png",
|
||||
escapeshellarg($tmpname),
|
||||
];
|
||||
|
||||
$cmd = escapeshellcmd(implode(" ", $args));
|
||||
@ -390,6 +398,10 @@ class Media extends Extension
|
||||
|
||||
if ((int)$ret == (int)0) {
|
||||
log_debug('media', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
|
||||
create_scaled_image($tmpname, $outname, $scaled_size, "png");
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
log_error('media', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
@ -436,7 +448,7 @@ class Media extends Extension
|
||||
switch ($format) {
|
||||
case self::WEBP_LOSSLESS:
|
||||
case self::WEBP_LOSSY:
|
||||
return "webp";
|
||||
return EXTENSION_WEBP;
|
||||
default:
|
||||
return $format;
|
||||
}
|
||||
@ -445,7 +457,7 @@ class Media extends Extension
|
||||
// private static function image_save_imagick(Imagick $image, string $path, string $format, int $output_quality = 80, bool $minimize)
|
||||
// {
|
||||
// switch ($format) {
|
||||
// case "png":
|
||||
// case EXTENSION_PNG:
|
||||
// $result = $image->setOption('png:compression-level', 9);
|
||||
// if ($result !== true) {
|
||||
// throw new GraphicsException("Could not set png compression option");
|
||||
@ -555,7 +567,7 @@ class Media extends Extension
|
||||
return true;
|
||||
}
|
||||
switch ($format) {
|
||||
case "webp":
|
||||
case EXTENSION_WEBP:
|
||||
return self::is_lossless_webp($filename);
|
||||
break;
|
||||
}
|
||||
@ -569,7 +581,7 @@ class Media extends Extension
|
||||
int $new_height,
|
||||
string $output_filename,
|
||||
string $output_type = null,
|
||||
bool $ignore_aspect_ratio = false,
|
||||
string $resize_type = self::RESIZE_TYPE_FIT,
|
||||
int $output_quality = 80,
|
||||
bool $minimize = false,
|
||||
bool $allow_upscale = true
|
||||
@ -586,7 +598,7 @@ class Media extends Extension
|
||||
$output_type = $input_type;
|
||||
}
|
||||
|
||||
if ($output_type=="webp" && self::is_lossless($input_path, $input_type)) {
|
||||
if ($output_type==EXTENSION_WEBP && self::is_lossless($input_path, $input_type)) {
|
||||
$output_type = self::WEBP_LOSSLESS;
|
||||
}
|
||||
|
||||
@ -598,36 +610,58 @@ class Media extends Extension
|
||||
$input_type = $input_type . ":";
|
||||
}
|
||||
|
||||
|
||||
$resize_args = "";
|
||||
$resize_suffix = "";
|
||||
if (!$allow_upscale) {
|
||||
$resize_args .= "\>";
|
||||
$resize_suffix .= "\>";
|
||||
}
|
||||
if ($ignore_aspect_ratio) {
|
||||
$resize_args .= "\!";
|
||||
if ($resize_type==Media::RESIZE_TYPE_STRETCH) {
|
||||
$resize_suffix .= "\!";
|
||||
}
|
||||
|
||||
$args = "";
|
||||
$resize_arg = "-resize";
|
||||
if ($minimize) {
|
||||
$args .= "-strip ";
|
||||
$resize_arg = "-thumbnail";
|
||||
}
|
||||
|
||||
$file_arg = "${input_type}\"${input_path}[0]\"";
|
||||
|
||||
switch ($resize_type) {
|
||||
case Media::RESIZE_TYPE_FIT:
|
||||
case Media::RESIZE_TYPE_STRETCH:
|
||||
$args .= "${resize_arg} ${new_width}x${new_height}${resize_suffix} ${file_arg}";
|
||||
break;
|
||||
case Media::RESIZE_TYPE_FILL:
|
||||
$args .= "${resize_arg} ${new_width}x${new_height}\^ -gravity center -extent ${new_width}x${new_height} ${file_arg}";
|
||||
break;
|
||||
case Media::RESIZE_TYPE_FIT_BLUR:
|
||||
$blur_size = max(ceil(max($new_width, $new_height) / 25), 5);
|
||||
$args .= "${file_arg} ".
|
||||
"\( -clone 0 -resize ${new_width}x${new_height}\^ -gravity center -fill black -colorize 50% -extent ${new_width}x${new_height} -blur 0x${blur_size} \) ".
|
||||
"\( -clone 0 -resize ${new_width}x${new_height} \) ".
|
||||
"-delete 0 -gravity center -compose over -composite";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
switch ($output_type) {
|
||||
case Media::WEBP_LOSSLESS:
|
||||
$args .= '-define webp:lossless=true';
|
||||
break;
|
||||
case "png":
|
||||
case EXTENSION_PNG:
|
||||
$args .= '-define png:compression-level=9';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($minimize) {
|
||||
$args .= " -strip -thumbnail";
|
||||
} else {
|
||||
$args .= " -resize";
|
||||
}
|
||||
|
||||
$args .= " -quality ${output_quality} -background ${bg}";
|
||||
|
||||
|
||||
$output_ext = self::determine_ext($output_type);
|
||||
|
||||
$format = '"%s" %s %ux%u%s -quality %u -background %s %s"%s[0]" %s:"%s" 2>&1';
|
||||
$cmd = sprintf($format, $convert, $args, $new_width, $new_height, $resize_args, $output_quality, $bg, $input_type, $input_path, $output_ext, $output_filename);
|
||||
$format = '"%s" %s %s:"%s" 2>&1';
|
||||
$cmd = sprintf($format, $convert, $args, $output_ext, $output_filename);
|
||||
$cmd = str_replace("\"convert\"", "convert", $cmd); // quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27
|
||||
exec($cmd, $output, $ret);
|
||||
if ($ret != 0) {
|
||||
@ -657,7 +691,7 @@ class Media extends Extension
|
||||
int $new_height,
|
||||
string $output_filename,
|
||||
string $output_type = null,
|
||||
bool $ignore_aspect_ratio = false,
|
||||
string $resize_type = self::RESIZE_TYPE_FIT,
|
||||
int $output_quality = 80,
|
||||
bool $allow_upscale = true
|
||||
) {
|
||||
@ -668,19 +702,19 @@ class Media extends Extension
|
||||
/* If not specified, output to the same format as the original image */
|
||||
switch ($info[2]) {
|
||||
case IMAGETYPE_GIF:
|
||||
$output_type = "gif";
|
||||
$output_type = EXTENSION_GIF;
|
||||
break;
|
||||
case IMAGETYPE_JPEG:
|
||||
$output_type = "jpeg";
|
||||
$output_type = EXTENSION_JPEG;
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
$output_type = "png";
|
||||
$output_type = EXTENSION_PNG;
|
||||
break;
|
||||
case IMAGETYPE_WEBP:
|
||||
$output_type = "webp";
|
||||
$output_type = EXTENSION_WEBP;
|
||||
break;
|
||||
case IMAGETYPE_BMP:
|
||||
$output_type = "bmp";
|
||||
$output_type = EXTENSION_BMP;
|
||||
break;
|
||||
default:
|
||||
throw new MediaException("Failed to save the new image - Unsupported image type.");
|
||||
@ -693,7 +727,7 @@ class Media extends Extension
|
||||
throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)");
|
||||
}
|
||||
|
||||
if (!$ignore_aspect_ratio) {
|
||||
if ($resize_type==Media::RESIZE_TYPE_FIT) {
|
||||
list($new_width, $new_height) = get_scaled_by_aspect_ratio($width, $height, $new_width, $new_height);
|
||||
}
|
||||
if (!$allow_upscale &&
|
||||
@ -741,7 +775,7 @@ class Media extends Extension
|
||||
case IMAGETYPE_PNG:
|
||||
case IMAGETYPE_WEBP:
|
||||
//
|
||||
// More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
|
||||
// More info here: https://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
|
||||
//
|
||||
if (imagealphablending($image_resized, false) === false) {
|
||||
throw new MediaException("Unable to disable image alpha blending");
|
||||
@ -776,21 +810,21 @@ class Media extends Extension
|
||||
}
|
||||
|
||||
switch ($output_type) {
|
||||
case "bmp":
|
||||
case EXTENSION_BMP:
|
||||
$result = imagebmp($image_resized, $output_filename, true);
|
||||
break;
|
||||
case "webp":
|
||||
case EXTENSION_WEBP:
|
||||
case Media::WEBP_LOSSY:
|
||||
$result = imagewebp($image_resized, $output_filename, $output_quality);
|
||||
break;
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case EXTENSION_JPG:
|
||||
case EXTENSION_JPEG:
|
||||
$result = imagejpeg($image_resized, $output_filename, $output_quality);
|
||||
break;
|
||||
case "png":
|
||||
case EXTENSION_PNG:
|
||||
$result = imagepng($image_resized, $output_filename, 9);
|
||||
break;
|
||||
case "gif":
|
||||
case EXTENSION_GIF:
|
||||
$result = imagegif($image_resized, $output_filename);
|
||||
break;
|
||||
default:
|
||||
@ -816,7 +850,7 @@ class Media extends Extension
|
||||
$is_anim_gif = 0;
|
||||
if (($fh = @fopen($image_filename, 'rb'))) {
|
||||
try {
|
||||
//check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473)
|
||||
//check if gif is animated (via https://www.php.net/manual/en/function.imagecreatefromgif.php#104473)
|
||||
while (!feof($fh) && $is_anim_gif < 2) {
|
||||
$chunk = fread($fh, 1024 * 100);
|
||||
$is_anim_gif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x21]#s', $chunk, $matches);
|
||||
@ -904,7 +938,7 @@ class Media extends Extension
|
||||
*/
|
||||
public static function normalize_format(string $format, ?bool $lossless = null): ?string
|
||||
{
|
||||
if ($format == "webp") {
|
||||
if ($format == EXTENSION_WEBP) {
|
||||
if ($lossless === true) {
|
||||
$format = Media::WEBP_LOSSLESS;
|
||||
} else {
|
||||
@ -1016,7 +1050,7 @@ class Media extends Extension
|
||||
|
||||
$this->set_version(MediaConfig::VERSION, 2);
|
||||
|
||||
$database->beginTransaction();
|
||||
$database->begin_transaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,63 +15,81 @@ abstract class MediaEngine
|
||||
];
|
||||
public const OUTPUT_SUPPORT = [
|
||||
MediaEngine::GD => [
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"webp",
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_WEBP,
|
||||
Media::WEBP_LOSSY,
|
||||
],
|
||||
MediaEngine::IMAGICK => [
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"webp",
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_WEBP,
|
||||
Media::WEBP_LOSSY,
|
||||
Media::WEBP_LOSSLESS,
|
||||
],
|
||||
MediaEngine::FFMPEG => [
|
||||
"jpg",
|
||||
"webp",
|
||||
"png",
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_WEBP,
|
||||
EXTENSION_PNG,
|
||||
],
|
||||
MediaEngine::STATIC => [
|
||||
"jpg",
|
||||
EXTENSION_JPG,
|
||||
],
|
||||
];
|
||||
public const INPUT_SUPPORT = [
|
||||
MediaEngine::GD => [
|
||||
"bmp",
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"webp",
|
||||
EXTENSION_BMP,
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_WEBP,
|
||||
Media::WEBP_LOSSY,
|
||||
Media::WEBP_LOSSLESS,
|
||||
],
|
||||
MediaEngine::IMAGICK => [
|
||||
"bmp",
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"psd",
|
||||
"tiff",
|
||||
"webp",
|
||||
EXTENSION_BMP,
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_PSD,
|
||||
EXTENSION_TIFF,
|
||||
EXTENSION_WEBP,
|
||||
Media::WEBP_LOSSY,
|
||||
Media::WEBP_LOSSLESS,
|
||||
"ico",
|
||||
EXTENSION_ICO,
|
||||
],
|
||||
MediaEngine::FFMPEG => [
|
||||
"avi",
|
||||
"mkv",
|
||||
"webm",
|
||||
"mp4",
|
||||
"mov",
|
||||
"flv",
|
||||
EXTENSION_AVI,
|
||||
EXTENSION_MKV,
|
||||
EXTENSION_WEBM,
|
||||
EXTENSION_MP4,
|
||||
EXTENSION_MOV,
|
||||
EXTENSION_FLASH_VIDEO,
|
||||
],
|
||||
MediaEngine::STATIC => [
|
||||
"jpg",
|
||||
"gif",
|
||||
"png",
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_PNG,
|
||||
],
|
||||
];
|
||||
public const RESIZE_TYPE_SUPPORT = [
|
||||
MediaEngine::GD => [
|
||||
Media::RESIZE_TYPE_FIT,
|
||||
Media::RESIZE_TYPE_STRETCH
|
||||
],
|
||||
MediaEngine::IMAGICK => [
|
||||
Media::RESIZE_TYPE_FIT,
|
||||
Media::RESIZE_TYPE_FIT_BLUR,
|
||||
Media::RESIZE_TYPE_FILL,
|
||||
Media::RESIZE_TYPE_STRETCH,
|
||||
],
|
||||
MediaEngine::FFMPEG => [
|
||||
Media::RESIZE_TYPE_FIT
|
||||
],
|
||||
MediaEngine::STATIC => [
|
||||
Media::RESIZE_TYPE_FIT
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ class NotATag extends Extension
|
||||
["tag"=>$input['c_tag'], "redirect"=>$input['c_redirect']]
|
||||
);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
} elseif ($event->get_arg(0) == "remove") {
|
||||
$user->ensure_authed();
|
||||
$input = validate_input(["d_tag"=>"string"]);
|
||||
@ -123,7 +123,7 @@ class NotATag extends Extension
|
||||
);
|
||||
$page->flash("Image ban removed");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
} elseif ($event->get_arg(0) == "list") {
|
||||
$t = new NotATagTable($database->raw_db());
|
||||
$t->token = $user->get_auth_token();
|
||||
|
@ -355,7 +355,7 @@ class Notes extends Extension
|
||||
{
|
||||
global $database, $config;
|
||||
|
||||
$pageNumber = $event->try_page_num(1) - 1;
|
||||
$pageNumber = $event->try_page_num(1);
|
||||
|
||||
$notesPerPage = $config->get_int('notesNotesPerPage');
|
||||
|
||||
@ -383,7 +383,7 @@ class Notes extends Extension
|
||||
{
|
||||
global $config, $database;
|
||||
|
||||
$pageNumber = $event->try_page_num(1) - 1;
|
||||
$pageNumber = $event->try_page_num(1);
|
||||
|
||||
$requestsPerPage = $config->get_int('notesRequestsPerPage');
|
||||
|
||||
@ -430,7 +430,7 @@ class Notes extends Extension
|
||||
{
|
||||
global $config, $database;
|
||||
|
||||
$pageNumber = $event->try_page_num(1) - 1;
|
||||
$pageNumber = $event->try_page_num(1);
|
||||
|
||||
$historiesPerPage = $config->get_int('notesHistoriesPerPage');
|
||||
|
||||
|
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