Compare commits

..

No commits in common. "fembooru" and "v2.8.0" have entirely different histories.

185 changed files with 4127 additions and 3605 deletions

View File

@ -4,25 +4,27 @@ about: Create a report to help us improve
---
**Server Software**
(You can get all these stats from `http://<your site>/system_info`)
**Describe the bug**
A clear and concise description of what the bug is.
- 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**
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**What did you expect to happen?**
**Expected behavior**
A clear and concise description of what you expected to happen.
**What actually happened?**
**Screenshots**
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]

View File

@ -1,62 +0,0 @@
name: Create Release
on:
push:
tags:
- 'v*'
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Get version from tag
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
- name: Test version in sys_config
run: grep ${{ steps.get_version.outputs.VERSION }} core/sys_config.php
- name: Build
run: |
composer install --no-dev
cd ..
tar cvzf shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz shimmie2
zip -r shimmie2-${{ steps.get_version.outputs.VERSION }}.zip shimmie2
- name: Create Release
id: create_release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Shimmie ${{ steps.get_version.outputs.VERSION }}
body: Automated release from tags
draft: false
prerelease: false
- name: Upload Zip
id: upload-release-asset-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ../shimmie2-${{ steps.get_version.outputs.VERSION }}.zip
asset_name: shimmie2-${{ steps.get_version.outputs.VERSION }}.zip
asset_content_type: application/zip
- name: Upload Tar
id: upload-release-asset-tar
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ../shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz
asset_name: shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz
asset_content_type: application/gzip

View File

@ -32,7 +32,7 @@ jobs:
run: |
mkdir -p data/config
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
sudo systemctl start postgresql ;
sudo apt update && sudo apt-get install -y postgresql postgresql-client ;
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 ;

View File

@ -17,7 +17,8 @@
# rather than link to images/ha/hash and have an ugly filename,
# we link to images/hash/tags.ext; mod_rewrite splits things so
# that shimmie sees hash and the user sees tags.ext
RewriteRule ^_(images|thumbs)/([0-9a-f]{2})([0-9a-f]{30}).*$ data/$1/$2/$2$3 [L]
RewriteRule ^_images/([0-9a-f]{2})([0-9a-f]{30}).*$ data/images/$1/$1$2 [L]
RewriteRule ^_thumbs/([0-9a-f]{2})([0-9a-f]{30}).*$ data/thumbs/$1/$1$2 [L]
# any requests for files which don't physically exist should be handled by index.php
RewriteCond %{REQUEST_FILENAME} !-f

View File

@ -1,17 +1,7 @@
# "Build" shimmie (composer install - done in its own stage so that we don't
# need to include all the composer fluff in the final image)
FROM debian:stable-slim AS app
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
COPY composer.json composer.lock /app/
WORKDIR /app
RUN composer install --no-dev
COPY . /app/
# Tests in their own image. Really we should inherit from app and then
# `composer install` phpunit on top of that; but for some reason
# `composer install --no-dev && composer install` doesn't install dev
FROM debian:stable-slim AS tests
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
FROM debian:stable-slim
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 imagemagick
COPY composer.json composer.lock /app/
WORKDIR /app
RUN composer install
@ -25,7 +15,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 AS suexec
FROM debian:stable-slim
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; \
@ -39,11 +29,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 \
imagemagick 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 php-xdebug \
composer imagemagick vim zip unzip && \
rm -rf /var/lib/apt/lists/*
COPY --from=app /app /app
COPY --from=suexec /usr/local/bin/su-exec /usr/local/bin/su-exec
COPY --from=0 /app /app
COPY --from=1 /usr/local/bin/su-exec /usr/local/bin/su-exec
WORKDIR /app
CMD ["/bin/sh", "/app/tests/docker-init.sh"]

View File

@ -17,25 +17,24 @@
# Documentation
* [Install straight on disk](https://github.com/shish/shimmie2/wiki/Install)
* [Install in docker container](https://github.com/shish/shimmie2/wiki/Docker)
* [Upgrade process](https://github.com/shish/shimmie2/wiki/Upgrade)
* [Basic settings](https://github.com/shish/shimmie2/wiki/Settings)
* [Advanced config](https://github.com/shish/shimmie2/wiki/Advanced-Config)
* [Developer notes](https://github.com/shish/shimmie2/wiki/Development-Info)
* [High-performance notes](https://github.com/shish/shimmie2/wiki/Performance)
* [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)
# Contact
Email: webmaster at shishnet.org
Issue/Bug tracker: https://github.com/shish/shimmie2/issues
Issue/Bug tracker: http://github.com/shish/shimmie2/issues
# Licence
All code is released under the [GNU GPL Version 2](https://www.gnu.org/licenses/gpl-2.0.html) unless mentioned otherwise.
All code is released under the [GNU GPL Version 2](http://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

View File

@ -6,6 +6,10 @@
"minimum-stability" : "dev",
"repositories" : [
{
"type": "composer",
"url": "https://asset-packagist.org"
},
{
"type" : "package",
"package" : {
@ -21,29 +25,30 @@
],
"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" : "^2.0.0",
"shish/ffsphp" : "^1.0.0",
"shish/microcrud" : "^2.0.0",
"shish/microhtml" : "^2.0.0",
"shish/eventtracer-php" : "dev-master",
"shish/ffsphp" : "0.0.*",
"shish/microcrud" : "dev-master",
"shish/microhtml" : "^1.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",
@ -54,14 +59,9 @@
"ext-curl": "some extensions",
"ext-ctype": "some extensions",
"ext-json": "some extensions",
"ext-zip": "self-updater extension, bulk import/export",
"ext-zip": "self-updater extension",
"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"
}
}
}

1659
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,6 @@ abstract class PageMode
const DATA = 'data';
const PAGE = 'page';
const FILE = 'file';
const MANUAL = 'manual';
}
/**
@ -239,13 +238,16 @@ class BasePage
// ==============================================
public function send_headers(): void
/**
* Display the page according to the mode and data given.
*/
public function display(): void
{
if (!headers_sent()) {
header("HTTP/1.0 {$this->code} Shimmie");
header("Content-type: " . $this->type);
header("X-Powered-By: Shimmie-" . VERSION);
header("HTTP/1.0 {$this->code} Shimmie");
header("Content-type: " . $this->type);
header("X-Powered-By: Shimmie-" . VERSION);
if (!headers_sent()) {
foreach ($this->http_headers as $head) {
header($head);
}
@ -255,20 +257,8 @@ 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();
@ -404,7 +394,9 @@ 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",
],

View File

@ -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([EXTENSION_FLASH, EXTENSION_SVG, EXTENSION_MP3]); //List of thumbless filetypes
$extArr = array_flip(['swf', 'svg', 'mp3']); //List of thumbless filetypes
if (!isset($extArr[$image->ext])) {
$tsize = get_thumbnail_size($image->width, $image->height);
} else {

View File

@ -60,7 +60,7 @@ class Database
$this->connect_engine();
$this->engine->init($this->db);
$this->begin_transaction();
$this->beginTransaction();
}
private function connect_engine(): void
@ -78,14 +78,11 @@ class Database
} elseif ($db_proto === DatabaseDriver::SQLITE) {
$this->engine = new SQLite();
} else {
die_nicely(
'Unknown PDO driver: '.$db_proto,
"Please check that this is a valid driver, installing the PHP modules if needed"
);
die('Unknown PDO driver: '.$db_proto);
}
}
public function begin_transaction(): void
public function beginTransaction(): void
{
if ($this->transaction === false) {
$this->db->beginTransaction();
@ -93,14 +90,9 @@ class Database
}
}
public function is_transaction_open(): bool
{
return !is_null($this->db) && $this->transaction === true;
}
public function commit(): bool
{
if ($this->is_transaction_open()) {
if (!is_null($this->db) && $this->transaction === true) {
$this->transaction = false;
return $this->db->commit();
} else {
@ -110,7 +102,7 @@ class Database
public function rollback(): bool
{
if ($this->is_transaction_open()) {
if (!is_null($this->db) && $this->transaction === true) {
$this->transaction = false;
return $this->db->rollback();
} else {
@ -147,11 +139,6 @@ 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;

View File

@ -31,8 +31,6 @@ abstract class DBEngine
}
abstract public function set_timeout(PDO $db, int $time);
abstract public function get_version(PDO $db): string;
}
class MySQL extends DBEngine
@ -70,15 +68,12 @@ 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;
@ -92,9 +87,7 @@ class PostgreSQL extends DBEngine
} else {
$db->exec("SET application_name TO 'shimmie [local]';");
}
if (defined("DATABASE_TIMEOUT")) {
$this->set_timeout($db, DATABASE_TIMEOUT);
}
$this->set_timeout($db, DATABASE_TIMEOUT);
}
public function scoreql_to_sql(string $data): string
@ -117,11 +110,6 @@ 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
@ -228,9 +216,4 @@ 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];
}
}

View File

@ -108,26 +108,21 @@ class PageRequestEvent extends Event
if ($offset >= 0 && $offset < $this->arg_count) {
return $this->args[$offset];
} else {
$nm1 = $this->arg_count - 1;
throw new SCoreException("Requested an invalid page argument {$offset} / {$nm1}");
throw new SCoreException("Requested an invalid argument #$n");
}
}
/**
* 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
public function try_page_num(int $n): int
{
if ($this->count_args() > $n) {
$i = $this->get_arg($n);
if (is_numeric($i) && int_escape($i) > 0) {
return page_number($i, $max);
return int_escape($i);
} else {
return 0;
return 1;
}
} else {
return 0;
return 1;
}
}

View File

@ -7,7 +7,7 @@
* Also loads the theme object into $this->theme if available
*
* The original concept came from Artanis's Extension extension
* --> https://github.com/Artanis/simple-extension/tree/master
* --> http://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 = "https://code.shishnet.org/shimmie2/";
public const SHIMMIE_URL = "http://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_MIME = [];
protected $SUPPORTED_EXT = [];
protected function move_upload_to_archive(DataUploadEvent $event)
{
@ -298,11 +298,14 @@ abstract class DataHandlerExtension extends Extension
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
/* Check if we are replacing an image */
if (!is_null($event->replace_id)) {
if (array_key_exists('replace', $event->metadata) && isset($event->metadata['replace'])) {
/* 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($event->replace_id);
$existing = Image::by_id($image_id);
if (is_null($existing)) {
throw new UploadException("Image to replace does not exist!");
@ -317,25 +320,19 @@ 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($event->replace_id, $image));
$event->image_id = $event->replace_id;
send_event(new ImageReplaceEvent($image_id, $image));
$event->image_id = $image_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) {
@ -409,12 +406,10 @@ 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_for_file($filename);
}
if (empty($image->ext)) {
$image->ext = get_extension(getMimeType($filename));
} else {
$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'];
@ -427,20 +422,15 @@ abstract class DataHandlerExtension extends Extension
protected function supported_ext(string $ext): bool
{
return in_array(get_mime_for_extension($ext), $this->SUPPORTED_MIME);
return in_array(strtolower($ext), $this->SUPPORTED_EXT);
}
public static function get_all_supported_exts(): array
{
$arr = [];
foreach (getSubclassesOf("DataHandlerExtension") as $handler) {
$handler = (new $handler());
foreach ($handler->SUPPORTED_MIME as $mime) {
$arr = array_merge($arr, get_all_extension_for_mime($mime));
}
$arr = array_merge($arr, (new $handler())->SUPPORTED_EXT);
}
$arr = array_unique($arr);
return $arr;
}
}

View File

@ -1,448 +0,0 @@
<?php
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* MIME types and extension information and resolvers *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const EXTENSION_ANI = 'ani';
const EXTENSION_ASC = 'asc';
const EXTENSION_ASF = 'asf';
const EXTENSION_AVI = 'avi';
const EXTENSION_BMP = 'bmp';
const EXTENSION_BZIP = 'bz';
const EXTENSION_BZIP2 = 'bz2';
const EXTENSION_CBR = 'cbr';
const EXTENSION_CBZ = 'cbz';
const EXTENSION_CBT = 'cbt';
const EXTENSION_CBA = 'cbA';
const EXTENSION_CB7 = 'cb7';
const EXTENSION_CSS = 'css';
const EXTENSION_CSV = 'csv';
const EXTENSION_CUR = 'cur';
const EXTENSION_FLASH = 'swf';
const EXTENSION_FLASH_VIDEO = 'flv';
const EXTENSION_GIF = 'gif';
const EXTENSION_GZIP = 'gz';
const EXTENSION_HTML = 'html';
const EXTENSION_HTM = 'htm';
const EXTENSION_ICO = 'ico';
const EXTENSION_JFIF = 'jfif';
const EXTENSION_JFI = 'jfi';
const EXTENSION_JPEG = 'jpeg';
const EXTENSION_JPG = 'jpg';
const EXTENSION_JS = 'js';
const EXTENSION_JSON = 'json';
const EXTENSION_MKV = 'mkv';
const EXTENSION_MP3 = 'mp3';
const EXTENSION_MP4 = 'mp4';
const EXTENSION_M4V = 'm4v';
const EXTENSION_M4A = 'm4a';
const EXTENSION_MPEG = 'mpeg';
const EXTENSION_MPG = 'mpg';
const EXTENSION_OGG = 'ogg';
const EXTENSION_OGG_VIDEO = 'ogv';
const EXTENSION_OGG_AUDIO = 'oga';
const EXTENSION_PDF = 'pdf';
const EXTENSION_PHP = 'php';
const EXTENSION_PHP5 = 'php5';
const EXTENSION_PNG = 'png';
const EXTENSION_PSD = 'psd';
const EXTENSION_MOV = 'mov';
const EXTENSION_RSS = 'rss';
const EXTENSION_SVG = 'svg';
const EXTENSION_TAR = 'tar';
const EXTENSION_TEXT = 'txt';
const EXTENSION_TIFF = 'tiff';
const EXTENSION_TIF = 'tif';
const EXTENSION_WAV = 'wav';
const EXTENSION_WEBM = 'webm';
const EXTENSION_WEBP = 'webp';
const EXTENSION_WMA = 'wma';
const EXTENSION_WMV = 'wmv';
const EXTENSION_XML = 'xml';
const EXTENSION_XSL = 'xsl';
const EXTENSION_ZIP = 'zip';
// Couldn't find a mimetype for ani, so made one up based on it being a riff container
const MIME_TYPE_ANI = 'application/riff+ani';
const MIME_TYPE_ASF = 'video/x-ms-asf';
const MIME_TYPE_AVI = 'video/x-msvideo';
// Went with mime types from http://fileformats.archiveteam.org/wiki/Comic_Book_Archive
const MIME_TYPE_COMIC_ZIP = 'application/vnd.comicbook+zip';
const MIME_TYPE_COMIC_RAR = 'application/vnd.comicbook-rar';
const MIME_TYPE_BMP = 'image/x-ms-bmp';
const MIME_TYPE_BZIP = 'application/x-bzip';
const MIME_TYPE_BZIP2 = 'application/x-bzip2';
const MIME_TYPE_CSS = 'text/css';
const MIME_TYPE_CSV = 'text/csv';
const MIME_TYPE_FLASH = 'application/x-shockwave-flash';
const MIME_TYPE_FLASH_VIDEO = 'video/x-flv';
const MIME_TYPE_GIF = 'image/gif';
const MIME_TYPE_GZIP = 'application/x-gzip';
const MIME_TYPE_HTML = 'text/html';
const MIME_TYPE_ICO = 'image/x-icon';
const MIME_TYPE_JPEG = 'image/jpeg';
const MIME_TYPE_JS = 'text/javascript';
const MIME_TYPE_JSON = 'application/json';
const MIME_TYPE_MKV = 'video/x-matroska';
const MIME_TYPE_MP3 = 'audio/mpeg';
const MIME_TYPE_MP4_AUDIO = 'audio/mp4';
const MIME_TYPE_MP4_VIDEO = 'video/mp4';
const MIME_TYPE_MPEG = 'video/mpeg';
const MIME_TYPE_OCTET_STREAM = 'application/octet-stream';
const MIME_TYPE_OGG = 'application/ogg';
const MIME_TYPE_OGG_VIDEO = 'video/ogg';
const MIME_TYPE_OGG_AUDIO = 'audio/ogg';
const MIME_TYPE_PDF = 'application/pdf';
const MIME_TYPE_PHP = 'text/x-php';
const MIME_TYPE_PNG = 'image/png';
const MIME_TYPE_PSD = 'image/vnd.adobe.photoshop';
const MIME_TYPE_QUICKTIME = 'video/quicktime';
const MIME_TYPE_RSS = 'application/rss+xml';
const MIME_TYPE_SVG = 'image/svg+xml';
const MIME_TYPE_TAR = 'application/x-tar';
const MIME_TYPE_TEXT = 'text/plain';
const MIME_TYPE_TIFF = 'image/tiff';
const MIME_TYPE_WAV = 'audio/x-wav';
const MIME_TYPE_WEBM = 'video/webm';
const MIME_TYPE_WEBP = 'image/webp';
const MIME_TYPE_WIN_BITMAP = 'image/x-win-bitmap';
const MIME_TYPE_XML = 'text/xml';
const MIME_TYPE_XML_APPLICATION = 'application/xml';
const MIME_TYPE_XSL = 'application/xsl+xml';
const MIME_TYPE_ZIP = 'application/zip';
const MIME_TYPE_MAP_NAME = 'name';
const MIME_TYPE_MAP_EXT = 'ext';
const MIME_TYPE_MAP_MIME = 'mime';
// Mime type map. Each entry in the MIME_TYPE_ARRAY represents a kind of file, identified by the "correct" mimetype as the key.
// The value for each entry is a map of twokeys, ext and mime.
// ext's value is an array of all of the extensions that the file type can use, with the "correct" one being first.
// mime's value is an array of all mime types that the file type is known to use, with the current "correct" one being first.
const MIME_TYPE_MAP = [
MIME_TYPE_ANI => [
MIME_TYPE_MAP_NAME => "ANI Cursor",
MIME_TYPE_MAP_EXT => [EXTENSION_ANI],
MIME_TYPE_MAP_MIME => [MIME_TYPE_ANI],
],
MIME_TYPE_AVI => [
MIME_TYPE_MAP_NAME => "AVI",
MIME_TYPE_MAP_EXT => [EXTENSION_AVI],
MIME_TYPE_MAP_MIME => [MIME_TYPE_AVI,'video/avi','video/msvideo'],
],
MIME_TYPE_ASF => [
MIME_TYPE_MAP_NAME => "ASF/WMV",
MIME_TYPE_MAP_EXT => [EXTENSION_ASF,EXTENSION_WMA,EXTENSION_WMV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_ASF,'audio/x-ms-wma','video/x-ms-wmv'],
],
MIME_TYPE_BMP => [
MIME_TYPE_MAP_NAME => "BMP",
MIME_TYPE_MAP_EXT => [EXTENSION_BMP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_BMP],
],
MIME_TYPE_BZIP => [
MIME_TYPE_MAP_NAME => "BZIP",
MIME_TYPE_MAP_EXT => [EXTENSION_BZIP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP],
],
MIME_TYPE_BZIP2 => [
MIME_TYPE_MAP_NAME => "BZIP2",
MIME_TYPE_MAP_EXT => [EXTENSION_BZIP2],
MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP2],
],
MIME_TYPE_COMIC_ZIP => [
MIME_TYPE_MAP_NAME => "CBZ",
MIME_TYPE_MAP_EXT => [EXTENSION_CBZ],
MIME_TYPE_MAP_MIME => [MIME_TYPE_COMIC_ZIP],
],
MIME_TYPE_CSS => [
MIME_TYPE_MAP_NAME => "Cascading Style Sheet",
MIME_TYPE_MAP_EXT => [EXTENSION_CSS],
MIME_TYPE_MAP_MIME => [MIME_TYPE_CSS],
],
MIME_TYPE_CSV => [
MIME_TYPE_MAP_NAME => "CSV",
MIME_TYPE_MAP_EXT => [EXTENSION_CSV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_CSV],
],
MIME_TYPE_FLASH => [
MIME_TYPE_MAP_NAME => "Flash",
MIME_TYPE_MAP_EXT => [EXTENSION_FLASH],
MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH],
],
MIME_TYPE_FLASH_VIDEO => [
MIME_TYPE_MAP_NAME => "Flash Video",
MIME_TYPE_MAP_EXT => [EXTENSION_FLASH_VIDEO],
MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH_VIDEO,'video/flv'],
],
MIME_TYPE_GIF => [
MIME_TYPE_MAP_NAME => "GIF",
MIME_TYPE_MAP_EXT => [EXTENSION_GIF],
MIME_TYPE_MAP_MIME => [MIME_TYPE_GIF],
],
MIME_TYPE_GZIP => [
MIME_TYPE_MAP_NAME => "GZIP",
MIME_TYPE_MAP_EXT => [EXTENSION_GZIP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR],
],
MIME_TYPE_HTML => [
MIME_TYPE_MAP_NAME => "HTML",
MIME_TYPE_MAP_EXT => [EXTENSION_HTM, EXTENSION_HTML],
MIME_TYPE_MAP_MIME => [MIME_TYPE_HTML],
],
MIME_TYPE_ICO => [
MIME_TYPE_MAP_NAME => "Icon",
MIME_TYPE_MAP_EXT => [EXTENSION_ICO, EXTENSION_CUR],
MIME_TYPE_MAP_MIME => [MIME_TYPE_ICO, MIME_TYPE_WIN_BITMAP],
],
MIME_TYPE_JPEG => [
MIME_TYPE_MAP_NAME => "JPEG",
MIME_TYPE_MAP_EXT => [EXTENSION_JPG, EXTENSION_JPEG, EXTENSION_JFIF, EXTENSION_JFI],
MIME_TYPE_MAP_MIME => [MIME_TYPE_JPEG],
],
MIME_TYPE_JS => [
MIME_TYPE_MAP_NAME => "JavaScript",
MIME_TYPE_MAP_EXT => [EXTENSION_JS],
MIME_TYPE_MAP_MIME => [MIME_TYPE_JS],
],
MIME_TYPE_JSON => [
MIME_TYPE_MAP_NAME => "JSON",
MIME_TYPE_MAP_EXT => [EXTENSION_JSON],
MIME_TYPE_MAP_MIME => [MIME_TYPE_JSON],
],
MIME_TYPE_MKV => [
MIME_TYPE_MAP_NAME => "Matroska",
MIME_TYPE_MAP_EXT => [EXTENSION_MKV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MKV],
],
MIME_TYPE_MP3 => [
MIME_TYPE_MAP_NAME => "MP3",
MIME_TYPE_MAP_EXT => [EXTENSION_MP3],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP3],
],
MIME_TYPE_MP4_AUDIO => [
MIME_TYPE_MAP_NAME => "MP4 Audio",
MIME_TYPE_MAP_EXT => [EXTENSION_M4A],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_AUDIO,"audio/m4a"],
],
MIME_TYPE_MP4_VIDEO => [
MIME_TYPE_MAP_NAME => "MP4 Video",
MIME_TYPE_MAP_EXT => [EXTENSION_MP4,EXTENSION_M4V],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_VIDEO,'video/x-m4v'],
],
MIME_TYPE_MPEG => [
MIME_TYPE_MAP_NAME => "MPEG",
MIME_TYPE_MAP_EXT => [EXTENSION_MPG,EXTENSION_MPEG],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MPEG],
],
MIME_TYPE_PDF => [
MIME_TYPE_MAP_NAME => "PDF",
MIME_TYPE_MAP_EXT => [EXTENSION_PDF],
MIME_TYPE_MAP_MIME => [MIME_TYPE_PDF],
],
MIME_TYPE_PHP => [
MIME_TYPE_MAP_NAME => "PHP",
MIME_TYPE_MAP_EXT => [EXTENSION_PHP,EXTENSION_PHP5],
MIME_TYPE_MAP_MIME => [MIME_TYPE_PHP],
],
MIME_TYPE_PNG => [
MIME_TYPE_MAP_NAME => "PNG",
MIME_TYPE_MAP_EXT => [EXTENSION_PNG],
MIME_TYPE_MAP_MIME => [MIME_TYPE_PNG],
],
MIME_TYPE_PSD => [
MIME_TYPE_MAP_NAME => "PSD",
MIME_TYPE_MAP_EXT => [EXTENSION_PSD],
MIME_TYPE_MAP_MIME => [MIME_TYPE_PSD],
],
MIME_TYPE_OGG_AUDIO => [
MIME_TYPE_MAP_NAME => "Ogg Vorbis",
MIME_TYPE_MAP_EXT => [EXTENSION_OGG_AUDIO,EXTENSION_OGG],
MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_AUDIO,MIME_TYPE_OGG],
],
MIME_TYPE_OGG_VIDEO => [
MIME_TYPE_MAP_NAME => "Ogg Theora",
MIME_TYPE_MAP_EXT => [EXTENSION_OGG_VIDEO],
MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_VIDEO],
],
MIME_TYPE_QUICKTIME => [
MIME_TYPE_MAP_NAME => "Quicktime",
MIME_TYPE_MAP_EXT => [EXTENSION_MOV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_QUICKTIME],
],
MIME_TYPE_RSS => [
MIME_TYPE_MAP_NAME => "RSS",
MIME_TYPE_MAP_EXT => [EXTENSION_RSS],
MIME_TYPE_MAP_MIME => [MIME_TYPE_RSS],
],
MIME_TYPE_SVG => [
MIME_TYPE_MAP_NAME => "SVG",
MIME_TYPE_MAP_EXT => [EXTENSION_SVG],
MIME_TYPE_MAP_MIME => [MIME_TYPE_SVG],
],
MIME_TYPE_TAR => [
MIME_TYPE_MAP_NAME => "TAR",
MIME_TYPE_MAP_EXT => [EXTENSION_TAR],
MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR],
],
MIME_TYPE_TEXT => [
MIME_TYPE_MAP_NAME => "Text",
MIME_TYPE_MAP_EXT => [EXTENSION_TEXT, EXTENSION_ASC],
MIME_TYPE_MAP_MIME => [MIME_TYPE_TEXT],
],
MIME_TYPE_TIFF => [
MIME_TYPE_MAP_NAME => "TIFF",
MIME_TYPE_MAP_EXT => [EXTENSION_TIF,EXTENSION_TIFF],
MIME_TYPE_MAP_MIME => [MIME_TYPE_TIFF],
],
MIME_TYPE_WAV => [
MIME_TYPE_MAP_NAME => "Wave",
MIME_TYPE_MAP_EXT => [EXTENSION_WAV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_WAV],
],
MIME_TYPE_WEBM => [
MIME_TYPE_MAP_NAME => "WebM",
MIME_TYPE_MAP_EXT => [EXTENSION_WEBM],
MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBM],
],
MIME_TYPE_WEBP => [
MIME_TYPE_MAP_NAME => "WebP",
MIME_TYPE_MAP_EXT => [EXTENSION_WEBP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBP],
],
MIME_TYPE_XML => [
MIME_TYPE_MAP_NAME => "XML",
MIME_TYPE_MAP_EXT => [EXTENSION_XML],
MIME_TYPE_MAP_MIME => [MIME_TYPE_XML,MIME_TYPE_XML_APPLICATION],
],
MIME_TYPE_XSL => [
MIME_TYPE_MAP_NAME => "XSL",
MIME_TYPE_MAP_EXT => [EXTENSION_XSL],
MIME_TYPE_MAP_MIME => [MIME_TYPE_XSL],
],
MIME_TYPE_ZIP => [
MIME_TYPE_MAP_NAME => "ZIP",
MIME_TYPE_MAP_EXT => [EXTENSION_ZIP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_ZIP],
],
];
/**
* Returns the mimetype that matches the provided extension.
*/
function get_mime_for_extension(string $ext): ?string
{
$ext = strtolower($ext);
foreach (MIME_TYPE_MAP as $key=>$value) {
if (in_array($ext, $value[MIME_TYPE_MAP_EXT])) {
return $key;
}
}
return null;
}
/**
* Returns the mimetype for the specified file, trying file inspection methods before falling back on extension-based detection.
* @param String $file
* @param String $ext The files extension, for if the current filename somehow lacks the extension
* @return String The extension that was found.
*/
function get_mime(string $file, string $ext=""): string
{
if (!file_exists($file)) {
throw new SCoreException("File not found: ".$file);
}
$type = false;
if (extension_loaded('fileinfo')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
try {
$type = finfo_file($finfo, $file);
} finally {
finfo_close($finfo);
}
} elseif (function_exists('mime_content_type')) {
// If anyone is still using mime_content_type()
$type = trim(mime_content_type($file));
}
if ($type===false || empty($type)) {
// Checking by extension is our last resort
if ($ext==null||strlen($ext) == 0) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
}
$type = get_mime_for_extension($ext);
}
if ($type !== false && strlen($type) > 0) {
return $type;
}
return MIME_TYPE_OCTET_STREAM;
}
/**
* Returns the file extension associated with the specified mimetype.
*/
function get_extension(?string $mime_type): ?string
{
if (empty($mime_type)) {
return null;
}
if ($mime_type==MIME_TYPE_OCTET_STREAM) {
return null;
}
foreach (MIME_TYPE_MAP as $key=>$value) {
if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) {
return $value[MIME_TYPE_MAP_EXT][0];
}
}
return null;
}
/**
* Returns all of the file extensions associated with the specified mimetype.
*/
function get_all_extension_for_mime(?string $mime_type): array
{
$output = [];
if (empty($mime_type)) {
return $output;
}
foreach (MIME_TYPE_MAP as $key=>$value) {
if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) {
$output = array_merge($output, $value[MIME_TYPE_MAP_EXT]);
}
}
return $output;
}
/**
* Gets an the extension defined in MIME_TYPE_MAP for a file.
*
* @param String $file_path
* @return String The extension that was found, or null if one can not be found.
*/
function get_extension_for_file(String $file_path): ?String
{
$mime = get_mime($file_path);
if (!empty($mime)) {
if ($mime==MIME_TYPE_OCTET_STREAM) {
return null;
} else {
$ext = get_extension($mime);
}
if (!empty($ext)) {
return $ext;
}
}
return null;
}

View File

@ -213,27 +213,6 @@ 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
*
@ -591,7 +570,7 @@ class Image
*/
public function get_mime_type(): string
{
return get_mime($this->get_image_filename(), $this->get_ext());
return getMimeType($this->get_image_filename(), $this->get_ext());
}
/**

View File

@ -39,7 +39,7 @@ function add_dir(string $base): array
* @param string $tags
* @throws UploadException
*/
function add_image(string $tmpname, string $filename, string $tags): int
function add_image(string $tmpname, string $filename, string $tags): void
{
assert(file_exists($tmpname));
@ -52,11 +52,7 @@ function add_image(string $tmpname, string $filename, string $tags): int
$metadata['tags'] = Tag::explode($tags);
$metadata['source'] = null;
$due = new DataUploadEvent($tmpname, $metadata);
send_event($due);
return $due->image_id;
send_event(new DataUploadEvent($tmpname, $metadata));
}
/**
@ -73,12 +69,6 @@ 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;
}
@ -138,35 +128,21 @@ 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,
$config->get_string(ImageConfig::THUMB_FIT)
);
create_scaled_image($inname, $outname, $tsize, $type, $engine);
}
function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine = null, ?string $resize_type = null)
function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine)
{
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==EXTENSION_WEBP) {
if ($output_format=="webp") {
$output_format = Media::WEBP_LOSSY;
}
@ -177,10 +153,10 @@ function create_scaled_image(string $inname, string $outname, array $tsize, stri
$outname,
$tsize[0],
$tsize[1],
$resize_type,
false,
$output_format,
$config->get_int(ImageConfig::THUMB_QUALITY),
true,
true
$config->get_bool('thumb_upscale', false)
));
}

View File

@ -116,6 +116,8 @@ class Tag
for ($i = 0; $i < count($tags1); $i++) {
if ($tags1[$i]!==$tags2[$i]) {
var_dump($tags1);
var_dump($tags2);
return false;
}
}

View File

@ -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 https://code.shishnet.org/shimmie2/
* @license https://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
* @link http://code.shishnet.org/shimmie2/
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
* Initialise the database, check that folder
* permissions are set properly.
@ -20,12 +20,23 @@ function install()
date_default_timezone_set('UTC');
if (is_readable("data/config/shimmie.conf.php")) {
die_nicely(
exit_with_page(
"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;
@ -69,7 +80,7 @@ function do_install($dsn)
create_tables(new Database($dsn));
write_config($dsn);
} catch (InstallerException $e) {
die_nicely($e->title, $e->body, $e->code);
exit_with_page($e->title, $e->body, $e->code);
}
}
@ -117,7 +128,7 @@ function ask_questions()
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
die_nicely(
exit_with_page(
"Install Options",
<<<EOD
$warn_msg
@ -304,7 +315,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");
die_nicely(
exit_with_page(
"Installation Successful",
"<p>If you aren't redirected, <a href=\"index.php\">click here to Continue</a>."
);
@ -324,3 +335,25 @@ 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);
}

View File

@ -10,15 +10,6 @@ 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
*

View File

@ -18,7 +18,6 @@ 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
@ -96,11 +95,4 @@ 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";
}

View File

@ -3,9 +3,6 @@
* Things which should be in the core API *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
require_once "filetypes.php";
/**
* Return the unique elements of an array, case insensitively
*/
@ -30,7 +27,7 @@ function array_iunique(array $array): array
/**
* Figure out if an IP is in a specified range
*
* from https://uk.php.net/network
* from http://uk.php.net/network
*/
function ip_in_range(string $IP, string $CIDR): bool
{
@ -89,7 +86,7 @@ function deltree(string $f): void
/**
* Copy an entire file hierarchy
*
* from a comment on https://uk.php.net/copy
* from a comment on http://uk.php.net/copy
*/
function full_copy(string $source, string $target): void
{
@ -153,14 +150,6 @@ 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');
@ -173,7 +162,10 @@ function stream_file(string $file, int $start, int $end): void
$buffer = $end - $p + 1;
}
echo fread($fp, $buffer);
flush_output();
if (!defined("UNITTEST")) {
@ob_flush();
}
flush();
// 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
@ -256,6 +248,99 @@ 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)
{
@ -295,7 +380,7 @@ function zglob(string $pattern): array
/**
* Figure out the path to the shimmie install directory.
*
* eg if shimmie is visible at https://foo.com/gallery, this
* eg if shimmie is visible at http://foo.com/gallery, this
* function should return /gallery
*
* PHP really, really sucks.
@ -321,23 +406,6 @@ 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);
@ -410,7 +478,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!
https://php.net/manual/en/filter.filters.validate.php
http://php.net/manual/en/filter.filters.validate.php
*/
if (is_bool($input)) {
return $input;
@ -443,24 +511,6 @@ 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)) {
@ -520,14 +570,11 @@ function truncate(string $string, int $limit, string $break=" ", string $pad="..
*/
function parse_shorthand_int(string $limit): int
{
if (preg_match('/^([\d\.]+)([tgmk])?b?$/i', (string)$limit, $m)) {
if (preg_match('/^([\d\.]+)([gmk])?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
@ -551,9 +598,7 @@ function to_shorthand_int(int $int): string
{
assert($int >= 0);
if ($int >= pow(1024, 4)) {
return sprintf("%.1fTB", $int / pow(1024, 4));
} elseif ($int >= pow(1024, 3)) {
if ($int >= pow(1024, 3)) {
return sprintf("%.1fGB", $int / pow(1024, 3));
} elseif ($int >= pow(1024, 2)) {
return sprintf("%.1fMB", $int / pow(1024, 2));

View File

@ -1,63 +0,0 @@
<?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);
}

View File

@ -101,7 +101,7 @@ function send_event(Event $event): Event
if ($tracer_enabled) {
$_tracer->begin(get_class($event));
}
// SHIT: https://bugs.php.net/bug.php?id=35106
// SHIT: http://bugs.php.net/bug.php?id=35106
$my_event_listeners = $_shm_event_listeners[get_class($event)];
ksort($my_event_listeners);

View File

@ -18,7 +18,6 @@ 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
@ -26,7 +25,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.4$_g"); // string shimmie version
_d("VERSION", '2.8.0'); // 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)

View File

@ -1,18 +0,0 @@
<?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);
}
}

View File

@ -8,35 +8,15 @@ 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()
@ -55,47 +35,8 @@ class UrlsTest extends TestCase
// absolute
$this->assertEquals(
"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"])
"http://foo.com",
make_http("http://foo.com")
);
}
}

View File

@ -1,4 +1,7 @@
<?php declare(strict_types=1);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* HTML Generation *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
class Link
{
@ -23,27 +26,32 @@ class Link
*
* eg make_link("post/list") becomes "/v2/index.php?q=post/list"
*/
function make_link(?string $page=null, ?string $query=null, ?string $fragment=null): string
function make_link(?string $page=null, ?string $query=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)) {
$parts['path'] = "$install_dir/$page";
$base = $install_dir;
} else {
$parts['path'] = "$install_dir/index.php";
$query = empty($query) ? "q=$page" : "q=$page&$query";
$base = "$install_dir/index.php?q=";
}
$parts['query'] = $query; // http_build_query($query);
$parts['fragment'] = $fragment; // http_build_query($hash);
return unparse_url($parts);
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;
}
}
}
@ -52,28 +60,43 @@ function make_link(?string $page=null, ?string $query=null, ?string $fragment=nu
*/
function modify_current_url(array $changes): string
{
return modify_url($_SERVER['REQUEST_URI'], $changes);
return modify_url($_SERVER['QUERY_STRING'], $changes);
}
function modify_url(string $url, array $changes): string
{
$parts = parse_url($url);
// SHIT: PHP is officially the worst web API ever because it does not
// have a built-in function to do this.
// 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 = [];
if (isset($parts['query'])) {
parse_str($parts['query'], $params);
parse_str($url, $params);
if (isset($changes['q'])) {
$base = $changes['q'];
unset($changes['q']);
} else {
$base = _get_query();
}
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 unparse_url($parts);
return make_link($base, http_build_query($params));
}
/**
* Turn a relative link into an absolute one, including hostname
*/
@ -93,22 +116,3 @@ 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'];
}

View File

@ -247,9 +247,6 @@ 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());
}

View File

@ -98,8 +98,6 @@ 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", [
@ -120,7 +118,6 @@ 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,
@ -199,13 +196,6 @@ 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";

View File

@ -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.
https://ca2.php.net/manual/en/ini.core.php#ini.memory-limit
http://ca2.php.net/manual/en/ini.core.php#ini.memory-limit
*/
$memory = parse_shorthand_int(ini_get("memory_limit"));
@ -393,67 +393,6 @@ 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.
*/
@ -554,18 +493,31 @@ function _load_theme_files()
require_all(_get_themelet_files(get_theme()));
}
function _set_up_shimmie_environment(): void
function _sanitise_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_nicely("Upgrade error", "As of Shimmie 2.7 images and thumbs should be moved to data/images and data/thumbs");
die("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);
}
@ -574,6 +526,16 @@ function _set_up_shimmie_environment(): void
// 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>";
}
}
@ -636,7 +598,6 @@ 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>
';
@ -682,7 +643,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;
}
@ -716,7 +677,7 @@ function SHM_FORM(string $target, string $method="POST", bool $multipart=false,
global $user;
$attrs = [
"action"=>make_link($target),
"action"=>$target,
"method"=>$method
];
@ -782,18 +743,3 @@ 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;
}

36
docs/CONFIG.md Normal file
View File

@ -0,0 +1,36 @@
# 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 Normal file
View File

@ -0,0 +1,139 @@
# 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

20
docs/DOCKER.md Normal file
View File

@ -0,0 +1,20 @@
# 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 .
```

27
docs/INSTALL.md Normal file
View File

@ -0,0 +1,27 @@
# 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~

65
docs/SPEED.md Normal file
View File

@ -0,0 +1,65 @@
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.

9
docs/UPGRADE.md Normal file
View File

@ -0,0 +1,9 @@
# 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.

View File

@ -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(MIME_TYPE_CSV);
$page->set_type("text/csv");
$page->set_filename("aliases.csv");
$page->set_data($this->get_alias_csv($database));
} elseif ($event->get_arg(0) == "import") {

View File

@ -52,10 +52,6 @@ class DeleteAutoTagEvent extends Event
}
}
class AutoTaggerException extends SCoreException
{
}
class AddAutoTagException extends SCoreException
{
}
@ -102,7 +98,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(MIME_TYPE_CSV);
$page->set_type("text/csv");
$page->set_filename("auto_tag.csv");
$page->set_data($this->get_auto_tag_csv($database));
} elseif ($event->get_arg(0) == "import") {

View File

@ -20,7 +20,7 @@ class AutoComplete extends Extension
}
$page->set_mode(PageMode::DATA);
$page->set_type(MIME_TYPE_JSON);
$page->set_type("application/json");
$s = strtolower($_GET["s"]);
if (

View File

@ -15,7 +15,7 @@ class BBCodeInfo extends ExtensionInfo
" Supported tags:
<ul>
<li>[img]url[/img]
<li>[url]<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>[/url]
<li>[url]<a href=\"{self::SHIMMIE_URL}\">http://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]

View File

@ -67,12 +67,12 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
public function testURL()
{
$this->assertEquals(
$this->filter("[url]https://shishnet.org[/url]"),
"<a href=\"https://shishnet.org\">https://shishnet.org</a>"
$this->filter("[url]http://shishnet.org[/url]"),
"<a href=\"http://shishnet.org\">http://shishnet.org</a>"
);
$this->assertEquals(
$this->filter("[url=https://shishnet.org]ShishNet[/url]"),
"<a href=\"https://shishnet.org\">ShishNet</a>"
$this->filter("[url=http://shishnet.org]ShishNet[/url]"),
"<a href=\"http://shishnet.org\">ShishNet</a>"
);
$this->assertEquals(
$this->filter("[url=javascript:alert(\"owned\")]click to fail[/url]"),
@ -107,32 +107,4 @@ 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">&gt;&gt;123</a>',
$this->filter("&gt;&gt;123")
);
$this->assertEquals(
'<a class="shm-clink" data-clink-sel="#c456" href="/test/post/view/123#c456">&gt;&gt;123#c456</a>',
$this->filter("&gt;&gt;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]")
);
}
}

View File

@ -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 https://github.com/zshall/shimmie2/issues";
Development TODO at http://github.com/zshall/shimmie2/issues";
}

View File

@ -42,7 +42,7 @@ class BrowserSearch extends Extension
// And now to send it to the browser
$page->set_mode(PageMode::DATA);
$page->set_type(MIME_TYPE_XML);
$page->set_type("text/xml");
$page->set_data($xml);
} elseif ($event->page_matches("browser_search")) {
$suggestions = $config->get_string("search_suggestions_results_order");

View File

@ -1,8 +1,5 @@
<?php declare(strict_types=1);
class BulkActionException extends SCoreException
{
}
class BulkActionBlockBuildingEvent extends Event
{
/** @var array */
@ -42,8 +39,6 @@ class BulkActionEvent extends Event
public $action;
/** @var array */
public $items;
/** @var bool */
public $redirect = true;
public function __construct(String $action, Generator $items)
{
@ -169,38 +164,28 @@ class BulkActions extends Extension
$action = $_POST['bulk_action'];
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");
$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);
}
$bae = new BulkActionEvent($action, $items);
if (is_iterable($items)) {
send_event($bae);
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
$query = $_POST['bulk_query'];
if ($query != null && $query != "") {
$items = $this->yield_search_results($query);
}
} catch (BulkActionException $e) {
log_error(BulkActionsInfo::KEY, $e->getMessage(), $e->getMessage());
}
if ($bae->redirect) {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(referer_or(make_link()));
if (is_iterable($items)) {
send_event(new BulkActionEvent($action, $items));
}
$page->set_mode(PageMode::REDIRECT);
if (!isset($_SERVER['HTTP_REFERER'])) {
$_SERVER['HTTP_REFERER'] = make_link();
}
$page->set_redirect($_SERVER['HTTP_REFERER']);
}
}

View File

@ -53,7 +53,6 @@ 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;
}
@ -95,6 +94,7 @@ 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);

View File

@ -1,14 +0,0 @@
<?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];
}

View File

@ -1,78 +0,0 @@
<?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;
}
}
}
}

View File

@ -1,25 +0,0 @@
<?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;
}
}

View File

@ -1,15 +0,0 @@
<?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];
}

View File

@ -1,172 +0,0 @@
<?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;
}
}

View File

@ -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", null, "comment_on_$i_iid"));
$page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
} catch (CommentPostingException $ex) {
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
}
@ -226,7 +226,11 @@ class CommentList extends Extension
send_event(new CommentDeletionEvent(int_escape($event->get_arg(1))));
$page->flash("Deleted comment");
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(referer_or(make_link("post/view/" . $event->get_arg(2))));
if (!empty($_SERVER['HTTP_REFERER'])) {
$page->set_redirect($_SERVER['HTTP_REFERER']);
} else {
$page->set_redirect(make_link("post/view/" . $event->get_arg(2)));
}
}
} else {
$this->theme->display_permission_denied();
@ -260,51 +264,8 @@ class CommentList extends Extension
private function onPageRequest_list(PageRequestEvent $event)
{
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));
$page_num = $event->try_page_num(1);
$this->build_page($page_num);
}
private function onPageRequest_beta_search(PageRequestEvent $event)
@ -315,8 +276,9 @@ 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);
$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);
$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);
}
public function onAdminBuilding(AdminBuildingEvent $event)
@ -427,6 +389,56 @@ 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[]
*/
@ -551,9 +563,18 @@ class CommentList extends Extension
'website' => '',
'body' => $text,
'permalink' => '',
'referrer' => $_SERVER['HTTP_REFERER'] ?? 'none',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'none',
];
];
# 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");
}
$akismet = new Akismet(
$_SERVER['SERVER_NAME'],

View File

@ -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", null, "c$i_comment_id")."\">&gt;&gt;&gt;</a>
<a href=\"".make_link("post/view/$i_image_id#c$i_comment_id")."\">&gt;&gt;&gt;</a>
</div>
";
} else {

View File

@ -3,25 +3,22 @@
abstract class CronUploaderConfig
{
public const DEFAULT_PATH = "cron_uploader";
const DEFAULT_PATH = "cron_uploader";
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";
const KEY = "cron_uploader_key";
const COUNT = "cron_uploader_count";
const DIR = "cron_uploader_dir";
const USER = "cron_uploader_user";
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 = generate_key();
$upload_key = self::generate_key();
$config->set_string(self::KEY, $upload_key);
}
@ -51,6 +48,18 @@ 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;
@ -67,4 +76,21 @@ 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;
}
}

View File

@ -15,7 +15,7 @@ class CronUploader extends Extension
const UPLOADED_DIR = "uploaded";
const FAILED_DIR = "failed_to_upload";
private static $IMPORT_RUNNING = false;
public $output_buffer = [];
public function onInitExt(InitExtEvent $event)
{
@ -57,18 +57,10 @@ 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.");
@ -116,24 +108,6 @@ 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;
@ -149,46 +123,33 @@ class CronUploader extends Extension
$this->prep_root_dir();
$results = get_files_recursively($stage_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);
if (count($results) == 0) {
if (remove_empty_dirs($stage_dir)===false) {
if (rmdir($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) {
$new_path = join_path($queue_dir, substr($result, strlen($stage_dir)));
$original_path = join_path($stage_dir, $result);
$new_path = join_path($queue_dir, $result);
if (file_exists($new_path)) {
$page->flash("File already exists in queue folder: " .$result);
return;
}
rename($original_path, $new_path);
}
$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");
}
}
$page->flash("Re-staged $folder to queue");
rmdir($stage_dir);
}
private function clear_folder($folder)
@ -304,11 +265,7 @@ class CronUploader extends Extension
*/
public function process_upload(string $key, ?int $upload_count = null): bool
{
global $database, $config, $_shm_load_start;
$max_time = intval(ini_get('max_execution_time'))*.8;
$this->set_headers();
global $database;
if ($key!=CronUploaderConfig::get_key()) {
throw new SCoreException("Cron upload key incorrect");
@ -330,12 +287,24 @@ 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());
$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;
}
// Randomize Images
//shuffle($this->image_queue);
@ -345,18 +314,14 @@ class CronUploader extends Extension
$failed = 0;
// Upload the file(s)
foreach ($image_queue as $img) {
$execution_time = microtime(true) - $_shm_load_start;
if ($execution_time>$max_time) {
break;
}
for ($i = 0; $i < $upload_count && sizeof($image_queue) > 0; $i++) {
$img = array_pop($image_queue);
try {
$database->begin_transaction();
$database->beginTransaction();
$this->log_message(SCORE_LOG_INFO, "Adding file: {$img[0]} - tags: {$img[2]}");
$result = $this->add_image($img[0], $img[1], $img[2]);
if ($database->is_transaction_open()) {
$database->commit();
}
$database->commit();
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
if ($result->merged) {
$merged++;
@ -365,37 +330,28 @@ class CronUploader extends Extension
}
} catch (Exception $e) {
try {
if ($database->is_transaction_open()) {
$database->rollback();
}
$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);
}
@ -403,13 +359,7 @@ class CronUploader extends Extension
private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false)
{
$rootDir = CronUploaderConfig::get_dir();
$rootLength = strlen($rootDir);
if ($rootDir[$rootLength-1]=="/"||$rootDir[$rootLength-1]=="\\") {
$rootLength--;
}
$relativeDir = dirname(substr($path, $rootLength + 7));
$relativeDir = dirname(substr($path, strlen(CronUploaderConfig::get_dir()) + 7));
if ($relativeDir==".") {
$relativeDir = "";
@ -455,7 +405,7 @@ class CronUploader extends Extension
if (array_key_exists('extension', $pathinfo)) {
$metadata ['extension'] = $pathinfo ['extension'];
}
$metadata ['tags'] = $tagArray;
$metadata ['tags'] = $tagArray; // doesn't work when not logged in here, handled below
$metadata ['source'] = null;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
@ -470,46 +420,34 @@ 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_dir(string $path)
{
$info = pathinfo($path);
if (array_key_exists("basename", $info) && in_array(strtolower($info['basename']), self::SKIPPABLE_DIRECTORIES)) {
return true;
}
return false;
}
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)) {
if (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
private function generate_image_queue(string $root_dir, ?int $limit = null): array
{
$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);
@ -520,19 +458,31 @@ class CronUploader extends Extension
$relativePath = substr($fullpath, strlen($base));
$tags = path_to_tags($relativePath);
yield [
$img = [
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
@ -540,12 +490,16 @@ class CronUploader extends Extension
return join_path(CronUploaderConfig::get_dir(), "uploads.log");
}
private function set_headers(): void
/**
* This is run at the end to display & save the log.
*/
private function handle_log()
{
global $page;
$page->set_mode(PageMode::MANUAL);
$page->set_type(MIME_TYPE_TEXT);
$page->send_headers();
// Display message
$page->set_mode(PageMode::DATA);
$page->set_type("text/plain");
$page->set_data(implode("\r\n", $this->output_buffer));
}
}

View File

@ -57,9 +57,6 @@ 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>
@ -74,7 +71,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 for up to ".number_format($max_time)." seconds. This is controlled by the PHP max execution time.</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>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>
@ -110,7 +107,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'>";
$html .= "<tr><th>Failed dir</th><td><select name='failed_dir' required='required'><option></option>";
foreach ($failed_dirs as $dir) {
$html .= "<option value='$dir'>$dir</option>";

View File

@ -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(MIME_TYPE_TEXT);
$page->set_type("text/plain");
$this->api_add_post();
} elseif ($event->page_matches("api/danbooru/find_posts") || $event->page_matches("api/danbooru/post/index.xml")) {
$page->set_type(MIME_TYPE_XML_APPLICATION);
$page->set_type("application/xml");
$page->set_data($this->api_find_posts());
} elseif ($event->page_matches("api/danbooru/find_tags")) {
$page->set_type(MIME_TYPE_XML_APPLICATION);
$page->set_type("application/xml");
$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 https://shimmie/api/danbooru/post/show/123
// This redirects that to https://shimmie/post/view/123
// Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123
// This redirects that to http://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);

View File

@ -9,7 +9,6 @@ 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

View File

@ -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->to_yaml($this->get_info()));
$this->theme->display_info_page($this->get_info());
}
}
}
@ -29,18 +29,20 @@ class ET extends Extension
{
global $user;
if ($user->can(Permissions::VIEW_SYSINTO)) {
$event->add_link("System Info", make_link("system_info"), 99);
$event->add_link("System Info", make_link("system_info"));
}
}
public function onCommand(CommandEvent $event)
{
if ($event->cmd == "help") {
print "\tshimmie-info\n";
print "\tget-info\n";
print "\t\tList a bunch of info\n\n";
}
if ($event->cmd == "shimmie-info") {
print($this->to_yaml($this->get_info()));
if ($event->cmd == "info") {
foreach ($this->get_info() as $k => $v) {
print("$k = $v\n");
}
}
}
@ -51,81 +53,51 @@ class ET extends Extension
{
global $config, $database;
$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 = [];
$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 = [
"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),
],
];
$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';
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) {
}
$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;
}
$info['sys_extensions'] = join(', ', $els);
$info['handled_extensions'] = join(', ', DataHandlerExtension::get_all_supported_exts());
//$cfs = array();
//foreach($database->get_all("SELECT name, value FROM config") as $pair) {
// $cfs[] = $pair['name']."=".$pair['value'];
//}
//$info[''] = "Config: ".join(", ", $cfs);
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;
}
}

View File

@ -1,10 +1,5 @@
<?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
{
/*
@ -12,32 +7,61 @@ class ETTheme extends Themelet
*
* $info = an array of ($name => $value)
*/
public function display_info_page($yaml)
public function display_info_page($info)
{
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($yaml)));
$page->add_block(new Block("Information:", $this->build_data_form($info)));
}
protected function build_data_form($yaml)
protected function build_data_form($info)
{
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;"]
)),
);
$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;
}
}

View File

@ -30,7 +30,7 @@ class ExtManagerTheme extends Themelet
$form = SHM_SIMPLE_FORM(
"ext_manager/set",
TABLE(
["id"=>'extensions', "class"=>'zebra'],
["id"=>'extensions', "class"=>'zebra sortable'],
THEAD(TR(
$editable ? TH("Enabled") : null,
TH("Name"),

View File

@ -1,8 +1,5 @@
<?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
{
@ -22,21 +19,16 @@ 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 (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()
])
)
);
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>
";
}
}

View File

@ -249,10 +249,19 @@ class Forum extends Extension
{
global $config, $database;
$threadsPerPage = $config->get_int('forumThreadsPerPage', 15);
$totalPages = (int)ceil($database->get_one("SELECT COUNT(*) FROM forum_threads") / $threadsPerPage);
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_threads") / $threadsPerPage);
if ($event->count_args() >= 2) {
$pageNumber = page_number($event->get_arg(1), $totalPages);
$pageNumber = $event->get_arg(1);
if (!is_numeric($pageNumber)) {
$pageNumber = 0;
} elseif ($pageNumber <= 0) {
$pageNumber = 0;
} elseif ($pageNumber >= $totalPages) {
$pageNumber = $totalPages - 1;
} else {
$pageNumber--;
}
} else {
$pageNumber = 0;
}
@ -277,11 +286,20 @@ class Forum extends Extension
global $config, $database;
$threadID = int_escape($event->get_arg(1));
$postsPerPage = $config->get_int('forumPostsPerPage', 15);
$totalPages = (int)ceil($database->get_one("SELECT COUNT(*) FROM forum_posts WHERE thread_id = :id", ['id'=>$threadID]) / $postsPerPage);
$totalPages = 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 = page_number($event->get_arg(2), $totalPages);
$pageNumber = $event->get_arg(2);
if (!is_numeric($pageNumber)) {
$pageNumber = 0;
} elseif ($pageNumber <= 0) {
$pageNumber = 0;
} elseif ($pageNumber >= $totalPages) {
$pageNumber = $totalPages - 1;
} else {
$pageNumber--;
}
} else {
$pageNumber = 0;
}

View File

@ -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_HIDDEN;
public $visibility = self::VISIBLE_ADMIN;
public $description = "If no other extension puts anything onto the page, show 404";
public $core = true;
}

View File

@ -2,7 +2,7 @@
class ArchiveFileHandler extends DataHandlerExtension
{
protected $SUPPORTED_MIME = [MIME_TYPE_ZIP];
protected $SUPPORTED_EXT = ["zip"];
public function onInitExt(InitExtEvent $event)
{

View File

@ -2,7 +2,7 @@
class CBZFileHandler extends DataHandlerExtension
{
public $SUPPORTED_MIME = [MIME_TYPE_COMIC_ZIP];
public $SUPPORTED_EXT = ["cbz"];
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(get_mime($cover)),
get_extension(getMimeType($cover)),
null
);
return true;

View File

@ -2,7 +2,7 @@
class FlashFileHandler extends DataHandlerExtension
{
protected $SUPPORTED_MIME = [MIME_TYPE_FLASH];
protected $SUPPORTED_EXT = ["swf"];
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
{

View File

@ -2,7 +2,7 @@
class IcoFileHandler extends DataHandlerExtension
{
protected $SUPPORTED_MIME = [MIME_TYPE_ICO, MIME_TYPE_ANI];
protected $SUPPORTED_EXT = ["ico", "ani", "cur"];
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
{

View File

@ -2,7 +2,7 @@
class MP3FileHandler extends DataHandlerExtension
{
protected $SUPPORTED_MIME = [MIME_TYPE_MP3];
protected $SUPPORTED_EXT = ["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 get_mime($tmpname) === MIME_TYPE_MP3;
return getMimeType($tmpname) == 'audio/mpeg';
}
}

View File

@ -2,13 +2,13 @@
class PixelFileHandler extends DataHandlerExtension
{
protected $SUPPORTED_MIME = [MIME_TYPE_JPEG, MIME_TYPE_GIF, MIME_TYPE_PNG, MIME_TYPE_WEBP];
protected $SUPPORTED_EXT = ["jpg", "jpeg", "gif", "png", "webp"];
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
{
if (in_array($event->ext, Media::LOSSLESS_FORMATS)) {
$event->image->lossless = true;
} elseif ($event->ext==EXTENSION_WEBP) {
} elseif ($event->ext=="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 EXTENSION_GIF:
case "gif":
$event->image->video = Media::is_animated_gif($event->file_name);
break;
case EXTENSION_WEBP:
case "webp":
$event->image->video = Media::is_animated_webp($event->file_name);
break;
default:

View File

@ -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(), "IFD0", true);
$exif = @exif_read_data($image->get_image_filename(), 0, true);
if ($exif) {
$head = "";
foreach ($exif as $key => $section) {

View File

@ -3,7 +3,7 @@ use enshrined\svgSanitize\Sanitizer;
class SVGFileHandler extends DataHandlerExtension
{
protected $SUPPORTED_MIME = [MIME_TYPE_SVG];
protected $SUPPORTED_EXT = ["svg"];
/** @var SVGFileHandlerTheme */
protected $theme;
@ -16,7 +16,7 @@ class SVGFileHandler extends DataHandlerExtension
$image = Image::by_id($id);
$hash = $image->hash;
$page->set_type(MIME_TYPE_SVG);
$page->set_type("image/svg+xml");
$page->set_mode(PageMode::DATA);
$sanitizer = new Sanitizer();
@ -67,7 +67,7 @@ class SVGFileHandler extends DataHandlerExtension
protected function check_contents(string $file): bool
{
if (get_mime($file)!==MIME_TYPE_SVG) {
if (getMimeType($file)!="image/svg+xml") {
return false;
}

View File

@ -1,55 +1,30 @@
<?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
{
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_MIME = [
'video/webm',
'video/mp4',
'video/ogg',
'video/flv',
'video/x-flv'
];
protected $SUPPORTED_MIME = self::SUPPORTED_MIME;
protected $SUPPORTED_EXT = ["flv", "mp4", "m4v", "ogv", "webm"];
public function onInitExt(InitExtEvent $event)
{
global $config;
$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;
$config->set_default_bool('video_playback_autoplay', true);
$config->set_default_bool('video_playback_loop', true);
}
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = new SetupBlock("Video Options");
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, "Autoplay: ");
$sb->add_bool_option("video_playback_autoplay", "Autoplay: ");
$sb->add_label("<br>");
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_LOOP, "Loop: ");
$sb->add_label("<br>Enabled Formats:");
$sb->add_multichoice_option(VideoFileHandlerConfig::ENABLED_FORMATS, $this->get_options());
$sb->add_bool_option("video_playback_loop", "Loop: ");
$event->panel->add_block($sb);
}
@ -105,19 +80,6 @@ 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);
@ -125,18 +87,6 @@ class VideoFileHandler extends DataHandlerExtension
protected function check_contents(string $tmpname): bool
{
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;
return in_array(getMimeType($tmpname), $this->SUPPORTED_MIME);
}
}

View File

@ -9,28 +9,18 @@ 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(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY);
$loop = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_LOOP);
$autoplay = $config->get_bool("video_playback_autoplay");
$loop = $config->get_bool("video_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
$mime = get_mime_for_extension($ext);
if (in_array($mime, VideoFileHandler::SUPPORTED_MIME)) {
$supportedExts = ['mp4' => 'video/mp4', 'm4v' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm', 'flv' => 'video/flv'];
if (array_key_exists($ext, $supportedExts)) {
//FLV isn't supported by <video>, but it should always fallback to the flash-based method.
if ($mime == MIME_TYPE_WEBM) {
//Several browsers still lack WebM support sadly: https://caniuse.com/#feat=webm
if ($ext == "webm") {
//Several browsers still lack WebM support sadly: http://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]-->";
}
@ -50,7 +40,7 @@ class VideoFileHandlerTheme extends Themelet
<img alt='thumb' src=\"{$thumb_url}\" />
</object>";
if ($mime == MIME_TYPE_FLASH_VIDEO) {
if ($ext == "flv") {
//FLV doesn't support <video>.
$html .= $html_fallback;
} else {
@ -59,8 +49,8 @@ class VideoFileHandlerTheme extends Themelet
$html .= "
<video controls class='shm-main-image' id='main_image' alt='main image' poster='$thumb_url' {$autoplay} {$loop}
style='height: $height; width: $width; max-width: 100%'>
<source src='{$ilink}' type='{$mime}'>
style='max-width: 100%'>
<source src='{$ilink}' type='{$supportedExts[$ext]}'>
<!-- If browser doesn't support filetype, fallback to flash -->
{$html_fallback}
@ -70,7 +60,7 @@ class VideoFileHandlerTheme extends Themelet
}
} else {
//This should never happen, but just in case let's have a fallback..
$html = "Video type '$mime' not recognised";
$html = "Video type '$ext' not recognised";
}
$page->add_block(new Block("Video", $html, "main", 10));
}

View File

@ -9,6 +9,5 @@ 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;
}

View File

@ -1,16 +0,0 @@
<?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);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -5,22 +5,6 @@ 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;
@ -50,27 +34,6 @@ 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()
{
@ -85,23 +48,14 @@ 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];
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' /> ";
}
$counter_text .= " <img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' /> ";
}
// get the homelinks and process them
@ -120,6 +74,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, $streak_comma);
return $this->theme->build_body($sitename, $main_links, $main_text, $contact_link, $num_comma, $counter_text);
}
}

View File

@ -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; }

View File

@ -50,7 +50,7 @@ EOD
<div class='space' id='foot'>
<small><small>
$contact_link Serving $num_comma posts &ndash;
Running <a href='https://code.shishnet.org/shimmie2/'>Shimmie2</a>
Running <a href='http://code.shishnet.org/shimmie2/'>Shimmie2</a>
</small></small>
</div>
</div>";

Binary file not shown.

View File

@ -8,7 +8,6 @@ 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';

View File

@ -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_HIDDEN;
public $visibility = self::VISIBLE_ADMIN;
public $core = true;
}

View File

@ -21,8 +21,8 @@ class ImageIO extends Extension
];
const THUMBNAIL_TYPES = [
'JPEG' => EXTENSION_JPG,
'WEBP (Not IE/Safari compatible)' => EXTENSION_WEBP
'JPEG' => "jpg",
'WEBP (Not IE/Safari compatible)' => "webp"
];
public function onInitExt(InitExtEvent $event)
@ -32,8 +32,7 @@ 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, EXTENSION_JPG);
$config->set_default_string(ImageConfig::THUMB_FIT, Media::RESIZE_TYPE_FIT);
$config->set_default_string(ImageConfig::THUMB_TYPE, 'jpg');
if (function_exists(self::EXIF_READ_FUNCTION)) {
$config->set_default_bool(ImageConfig::SHOW_META, false);
@ -54,13 +53,17 @@ class ImageIO extends Extension
if ($image) {
send_event(new ImageDeletionEvent($image));
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(referer_or(make_link("post/list"), ['post/view']));
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"));
}
}
}
} 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(int_escape($_POST['image_id']));
$image = Image::by_id($_POST['image_id']);
if ($image) {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link('upload/replace/'.$image->id));
@ -216,41 +219,35 @@ 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", true);
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "Upload collision handler", true);
$sb->add_text_option(ImageConfig::TIP, "Image tooltip: ");
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "<br>Upload collision handler: ");
if (function_exists(self::EXIF_READ_FUNCTION)) {
$sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true);
$sb->add_bool_option(ImageConfig::SHOW_META, "<br>Show metadata: ");
}
$sb->end_table();
$event->panel->add_block($sb);
$sb = new SetupBlock("Thumbnailing");
$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_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->add_int_option(ImageConfig::THUMB_WIDTH, "Max Width", true);
$sb->add_int_option(ImageConfig::THUMB_HEIGHT, "Max Height", 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 ");
$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();
$sb->add_label("<br>High-DPI scaling ");
$sb->add_int_option(ImageConfig::THUMB_SCALING);
$sb->add_label("%");
$event->panel->add_block($sb);
}
@ -278,7 +275,11 @@ class ImageIO extends Extension
if (!is_null($image)) {
if ($type == "thumb") {
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
$page->set_type(get_mime_for_extension($ext));
if (array_key_exists($ext, MIME_TYPE_MAP)) {
$page->set_type(MIME_TYPE_MAP[$ext]);
} else {
$page->set_type("image/jpeg");
}
$file = $image->get_thumb_filename();
} else {
@ -286,12 +287,6 @@ 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 {

View File

@ -20,23 +20,4 @@ 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);
}
}

View File

@ -105,7 +105,7 @@ class ImageBan extends Extension
}
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(referer_or(make_link()));
$page->set_redirect($_SERVER['HTTP_REFERER']);
}
} 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(referer_or(make_link()));
$page->set_redirect($_SERVER['HTTP_REFERER']);
} elseif ($event->get_arg(0) == "list") {
$t = new HashBanTable($database->raw_db());
$t->token = $user->get_auth_token();

View File

@ -3,13 +3,6 @@ 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();

View File

@ -245,18 +245,11 @@ 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)) {
// 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
//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
$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++;

View File

@ -190,17 +190,4 @@ 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);
}
}

View File

@ -15,7 +15,7 @@ class LinkImageTheme extends Themelet
<table><tr>
<td><fieldset>
<legend><a href='https://en.wikipedia.org/wiki/Bbcode' target='_blank'>BBCode</a></legend>
<legend><a href='http://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='https://en.wikipedia.org/wiki/Html' target='_blank'>HTML</a></legend>
<legend><a href='http://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").

View File

@ -234,11 +234,11 @@ class LogDatabase extends Extension
{
$sb = new SetupBlock("Logging (Database)");
$sb->add_choice_option("log_db_priority", [
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" => SCORE_LOG_DEBUG,
"Info" => SCORE_LOG_INFO,
"Warning" => SCORE_LOG_WARNING,
"Error" => SCORE_LOG_ERROR,
"Critical" => SCORE_LOG_CRITICAL,
], "Debug Level: ");
$event->panel->add_block($sb);
}

View File

@ -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,
string $resize_type = Media::RESIZE_TYPE_FIT,
bool $ignore_aspect_ratio = false,
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;
}
}

View File

@ -26,32 +26,28 @@ class Media extends Extension
const LOSSLESS_FORMATS = [
self::WEBP_LOSSLESS,
EXTENSION_PNG,
EXTENSION_PSD,
EXTENSION_BMP,
EXTENSION_ICO,
EXTENSION_CUR,
EXTENSION_ANI,
EXTENSION_GIF
"png",
"psd",
"bmp",
"ico",
"cur",
"ani",
"gif"
];
const ALPHA_FORMATS = [
self::WEBP_LOSSLESS,
self::WEBP_LOSSY,
EXTENSION_WEBP,
EXTENSION_PNG,
"webp",
"png",
];
const FORMAT_ALIASES = [
EXTENSION_TIF => EXTENSION_TIFF,
EXTENSION_JPEG => EXTENSION_JPG,
"tif" => "tiff",
"jpeg" => "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 =
@ -122,7 +118,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);
@ -210,10 +206,6 @@ 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);
@ -228,7 +220,7 @@ class Media extends Extension
$event->target_height,
$event->output_path,
$event->target_format,
$event->resize_type,
$event->ignore_aspect_ratio,
$event->target_quality,
$event->allow_upscale
);
@ -244,7 +236,7 @@ class Media extends Extension
$event->target_height,
$event->output_path,
$event->target_format,
$event->resize_type,
$event->ignore_aspect_ratio,
$event->target_quality,
$event->minimize,
$event->allow_upscale
@ -329,7 +321,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.
* https://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize
* http://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.
@ -364,7 +356,6 @@ 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);
@ -372,7 +363,7 @@ class Media extends Extension
$codec = "mjpeg";
$quality = $config->get_int(ImageConfig::THUMB_QUALITY);
if ($config->get_string(ImageConfig::THUMB_TYPE) == EXTENSION_WEBP) {
if ($config->get_string(ImageConfig::THUMB_TYPE) == "webp") {
$codec = "libwebp";
} else {
// mjpeg quality ranges from 2-31, with 2 being the best quality.
@ -385,11 +376,12 @@ class Media extends Extension
$args = [
escapeshellarg($ffmpeg),
"-y", "-i", escapeshellarg($inname),
"-vf", "thumbnail",
"-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}",
"-f", "image2",
"-vframes", "1",
"-c:v", "png",
escapeshellarg($tmpname),
"-c:v", $codec,
"-q:v", $quality,
escapeshellarg($outname),
];
$cmd = escapeshellcmd(implode(" ", $args));
@ -398,10 +390,6 @@ 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");
@ -448,7 +436,7 @@ class Media extends Extension
switch ($format) {
case self::WEBP_LOSSLESS:
case self::WEBP_LOSSY:
return EXTENSION_WEBP;
return "webp";
default:
return $format;
}
@ -457,7 +445,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 EXTENSION_PNG:
// case "png":
// $result = $image->setOption('png:compression-level', 9);
// if ($result !== true) {
// throw new GraphicsException("Could not set png compression option");
@ -567,7 +555,7 @@ class Media extends Extension
return true;
}
switch ($format) {
case EXTENSION_WEBP:
case "webp":
return self::is_lossless_webp($filename);
break;
}
@ -581,7 +569,7 @@ class Media extends Extension
int $new_height,
string $output_filename,
string $output_type = null,
string $resize_type = self::RESIZE_TYPE_FIT,
bool $ignore_aspect_ratio = false,
int $output_quality = 80,
bool $minimize = false,
bool $allow_upscale = true
@ -598,7 +586,7 @@ class Media extends Extension
$output_type = $input_type;
}
if ($output_type==EXTENSION_WEBP && self::is_lossless($input_path, $input_type)) {
if ($output_type=="webp" && self::is_lossless($input_path, $input_type)) {
$output_type = self::WEBP_LOSSLESS;
}
@ -610,58 +598,36 @@ class Media extends Extension
$input_type = $input_type . ":";
}
$resize_suffix = "";
$resize_args = "";
if (!$allow_upscale) {
$resize_suffix .= "\>";
$resize_args .= "\>";
}
if ($resize_type==Media::RESIZE_TYPE_STRETCH) {
$resize_suffix .= "\!";
if ($ignore_aspect_ratio) {
$resize_args .= "\!";
}
$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 EXTENSION_PNG:
case "png":
$args .= '-define png:compression-level=9';
break;
}
$args .= " -quality ${output_quality} -background ${bg}";
if ($minimize) {
$args .= " -strip -thumbnail";
} else {
$args .= " -resize";
}
$output_ext = self::determine_ext($output_type);
$format = '"%s" %s %s:"%s" 2>&1';
$cmd = sprintf($format, $convert, $args, $output_ext, $output_filename);
$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);
$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) {
@ -691,7 +657,7 @@ class Media extends Extension
int $new_height,
string $output_filename,
string $output_type = null,
string $resize_type = self::RESIZE_TYPE_FIT,
bool $ignore_aspect_ratio = false,
int $output_quality = 80,
bool $allow_upscale = true
) {
@ -702,19 +668,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 = EXTENSION_GIF;
$output_type = "gif";
break;
case IMAGETYPE_JPEG:
$output_type = EXTENSION_JPEG;
$output_type = "jpeg";
break;
case IMAGETYPE_PNG:
$output_type = EXTENSION_PNG;
$output_type = "png";
break;
case IMAGETYPE_WEBP:
$output_type = EXTENSION_WEBP;
$output_type = "webp";
break;
case IMAGETYPE_BMP:
$output_type = EXTENSION_BMP;
$output_type = "bmp";
break;
default:
throw new MediaException("Failed to save the new image - Unsupported image type.");
@ -727,7 +693,7 @@ class Media extends Extension
throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)");
}
if ($resize_type==Media::RESIZE_TYPE_FIT) {
if (!$ignore_aspect_ratio) {
list($new_width, $new_height) = get_scaled_by_aspect_ratio($width, $height, $new_width, $new_height);
}
if (!$allow_upscale &&
@ -775,7 +741,7 @@ class Media extends Extension
case IMAGETYPE_PNG:
case IMAGETYPE_WEBP:
//
// More info here: https://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
// More info here: http://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");
@ -810,21 +776,21 @@ class Media extends Extension
}
switch ($output_type) {
case EXTENSION_BMP:
case "bmp":
$result = imagebmp($image_resized, $output_filename, true);
break;
case EXTENSION_WEBP:
case "webp":
case Media::WEBP_LOSSY:
$result = imagewebp($image_resized, $output_filename, $output_quality);
break;
case EXTENSION_JPG:
case EXTENSION_JPEG:
case "jpg":
case "jpeg":
$result = imagejpeg($image_resized, $output_filename, $output_quality);
break;
case EXTENSION_PNG:
case "png":
$result = imagepng($image_resized, $output_filename, 9);
break;
case EXTENSION_GIF:
case "gif":
$result = imagegif($image_resized, $output_filename);
break;
default:
@ -850,7 +816,7 @@ class Media extends Extension
$is_anim_gif = 0;
if (($fh = @fopen($image_filename, 'rb'))) {
try {
//check if gif is animated (via https://www.php.net/manual/en/function.imagecreatefromgif.php#104473)
//check if gif is animated (via http://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);
@ -938,7 +904,7 @@ class Media extends Extension
*/
public static function normalize_format(string $format, ?bool $lossless = null): ?string
{
if ($format == EXTENSION_WEBP) {
if ($format == "webp") {
if ($lossless === true) {
$format = Media::WEBP_LOSSLESS;
} else {
@ -1050,7 +1016,7 @@ class Media extends Extension
$this->set_version(MediaConfig::VERSION, 2);
$database->begin_transaction();
$database->beginTransaction();
}
}
}

View File

@ -15,81 +15,63 @@ abstract class MediaEngine
];
public const OUTPUT_SUPPORT = [
MediaEngine::GD => [
EXTENSION_GIF,
EXTENSION_JPG,
EXTENSION_PNG,
EXTENSION_WEBP,
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
],
MediaEngine::IMAGICK => [
EXTENSION_GIF,
EXTENSION_JPG,
EXTENSION_PNG,
EXTENSION_WEBP,
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS,
],
MediaEngine::FFMPEG => [
EXTENSION_JPG,
EXTENSION_WEBP,
EXTENSION_PNG,
"jpg",
"webp",
"png",
],
MediaEngine::STATIC => [
EXTENSION_JPG,
"jpg",
],
];
public const INPUT_SUPPORT = [
MediaEngine::GD => [
EXTENSION_BMP,
EXTENSION_GIF,
EXTENSION_JPG,
EXTENSION_PNG,
EXTENSION_WEBP,
"bmp",
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS,
],
MediaEngine::IMAGICK => [
EXTENSION_BMP,
EXTENSION_GIF,
EXTENSION_JPG,
EXTENSION_PNG,
EXTENSION_PSD,
EXTENSION_TIFF,
EXTENSION_WEBP,
"bmp",
"gif",
"jpg",
"png",
"psd",
"tiff",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS,
EXTENSION_ICO,
"ico",
],
MediaEngine::FFMPEG => [
EXTENSION_AVI,
EXTENSION_MKV,
EXTENSION_WEBM,
EXTENSION_MP4,
EXTENSION_MOV,
EXTENSION_FLASH_VIDEO,
"avi",
"mkv",
"webm",
"mp4",
"mov",
"flv",
],
MediaEngine::STATIC => [
EXTENSION_JPG,
EXTENSION_GIF,
EXTENSION_PNG,
"jpg",
"gif",
"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
]
];
}

View File

@ -113,7 +113,7 @@ class NotATag extends Extension
["tag"=>$input['c_tag'], "redirect"=>$input['c_redirect']]
);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(referer_or(make_link()));
$page->set_redirect($_SERVER['HTTP_REFERER']);
} 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(referer_or(make_link()));
$page->set_redirect($_SERVER['HTTP_REFERER']);
} elseif ($event->get_arg(0) == "list") {
$t = new NotATagTable($database->raw_db());
$t->token = $user->get_auth_token();

View File

@ -355,7 +355,7 @@ class Notes extends Extension
{
global $database, $config;
$pageNumber = $event->try_page_num(1);
$pageNumber = $event->try_page_num(1) - 1;
$notesPerPage = $config->get_int('notesNotesPerPage');
@ -383,7 +383,7 @@ class Notes extends Extension
{
global $config, $database;
$pageNumber = $event->try_page_num(1);
$pageNumber = $event->try_page_num(1) - 1;
$requestsPerPage = $config->get_int('notesRequestsPerPage');
@ -430,7 +430,7 @@ class Notes extends Extension
{
global $config, $database;
$pageNumber = $event->try_page_num(1);
$pageNumber = $event->try_page_num(1) - 1;
$historiesPerPage = $config->get_int('notesHistoriesPerPage');

Some files were not shown because too many files have changed in this diff Show More