Compare commits
86 Commits
Author | SHA1 | Date | |
---|---|---|---|
6631e2cb6f | |||
|
7040b1b8e5 | ||
|
c1068f1b2b | ||
|
bb5614c5ef | ||
|
81417a5031 | ||
|
2197b15012 | ||
|
eecd35d175 | ||
|
1c216e8d51 | ||
|
04987ea70e | ||
|
844ec8b53e | ||
|
72de50aa71 | ||
|
ac63992efa | ||
|
7c32b1f7a8 | ||
|
919a3039c4 | ||
|
ea34d9b756 | ||
|
30f62c2ff8 | ||
|
9b9f1d0341 | ||
|
835c3b68a1 | ||
|
b937ad6255 | ||
|
8e976fb812 | ||
|
dd08b936e3 | ||
|
1fdd5bf575 | ||
|
6d16c52367 | ||
|
587735a866 | ||
|
a2fe0725f5 | ||
|
73660b376e | ||
|
d243867b18 | ||
|
fac2067069 | ||
|
2f313b704a | ||
|
8fe7038e73 | ||
|
c171e98591 | ||
|
edc8e5aa43 | ||
|
f6923af8ab | ||
|
18cd74f57d | ||
|
4d69e7ce34 | ||
|
04cde74226 | ||
|
b2405166b3 | ||
|
4d0b90921d | ||
|
05d4a3a592 | ||
|
b3fb923cd1 | ||
|
ad1e52bf05 | ||
|
549ec593bb | ||
|
ed8a9fca52 | ||
|
ec290d8676 | ||
|
5446f29141 | ||
|
86f7a06ed0 | ||
|
e1aefb78ab | ||
|
12c331cbd2 | ||
|
8b407e3df3 | ||
|
bccb206369 | ||
|
2bb5f349f9 | ||
|
007e07e507 | ||
|
63b2601e67 | ||
|
16c58e266b | ||
|
6145ecc6f8 | ||
|
830915adf2 | ||
|
06bd4589da | ||
|
1e76fb239e | ||
|
f7c6b662cd | ||
|
00060c34c2 | ||
|
10d46395d7 | ||
|
72645af9a4 | ||
|
2cae6cd273 | ||
|
0b2e36303d | ||
|
b0cb46abca | ||
|
78710166a1 | ||
|
c146a9f53d | ||
|
f6112d26a2 | ||
|
b04b5af190 | ||
|
02d42a01b4 | ||
|
0039aafe94 | ||
|
1d389f0156 | ||
|
69cb67fe24 | ||
|
5ea26a80cc | ||
|
126c629a1a | ||
|
ab4b745310 | ||
|
f47e35e4e5 | ||
|
fd359fb08c | ||
|
866b77ab19 | ||
|
b60e8ac5b4 | ||
|
70acc6015b | ||
|
a3a129df5f | ||
|
02675609b4 | ||
|
85662575c5 | ||
|
c16e3fd939 | ||
|
5ea7cc5b36 |
2
.github/workflows/test_and_publish.yml
vendored
2
.github/workflows/test_and_publish.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p data/config
|
||||
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
|
||||
sudo apt update && sudo apt-get install -y postgresql postgresql-client ;
|
||||
sudo systemctl start postgresql ;
|
||||
psql --version ;
|
||||
sudo -u postgres psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres ;
|
||||
sudo -u postgres psql -c "CREATE USER shimmie WITH PASSWORD 'shimmie';" -U postgres ;
|
||||
|
@ -17,8 +17,7 @@
|
||||
# rather than link to images/ha/hash and have an ugly filename,
|
||||
# we link to images/hash/tags.ext; mod_rewrite splits things so
|
||||
# that shimmie sees hash and the user sees tags.ext
|
||||
RewriteRule ^_images/([0-9a-f]{2})([0-9a-f]{30}).*$ data/images/$1/$1$2 [L]
|
||||
RewriteRule ^_thumbs/([0-9a-f]{2})([0-9a-f]{30}).*$ data/thumbs/$1/$1$2 [L]
|
||||
RewriteRule ^_(images|thumbs)/([0-9a-f]{2})([0-9a-f]{30}).*$ data/$1/$2/$2$3 [L]
|
||||
|
||||
# any requests for files which don't physically exist should be handled by index.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
|
@ -6,10 +6,6 @@
|
||||
"minimum-stability" : "dev",
|
||||
|
||||
"repositories" : [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
},
|
||||
{
|
||||
"type" : "package",
|
||||
"package" : {
|
||||
@ -25,18 +21,19 @@
|
||||
],
|
||||
|
||||
"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" : "^1.0.0",
|
||||
"shish/ffsphp" : "0.0.*",
|
||||
"shish/microcrud" : "^1.0.0",
|
||||
"shish/microhtml" : "^1.0.0",
|
||||
"shish/eventtracer-php" : "^2.0.0",
|
||||
"shish/ffsphp" : "^1.0.0",
|
||||
"shish/microcrud" : "^2.0.0",
|
||||
"shish/microhtml" : "^2.0.0",
|
||||
"enshrined/svg-sanitize" : "0.13.*",
|
||||
|
||||
"bower-asset/jquery" : "1.12.*",
|
||||
@ -46,8 +43,7 @@
|
||||
},
|
||||
|
||||
"require-dev" : {
|
||||
"phpunit/phpunit" : "8.*"
|
||||
},
|
||||
},
|
||||
|
||||
"suggest": {
|
||||
"ext-memcache": "memcache caching",
|
||||
@ -58,9 +54,14 @@
|
||||
"ext-curl": "some extensions",
|
||||
"ext-ctype": "some extensions",
|
||||
"ext-json": "some extensions",
|
||||
"ext-zip": "self-updater extension",
|
||||
"ext-zip": "self-updater extension, bulk import/export",
|
||||
"ext-zlib": "anti-spam",
|
||||
"ext-xml": "some extensions",
|
||||
"ext-gd": "GD-based thumbnailing"
|
||||
}
|
||||
},"replace": {
|
||||
"bower-asset/jquery": ">=1.11.0",
|
||||
"bower-asset/inputmask": ">=3.2.0",
|
||||
"bower-asset/punycode": ">=1.3.0",
|
||||
"bower-asset/yii2-pjax": ">=2.0.0"
|
||||
}
|
||||
}
|
||||
|
1636
composer.lock
generated
1636
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ abstract class PageMode
|
||||
const DATA = 'data';
|
||||
const PAGE = 'page';
|
||||
const FILE = 'file';
|
||||
const MANUAL = 'manual';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,16 +239,13 @@ class BasePage
|
||||
|
||||
// ==============================================
|
||||
|
||||
/**
|
||||
* Display the page according to the mode and data given.
|
||||
*/
|
||||
public function display(): void
|
||||
public function send_headers(): void
|
||||
{
|
||||
header("HTTP/1.0 {$this->code} Shimmie");
|
||||
header("Content-type: " . $this->type);
|
||||
header("X-Powered-By: Shimmie-" . VERSION);
|
||||
|
||||
if (!headers_sent()) {
|
||||
header("HTTP/1.0 {$this->code} Shimmie");
|
||||
header("Content-type: " . $this->type);
|
||||
header("X-Powered-By: Shimmie-" . VERSION);
|
||||
|
||||
foreach ($this->http_headers as $head) {
|
||||
header($head);
|
||||
}
|
||||
@ -257,8 +255,20 @@ class BasePage
|
||||
} else {
|
||||
print "Error: Headers have already been sent to the client.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the page according to the mode and data given.
|
||||
*/
|
||||
public function display(): void
|
||||
{
|
||||
if ($this->mode!=PageMode::MANUAL) {
|
||||
$this->send_headers();
|
||||
}
|
||||
|
||||
switch ($this->mode) {
|
||||
case PageMode::MANUAL:
|
||||
break;
|
||||
case PageMode::PAGE:
|
||||
usort($this->blocks, "blockcmp");
|
||||
$this->add_auto_html_headers();
|
||||
@ -394,7 +404,6 @@ 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/js-cookie/src/js.cookie.js",
|
||||
"ext/static_files/modernizr-3.3.1.custom.js",
|
||||
|
@ -53,7 +53,7 @@ class BaseThemelet
|
||||
$h_tip = html_escape($image->get_tooltip());
|
||||
$h_tags = html_escape(strtolower($image->get_tag_list()));
|
||||
|
||||
$extArr = array_flip(['swf', 'svg', 'mp3']); //List of thumbless filetypes
|
||||
$extArr = array_flip([EXTENSION_FLASH, EXTENSION_SVG, EXTENSION_MP3]); //List of thumbless filetypes
|
||||
if (!isset($extArr[$image->ext])) {
|
||||
$tsize = get_thumbnail_size($image->width, $image->height);
|
||||
} else {
|
||||
|
@ -60,7 +60,7 @@ class Database
|
||||
$this->connect_engine();
|
||||
$this->engine->init($this->db);
|
||||
|
||||
$this->beginTransaction();
|
||||
$this->begin_transaction();
|
||||
}
|
||||
|
||||
private function connect_engine(): void
|
||||
@ -78,11 +78,14 @@ class Database
|
||||
} elseif ($db_proto === DatabaseDriver::SQLITE) {
|
||||
$this->engine = new SQLite();
|
||||
} else {
|
||||
die('Unknown PDO driver: '.$db_proto);
|
||||
die_nicely(
|
||||
'Unknown PDO driver: '.$db_proto,
|
||||
"Please check that this is a valid driver, installing the PHP modules if needed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function beginTransaction(): void
|
||||
public function begin_transaction(): void
|
||||
{
|
||||
if ($this->transaction === false) {
|
||||
$this->db->beginTransaction();
|
||||
@ -90,9 +93,14 @@ class Database
|
||||
}
|
||||
}
|
||||
|
||||
public function is_transaction_open(): bool
|
||||
{
|
||||
return !is_null($this->db) && $this->transaction === true;
|
||||
}
|
||||
|
||||
public function commit(): bool
|
||||
{
|
||||
if (!is_null($this->db) && $this->transaction === true) {
|
||||
if ($this->is_transaction_open()) {
|
||||
$this->transaction = false;
|
||||
return $this->db->commit();
|
||||
} else {
|
||||
@ -102,7 +110,7 @@ class Database
|
||||
|
||||
public function rollback(): bool
|
||||
{
|
||||
if (!is_null($this->db) && $this->transaction === true) {
|
||||
if ($this->is_transaction_open()) {
|
||||
$this->transaction = false;
|
||||
return $this->db->rollback();
|
||||
} else {
|
||||
|
@ -92,7 +92,9 @@ class PostgreSQL extends DBEngine
|
||||
} else {
|
||||
$db->exec("SET application_name TO 'shimmie [local]';");
|
||||
}
|
||||
$this->set_timeout($db, DATABASE_TIMEOUT);
|
||||
if (defined("DATABASE_TIMEOUT")) {
|
||||
$this->set_timeout($db, DATABASE_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
public function scoreql_to_sql(string $data): string
|
||||
|
@ -275,7 +275,7 @@ abstract class FormatterExtension extends Extension
|
||||
*/
|
||||
abstract class DataHandlerExtension extends Extension
|
||||
{
|
||||
protected $SUPPORTED_EXT = [];
|
||||
protected $SUPPORTED_MIME = [];
|
||||
|
||||
protected function move_upload_to_archive(DataUploadEvent $event)
|
||||
{
|
||||
@ -298,14 +298,11 @@ abstract class DataHandlerExtension extends Extension
|
||||
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
|
||||
|
||||
/* Check if we are replacing an image */
|
||||
if (array_key_exists('replace', $event->metadata) && isset($event->metadata['replace'])) {
|
||||
if (!is_null($event->replace_id)) {
|
||||
/* hax: This seems like such a dirty way to do this.. */
|
||||
|
||||
/* Validate things */
|
||||
$image_id = int_escape($event->metadata['replace']);
|
||||
|
||||
/* Check to make sure the image exists. */
|
||||
$existing = Image::by_id($image_id);
|
||||
$existing = Image::by_id($event->replace_id);
|
||||
|
||||
if (is_null($existing)) {
|
||||
throw new UploadException("Image to replace does not exist!");
|
||||
@ -320,19 +317,25 @@ abstract class DataHandlerExtension extends Extension
|
||||
if (is_null($image)) {
|
||||
throw new UploadException("Data handler failed to create image object from data");
|
||||
}
|
||||
if (empty($image->ext)) {
|
||||
throw new UploadException("Unable to determine extension for ". $event->tmpname);
|
||||
}
|
||||
try {
|
||||
send_event(new MediaCheckPropertiesEvent($image));
|
||||
} catch (MediaException $e) {
|
||||
throw new UploadException("Unable to scan media properties: ".$e->getMessage());
|
||||
}
|
||||
|
||||
send_event(new ImageReplaceEvent($image_id, $image));
|
||||
$event->image_id = $image_id;
|
||||
send_event(new ImageReplaceEvent($event->replace_id, $image));
|
||||
$event->image_id = $event->replace_id;
|
||||
} else {
|
||||
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata);
|
||||
if (is_null($image)) {
|
||||
throw new UploadException("Data handler failed to create image object from data");
|
||||
}
|
||||
if (empty($image->ext)) {
|
||||
throw new UploadException("Unable to determine extension for ". $event->tmpname);
|
||||
}
|
||||
try {
|
||||
send_event(new MediaCheckPropertiesEvent($image));
|
||||
} catch (MediaException $e) {
|
||||
@ -406,10 +409,12 @@ abstract class DataHandlerExtension extends Extension
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename'];
|
||||
if ($config->get_bool("upload_use_mime")) {
|
||||
$image->ext = get_extension(getMimeType($filename));
|
||||
} else {
|
||||
$image->ext = get_extension_for_file($filename);
|
||||
}
|
||||
if (empty($image->ext)) {
|
||||
$image->ext = (($pos = strpos($metadata['extension'], '?')) !== false) ? substr($metadata['extension'], 0, $pos) : $metadata['extension'];
|
||||
}
|
||||
|
||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
@ -422,15 +427,20 @@ abstract class DataHandlerExtension extends Extension
|
||||
|
||||
protected function supported_ext(string $ext): bool
|
||||
{
|
||||
return in_array(strtolower($ext), $this->SUPPORTED_EXT);
|
||||
return in_array(get_mime_for_extension($ext), $this->SUPPORTED_MIME);
|
||||
}
|
||||
|
||||
public static function get_all_supported_exts(): array
|
||||
{
|
||||
$arr = [];
|
||||
foreach (getSubclassesOf("DataHandlerExtension") as $handler) {
|
||||
$arr = array_merge($arr, (new $handler())->SUPPORTED_EXT);
|
||||
$handler = (new $handler());
|
||||
|
||||
foreach ($handler->SUPPORTED_MIME as $mime) {
|
||||
$arr = array_merge($arr, get_all_extension_for_mime($mime));
|
||||
}
|
||||
}
|
||||
$arr = array_unique($arr);
|
||||
return $arr;
|
||||
}
|
||||
}
|
||||
|
448
core/filetypes.php
Normal file
448
core/filetypes.php
Normal file
@ -0,0 +1,448 @@
|
||||
<?php
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* MIME types and extension information and resolvers *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
const EXTENSION_ANI = 'ani';
|
||||
const EXTENSION_ASC = 'asc';
|
||||
const EXTENSION_ASF = 'asf';
|
||||
const EXTENSION_AVI = 'avi';
|
||||
const EXTENSION_BMP = 'bmp';
|
||||
const EXTENSION_BZIP = 'bz';
|
||||
const EXTENSION_BZIP2 = 'bz2';
|
||||
const EXTENSION_CBR = 'cbr';
|
||||
const EXTENSION_CBZ = 'cbz';
|
||||
const EXTENSION_CBT = 'cbt';
|
||||
const EXTENSION_CBA = 'cbA';
|
||||
const EXTENSION_CB7 = 'cb7';
|
||||
const EXTENSION_CSS = 'css';
|
||||
const EXTENSION_CSV = 'csv';
|
||||
const EXTENSION_CUR = 'cur';
|
||||
const EXTENSION_FLASH = 'swf';
|
||||
const EXTENSION_FLASH_VIDEO = 'flv';
|
||||
const EXTENSION_GIF = 'gif';
|
||||
const EXTENSION_GZIP = 'gz';
|
||||
const EXTENSION_HTML = 'html';
|
||||
const EXTENSION_HTM = 'htm';
|
||||
const EXTENSION_ICO = 'ico';
|
||||
const EXTENSION_JFIF = 'jfif';
|
||||
const EXTENSION_JFI = 'jfi';
|
||||
const EXTENSION_JPEG = 'jpeg';
|
||||
const EXTENSION_JPG = 'jpg';
|
||||
const EXTENSION_JS = 'js';
|
||||
const EXTENSION_JSON = 'json';
|
||||
const EXTENSION_MKV = 'mkv';
|
||||
const EXTENSION_MP3 = 'mp3';
|
||||
const EXTENSION_MP4 = 'mp4';
|
||||
const EXTENSION_M4V = 'm4v';
|
||||
const EXTENSION_M4A = 'm4a';
|
||||
const EXTENSION_MPEG = 'mpeg';
|
||||
const EXTENSION_MPG = 'mpg';
|
||||
const EXTENSION_OGG = 'ogg';
|
||||
const EXTENSION_OGG_VIDEO = 'ogv';
|
||||
const EXTENSION_OGG_AUDIO = 'oga';
|
||||
const EXTENSION_PDF = 'pdf';
|
||||
const EXTENSION_PHP = 'php';
|
||||
const EXTENSION_PHP5 = 'php5';
|
||||
const EXTENSION_PNG = 'png';
|
||||
const EXTENSION_PSD = 'psd';
|
||||
const EXTENSION_MOV = 'mov';
|
||||
const EXTENSION_RSS = 'rss';
|
||||
const EXTENSION_SVG = 'svg';
|
||||
const EXTENSION_TAR = 'tar';
|
||||
const EXTENSION_TEXT = 'txt';
|
||||
const EXTENSION_TIFF = 'tiff';
|
||||
const EXTENSION_TIF = 'tif';
|
||||
const EXTENSION_WAV = 'wav';
|
||||
const EXTENSION_WEBM = 'webm';
|
||||
const EXTENSION_WEBP = 'webp';
|
||||
const EXTENSION_WMA = 'wma';
|
||||
const EXTENSION_WMV = 'wmv';
|
||||
const EXTENSION_XML = 'xml';
|
||||
const EXTENSION_XSL = 'xsl';
|
||||
const EXTENSION_ZIP = 'zip';
|
||||
|
||||
|
||||
// Couldn't find a mimetype for ani, so made one up based on it being a riff container
|
||||
const MIME_TYPE_ANI = 'application/riff+ani';
|
||||
const MIME_TYPE_ASF = 'video/x-ms-asf';
|
||||
const MIME_TYPE_AVI = 'video/x-msvideo';
|
||||
// Went with mime types from http://fileformats.archiveteam.org/wiki/Comic_Book_Archive
|
||||
const MIME_TYPE_COMIC_ZIP = 'application/vnd.comicbook+zip';
|
||||
const MIME_TYPE_COMIC_RAR = 'application/vnd.comicbook-rar';
|
||||
const MIME_TYPE_BMP = 'image/x-ms-bmp';
|
||||
const MIME_TYPE_BZIP = 'application/x-bzip';
|
||||
const MIME_TYPE_BZIP2 = 'application/x-bzip2';
|
||||
const MIME_TYPE_CSS = 'text/css';
|
||||
const MIME_TYPE_CSV = 'text/csv';
|
||||
const MIME_TYPE_FLASH = 'application/x-shockwave-flash';
|
||||
const MIME_TYPE_FLASH_VIDEO = 'video/x-flv';
|
||||
const MIME_TYPE_GIF = 'image/gif';
|
||||
const MIME_TYPE_GZIP = 'application/x-gzip';
|
||||
const MIME_TYPE_HTML = 'text/html';
|
||||
const MIME_TYPE_ICO = 'image/x-icon';
|
||||
const MIME_TYPE_JPEG = 'image/jpeg';
|
||||
const MIME_TYPE_JS = 'text/javascript';
|
||||
const MIME_TYPE_JSON = 'application/json';
|
||||
const MIME_TYPE_MKV = 'video/x-matroska';
|
||||
const MIME_TYPE_MP3 = 'audio/mpeg';
|
||||
const MIME_TYPE_MP4_AUDIO = 'audio/mp4';
|
||||
const MIME_TYPE_MP4_VIDEO = 'video/mp4';
|
||||
const MIME_TYPE_MPEG = 'video/mpeg';
|
||||
const MIME_TYPE_OCTET_STREAM = 'application/octet-stream';
|
||||
const MIME_TYPE_OGG = 'application/ogg';
|
||||
const MIME_TYPE_OGG_VIDEO = 'video/ogg';
|
||||
const MIME_TYPE_OGG_AUDIO = 'audio/ogg';
|
||||
const MIME_TYPE_PDF = 'application/pdf';
|
||||
const MIME_TYPE_PHP = 'text/x-php';
|
||||
const MIME_TYPE_PNG = 'image/png';
|
||||
const MIME_TYPE_PSD = 'image/vnd.adobe.photoshop';
|
||||
const MIME_TYPE_QUICKTIME = 'video/quicktime';
|
||||
const MIME_TYPE_RSS = 'application/rss+xml';
|
||||
const MIME_TYPE_SVG = 'image/svg+xml';
|
||||
const MIME_TYPE_TAR = 'application/x-tar';
|
||||
const MIME_TYPE_TEXT = 'text/plain';
|
||||
const MIME_TYPE_TIFF = 'image/tiff';
|
||||
const MIME_TYPE_WAV = 'audio/x-wav';
|
||||
const MIME_TYPE_WEBM = 'video/webm';
|
||||
const MIME_TYPE_WEBP = 'image/webp';
|
||||
const MIME_TYPE_WIN_BITMAP = 'image/x-win-bitmap';
|
||||
const MIME_TYPE_XML = 'text/xml';
|
||||
const MIME_TYPE_XML_APPLICATION = 'application/xml';
|
||||
const MIME_TYPE_XSL = 'application/xsl+xml';
|
||||
const MIME_TYPE_ZIP = 'application/zip';
|
||||
|
||||
const MIME_TYPE_MAP_NAME = 'name';
|
||||
const MIME_TYPE_MAP_EXT = 'ext';
|
||||
const MIME_TYPE_MAP_MIME = 'mime';
|
||||
|
||||
// Mime type map. Each entry in the MIME_TYPE_ARRAY represents a kind of file, identified by the "correct" mimetype as the key.
|
||||
// The value for each entry is a map of twokeys, ext and mime.
|
||||
// ext's value is an array of all of the extensions that the file type can use, with the "correct" one being first.
|
||||
// mime's value is an array of all mime types that the file type is known to use, with the current "correct" one being first.
|
||||
|
||||
const MIME_TYPE_MAP = [
|
||||
MIME_TYPE_ANI => [
|
||||
MIME_TYPE_MAP_NAME => "ANI Cursor",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_ANI],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_ANI],
|
||||
],
|
||||
MIME_TYPE_AVI => [
|
||||
MIME_TYPE_MAP_NAME => "AVI",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_AVI],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_AVI,'video/avi','video/msvideo'],
|
||||
],
|
||||
MIME_TYPE_ASF => [
|
||||
MIME_TYPE_MAP_NAME => "ASF/WMV",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_ASF,EXTENSION_WMA,EXTENSION_WMV],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_ASF,'audio/x-ms-wma','video/x-ms-wmv'],
|
||||
],
|
||||
MIME_TYPE_BMP => [
|
||||
MIME_TYPE_MAP_NAME => "BMP",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_BMP],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_BMP],
|
||||
],
|
||||
MIME_TYPE_BZIP => [
|
||||
MIME_TYPE_MAP_NAME => "BZIP",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_BZIP],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP],
|
||||
],
|
||||
MIME_TYPE_BZIP2 => [
|
||||
MIME_TYPE_MAP_NAME => "BZIP2",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_BZIP2],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP2],
|
||||
],
|
||||
MIME_TYPE_COMIC_ZIP => [
|
||||
MIME_TYPE_MAP_NAME => "CBZ",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_CBZ],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_COMIC_ZIP],
|
||||
],
|
||||
MIME_TYPE_CSS => [
|
||||
MIME_TYPE_MAP_NAME => "Cascading Style Sheet",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_CSS],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_CSS],
|
||||
],
|
||||
MIME_TYPE_CSV => [
|
||||
MIME_TYPE_MAP_NAME => "CSV",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_CSV],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_CSV],
|
||||
],
|
||||
MIME_TYPE_FLASH => [
|
||||
MIME_TYPE_MAP_NAME => "Flash",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_FLASH],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH],
|
||||
],
|
||||
MIME_TYPE_FLASH_VIDEO => [
|
||||
MIME_TYPE_MAP_NAME => "Flash Video",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_FLASH_VIDEO],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH_VIDEO,'video/flv'],
|
||||
],
|
||||
MIME_TYPE_GIF => [
|
||||
MIME_TYPE_MAP_NAME => "GIF",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_GIF],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_GIF],
|
||||
],
|
||||
MIME_TYPE_GZIP => [
|
||||
MIME_TYPE_MAP_NAME => "GZIP",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_GZIP],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR],
|
||||
],
|
||||
MIME_TYPE_HTML => [
|
||||
MIME_TYPE_MAP_NAME => "HTML",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_HTM, EXTENSION_HTML],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_HTML],
|
||||
],
|
||||
MIME_TYPE_ICO => [
|
||||
MIME_TYPE_MAP_NAME => "Icon",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_ICO, EXTENSION_CUR],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_ICO, MIME_TYPE_WIN_BITMAP],
|
||||
],
|
||||
MIME_TYPE_JPEG => [
|
||||
MIME_TYPE_MAP_NAME => "JPEG",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_JPG, EXTENSION_JPEG, EXTENSION_JFIF, EXTENSION_JFI],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_JPEG],
|
||||
],
|
||||
MIME_TYPE_JS => [
|
||||
MIME_TYPE_MAP_NAME => "JavaScript",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_JS],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_JS],
|
||||
],
|
||||
MIME_TYPE_JSON => [
|
||||
MIME_TYPE_MAP_NAME => "JSON",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_JSON],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_JSON],
|
||||
],
|
||||
MIME_TYPE_MKV => [
|
||||
MIME_TYPE_MAP_NAME => "Matroska",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_MKV],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MKV],
|
||||
],
|
||||
MIME_TYPE_MP3 => [
|
||||
MIME_TYPE_MAP_NAME => "MP3",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_MP3],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP3],
|
||||
],
|
||||
MIME_TYPE_MP4_AUDIO => [
|
||||
MIME_TYPE_MAP_NAME => "MP4 Audio",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_M4A],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_AUDIO,"audio/m4a"],
|
||||
],
|
||||
MIME_TYPE_MP4_VIDEO => [
|
||||
MIME_TYPE_MAP_NAME => "MP4 Video",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_MP4,EXTENSION_M4V],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_VIDEO,'video/x-m4v'],
|
||||
],
|
||||
MIME_TYPE_MPEG => [
|
||||
MIME_TYPE_MAP_NAME => "MPEG",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_MPG,EXTENSION_MPEG],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_MPEG],
|
||||
],
|
||||
MIME_TYPE_PDF => [
|
||||
MIME_TYPE_MAP_NAME => "PDF",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_PDF],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_PDF],
|
||||
],
|
||||
MIME_TYPE_PHP => [
|
||||
MIME_TYPE_MAP_NAME => "PHP",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_PHP,EXTENSION_PHP5],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_PHP],
|
||||
],
|
||||
MIME_TYPE_PNG => [
|
||||
MIME_TYPE_MAP_NAME => "PNG",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_PNG],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_PNG],
|
||||
],
|
||||
MIME_TYPE_PSD => [
|
||||
MIME_TYPE_MAP_NAME => "PSD",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_PSD],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_PSD],
|
||||
],
|
||||
MIME_TYPE_OGG_AUDIO => [
|
||||
MIME_TYPE_MAP_NAME => "Ogg Vorbis",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_OGG_AUDIO,EXTENSION_OGG],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_AUDIO,MIME_TYPE_OGG],
|
||||
],
|
||||
MIME_TYPE_OGG_VIDEO => [
|
||||
MIME_TYPE_MAP_NAME => "Ogg Theora",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_OGG_VIDEO],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_VIDEO],
|
||||
],
|
||||
MIME_TYPE_QUICKTIME => [
|
||||
MIME_TYPE_MAP_NAME => "Quicktime",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_MOV],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_QUICKTIME],
|
||||
],
|
||||
MIME_TYPE_RSS => [
|
||||
MIME_TYPE_MAP_NAME => "RSS",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_RSS],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_RSS],
|
||||
],
|
||||
MIME_TYPE_SVG => [
|
||||
MIME_TYPE_MAP_NAME => "SVG",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_SVG],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_SVG],
|
||||
],
|
||||
MIME_TYPE_TAR => [
|
||||
MIME_TYPE_MAP_NAME => "TAR",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_TAR],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR],
|
||||
],
|
||||
MIME_TYPE_TEXT => [
|
||||
MIME_TYPE_MAP_NAME => "Text",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_TEXT, EXTENSION_ASC],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_TEXT],
|
||||
],
|
||||
MIME_TYPE_TIFF => [
|
||||
MIME_TYPE_MAP_NAME => "TIFF",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_TIF,EXTENSION_TIFF],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_TIFF],
|
||||
],
|
||||
MIME_TYPE_WAV => [
|
||||
MIME_TYPE_MAP_NAME => "Wave",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_WAV],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_WAV],
|
||||
],
|
||||
MIME_TYPE_WEBM => [
|
||||
MIME_TYPE_MAP_NAME => "WebM",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_WEBM],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBM],
|
||||
],
|
||||
MIME_TYPE_WEBP => [
|
||||
MIME_TYPE_MAP_NAME => "WebP",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_WEBP],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBP],
|
||||
],
|
||||
MIME_TYPE_XML => [
|
||||
MIME_TYPE_MAP_NAME => "XML",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_XML],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_XML,MIME_TYPE_XML_APPLICATION],
|
||||
],
|
||||
MIME_TYPE_XSL => [
|
||||
MIME_TYPE_MAP_NAME => "XSL",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_XSL],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_XSL],
|
||||
],
|
||||
MIME_TYPE_ZIP => [
|
||||
MIME_TYPE_MAP_NAME => "ZIP",
|
||||
MIME_TYPE_MAP_EXT => [EXTENSION_ZIP],
|
||||
MIME_TYPE_MAP_MIME => [MIME_TYPE_ZIP],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the mimetype that matches the provided extension.
|
||||
*/
|
||||
function get_mime_for_extension(string $ext): ?string
|
||||
{
|
||||
$ext = strtolower($ext);
|
||||
|
||||
foreach (MIME_TYPE_MAP as $key=>$value) {
|
||||
if (in_array($ext, $value[MIME_TYPE_MAP_EXT])) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mimetype for the specified file, trying file inspection methods before falling back on extension-based detection.
|
||||
* @param String $file
|
||||
* @param String $ext The files extension, for if the current filename somehow lacks the extension
|
||||
* @return String The extension that was found.
|
||||
*/
|
||||
function get_mime(string $file, string $ext=""): string
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
throw new SCoreException("File not found: ".$file);
|
||||
}
|
||||
|
||||
$type = false;
|
||||
|
||||
if (extension_loaded('fileinfo')) {
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
try {
|
||||
$type = finfo_file($finfo, $file);
|
||||
} finally {
|
||||
finfo_close($finfo);
|
||||
}
|
||||
} elseif (function_exists('mime_content_type')) {
|
||||
// If anyone is still using mime_content_type()
|
||||
$type = trim(mime_content_type($file));
|
||||
}
|
||||
|
||||
if ($type===false || empty($type)) {
|
||||
// Checking by extension is our last resort
|
||||
if ($ext==null||strlen($ext) == 0) {
|
||||
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
$type = get_mime_for_extension($ext);
|
||||
}
|
||||
|
||||
if ($type !== false && strlen($type) > 0) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
return MIME_TYPE_OCTET_STREAM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension associated with the specified mimetype.
|
||||
*/
|
||||
function get_extension(?string $mime_type): ?string
|
||||
{
|
||||
if (empty($mime_type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($mime_type==MIME_TYPE_OCTET_STREAM) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (MIME_TYPE_MAP as $key=>$value) {
|
||||
if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) {
|
||||
return $value[MIME_TYPE_MAP_EXT][0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the file extensions associated with the specified mimetype.
|
||||
*/
|
||||
function get_all_extension_for_mime(?string $mime_type): array
|
||||
{
|
||||
$output = [];
|
||||
if (empty($mime_type)) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
foreach (MIME_TYPE_MAP as $key=>$value) {
|
||||
if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) {
|
||||
$output = array_merge($output, $value[MIME_TYPE_MAP_EXT]);
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an the extension defined in MIME_TYPE_MAP for a file.
|
||||
*
|
||||
* @param String $file_path
|
||||
* @return String The extension that was found, or null if one can not be found.
|
||||
*/
|
||||
function get_extension_for_file(String $file_path): ?String
|
||||
{
|
||||
$mime = get_mime($file_path);
|
||||
if (!empty($mime)) {
|
||||
if ($mime==MIME_TYPE_OCTET_STREAM) {
|
||||
return null;
|
||||
} else {
|
||||
$ext = get_extension($mime);
|
||||
}
|
||||
if (!empty($ext)) {
|
||||
return $ext;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
@ -213,6 +213,27 @@ class Image
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts consecutive days of image uploads
|
||||
*/
|
||||
public static function count_upload_streak(): int
|
||||
{
|
||||
$now = date_create();
|
||||
$last_date = $now;
|
||||
foreach (self::find_images_iterable() as $img) {
|
||||
$next_date = date_create($img->posted);
|
||||
if (date_diff($next_date, $last_date)->days > 0) {
|
||||
break;
|
||||
}
|
||||
$last_date = $next_date;
|
||||
}
|
||||
if ($last_date === $now) {
|
||||
return 0;
|
||||
}
|
||||
$diff_d = ($now->getTimestamp() - $last_date->getTimestamp()) / 86400;
|
||||
return (int)ceil($diff_d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of image results for a given search
|
||||
*
|
||||
@ -570,7 +591,7 @@ class Image
|
||||
*/
|
||||
public function get_mime_type(): string
|
||||
{
|
||||
return getMimeType($this->get_image_filename(), $this->get_ext());
|
||||
return get_mime($this->get_image_filename(), $this->get_ext());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,7 +39,7 @@ function add_dir(string $base): array
|
||||
* @param string $tags
|
||||
* @throws UploadException
|
||||
*/
|
||||
function add_image(string $tmpname, string $filename, string $tags): void
|
||||
function add_image(string $tmpname, string $filename, string $tags): int
|
||||
{
|
||||
assert(file_exists($tmpname));
|
||||
|
||||
@ -52,7 +52,11 @@ function add_image(string $tmpname, string $filename, string $tags): void
|
||||
|
||||
$metadata['tags'] = Tag::explode($tags);
|
||||
$metadata['source'] = null;
|
||||
send_event(new DataUploadEvent($tmpname, $metadata));
|
||||
|
||||
$due = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($due);
|
||||
|
||||
return $due->image_id;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,6 +73,12 @@ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_sca
|
||||
{
|
||||
global $config;
|
||||
|
||||
$fit = $config->get_string(ImageConfig::THUMB_FIT);
|
||||
|
||||
if (in_array($fit, [Media::RESIZE_TYPE_FILL, Media::RESIZE_TYPE_STRETCH, Media::RESIZE_TYPE_FIT_BLUR])) {
|
||||
return [$config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_HEIGHT)];
|
||||
}
|
||||
|
||||
if ($orig_width === 0) {
|
||||
$orig_width = 192;
|
||||
}
|
||||
@ -128,21 +138,35 @@ function get_thumbnail_max_size_scaled(): array
|
||||
|
||||
function create_image_thumb(string $hash, string $type, string $engine = null)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$inname = warehouse_path(Image::IMAGE_DIR, $hash);
|
||||
$outname = warehouse_path(Image::THUMBNAIL_DIR, $hash);
|
||||
$tsize = get_thumbnail_max_size_scaled();
|
||||
create_scaled_image($inname, $outname, $tsize, $type, $engine);
|
||||
create_scaled_image(
|
||||
$inname,
|
||||
$outname,
|
||||
$tsize,
|
||||
$type,
|
||||
$engine,
|
||||
$config->get_string(ImageConfig::THUMB_FIT)
|
||||
);
|
||||
}
|
||||
|
||||
function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine)
|
||||
|
||||
|
||||
function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine = null, ?string $resize_type = null)
|
||||
{
|
||||
global $config;
|
||||
if (empty($engine)) {
|
||||
$engine = $config->get_string(ImageConfig::THUMB_ENGINE);
|
||||
}
|
||||
if (empty($resize_type)) {
|
||||
$resize_type = $config->get_string(ImageConfig::THUMB_FIT);
|
||||
}
|
||||
|
||||
$output_format = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||
if ($output_format=="webp") {
|
||||
if ($output_format==EXTENSION_WEBP) {
|
||||
$output_format = Media::WEBP_LOSSY;
|
||||
}
|
||||
|
||||
@ -153,10 +177,10 @@ function create_scaled_image(string $inname, string $outname, array $tsize, stri
|
||||
$outname,
|
||||
$tsize[0],
|
||||
$tsize[1],
|
||||
false,
|
||||
$resize_type,
|
||||
$output_format,
|
||||
$config->get_int(ImageConfig::THUMB_QUALITY),
|
||||
true,
|
||||
$config->get_bool('thumb_upscale', false)
|
||||
true
|
||||
));
|
||||
}
|
||||
|
@ -116,8 +116,6 @@ class Tag
|
||||
|
||||
for ($i = 0; $i < count($tags1); $i++) {
|
||||
if ($tags1[$i]!==$tags2[$i]) {
|
||||
var_dump($tags1);
|
||||
var_dump($tags2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ function install()
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
if (is_readable("data/config/shimmie.conf.php")) {
|
||||
exit_with_page(
|
||||
die_nicely(
|
||||
"Shimmie is already installed.",
|
||||
"data/config/shimmie.conf.php exists, how did you get here?"
|
||||
);
|
||||
@ -69,7 +69,7 @@ function do_install($dsn)
|
||||
create_tables(new Database($dsn));
|
||||
write_config($dsn);
|
||||
} catch (InstallerException $e) {
|
||||
exit_with_page($e->title, $e->body, $e->code);
|
||||
die_nicely($e->title, $e->body, $e->code);
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ function ask_questions()
|
||||
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
|
||||
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
|
||||
|
||||
exit_with_page(
|
||||
die_nicely(
|
||||
"Install Options",
|
||||
<<<EOD
|
||||
$warn_msg
|
||||
@ -304,7 +304,7 @@ function write_config($dsn)
|
||||
|
||||
if (file_put_contents("data/config/shimmie.conf.php", $file_content, LOCK_EX)) {
|
||||
header("Location: index.php?flash=Installation%20complete");
|
||||
exit_with_page(
|
||||
die_nicely(
|
||||
"Installation Successful",
|
||||
"<p>If you aren't redirected, <a href=\"index.php\">click here to Continue</a>."
|
||||
);
|
||||
@ -324,25 +324,3 @@ function write_config($dsn)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function exit_with_page($title, $body, $code=0)
|
||||
{
|
||||
print("<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>Shimmie Installer</title>
|
||||
<link rel=\"shortcut icon\" href=\"ext/static_files/static/favicon.ico\">
|
||||
<link rel=\"stylesheet\" href=\"ext/static_files/style.css\" type=\"text/css\">
|
||||
</head>
|
||||
<body>
|
||||
<div id=\"installer\">
|
||||
<h1>Shimmie Installer</h1>
|
||||
<h3>$title</h3>
|
||||
<div class=\"container\">
|
||||
$body
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>");
|
||||
exit($code);
|
||||
}
|
||||
|
@ -10,6 +10,15 @@ define("SCORE_LOG_INFO", 20);
|
||||
define("SCORE_LOG_DEBUG", 10);
|
||||
define("SCORE_LOG_NOTSET", 0);
|
||||
|
||||
const LOGGING_LEVEL_NAMES = [
|
||||
SCORE_LOG_NOTSET=>"Not Set",
|
||||
SCORE_LOG_DEBUG=>"Debug",
|
||||
SCORE_LOG_INFO=>"Info",
|
||||
SCORE_LOG_WARNING=>"Warning",
|
||||
SCORE_LOG_ERROR=>"Error",
|
||||
SCORE_LOG_CRITICAL=>"Critical",
|
||||
];
|
||||
|
||||
/**
|
||||
* A shorthand way to send a LogEvent
|
||||
*
|
||||
|
@ -18,6 +18,7 @@ abstract class Permissions
|
||||
public const BAN_IP = "ban_ip";
|
||||
|
||||
public const CREATE_USER = "create_user";
|
||||
public const CREATE_OTHER_USER = "create_other_user";
|
||||
public const EDIT_USER_NAME = "edit_user_name";
|
||||
public const EDIT_USER_PASSWORD = "edit_user_password";
|
||||
public const EDIT_USER_INFO = "edit_user_info"; # email address, etc
|
||||
@ -95,4 +96,11 @@ abstract class Permissions
|
||||
public const CRON_ADMIN = "cron_admin";
|
||||
public const APPROVE_IMAGE = "approve_image";
|
||||
public const APPROVE_COMMENT = "approve_comment";
|
||||
|
||||
public const SET_PRIVATE_IMAGE = "set_private_image";
|
||||
public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images";
|
||||
|
||||
public const BULK_IMPORT = "bulk_import";
|
||||
public const BULK_EXPORT = "bulk_export";
|
||||
public const BULK_DOWNLOAD = "bulk_download";
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
* Things which should be in the core API *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
require_once "filetypes.php";
|
||||
|
||||
|
||||
/**
|
||||
* Return the unique elements of an array, case insensitively
|
||||
*/
|
||||
@ -150,6 +153,14 @@ function list_files(string $base, string $_sub_dir=""): array
|
||||
return $file_list;
|
||||
}
|
||||
|
||||
function flush_output(): void
|
||||
{
|
||||
if (!defined("UNITTEST")) {
|
||||
@ob_flush();
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
function stream_file(string $file, int $start, int $end): void
|
||||
{
|
||||
$fp = fopen($file, 'r');
|
||||
@ -162,10 +173,7 @@ function stream_file(string $file, int $start, int $end): void
|
||||
$buffer = $end - $p + 1;
|
||||
}
|
||||
echo fread($fp, $buffer);
|
||||
if (!defined("UNITTEST")) {
|
||||
@ob_flush();
|
||||
}
|
||||
flush();
|
||||
flush_output();
|
||||
|
||||
// After flush, we can tell if the client browser has disconnected.
|
||||
// This means we can start sending a large file, and if we detect they disappeared
|
||||
@ -248,99 +256,6 @@ if (!function_exists('mb_strlen')) {
|
||||
}
|
||||
}
|
||||
|
||||
const MIME_TYPE_MAP = [
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'ico' => 'image/x-icon',
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'flv' => 'video/x-flv',
|
||||
'svg' => 'image/svg+xml',
|
||||
'pdf' => 'application/pdf',
|
||||
'zip' => 'application/zip',
|
||||
'gz' => 'application/x-gzip',
|
||||
'tar' => 'application/x-tar',
|
||||
'bz' => 'application/x-bzip',
|
||||
'bz2' => 'application/x-bzip2',
|
||||
'txt' => 'text/plain',
|
||||
'asc' => 'text/plain',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'css' => 'text/css',
|
||||
'js' => 'text/javascript',
|
||||
'xml' => 'text/xml',
|
||||
'xsl' => 'application/xsl+xml',
|
||||
'ogg' => 'application/ogg',
|
||||
'mp3' => 'audio/mpeg',
|
||||
'wav' => 'audio/x-wav',
|
||||
'avi' => 'video/x-msvideo',
|
||||
'mpg' => 'video/mpeg',
|
||||
'mpeg' => 'video/mpeg',
|
||||
'mov' => 'video/quicktime',
|
||||
'php' => 'text/x-php',
|
||||
'mp4' => 'video/mp4',
|
||||
'ogv' => 'video/ogg',
|
||||
'webm' => 'video/webm',
|
||||
'webp' => 'image/webp',
|
||||
'bmp' =>'image/x-ms-bmp',
|
||||
'psd' => 'image/vnd.adobe.photoshop',
|
||||
'mkv' => 'video/x-matroska'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get MIME type for file
|
||||
*
|
||||
* The contents of this function are taken from the __getMimeType() function
|
||||
* from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Schönknecht
|
||||
* and released under the 'Simplified BSD License'.
|
||||
*/
|
||||
function getMimeType(string $file, string $ext=""): string
|
||||
{
|
||||
// Static extension lookup
|
||||
$ext = strtolower($ext);
|
||||
|
||||
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
||||
return MIME_TYPE_MAP[$ext];
|
||||
}
|
||||
|
||||
$type = false;
|
||||
// Fileinfo documentation says fileinfo_open() will use the
|
||||
// MAGIC env var for the magic file
|
||||
if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
|
||||
($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) {
|
||||
if (($type = finfo_file($finfo, $file)) !== false) {
|
||||
// Remove the charset and grab the last content-type
|
||||
$type = explode(' ', str_replace('; charset=', ';charset=', $type));
|
||||
$type = array_pop($type);
|
||||
$type = explode(';', $type);
|
||||
$type = trim(array_shift($type));
|
||||
}
|
||||
finfo_close($finfo);
|
||||
|
||||
// If anyone is still using mime_content_type()
|
||||
} elseif (function_exists('mime_content_type')) {
|
||||
$type = trim(mime_content_type($file));
|
||||
}
|
||||
|
||||
if ($type !== false && strlen($type) > 0) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
function get_extension(?string $mime_type): ?string
|
||||
{
|
||||
if (empty($mime_type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ext = array_search($mime_type, MIME_TYPE_MAP);
|
||||
return ($ext ? $ext : null);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
function getSubclassesOf(string $parent)
|
||||
{
|
||||
@ -406,6 +321,23 @@ function get_base_href(): string
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* The opposite of the standard library's parse_url
|
||||
*/
|
||||
function unparse_url($parsed_url)
|
||||
{
|
||||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
|
||||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
|
||||
$pass = ($user || $pass) ? "$pass@" : '';
|
||||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||||
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
|
||||
$fragment = !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||||
return "$scheme$user$pass$host$port$path$query$fragment";
|
||||
}
|
||||
|
||||
function startsWith(string $haystack, string $needle): bool
|
||||
{
|
||||
$length = strlen($needle);
|
||||
@ -588,11 +520,14 @@ function truncate(string $string, int $limit, string $break=" ", string $pad="..
|
||||
*/
|
||||
function parse_shorthand_int(string $limit): int
|
||||
{
|
||||
if (preg_match('/^([\d\.]+)([gmk])?b?$/i', (string)$limit, $m)) {
|
||||
if (preg_match('/^([\d\.]+)([tgmk])?b?$/i', (string)$limit, $m)) {
|
||||
$value = $m[1];
|
||||
if (isset($m[2])) {
|
||||
switch (strtolower($m[2])) {
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case 't': $value *= 1024; // fall through
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
case 'g': $value *= 1024; // fall through
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
@ -616,7 +551,9 @@ function to_shorthand_int(int $int): string
|
||||
{
|
||||
assert($int >= 0);
|
||||
|
||||
if ($int >= pow(1024, 3)) {
|
||||
if ($int >= pow(1024, 4)) {
|
||||
return sprintf("%.1fTB", $int / pow(1024, 4));
|
||||
} elseif ($int >= pow(1024, 3)) {
|
||||
return sprintf("%.1fGB", $int / pow(1024, 3));
|
||||
} elseif ($int >= pow(1024, 2)) {
|
||||
return sprintf("%.1fMB", $int / pow(1024, 2));
|
||||
|
63
core/sanitize_php.php
Normal file
63
core/sanitize_php.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* A small number of PHP-sanity things (eg don't silently ignore errors) to
|
||||
* be included right at the very start of index.php and tests/bootstrap.php
|
||||
*/
|
||||
|
||||
$min_php = "7.3";
|
||||
if (version_compare(phpversion(), $min_php, ">=") === false) {
|
||||
print "
|
||||
Shimmie does not support versions of PHP lower than $min_php
|
||||
(PHP reports that it is version ".phpversion().").
|
||||
If your web host is running an older version, they are dangerously out of
|
||||
date and you should plan on moving elsewhere.
|
||||
";
|
||||
exit;
|
||||
}
|
||||
|
||||
# ini_set('zend.assertions', '1'); // generate assertions
|
||||
ini_set('assert.exception', '1'); // throw exceptions when failed
|
||||
set_error_handler(function ($errNo, $errStr) {
|
||||
// Should we turn ALL notices into errors? PHP allows a lot of
|
||||
// terrible things to happen by default...
|
||||
if (strpos($errStr, 'Use of undefined constant ') === 0) {
|
||||
throw new Exception("PHP Error#$errNo: $errStr");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
ob_start();
|
||||
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
die("CLI with remote addr? Confused, not taking the risk.");
|
||||
}
|
||||
$_SERVER['REMOTE_ADDR'] = "0.0.0.0";
|
||||
$_SERVER['HTTP_HOST'] = "<cli command>";
|
||||
}
|
||||
|
||||
function die_nicely($title, $body, $code=0)
|
||||
{
|
||||
print("<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>Shimmie</title>
|
||||
<link rel=\"shortcut icon\" href=\"ext/static_files/static/favicon.ico\">
|
||||
<link rel=\"stylesheet\" href=\"ext/static_files/style.css\" type=\"text/css\">
|
||||
</head>
|
||||
<body>
|
||||
<div id=\"installer\">
|
||||
<h1>Shimmie</h1>
|
||||
<h3>$title</h3>
|
||||
<div class=\"container\">
|
||||
$body
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>");
|
||||
if ($code != 0) {
|
||||
http_response_code(500);
|
||||
}
|
||||
exit($code);
|
||||
}
|
@ -26,7 +26,7 @@ _d("DEBUG", false); // boolean print various debugging details
|
||||
_d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-shared logins, give them different prefixes
|
||||
_d("SPEED_HAX", false); // boolean do some questionable things in the name of performance
|
||||
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
|
||||
_d("VERSION", "2.8.3$_g"); // string shimmie version
|
||||
_d("VERSION", "2.8.4$_g"); // string shimmie version
|
||||
_d("TIMEZONE", null); // string timezone
|
||||
_d("EXTRA_EXTS", ""); // string optional extra extensions
|
||||
_d("BASE_HREF", null); // string force a specific base URL (default is auto-detect)
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class TestInit extends TestCase {
|
||||
class TestInit extends TestCase
|
||||
{
|
||||
public function testInitExt()
|
||||
{
|
||||
send_event(new InitExtEvent());
|
||||
|
@ -8,15 +8,35 @@ class UrlsTest extends TestCase
|
||||
{
|
||||
public function test_make_link()
|
||||
{
|
||||
// basic
|
||||
$this->assertEquals(
|
||||
"/test/foo",
|
||||
make_link("foo")
|
||||
);
|
||||
|
||||
// remove leading slash from path
|
||||
$this->assertEquals(
|
||||
"/test/foo",
|
||||
make_link("/foo")
|
||||
);
|
||||
|
||||
// query
|
||||
$this->assertEquals(
|
||||
"/test/foo?a=1&b=2",
|
||||
make_link("foo", "a=1&b=2")
|
||||
);
|
||||
|
||||
// hash
|
||||
$this->assertEquals(
|
||||
"/test/foo#cake",
|
||||
make_link("foo", null, "cake")
|
||||
);
|
||||
|
||||
// query + hash
|
||||
$this->assertEquals(
|
||||
"/test/foo?a=1&b=2#cake",
|
||||
make_link("foo", "a=1&b=2", "cake")
|
||||
);
|
||||
}
|
||||
|
||||
public function test_make_http()
|
||||
@ -39,4 +59,43 @@ class UrlsTest extends TestCase
|
||||
make_http("https://foo.com")
|
||||
);
|
||||
}
|
||||
|
||||
public function test_modify_url()
|
||||
{
|
||||
$this->assertEquals(
|
||||
"/foo/bar?a=3&b=2",
|
||||
modify_url("/foo/bar?a=1&b=2", ["a"=>"3"])
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
"https://blah.com/foo/bar?b=2",
|
||||
modify_url("https://blah.com/foo/bar?a=1&b=2", ["a"=>null])
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
"/foo/bar",
|
||||
modify_url("/foo/bar?a=1&b=2", ["a"=>null, "b"=>null])
|
||||
);
|
||||
}
|
||||
|
||||
public function test_referer_or()
|
||||
{
|
||||
unset($_SERVER['HTTP_REFERER']);
|
||||
$this->assertEquals(
|
||||
"foo",
|
||||
referer_or("foo")
|
||||
);
|
||||
|
||||
$_SERVER['HTTP_REFERER'] = "cake";
|
||||
$this->assertEquals(
|
||||
"cake",
|
||||
referer_or("foo")
|
||||
);
|
||||
|
||||
$_SERVER['HTTP_REFERER'] = "cake";
|
||||
$this->assertEquals(
|
||||
"foo",
|
||||
referer_or("foo", ["cake"])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
<?php declare(strict_types=1);
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* HTML Generation *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
class Link
|
||||
{
|
||||
@ -26,32 +23,27 @@ class Link
|
||||
*
|
||||
* eg make_link("post/list") becomes "/v2/index.php?q=post/list"
|
||||
*/
|
||||
function make_link(?string $page=null, ?string $query=null): string
|
||||
function make_link(?string $page=null, ?string $query=null, ?string $fragment=null): string
|
||||
{
|
||||
global $config;
|
||||
|
||||
if (is_null($page)) {
|
||||
$page = $config->get_string(SetupConfig::MAIN_PAGE);
|
||||
}
|
||||
$page = trim($page, "/");
|
||||
|
||||
$parts = [];
|
||||
$install_dir = get_base_href();
|
||||
if (SPEED_HAX || $config->get_bool('nice_urls', false)) {
|
||||
$base = $install_dir;
|
||||
$parts['path'] = "$install_dir/$page";
|
||||
} else {
|
||||
$base = "$install_dir/index.php?q=";
|
||||
$parts['path'] = "$install_dir/index.php";
|
||||
$query = empty($query) ? "q=$page" : "q=$page&$query";
|
||||
}
|
||||
$parts['query'] = $query; // http_build_query($query);
|
||||
$parts['fragment'] = $fragment; // http_build_query($hash);
|
||||
|
||||
if (is_null($query)) {
|
||||
return str_replace("//", "/", $base.'/'.$page);
|
||||
} else {
|
||||
if (strpos($base, "?")) {
|
||||
return $base .'/'. $page .'&'. $query;
|
||||
} elseif (strpos($query, "#") === 0) {
|
||||
return $base .'/'. $page . $query;
|
||||
} else {
|
||||
return $base .'/'. $page .'?'. $query;
|
||||
}
|
||||
}
|
||||
return unparse_url($parts);
|
||||
}
|
||||
|
||||
|
||||
@ -60,43 +52,28 @@ function make_link(?string $page=null, ?string $query=null): string
|
||||
*/
|
||||
function modify_current_url(array $changes): string
|
||||
{
|
||||
return modify_url($_SERVER['QUERY_STRING'], $changes);
|
||||
return modify_url($_SERVER['REQUEST_URI'], $changes);
|
||||
}
|
||||
|
||||
function modify_url(string $url, array $changes): string
|
||||
{
|
||||
// SHIT: PHP is officially the worst web API ever because it does not
|
||||
// have a built-in function to do this.
|
||||
$parts = parse_url($url);
|
||||
|
||||
// SHIT: parse_str is magically retarded; not only is it a useless name, it also
|
||||
// didn't return the parsed array, preferring to overwrite global variables with
|
||||
// whatever data the user supplied. Thankfully, 4.0.3 added an extra option to
|
||||
// give it an array to use...
|
||||
$params = [];
|
||||
parse_str($url, $params);
|
||||
|
||||
if (isset($changes['q'])) {
|
||||
$base = $changes['q'];
|
||||
unset($changes['q']);
|
||||
} else {
|
||||
$base = _get_query();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $params);
|
||||
}
|
||||
|
||||
if (isset($params['q'])) {
|
||||
unset($params['q']);
|
||||
}
|
||||
|
||||
foreach ($changes as $k => $v) {
|
||||
if (is_null($v) and isset($params[$k])) {
|
||||
unset($params[$k]);
|
||||
}
|
||||
$params[$k] = $v;
|
||||
}
|
||||
$parts['query'] = http_build_query($params);
|
||||
|
||||
return make_link($base, http_build_query($params));
|
||||
return unparse_url($parts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Turn a relative link into an absolute one, including hostname
|
||||
*/
|
||||
@ -116,3 +93,22 @@ function make_http(string $link): string
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* If HTTP_REFERER is set, and not blacklisted, then return it
|
||||
* Else return a default $dest
|
||||
*/
|
||||
function referer_or(string $dest, ?array $blacklist=null): string
|
||||
{
|
||||
if (empty($_SERVER['HTTP_REFERER'])) {
|
||||
return $dest;
|
||||
}
|
||||
if ($blacklist) {
|
||||
foreach ($blacklist as $b) {
|
||||
if (strstr($_SERVER['HTTP_REFERER'], $b)) {
|
||||
return $dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $_SERVER['HTTP_REFERER'];
|
||||
}
|
||||
|
@ -247,6 +247,9 @@ class User
|
||||
|
||||
public function check_auth_token(): bool
|
||||
{
|
||||
if (defined("UNITTEST")) {
|
||||
return true;
|
||||
}
|
||||
return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token());
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,8 @@ new UserClass("user", "base", [
|
||||
Permissions::EDIT_FAVOURITES => true,
|
||||
Permissions::SEND_PM => true,
|
||||
Permissions::READ_PM => true,
|
||||
Permissions::SET_PRIVATE_IMAGE => true,
|
||||
Permissions::BULK_DOWNLOAD => true,
|
||||
]);
|
||||
|
||||
new UserClass("hellbanned", "user", [
|
||||
@ -118,6 +120,7 @@ new UserClass("admin", "base", [
|
||||
Permissions::BAN_IP => true,
|
||||
|
||||
Permissions::CREATE_USER => true,
|
||||
Permissions::CREATE_OTHER_USER => true,
|
||||
Permissions::EDIT_USER_NAME => true,
|
||||
Permissions::EDIT_USER_PASSWORD => true,
|
||||
Permissions::EDIT_USER_INFO => true,
|
||||
@ -196,6 +199,13 @@ new UserClass("admin", "base", [
|
||||
|
||||
Permissions::APPROVE_IMAGE => true,
|
||||
Permissions::APPROVE_COMMENT => true,
|
||||
|
||||
Permissions::BULK_IMPORT =>true,
|
||||
Permissions::BULK_EXPORT =>true,
|
||||
Permissions::BULK_DOWNLOAD => true,
|
||||
|
||||
Permissions::SET_PRIVATE_IMAGE => true,
|
||||
Permissions::SET_OTHERS_PRIVATE_IMAGES => true,
|
||||
]);
|
||||
|
||||
@include_once "data/config/user-classes.conf.php";
|
||||
|
106
core/util.php
106
core/util.php
@ -393,6 +393,67 @@ function get_dir_contents(string $dir): array
|
||||
);
|
||||
}
|
||||
|
||||
function remove_empty_dirs(string $dir): bool
|
||||
{
|
||||
assert(!empty($dir));
|
||||
|
||||
$result = true;
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$items = array_diff(
|
||||
scandir(
|
||||
$dir
|
||||
),
|
||||
['..', '.']
|
||||
);
|
||||
foreach ($items as $item) {
|
||||
$path = join_path($dir, $item);
|
||||
if (is_dir($path)) {
|
||||
$result = $result && remove_empty_dirs($path);
|
||||
} else {
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
if ($result===true) {
|
||||
$result = $result && rmdir($dir);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
function get_files_recursively(string $dir): array
|
||||
{
|
||||
assert(!empty($dir));
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$things = array_diff(
|
||||
scandir(
|
||||
$dir
|
||||
),
|
||||
['..', '.']
|
||||
);
|
||||
|
||||
$output = [];
|
||||
|
||||
|
||||
foreach ($things as $thing) {
|
||||
$path = join_path($dir, $thing);
|
||||
if (is_file($path)) {
|
||||
$output[] = $path;
|
||||
} else {
|
||||
$output = array_merge($output, get_files_recursively($path));
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns amount of files & total size of dir.
|
||||
*/
|
||||
@ -493,31 +554,18 @@ function _load_theme_files()
|
||||
require_all(_get_themelet_files(get_theme()));
|
||||
}
|
||||
|
||||
function _sanitise_environment(): void
|
||||
function _set_up_shimmie_environment(): void
|
||||
{
|
||||
global $tracer_enabled;
|
||||
|
||||
$min_php = "7.3";
|
||||
if (version_compare(phpversion(), $min_php, ">=") === false) {
|
||||
print "
|
||||
Shimmie does not support versions of PHP lower than $min_php
|
||||
(PHP reports that it is version ".phpversion().").
|
||||
If your web host is running an older version, they are dangerously out of
|
||||
date and you should plan on moving elsewhere.
|
||||
";
|
||||
exit;
|
||||
}
|
||||
|
||||
if (file_exists("images") && !file_exists("data/images")) {
|
||||
die("As of Shimmie 2.7 images and thumbs should be moved to data/images and data/thumbs");
|
||||
die_nicely("Upgrade error", "As of Shimmie 2.7 images and thumbs should be moved to data/images and data/thumbs");
|
||||
}
|
||||
|
||||
if (TIMEZONE) {
|
||||
date_default_timezone_set(TIMEZONE);
|
||||
}
|
||||
|
||||
# ini_set('zend.assertions', '1'); // generate assertions
|
||||
ini_set('assert.exception', '1'); // throw exceptions when failed
|
||||
if (DEBUG) {
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
@ -526,16 +574,6 @@ date and you should plan on moving elsewhere.
|
||||
// so to prevent running out of memory during complex operations code that uses it should
|
||||
// check if tracer output is enabled before making use of it.
|
||||
$tracer_enabled = constant('TRACE_FILE')!==null;
|
||||
|
||||
ob_start();
|
||||
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
die("CLI with remote addr? Confused, not taking the risk.");
|
||||
}
|
||||
$_SERVER['REMOTE_ADDR'] = "0.0.0.0";
|
||||
$_SERVER['HTTP_HOST'] = "<cli command>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -598,6 +636,7 @@ function _fatal_error(Exception $e): void
|
||||
<p><b>Message:</b> '.html_escape($message).'
|
||||
'.$q.'
|
||||
<p><b>Version:</b> '.$version.' (on '.$phpver.')
|
||||
<p><b>Stack Trace:</b></p><pre>'.$e->getTraceAsString().'</pre>
|
||||
</body>
|
||||
</html>
|
||||
';
|
||||
@ -643,7 +682,7 @@ function show_ip(string $ip, string $ban_reason): string
|
||||
global $user;
|
||||
$u_reason = url_escape($ban_reason);
|
||||
$u_end = url_escape("+1 week");
|
||||
$ban = $user->can(Permissions::BAN_IP) ? ", <a href='".make_link("ip_ban/list", "c_ip=$ip&c_reason=$u_reason&c_expires=$u_end#create")."'>Ban</a>" : "";
|
||||
$ban = $user->can(Permissions::BAN_IP) ? ", <a href='".make_link("ip_ban/list", "c_ip=$ip&c_reason=$u_reason&c_expires=$u_end", "create")."'>Ban</a>" : "";
|
||||
$ip = $user->can(Permissions::VIEW_IP) ? $ip.$ban : "";
|
||||
return $ip;
|
||||
}
|
||||
@ -743,3 +782,18 @@ function human_filesize(int $bytes, $decimals = 2)
|
||||
$factor = floor((strlen(strval($bytes)) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @BYTE_DENOMINATIONS[$factor];
|
||||
}
|
||||
|
||||
/*
|
||||
* Generates a unique key for the website to prevent unauthorized access.
|
||||
*/
|
||||
function generate_key(int $length = 20)
|
||||
{
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$randomString = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters [rand(0, strlen($characters) - 1)];
|
||||
}
|
||||
|
||||
return $randomString;
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ class AliasEditor extends Extension
|
||||
$this->theme->display_aliases($t->table($t->query()), $t->paginator());
|
||||
} elseif ($event->get_arg(0) == "export") {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/csv");
|
||||
$page->set_type(MIME_TYPE_CSV);
|
||||
$page->set_filename("aliases.csv");
|
||||
$page->set_data($this->get_alias_csv($database));
|
||||
} elseif ($event->get_arg(0) == "import") {
|
||||
|
@ -52,6 +52,10 @@ class DeleteAutoTagEvent extends Event
|
||||
}
|
||||
}
|
||||
|
||||
class AutoTaggerException extends SCoreException
|
||||
{
|
||||
}
|
||||
|
||||
class AddAutoTagException extends SCoreException
|
||||
{
|
||||
}
|
||||
@ -98,7 +102,7 @@ class AutoTagger extends Extension
|
||||
$this->theme->display_auto_tagtable($t->table($t->query()), $t->paginator());
|
||||
} elseif ($event->get_arg(0) == "export") {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/csv");
|
||||
$page->set_type(MIME_TYPE_CSV);
|
||||
$page->set_filename("auto_tag.csv");
|
||||
$page->set_data($this->get_auto_tag_csv($database));
|
||||
} elseif ($event->get_arg(0) == "import") {
|
||||
|
@ -20,7 +20,7 @@ class AutoComplete extends Extension
|
||||
}
|
||||
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("application/json");
|
||||
$page->set_type(MIME_TYPE_JSON);
|
||||
|
||||
$s = strtolower($_GET["s"]);
|
||||
if (
|
||||
|
@ -107,4 +107,32 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
|
||||
$bb = new BBCode();
|
||||
return $bb->strip($in);
|
||||
}
|
||||
|
||||
public function testSiteLinks()
|
||||
{
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="" href="/test/post/view/123">>>123</a>',
|
||||
$this->filter(">>123")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="#c456" href="/test/post/view/123#c456">>>123#c456</a>',
|
||||
$this->filter(">>123#c456")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="" href="/test/foo/bar">foo/bar</a>',
|
||||
$this->filter("[url]site://foo/bar[/url]")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="#c123" href="/test/foo/bar#c123">foo/bar#c123</a>',
|
||||
$this->filter("[url]site://foo/bar#c123[/url]")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="" href="/test/foo/bar">look at my post</a>',
|
||||
$this->filter("[url=site://foo/bar]look at my post[/url]")
|
||||
);
|
||||
$this->assertEquals(
|
||||
'<a class="shm-clink" data-clink-sel="#c123" href="/test/foo/bar#c123">look at my comment</a>',
|
||||
$this->filter("[url=site://foo/bar#c123]look at my comment[/url]")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class BrowserSearch extends Extension
|
||||
|
||||
// And now to send it to the browser
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/xml");
|
||||
$page->set_type(MIME_TYPE_XML);
|
||||
$page->set_data($xml);
|
||||
} elseif ($event->page_matches("browser_search")) {
|
||||
$suggestions = $config->get_string("search_suggestions_results_order");
|
||||
|
@ -1,5 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
class BulkActionException extends SCoreException
|
||||
{
|
||||
}
|
||||
class BulkActionBlockBuildingEvent extends Event
|
||||
{
|
||||
/** @var array */
|
||||
@ -39,6 +42,8 @@ class BulkActionEvent extends Event
|
||||
public $action;
|
||||
/** @var array */
|
||||
public $items;
|
||||
/** @var bool */
|
||||
public $redirect = true;
|
||||
|
||||
public function __construct(String $action, Generator $items)
|
||||
{
|
||||
@ -164,28 +169,38 @@ class BulkActions extends Extension
|
||||
|
||||
$action = $_POST['bulk_action'];
|
||||
|
||||
$items = null;
|
||||
if (isset($_POST['bulk_selected_ids']) && $_POST['bulk_selected_ids'] != "") {
|
||||
$data = json_decode($_POST['bulk_selected_ids']);
|
||||
if (is_array($data)&&!empty($data)) {
|
||||
$items = $this->yield_items($data);
|
||||
try {
|
||||
$items = null;
|
||||
if (isset($_POST['bulk_selected_ids']) && !empty($_POST['bulk_selected_ids'])) {
|
||||
$data = json_decode($_POST['bulk_selected_ids']);
|
||||
if (empty($data)) {
|
||||
throw new BulkActionException("No ids specified in bulk_selected_ids");
|
||||
}
|
||||
if (is_array($data) && !empty($data)) {
|
||||
$items = $this->yield_items($data);
|
||||
}
|
||||
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||
$query = $_POST['bulk_query'];
|
||||
if ($query != null && $query != "") {
|
||||
$items = $this->yield_search_results($query);
|
||||
}
|
||||
} else {
|
||||
throw new BulkActionException("No ids selected and no query present, cannot perform bulk operation on entire collection");
|
||||
}
|
||||
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||
$query = $_POST['bulk_query'];
|
||||
if ($query != null && $query != "") {
|
||||
$items = $this->yield_search_results($query);
|
||||
|
||||
$bae = new BulkActionEvent($action, $items);
|
||||
|
||||
if (is_iterable($items)) {
|
||||
send_event($bae);
|
||||
}
|
||||
} catch (BulkActionException $e) {
|
||||
log_error(BulkActionsInfo::KEY, $e->getMessage(), $e->getMessage());
|
||||
}
|
||||
|
||||
if (is_iterable($items)) {
|
||||
send_event(new BulkActionEvent($action, $items));
|
||||
if ($bae->redirect) {
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
}
|
||||
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
$_SERVER['HTTP_REFERER'] = make_link();
|
||||
}
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ function deactivate_bulk_selector() {
|
||||
set_selected_items([]);
|
||||
$('#bulk_selector_controls').hide();
|
||||
$('#bulk_selector_activate').show();
|
||||
$('input[name="bulk_selected_ids"]').val("");
|
||||
bulk_selector_active = false;
|
||||
}
|
||||
|
||||
@ -94,7 +95,6 @@ function deselect_item(id) {
|
||||
|
||||
function toggle_selection( id ) {
|
||||
var data = get_selected_items();
|
||||
console.log(id);
|
||||
if(data.includes(id)) {
|
||||
data.splice(data.indexOf(id),1);
|
||||
set_selected_items(data);
|
||||
|
14
ext/bulk_download/info.php
Normal file
14
ext/bulk_download/info.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
|
||||
class BulkDownloadInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "bulk_download";
|
||||
|
||||
public $key = self::KEY;
|
||||
public $name = "Bulk Download";
|
||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||
public $license = self::LICENSE_WTFPL;
|
||||
public $description = "Allows bulk downloading images.";
|
||||
public $dependencies = [BulkActionsInfo::KEY];
|
||||
}
|
78
ext/bulk_download/main.php
Normal file
78
ext/bulk_download/main.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
class BulkDownloadConfig
|
||||
{
|
||||
public const SIZE_LIMIT = "bulk_download_size_limit";
|
||||
}
|
||||
class BulkDownloadException extends BulkActionException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
class BulkDownload extends Extension
|
||||
{
|
||||
private const DOWNLOAD_ACTION_NAME = "bulk_download";
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
global $config;
|
||||
$config->set_default_int(BulkDownloadConfig::SIZE_LIMIT, parse_shorthand_int('100MB'));
|
||||
}
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
|
||||
if ($user->can(Permissions::BULK_DOWNLOAD)) {
|
||||
$event->add_action(BulkDownload::DOWNLOAD_ACTION_NAME, "Download ZIP");
|
||||
}
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$sb = new SetupBlock("Bulk Download");
|
||||
|
||||
$sb->start_table();
|
||||
$sb->add_shorthand_int_option(BulkDownloadConfig::SIZE_LIMIT, "Size Limit", true);
|
||||
$sb->end_table();
|
||||
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $user, $page, $config;
|
||||
|
||||
if ($user->can(Permissions::BULK_DOWNLOAD)&&
|
||||
($event->action == BulkDownload::DOWNLOAD_ACTION_NAME)) {
|
||||
$download_filename = $user->name . '-' . date('YmdHis') . '.zip';
|
||||
$zip_filename = tempnam(sys_get_temp_dir(), "shimmie_bulk_download");
|
||||
$zip = new ZipArchive;
|
||||
$size_total = 0;
|
||||
$max_size = $config->get_int(BulkDownloadConfig::SIZE_LIMIT);
|
||||
|
||||
if ($zip->open($zip_filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
|
||||
foreach ($event->items as $image) {
|
||||
$img_loc = warehouse_path(Image::IMAGE_DIR, $image->hash, false);
|
||||
$size_total += filesize($img_loc);
|
||||
if ($size_total>$max_size) {
|
||||
throw new BulkDownloadException("Bulk download limited to ".human_filesize($max_size));
|
||||
}
|
||||
|
||||
|
||||
$filename = urldecode($image->get_nice_image_name());
|
||||
$filename = str_replace(":", ";", $filename);
|
||||
$zip->addFile($img_loc, $filename);
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
$page->set_mode(PageMode::FILE);
|
||||
$page->set_file($zip_filename, true);
|
||||
$page->set_filename($download_filename);
|
||||
|
||||
$event->redirect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
ext/bulk_import_export/events.php
Normal file
25
ext/bulk_import_export/events.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
class BulkExportEvent extends Event
|
||||
{
|
||||
public $image;
|
||||
public $fields = [];
|
||||
|
||||
public function __construct(Image $image)
|
||||
{
|
||||
$this->image = $image;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BulkImportEvent extends Event
|
||||
{
|
||||
public $image;
|
||||
public $fields = [];
|
||||
|
||||
public function __construct(Image $image, $fields)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->fields = $fields;
|
||||
}
|
||||
}
|
15
ext/bulk_import_export/info.php
Normal file
15
ext/bulk_import_export/info.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
include_once "events.php";
|
||||
|
||||
class BulkImportExportInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "bulk_import_export";
|
||||
|
||||
public $key = self::KEY;
|
||||
public $name = "Bulk Import/Export";
|
||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||
public $license = self::LICENSE_WTFPL;
|
||||
public $description = "Allows bulk exporting/importing of images and associated data.";
|
||||
public $dependencies = [BulkActionsInfo::KEY];
|
||||
}
|
172
ext/bulk_import_export/main.php
Normal file
172
ext/bulk_import_export/main.php
Normal file
@ -0,0 +1,172 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
|
||||
class BulkImportExport extends DataHandlerExtension
|
||||
{
|
||||
const EXPORT_ACTION_NAME = "bulk_export";
|
||||
const EXPORT_INFO_FILE_NAME = "export.json";
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_ZIP];
|
||||
|
||||
|
||||
public function onDataUpload(DataUploadEvent $event)
|
||||
{
|
||||
global $user, $database;
|
||||
|
||||
if ($this->supported_ext($event->type) &&
|
||||
$user->can(Permissions::BULK_IMPORT)) {
|
||||
$zip = new ZipArchive;
|
||||
|
||||
if ($zip->open($event->tmpname) === true) {
|
||||
$info = $zip->getStream(self::EXPORT_INFO_FILE_NAME);
|
||||
$json_data = [];
|
||||
if ($info !== false) {
|
||||
try {
|
||||
$json_string = stream_get_contents($info);
|
||||
$json_data = json_decode($json_string);
|
||||
} finally {
|
||||
fclose($info);
|
||||
}
|
||||
} else {
|
||||
throw new SCoreException("Could not get " . self::EXPORT_INFO_FILE_NAME . " from archive");
|
||||
}
|
||||
$total = 0;
|
||||
$skipped = 0;
|
||||
$failed = 0;
|
||||
|
||||
$database->commit();
|
||||
|
||||
while (!empty($json_data)) {
|
||||
$item = array_pop($json_data);
|
||||
$database->begin_transaction();
|
||||
try {
|
||||
$image = Image::by_hash($item->hash);
|
||||
if ($image!=null) {
|
||||
$skipped++;
|
||||
log_info(BulkImportExportInfo::KEY, "Image $item->hash already present, skipping");
|
||||
$database->commit();
|
||||
continue;
|
||||
}
|
||||
|
||||
$tmpfile = tempnam(sys_get_temp_dir(), "shimmie_bulk_import");
|
||||
$stream = $zip->getStream($item->hash);
|
||||
if ($zip === false) {
|
||||
throw new SCoreException("Could not import " . $item->hash . ": File not in zip");
|
||||
}
|
||||
|
||||
file_put_contents($tmpfile, $stream);
|
||||
|
||||
$id = add_image($tmpfile, $item->filename, Tag::implode($item->tags));
|
||||
|
||||
if ($id==-1) {
|
||||
throw new SCoreException("Unable to import file $item->hash");
|
||||
}
|
||||
|
||||
$image = Image::by_id($id);
|
||||
|
||||
if ($image==null) {
|
||||
throw new SCoreException("Unable to import file $item->hash");
|
||||
}
|
||||
|
||||
if ($item->source!=null) {
|
||||
$image->set_source($item->source);
|
||||
}
|
||||
send_event(new BulkImportEvent($image, $item));
|
||||
|
||||
$database->commit();
|
||||
$total++;
|
||||
} catch (Exception $ex) {
|
||||
$failed++;
|
||||
try {
|
||||
$database->rollBack();
|
||||
} catch (Exception $ex2) {
|
||||
log_error(BulkImportExportInfo::KEY, "Could not roll back transaction: " . $ex2->getMessage(), "Could not import " . $item->hash . ": " . $ex->getMessage());
|
||||
}
|
||||
log_error(BulkImportExportInfo::KEY, "Could not import " . $item->hash . ": " . $ex->getMessage(), "Could not import " . $item->hash . ": " . $ex->getMessage());
|
||||
continue;
|
||||
} finally {
|
||||
if (!empty($tmpfile) && is_file($tmpfile)) {
|
||||
unlink($tmpfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
$event->image_id = -2; // default -1 = upload wasn't handled
|
||||
|
||||
log_info(
|
||||
BulkImportExportInfo::KEY,
|
||||
"Imported $total items, skipped $skipped, $failed failed",
|
||||
"Imported $total items, skipped $skipped, $failed failed"
|
||||
);
|
||||
} else {
|
||||
throw new SCoreException("Could not open zip archive");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
|
||||
if ($user->can(Permissions::BULK_EXPORT)) {
|
||||
$event->add_action(self::EXPORT_ACTION_NAME, "Export");
|
||||
}
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $user, $page;
|
||||
|
||||
if ($user->can(Permissions::BULK_EXPORT) &&
|
||||
($event->action == self::EXPORT_ACTION_NAME)) {
|
||||
$download_filename = $user->name . '-' . date('YmdHis') . '.zip';
|
||||
$zip_filename = tempnam(sys_get_temp_dir(), "shimmie_bulk_export");
|
||||
$zip = new ZipArchive;
|
||||
|
||||
$json_data = [];
|
||||
|
||||
if ($zip->open($zip_filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
|
||||
foreach ($event->items as $image) {
|
||||
$img_loc = warehouse_path(Image::IMAGE_DIR, $image->hash, false);
|
||||
|
||||
$export_event = new BulkExportEvent($image);
|
||||
send_event($export_event);
|
||||
$data = $export_event->fields;
|
||||
$data["hash"] = $image->hash;
|
||||
$data["tags"] = $image->get_tag_array();
|
||||
$data["filename"] = $image->filename;
|
||||
$data["source"] = $image->source;
|
||||
|
||||
array_push($json_data, $data);
|
||||
|
||||
$zip->addFile($img_loc, $image->hash);
|
||||
}
|
||||
|
||||
$json_data = json_encode($json_data, JSON_PRETTY_PRINT);
|
||||
$zip->addFromString(self::EXPORT_INFO_FILE_NAME, $json_data);
|
||||
|
||||
$zip->close();
|
||||
|
||||
$page->set_mode(PageMode::FILE);
|
||||
$page->set_file($zip_filename, true);
|
||||
$page->set_filename($download_filename);
|
||||
|
||||
$event->redirect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// we don't actually do anything, just accept one upload and spawn several
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function check_contents(string $tmpname): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function create_thumb(string $hash, string $type): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -210,7 +210,7 @@ class CommentList extends Extension
|
||||
$cpe = new CommentPostingEvent(int_escape($_POST['image_id']), $user, $_POST['comment']);
|
||||
send_event($cpe);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
|
||||
$page->set_redirect(make_link("post/view/$i_iid", null, "comment_on_$i_iid"));
|
||||
} catch (CommentPostingException $ex) {
|
||||
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
|
||||
}
|
||||
@ -226,11 +226,7 @@ class CommentList extends Extension
|
||||
send_event(new CommentDeletionEvent(int_escape($event->get_arg(1))));
|
||||
$page->flash("Deleted comment");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
if (!empty($_SERVER['HTTP_REFERER'])) {
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
} else {
|
||||
$page->set_redirect(make_link("post/view/" . $event->get_arg(2)));
|
||||
}
|
||||
$page->set_redirect(referer_or(make_link("post/view/" . $event->get_arg(2))));
|
||||
}
|
||||
} else {
|
||||
$this->theme->display_permission_denied();
|
||||
@ -555,18 +551,9 @@ class CommentList extends Extension
|
||||
'website' => '',
|
||||
'body' => $text,
|
||||
'permalink' => '',
|
||||
];
|
||||
|
||||
# akismet breaks if there's no referrer in the environment; so if there
|
||||
# isn't, supply one manually
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
$comment['referrer'] = 'none';
|
||||
log_warning("comment", "User '{$user->name}' commented with no referrer: $text");
|
||||
}
|
||||
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||
$comment['user_agent'] = 'none';
|
||||
log_warning("comment", "User '{$user->name}' commented with no user-agent: $text");
|
||||
}
|
||||
'referrer' => $_SERVER['HTTP_REFERER'] ?? 'none',
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'none',
|
||||
];
|
||||
|
||||
$akismet = new Akismet(
|
||||
$_SERVER['SERVER_NAME'],
|
||||
|
@ -234,7 +234,7 @@ class CommentListTheme extends Themelet
|
||||
$html = "
|
||||
<div class=\"comment $hb\">
|
||||
$h_userlink: $h_comment
|
||||
<a href=\"".make_link("post/view/$i_image_id#c$i_comment_id")."\">>>></a>
|
||||
<a href=\"".make_link("post/view/$i_image_id", null, "c$i_comment_id")."\">>>></a>
|
||||
</div>
|
||||
";
|
||||
} else {
|
||||
|
@ -3,22 +3,25 @@
|
||||
|
||||
abstract class CronUploaderConfig
|
||||
{
|
||||
const DEFAULT_PATH = "cron_uploader";
|
||||
public const DEFAULT_PATH = "cron_uploader";
|
||||
|
||||
const KEY = "cron_uploader_key";
|
||||
const COUNT = "cron_uploader_count";
|
||||
const DIR = "cron_uploader_dir";
|
||||
const USER = "cron_uploader_user";
|
||||
public const KEY = "cron_uploader_key";
|
||||
public const DIR = "cron_uploader_dir";
|
||||
public const USER = "cron_uploader_user";
|
||||
public const STOP_ON_ERROR = "cron_uploader_stop_on_error";
|
||||
public const INCLUDE_ALL_LOGS = "cron_uploader_include_all_logs";
|
||||
public const LOG_LEVEL = "cron_uploader_log_level";
|
||||
|
||||
public static function set_defaults(): void
|
||||
{
|
||||
global $config;
|
||||
$config->set_default_int(self::COUNT, 1);
|
||||
$config->set_default_string(self::DIR, data_path(self::DEFAULT_PATH));
|
||||
|
||||
$config->set_default_bool(self::INCLUDE_ALL_LOGS, false);
|
||||
$config->set_default_bool(self::STOP_ON_ERROR, false);
|
||||
$config->set_default_int(self::LOG_LEVEL, SCORE_LOG_INFO);
|
||||
$upload_key = $config->get_string(self::KEY, "");
|
||||
if (empty($upload_key)) {
|
||||
$upload_key = self::generate_key();
|
||||
$upload_key = generate_key();
|
||||
|
||||
$config->set_string(self::KEY, $upload_key);
|
||||
}
|
||||
@ -48,18 +51,6 @@ abstract class CronUploaderConfig
|
||||
$config->set_string(self::KEY, $value);
|
||||
}
|
||||
|
||||
public static function get_count(): int
|
||||
{
|
||||
global $config;
|
||||
return $config->get_int(self::COUNT);
|
||||
}
|
||||
|
||||
public static function set_count(int $value): void
|
||||
{
|
||||
global $config;
|
||||
$config->set_int(self::COUNT, $value);
|
||||
}
|
||||
|
||||
public static function get_dir(): string
|
||||
{
|
||||
global $config;
|
||||
@ -76,21 +67,4 @@ abstract class CronUploaderConfig
|
||||
global $config;
|
||||
$config->set_string(self::DIR, $value);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generates a unique key for the website to prevent unauthorized access.
|
||||
*/
|
||||
private static function generate_key()
|
||||
{
|
||||
$length = 20;
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$randomString = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters [rand(0, strlen($characters) - 1)];
|
||||
}
|
||||
|
||||
return $randomString;
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class CronUploader extends Extension
|
||||
const UPLOADED_DIR = "uploaded";
|
||||
const FAILED_DIR = "failed_to_upload";
|
||||
|
||||
public $output_buffer = [];
|
||||
private static $IMPORT_RUNNING = false;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
@ -57,10 +57,18 @@ class CronUploader extends Extension
|
||||
|
||||
$sb = new SetupBlock("Cron Uploader");
|
||||
$sb->start_table();
|
||||
$sb->add_int_option(CronUploaderConfig::COUNT, "Upload per run", true);
|
||||
$sb->add_text_option(CronUploaderConfig::DIR, "Root dir", true);
|
||||
$sb->add_text_option(CronUploaderConfig::KEY, "Key", true);
|
||||
$sb->add_choice_option(CronUploaderConfig::USER, $users, "User", true);
|
||||
$sb->add_bool_option(CronUploaderConfig::STOP_ON_ERROR, "Stop On Error", true);
|
||||
$sb->add_choice_option(CronUploaderConfig::LOG_LEVEL, [
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_INFO] => SCORE_LOG_INFO,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_WARNING] => SCORE_LOG_WARNING,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_ERROR] => SCORE_LOG_ERROR,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_CRITICAL] => SCORE_LOG_CRITICAL,
|
||||
], "Output Log Level: ", true);
|
||||
$sb->add_bool_option(CronUploaderConfig::INCLUDE_ALL_LOGS, "Include All Logs", true);
|
||||
$sb->end_table();
|
||||
$sb->add_label("<a href='$documentation_link'>Read the documentation</a> for cron setup instructions.");
|
||||
|
||||
@ -108,6 +116,24 @@ class CronUploader extends Extension
|
||||
}
|
||||
}
|
||||
|
||||
public function onLog(LogEvent $event)
|
||||
{
|
||||
global $config;
|
||||
$all = $config->get_bool(CronUploaderConfig::INCLUDE_ALL_LOGS);
|
||||
if (self::$IMPORT_RUNNING &&
|
||||
$event->priority >= $config->get_int(CronUploaderConfig::LOG_LEVEL) &&
|
||||
($event->section==self::NAME || $all)
|
||||
) {
|
||||
$output = "[" . date('Y-m-d H:i:s') . "] " . ($all ? '['. $event->section .'] ' :'') . "[" . LOGGING_LEVEL_NAMES[$event->priority] . "] " . $event->message ;
|
||||
|
||||
echo $output . "\r\n";
|
||||
flush_output();
|
||||
|
||||
$log_path = $this->get_log_file();
|
||||
file_put_contents($log_path, $output);
|
||||
}
|
||||
}
|
||||
|
||||
private function restage_folder(string $folder)
|
||||
{
|
||||
global $page;
|
||||
@ -123,33 +149,46 @@ class CronUploader extends Extension
|
||||
|
||||
$this->prep_root_dir();
|
||||
|
||||
$results = get_dir_contents($queue_dir);
|
||||
|
||||
if (count($results) > 0) {
|
||||
$page->flash("Queue folder must be empty to re-stage");
|
||||
return;
|
||||
}
|
||||
|
||||
$results = get_dir_contents($stage_dir);
|
||||
$results = get_files_recursively($stage_dir);
|
||||
|
||||
if (count($results) == 0) {
|
||||
if (rmdir($stage_dir)===false) {
|
||||
if (remove_empty_dirs($stage_dir)===false) {
|
||||
$page->flash("Nothing to stage from $folder, cannot remove folder");
|
||||
} else {
|
||||
$page->flash("Nothing to stage from $folder, removing folder");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($results as $result) {
|
||||
$original_path = join_path($stage_dir, $result);
|
||||
$new_path = join_path($queue_dir, $result);
|
||||
$new_path = join_path($queue_dir, substr($result, strlen($stage_dir)));
|
||||
|
||||
rename($original_path, $new_path);
|
||||
if (file_exists($new_path)) {
|
||||
$page->flash("File already exists in queue folder: " .$result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$page->flash("Re-staged $folder to queue");
|
||||
rmdir($stage_dir);
|
||||
$success = true;
|
||||
foreach ($results as $result) {
|
||||
$new_path = join_path($queue_dir, substr($result, strlen($stage_dir)));
|
||||
|
||||
$dir = dirname($new_path);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0775, true);
|
||||
}
|
||||
|
||||
if (rename($result, $new_path)===false) {
|
||||
$page->flash("Could not move file: " .$result);
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($success===true) {
|
||||
$page->flash("Re-staged $folder to queue");
|
||||
if (remove_empty_dirs($stage_dir)===false) {
|
||||
$page->flash("Could not remove $folder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function clear_folder($folder)
|
||||
@ -265,7 +304,11 @@ class CronUploader extends Extension
|
||||
*/
|
||||
public function process_upload(string $key, ?int $upload_count = null): bool
|
||||
{
|
||||
global $database;
|
||||
global $database, $config, $_shm_load_start;
|
||||
|
||||
$max_time = intval(ini_get('max_execution_time'))*.8;
|
||||
|
||||
$this->set_headers();
|
||||
|
||||
if ($key!=CronUploaderConfig::get_key()) {
|
||||
throw new SCoreException("Cron upload key incorrect");
|
||||
@ -287,24 +330,12 @@ class CronUploader extends Extension
|
||||
throw new SCoreException("Cron upload process is already running");
|
||||
}
|
||||
|
||||
self::$IMPORT_RUNNING = true;
|
||||
try {
|
||||
//set_time_limit(0);
|
||||
|
||||
// Gets amount of imgs to upload
|
||||
if ($upload_count == null) {
|
||||
$upload_count = CronUploaderConfig::get_count();
|
||||
}
|
||||
|
||||
$output_subdir = date('Ymd-His', time());
|
||||
$image_queue = $this->generate_image_queue(CronUploaderConfig::get_dir(), $upload_count);
|
||||
|
||||
|
||||
// Throw exception if there's nothing in the queue
|
||||
if (count($image_queue) == 0) {
|
||||
$this->log_message(SCORE_LOG_WARNING, "Your queue is empty so nothing could be uploaded.");
|
||||
$this->handle_log();
|
||||
return false;
|
||||
}
|
||||
$image_queue = $this->generate_image_queue(CronUploaderConfig::get_dir());
|
||||
|
||||
// Randomize Images
|
||||
//shuffle($this->image_queue);
|
||||
@ -314,14 +345,18 @@ class CronUploader extends Extension
|
||||
$failed = 0;
|
||||
|
||||
// Upload the file(s)
|
||||
for ($i = 0; $i < $upload_count && sizeof($image_queue) > 0; $i++) {
|
||||
$img = array_pop($image_queue);
|
||||
|
||||
foreach ($image_queue as $img) {
|
||||
$execution_time = microtime(true) - $_shm_load_start;
|
||||
if ($execution_time>$max_time) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
$database->beginTransaction();
|
||||
$database->begin_transaction();
|
||||
$this->log_message(SCORE_LOG_INFO, "Adding file: {$img[0]} - tags: {$img[2]}");
|
||||
$result = $this->add_image($img[0], $img[1], $img[2]);
|
||||
$database->commit();
|
||||
if ($database->is_transaction_open()) {
|
||||
$database->commit();
|
||||
}
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
|
||||
if ($result->merged) {
|
||||
$merged++;
|
||||
@ -330,28 +365,37 @@ class CronUploader extends Extension
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
try {
|
||||
$database->rollback();
|
||||
if ($database->is_transaction_open()) {
|
||||
$database->rollback();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
$failed++;
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
|
||||
$this->log_message(SCORE_LOG_ERROR, "(" . gettype($e) . ") " . $e->getMessage());
|
||||
$this->log_message(SCORE_LOG_ERROR, $e->getTraceAsString());
|
||||
if ($config->get_bool(CronUploaderConfig::STOP_ON_ERROR)) {
|
||||
break;
|
||||
} else {
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Throw exception if there's nothing in the queue
|
||||
if ($merged+$failed+$added === 0) {
|
||||
$this->log_message(SCORE_LOG_WARNING, "Your queue is empty so nothing could be uploaded.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->log_message(SCORE_LOG_INFO, "Items added: $added");
|
||||
$this->log_message(SCORE_LOG_INFO, "Items merged: $merged");
|
||||
$this->log_message(SCORE_LOG_INFO, "Items failed: $failed");
|
||||
|
||||
|
||||
// Display upload log
|
||||
$this->handle_log();
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
self::$IMPORT_RUNNING = false;
|
||||
flock($lockfile, LOCK_UN);
|
||||
fclose($lockfile);
|
||||
}
|
||||
@ -359,7 +403,13 @@ class CronUploader extends Extension
|
||||
|
||||
private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false)
|
||||
{
|
||||
$relativeDir = dirname(substr($path, strlen(CronUploaderConfig::get_dir()) + 7));
|
||||
$rootDir = CronUploaderConfig::get_dir();
|
||||
$rootLength = strlen($rootDir);
|
||||
if ($rootDir[$rootLength-1]=="/"||$rootDir[$rootLength-1]=="\\") {
|
||||
$rootLength--;
|
||||
}
|
||||
|
||||
$relativeDir = dirname(substr($path, $rootLength + 7));
|
||||
|
||||
if ($relativeDir==".") {
|
||||
$relativeDir = "";
|
||||
@ -405,7 +455,7 @@ class CronUploader extends Extension
|
||||
if (array_key_exists('extension', $pathinfo)) {
|
||||
$metadata ['extension'] = $pathinfo ['extension'];
|
||||
}
|
||||
$metadata ['tags'] = $tagArray; // doesn't work when not logged in here, handled below
|
||||
$metadata ['tags'] = $tagArray;
|
||||
$metadata ['source'] = null;
|
||||
$event = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($event);
|
||||
@ -420,34 +470,46 @@ class CronUploader extends Extension
|
||||
}
|
||||
$this->log_message(SCORE_LOG_INFO, $infomsg);
|
||||
|
||||
// Set tags
|
||||
$img = Image::by_id($event->image_id);
|
||||
$img->set_tags(array_merge($tagArray, $img->get_tag_array()));
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
private const PARTIAL_DOWNLOAD_EXTENSIONS = ['crdownload','part'];
|
||||
private const SKIPPABLE_FILES = ['.ds_store','thumbs.db'];
|
||||
private const SKIPPABLE_DIRECTORIES = ['__macosx'];
|
||||
|
||||
private function is_skippable_file(string $path)
|
||||
private function is_skippable_dir(string $path)
|
||||
{
|
||||
$info = pathinfo($path);
|
||||
|
||||
if (in_array(strtolower($info['extension']), self::PARTIAL_DOWNLOAD_EXTENSIONS)) {
|
||||
if (array_key_exists("basename", $info) && in_array(strtolower($info['basename']), self::SKIPPABLE_DIRECTORIES)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function generate_image_queue(string $root_dir, ?int $limit = null): array
|
||||
private function is_skippable_file(string $path)
|
||||
{
|
||||
$info = pathinfo($path);
|
||||
|
||||
if (array_key_exists("basename", $info) && in_array(strtolower($info['basename']), self::SKIPPABLE_FILES)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (array_key_exists("extension", $info) && in_array(strtolower($info['extension']), self::PARTIAL_DOWNLOAD_EXTENSIONS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function generate_image_queue(string $root_dir, ?int $limit = null): Generator
|
||||
{
|
||||
$base = $this->get_queue_dir();
|
||||
$output = [];
|
||||
|
||||
if (!is_dir($base)) {
|
||||
$this->log_message(SCORE_LOG_WARNING, "Image Queue Directory could not be found at \"$base\".");
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
|
||||
$ite = new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS);
|
||||
@ -458,31 +520,19 @@ class CronUploader extends Extension
|
||||
$relativePath = substr($fullpath, strlen($base));
|
||||
$tags = path_to_tags($relativePath);
|
||||
|
||||
$img = [
|
||||
yield [
|
||||
0 => $fullpath,
|
||||
1 => $pathinfo ["basename"],
|
||||
2 => $tags
|
||||
];
|
||||
$output[] = $img;
|
||||
if (!empty($limit) && count($output) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
private function log_message(int $severity, string $message): void
|
||||
{
|
||||
log_msg(self::NAME, $severity, $message);
|
||||
|
||||
$time = "[" . date('Y-m-d H:i:s') . "]";
|
||||
$this->output_buffer[] = $time . " " . $message;
|
||||
|
||||
$log_path = $this->get_log_file();
|
||||
|
||||
file_put_contents($log_path, $time . " " . $message);
|
||||
}
|
||||
|
||||
private function get_log_file(): string
|
||||
@ -490,16 +540,12 @@ class CronUploader extends Extension
|
||||
return join_path(CronUploaderConfig::get_dir(), "uploads.log");
|
||||
}
|
||||
|
||||
/**
|
||||
* This is run at the end to display & save the log.
|
||||
*/
|
||||
private function handle_log()
|
||||
private function set_headers(): void
|
||||
{
|
||||
global $page;
|
||||
|
||||
// Display message
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/plain");
|
||||
$page->set_data(implode("\r\n", $this->output_buffer));
|
||||
$page->set_mode(PageMode::MANUAL);
|
||||
$page->set_type(MIME_TYPE_TEXT);
|
||||
$page->send_headers();
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,9 @@ class CronUploaderTheme extends Themelet
|
||||
<br />When you create the cron job, you choose when to upload new images.</li>
|
||||
</ol>";
|
||||
|
||||
|
||||
$max_time = intval(ini_get('max_execution_time'))*.8;
|
||||
|
||||
$usage_html = "Upload your images you want to be uploaded to the queue directory using your FTP client or other means.
|
||||
<br />(<b>{$queue_dirinfo['path']}</b>)
|
||||
<ol>
|
||||
@ -71,7 +74,7 @@ class CronUploaderTheme extends Themelet
|
||||
|
||||
<ul>
|
||||
<li>If an import is already running, another cannot start until it is done.</li>
|
||||
<li>Each time it runs it will import up to ".CronUploaderConfig::get_count()." file(s). This is controlled from <a href='".make_link("setup")."'>Board Config</a>.</li>
|
||||
<li>Each time it runs it will import for up to ".number_format($max_time)." seconds. This is controlled by the PHP max execution time.</li>
|
||||
<li>Uploaded images will be moved to the 'uploaded' directory into a subfolder named after the time the import started. It's recommended that you remove everything out of this directory from time to time. If you have admin controls enabled, this can be done from <a href='".make_link("admin")."'>Board Admin</a>.</li>
|
||||
<li>If you enable the db logging extension, you can view the log output on this screen. Otherwise the log will be written to a file at ".CronUploaderConfig::get_dir().DIRECTORY_SEPARATOR."uploads.log</li>
|
||||
</ul>
|
||||
@ -107,7 +110,7 @@ class CronUploaderTheme extends Themelet
|
||||
|
||||
$html .= make_form(make_link("admin/cron_uploader_restage"));
|
||||
$html .= "<table class='form'>";
|
||||
$html .= "<tr><th>Failed dir</th><td><select name='failed_dir' required='required'><option></option>";
|
||||
$html .= "<tr><th>Failed dir</th><td><select name='failed_dir' required='required'>";
|
||||
|
||||
foreach ($failed_dirs as $dir) {
|
||||
$html .= "<option value='$dir'>$dir</option>";
|
||||
|
@ -10,13 +10,13 @@ class DanbooruApi extends Extension
|
||||
|
||||
if ($event->page_matches("api/danbooru/add_post") || $event->page_matches("api/danbooru/post/create.xml")) {
|
||||
// No XML data is returned from this function
|
||||
$page->set_type("text/plain");
|
||||
$page->set_type(MIME_TYPE_TEXT);
|
||||
$this->api_add_post();
|
||||
} elseif ($event->page_matches("api/danbooru/find_posts") || $event->page_matches("api/danbooru/post/index.xml")) {
|
||||
$page->set_type("application/xml");
|
||||
$page->set_type(MIME_TYPE_XML_APPLICATION);
|
||||
$page->set_data($this->api_find_posts());
|
||||
} elseif ($event->page_matches("api/danbooru/find_tags")) {
|
||||
$page->set_type("application/xml");
|
||||
$page->set_type(MIME_TYPE_XML_APPLICATION);
|
||||
$page->set_data($this->api_find_tags());
|
||||
}
|
||||
|
||||
|
@ -36,10 +36,10 @@ class ET extends Extension
|
||||
public function onCommand(CommandEvent $event)
|
||||
{
|
||||
if ($event->cmd == "help") {
|
||||
print "\tget-info\n";
|
||||
print "\tshimmie-info\n";
|
||||
print "\t\tList a bunch of info\n\n";
|
||||
}
|
||||
if ($event->cmd == "info") {
|
||||
if ($event->cmd == "shimmie-info") {
|
||||
print($this->to_yaml($this->get_info()));
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,7 @@ class ET extends Extension
|
||||
"about" => [
|
||||
'title' => $config->get_string(SetupConfig::TITLE),
|
||||
'theme' => $config->get_string(SetupConfig::THEME),
|
||||
'url' => "http://" . $_SERVER["HTTP_HOST"] . get_base_href(),
|
||||
'url' => make_http(make_link("/")),
|
||||
],
|
||||
"versions" => [
|
||||
'shimmie' => VERSION,
|
||||
@ -71,7 +71,7 @@ class ET extends Extension
|
||||
'php' => phpversion(),
|
||||
'db' => $database->get_driver_name() . " " . $database->get_version(),
|
||||
'os' => php_uname(),
|
||||
'server' => isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown',
|
||||
'server' => $_SERVER["SERVER_SOFTWARE"] ?? 'unknown',
|
||||
],
|
||||
"extensions" => [
|
||||
"core" => $core_exts,
|
||||
@ -98,6 +98,21 @@ class ET extends Extension
|
||||
],
|
||||
];
|
||||
|
||||
if (file_exists(".git")) {
|
||||
try {
|
||||
$commitHash = trim(exec('git log --pretty="%h" -n1 HEAD'));
|
||||
$commitBranch= trim(exec('git rev-parse --abbrev-ref HEAD'));
|
||||
$commitOrigin= trim(exec('git config --get remote.origin.url'));
|
||||
$commitOrigin= preg_replace("#//.*@#", "//xxx@", $commitOrigin);
|
||||
$info['git'] = [
|
||||
'commit' => $commitHash,
|
||||
'branch' => $commitBranch,
|
||||
'origin' => $commitOrigin,
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
use function MicroHTML\INPUT;
|
||||
use function MicroHTML\DIV;
|
||||
use function MicroHTML\A;
|
||||
use function MicroHTML\IMG;
|
||||
|
||||
class FeaturedTheme extends Themelet
|
||||
{
|
||||
@ -19,16 +22,21 @@ class FeaturedTheme extends Themelet
|
||||
|
||||
public function build_featured_html(Image $image, ?string $query=null): string
|
||||
{
|
||||
$i_id = $image->id;
|
||||
$h_view_link = make_link("post/view/$i_id", $query);
|
||||
$h_thumb_link = $image->get_thumb_link();
|
||||
$h_tip = html_escape($image->get_tooltip());
|
||||
$tsize = get_thumbnail_size($image->width, $image->height);
|
||||
|
||||
return "
|
||||
<a href='$h_view_link'>
|
||||
<img id='thumb_{$i_id}' title='{$h_tip}' alt='{$h_tip}' class='highlighted' style='height: {$tsize[1]}px; width: {$tsize[0]}px;' src='{$h_thumb_link}'>
|
||||
</a>
|
||||
";
|
||||
return (string)DIV(
|
||||
["style"=>"text-align: center;"],
|
||||
A(
|
||||
["href"=>make_link("post/view/{$image->id}", $query)],
|
||||
IMG([
|
||||
"id"=>"thumb_rand_{$image->id}",
|
||||
"title"=>$image->get_tooltip(),
|
||||
"alt"=>$image->get_tooltip(),
|
||||
"class"=>'highlighted',
|
||||
"style"=>"max-height: {$tsize[1]}px; max-width: 100%;",
|
||||
"src"=>$image->get_thumb_link()
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class FourOhFourInfo extends ExtensionInfo
|
||||
public $url = self::SHIMMIE_URL;
|
||||
public $authors = self::SHISH_AUTHOR;
|
||||
public $license = self::LICENSE_GPLV2;
|
||||
public $visibility = self::VISIBLE_ADMIN;
|
||||
public $visibility = self::VISIBLE_HIDDEN;
|
||||
public $description = "If no other extension puts anything onto the page, show 404";
|
||||
public $core = true;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class ArchiveFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["zip"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_ZIP];
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class CBZFileHandler extends DataHandlerExtension
|
||||
{
|
||||
public $SUPPORTED_EXT = ["cbz"];
|
||||
public $SUPPORTED_MIME = [MIME_TYPE_COMIC_ZIP];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
@ -27,7 +27,7 @@ class CBZFileHandler extends DataHandlerExtension
|
||||
$cover,
|
||||
warehouse_path(Image::THUMBNAIL_DIR, $hash),
|
||||
get_thumbnail_max_size_scaled(),
|
||||
get_extension(getMimeType($cover)),
|
||||
get_extension(get_mime($cover)),
|
||||
null
|
||||
);
|
||||
return true;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class FlashFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["swf"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_FLASH];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class IcoFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["ico", "ani", "cur"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_ICO, MIME_TYPE_ANI];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class MP3FileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["mp3"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_MP3];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
@ -23,6 +23,6 @@ class MP3FileHandler extends DataHandlerExtension
|
||||
|
||||
protected function check_contents(string $tmpname): bool
|
||||
{
|
||||
return getMimeType($tmpname) == 'audio/mpeg';
|
||||
return get_mime($tmpname) === MIME_TYPE_MP3;
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
class PixelFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["jpg", "jpeg", "gif", "png", "webp"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_JPEG, MIME_TYPE_GIF, MIME_TYPE_PNG, MIME_TYPE_WEBP];
|
||||
|
||||
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
|
||||
{
|
||||
if (in_array($event->ext, Media::LOSSLESS_FORMATS)) {
|
||||
$event->image->lossless = true;
|
||||
} elseif ($event->ext=="webp") {
|
||||
} elseif ($event->ext==EXTENSION_WEBP) {
|
||||
$event->image->lossless = Media::is_lossless_webp($event->file_name);
|
||||
}
|
||||
|
||||
@ -17,10 +17,10 @@ class PixelFileHandler extends DataHandlerExtension
|
||||
}
|
||||
$event->image->audio = false;
|
||||
switch ($event->ext) {
|
||||
case "gif":
|
||||
case EXTENSION_GIF:
|
||||
$event->image->video = Media::is_animated_gif($event->file_name);
|
||||
break;
|
||||
case "webp":
|
||||
case EXTENSION_WEBP:
|
||||
$event->image->video = Media::is_animated_webp($event->file_name);
|
||||
break;
|
||||
default:
|
||||
|
@ -3,7 +3,7 @@ use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class SVGFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_EXT = ["svg"];
|
||||
protected $SUPPORTED_MIME = [MIME_TYPE_SVG];
|
||||
|
||||
/** @var SVGFileHandlerTheme */
|
||||
protected $theme;
|
||||
@ -16,7 +16,7 @@ class SVGFileHandler extends DataHandlerExtension
|
||||
$image = Image::by_id($id);
|
||||
$hash = $image->hash;
|
||||
|
||||
$page->set_type("image/svg+xml");
|
||||
$page->set_type(MIME_TYPE_SVG);
|
||||
$page->set_mode(PageMode::DATA);
|
||||
|
||||
$sanitizer = new Sanitizer();
|
||||
@ -67,7 +67,7 @@ class SVGFileHandler extends DataHandlerExtension
|
||||
|
||||
protected function check_contents(string $file): bool
|
||||
{
|
||||
if (getMimeType($file)!="image/svg+xml") {
|
||||
if (get_mime($file)!==MIME_TYPE_SVG) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,30 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
abstract class VideoFileHandlerConfig
|
||||
{
|
||||
public const PLAYBACK_AUTOPLAY = "video_playback_autoplay";
|
||||
public const PLAYBACK_LOOP = "video_playback_loop";
|
||||
public const ENABLED_FORMATS = "video_enabled_formats";
|
||||
}
|
||||
|
||||
class VideoFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected $SUPPORTED_MIME = [
|
||||
'video/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/flv',
|
||||
'video/x-flv'
|
||||
public const SUPPORTED_MIME = [
|
||||
MIME_TYPE_ASF,
|
||||
MIME_TYPE_AVI,
|
||||
MIME_TYPE_FLASH_VIDEO,
|
||||
MIME_TYPE_MKV,
|
||||
MIME_TYPE_MP4_VIDEO,
|
||||
MIME_TYPE_OGG_VIDEO,
|
||||
MIME_TYPE_QUICKTIME,
|
||||
MIME_TYPE_WEBM,
|
||||
];
|
||||
protected $SUPPORTED_EXT = ["flv", "mp4", "m4v", "ogv", "webm"];
|
||||
protected $SUPPORTED_MIME = self::SUPPORTED_MIME;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$config->set_default_bool('video_playback_autoplay', true);
|
||||
$config->set_default_bool('video_playback_loop', true);
|
||||
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, true);
|
||||
$config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_LOOP, true);
|
||||
$config->set_default_array(
|
||||
VideoFileHandlerConfig::ENABLED_FORMATS,
|
||||
[MIME_TYPE_FLASH_VIDEO, MIME_TYPE_MP4_VIDEO, MIME_TYPE_OGG_VIDEO, MIME_TYPE_WEBM]
|
||||
);
|
||||
}
|
||||
|
||||
private function get_options(): array
|
||||
{
|
||||
$output = [];
|
||||
foreach ($this->SUPPORTED_MIME as $format) {
|
||||
$output[MIME_TYPE_MAP[$format][MIME_TYPE_MAP_NAME]] = $format;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$sb = new SetupBlock("Video Options");
|
||||
$sb->add_bool_option("video_playback_autoplay", "Autoplay: ");
|
||||
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, "Autoplay: ");
|
||||
$sb->add_label("<br>");
|
||||
$sb->add_bool_option("video_playback_loop", "Loop: ");
|
||||
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_LOOP, "Loop: ");
|
||||
$sb->add_label("<br>Enabled Formats:");
|
||||
$sb->add_multichoice_option(VideoFileHandlerConfig::ENABLED_FORMATS, $this->get_options());
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
@ -80,6 +105,19 @@ class VideoFileHandler extends DataHandlerExtension
|
||||
}
|
||||
}
|
||||
|
||||
protected function supported_ext(string $ext): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
$enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS);
|
||||
foreach ($enabled_formats as $format) {
|
||||
if (in_array($ext, MIME_TYPE_MAP[$format][MIME_TYPE_MAP_EXT])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function create_thumb(string $hash, string $type): bool
|
||||
{
|
||||
return Media::create_thumbnail_ffmpeg($hash);
|
||||
@ -87,6 +125,18 @@ class VideoFileHandler extends DataHandlerExtension
|
||||
|
||||
protected function check_contents(string $tmpname): bool
|
||||
{
|
||||
return in_array(getMimeType($tmpname), $this->SUPPORTED_MIME);
|
||||
global $config;
|
||||
|
||||
if (file_exists($tmpname)) {
|
||||
$mime = get_mime($tmpname);
|
||||
|
||||
$enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS);
|
||||
foreach ($enabled_formats as $format) {
|
||||
if (in_array($mime, MIME_TYPE_MAP[$format][MIME_TYPE_MAP_MIME])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -9,17 +9,27 @@ class VideoFileHandlerTheme extends Themelet
|
||||
$thumb_url = make_http($image->get_thumb_link()); //used as fallback image
|
||||
$ext = strtolower($image->get_ext());
|
||||
$full_url = make_http($ilink);
|
||||
$autoplay = $config->get_bool("video_playback_autoplay");
|
||||
$loop = $config->get_bool("video_playback_loop");
|
||||
$autoplay = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY);
|
||||
$loop = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_LOOP);
|
||||
$player = make_link('vendor/bower-asset/mediaelement/build/flashmediaelement.swf');
|
||||
|
||||
$width="auto";
|
||||
if ($image->width>1) {
|
||||
$width = $image->width."px";
|
||||
}
|
||||
$height="auto";
|
||||
if ($image->height>1) {
|
||||
$height = $image->height."px";
|
||||
}
|
||||
|
||||
$html = "Video not playing? <a href='$ilink'>Click here</a> to download the file.<br/>";
|
||||
|
||||
//Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
||||
$supportedExts = ['mp4' => 'video/mp4', 'm4v' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm', 'flv' => 'video/flv'];
|
||||
if (array_key_exists($ext, $supportedExts)) {
|
||||
$mime = get_mime_for_extension($ext);
|
||||
|
||||
if (in_array($mime, VideoFileHandler::SUPPORTED_MIME)) {
|
||||
//FLV isn't supported by <video>, but it should always fallback to the flash-based method.
|
||||
if ($ext == "webm") {
|
||||
if ($mime == MIME_TYPE_WEBM) {
|
||||
//Several browsers still lack WebM support sadly: https://caniuse.com/#feat=webm
|
||||
$html .= "<!--[if IE]><p>To view webm files with IE, please <a href='https://tools.google.com/dlpage/webmmf/' target='_blank'>download this plugin</a>.</p><![endif]-->";
|
||||
}
|
||||
@ -40,7 +50,7 @@ class VideoFileHandlerTheme extends Themelet
|
||||
<img alt='thumb' src=\"{$thumb_url}\" />
|
||||
</object>";
|
||||
|
||||
if ($ext == "flv") {
|
||||
if ($mime == MIME_TYPE_FLASH_VIDEO) {
|
||||
//FLV doesn't support <video>.
|
||||
$html .= $html_fallback;
|
||||
} else {
|
||||
@ -49,8 +59,8 @@ class VideoFileHandlerTheme extends Themelet
|
||||
|
||||
$html .= "
|
||||
<video controls class='shm-main-image' id='main_image' alt='main image' poster='$thumb_url' {$autoplay} {$loop}
|
||||
style='max-width: 100%'>
|
||||
<source src='{$ilink}' type='{$supportedExts[$ext]}'>
|
||||
style='height: $height; width: $width; max-width: 100%'>
|
||||
<source src='{$ilink}' type='{$mime}'>
|
||||
|
||||
<!-- If browser doesn't support filetype, fallback to flash -->
|
||||
{$html_fallback}
|
||||
@ -60,7 +70,7 @@ class VideoFileHandlerTheme extends Themelet
|
||||
}
|
||||
} else {
|
||||
//This should never happen, but just in case let's have a fallback..
|
||||
$html = "Video type '$ext' not recognised";
|
||||
$html = "Video type '$mime' not recognised";
|
||||
}
|
||||
$page->add_block(new Block("Video", $html, "main", 10));
|
||||
}
|
||||
|
@ -9,5 +9,6 @@ class HelpPagesInfo extends ExtensionInfo
|
||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||
public $license = self::LICENSE_WTFPL;
|
||||
public $description = "Provides documentation screens";
|
||||
public $visibility = self::VISIBLE_HIDDEN;
|
||||
public $core = true;
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
class HelpPagesTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
|
||||
public function test_list()
|
||||
{
|
||||
send_event(new HelpPageListBuildingEvent());
|
||||
|
BIN
ext/home/counters/femcounter/0.png
Normal file
BIN
ext/home/counters/femcounter/0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
BIN
ext/home/counters/femcounter/1.png
Normal file
BIN
ext/home/counters/femcounter/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
ext/home/counters/femcounter/2.png
Normal file
BIN
ext/home/counters/femcounter/2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
BIN
ext/home/counters/femcounter/3.png
Normal file
BIN
ext/home/counters/femcounter/3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
BIN
ext/home/counters/femcounter/4.png
Normal file
BIN
ext/home/counters/femcounter/4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -5,6 +5,22 @@ class Home extends Extension
|
||||
/** @var HomeTheme */
|
||||
protected $theme;
|
||||
|
||||
private $femDimensions = array(
|
||||
array(23, 64),
|
||||
array(14, 86),
|
||||
array(31, 63),
|
||||
array(37, 100),
|
||||
array(24, 90)
|
||||
);
|
||||
|
||||
private $femTags = array(
|
||||
"Hatsune_Miku",
|
||||
"Monika",
|
||||
"Violet_Parr",
|
||||
"Keith_Kogane",
|
||||
"Rin_Kagamine",
|
||||
);
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $config, $page;
|
||||
@ -34,6 +50,27 @@ class Home extends Extension
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
private function addCountToBlankImage($charId, $digit)
|
||||
{
|
||||
$font = realpath('ext/home/vga.ttf');
|
||||
$file = "ext/home/counters/femcounter/$charId.png";
|
||||
$img = imagecreatefrompng($file);
|
||||
$black = imagecolorallocate($img, 0, 0, 0);
|
||||
$x = $this->femDimensions[$charId][0];
|
||||
$y = $this->femDimensions[$charId][1];
|
||||
imagettftext($img, 20, 0, $x, $y + 20, $black, $font, $digit);
|
||||
imagetruecolortopalette($img, true, 16);
|
||||
imagesavealpha($img, true);
|
||||
imagecolortransparent($img, imagecolorat($img, 0, 0));
|
||||
ob_start();
|
||||
imagegif($img);
|
||||
$image_data = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$data = base64_encode($image_data);
|
||||
imagedestroy($img);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
private function get_body()
|
||||
{
|
||||
@ -48,14 +85,23 @@ class Home extends Extension
|
||||
$counter_dir = $config->get_string('home_counter', 'default');
|
||||
|
||||
$total = Image::count_images();
|
||||
$streak = Image::count_upload_streak();
|
||||
$strtotal = "$total";
|
||||
$num_comma = number_format($total);
|
||||
$streak_comma = number_format($streak);
|
||||
|
||||
$counter_text = "";
|
||||
$length = strlen($strtotal);
|
||||
for ($n=0; $n<$length; $n++) {
|
||||
$cur = $strtotal[$n];
|
||||
$counter_text .= " <img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' /> ";
|
||||
if ($counter_dir === 'femcounter') {
|
||||
$charId = $n % count($this->femTags);
|
||||
$base64url = $this->addCountToBlankImage($charId, $cur);
|
||||
$tag = $this->femTags[$charId];
|
||||
$counter_text .= " <a href='$base_href/post/list/$tag/1'><img alt='$cur' title='$tag' src='data:image/gif;base64,$base64url' /></a> ";
|
||||
} else {
|
||||
$counter_text .= " <img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' /> ";
|
||||
}
|
||||
}
|
||||
|
||||
// get the homelinks and process them
|
||||
@ -74,6 +120,6 @@ class Home extends Extension
|
||||
$main_links = format_text($main_links);
|
||||
$main_text = $config->get_string('home_text', '');
|
||||
|
||||
return $this->theme->build_body($sitename, $main_links, $main_text, $contact_link, $num_comma, $counter_text);
|
||||
return $this->theme->build_body($sitename, $main_links, $main_text, $contact_link, $num_comma, $counter_text, $streak_comma);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ div#front-page div#links a {margin: 0 0.5em;}
|
||||
div#front-page li {list-style-type: none; margin: 0;}
|
||||
@media (max-width: 800px) {
|
||||
div#front-page h1 {font-size: 3em; margin-top: 0.5em; margin-bottom: 0.5em;}
|
||||
#counter {display: none;}
|
||||
/*#counter {display: none;}*/
|
||||
}
|
||||
|
||||
div#front-page > #search > form { margin: 0 auto; }
|
||||
|
BIN
ext/home/vga.ttf
Normal file
BIN
ext/home/vga.ttf
Normal file
Binary file not shown.
@ -8,6 +8,7 @@ abstract class ImageConfig
|
||||
const THUMB_SCALING = 'thumb_scaling';
|
||||
const THUMB_QUALITY = 'thumb_quality';
|
||||
const THUMB_TYPE = 'thumb_type';
|
||||
const THUMB_FIT = 'thumb_fit';
|
||||
|
||||
const SHOW_META = 'image_show_meta';
|
||||
const ILINK = 'image_ilink';
|
||||
|
@ -10,6 +10,6 @@ class ImageIOInfo extends ExtensionInfo
|
||||
public $authors = [self::SHISH_NAME=> self::SHISH_EMAIL, "jgen"=>"jgen.tech@gmail.com"];
|
||||
public $license = self::LICENSE_GPLV2;
|
||||
public $description = "Handle the image database";
|
||||
public $visibility = self::VISIBLE_ADMIN;
|
||||
public $visibility = self::VISIBLE_HIDDEN;
|
||||
public $core = true;
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ class ImageIO extends Extension
|
||||
];
|
||||
|
||||
const THUMBNAIL_TYPES = [
|
||||
'JPEG' => "jpg",
|
||||
'WEBP (Not IE/Safari compatible)' => "webp"
|
||||
'JPEG' => EXTENSION_JPG,
|
||||
'WEBP (Not IE/Safari compatible)' => EXTENSION_WEBP
|
||||
];
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
@ -32,7 +32,8 @@ class ImageIO extends Extension
|
||||
$config->set_default_int(ImageConfig::THUMB_HEIGHT, 192);
|
||||
$config->set_default_int(ImageConfig::THUMB_SCALING, 100);
|
||||
$config->set_default_int(ImageConfig::THUMB_QUALITY, 75);
|
||||
$config->set_default_string(ImageConfig::THUMB_TYPE, 'jpg');
|
||||
$config->set_default_string(ImageConfig::THUMB_TYPE, EXTENSION_JPG);
|
||||
$config->set_default_string(ImageConfig::THUMB_FIT, Media::RESIZE_TYPE_FIT);
|
||||
|
||||
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||
$config->set_default_bool(ImageConfig::SHOW_META, false);
|
||||
@ -53,17 +54,13 @@ class ImageIO extends Extension
|
||||
if ($image) {
|
||||
send_event(new ImageDeletionEvent($image));
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
if (isset($_SERVER['HTTP_REFERER']) && !strstr($_SERVER['HTTP_REFERER'], 'post/view')) {
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
} else {
|
||||
$page->set_redirect(make_link("post/list"));
|
||||
}
|
||||
$page->set_redirect(referer_or(make_link("post/list"), ['post/view']));
|
||||
}
|
||||
}
|
||||
} elseif ($event->page_matches("image/replace")) {
|
||||
global $page, $user;
|
||||
if ($user->can(Permissions::REPLACE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
|
||||
$image = Image::by_id($_POST['image_id']);
|
||||
$image = Image::by_id(int_escape($_POST['image_id']));
|
||||
if ($image) {
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link('upload/replace/'.$image->id));
|
||||
@ -219,35 +216,41 @@ class ImageIO extends Extension
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$sb = new SetupBlock("Image Options");
|
||||
$sb->start_table();
|
||||
$sb->position = 30;
|
||||
// advanced only
|
||||
//$sb->add_text_option(ImageConfig::ILINK, "Image link: ");
|
||||
//$sb->add_text_option(ImageConfig::TLINK, "<br>Thumbnail link: ");
|
||||
$sb->add_text_option(ImageConfig::TIP, "Image tooltip: ");
|
||||
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "<br>Upload collision handler: ");
|
||||
$sb->add_text_option(ImageConfig::TIP, "Image tooltip", true);
|
||||
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "Upload collision handler", true);
|
||||
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||
$sb->add_bool_option(ImageConfig::SHOW_META, "<br>Show metadata: ");
|
||||
$sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true);
|
||||
}
|
||||
|
||||
$sb->end_table();
|
||||
$event->panel->add_block($sb);
|
||||
|
||||
$sb = new SetupBlock("Thumbnailing");
|
||||
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine: ");
|
||||
$sb->add_label("<br>");
|
||||
$sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype: ");
|
||||
$sb->start_table();
|
||||
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine", true);
|
||||
$sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype", true);
|
||||
|
||||
$sb->add_label("<br>Size ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_WIDTH);
|
||||
$sb->add_label(" x ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_HEIGHT);
|
||||
$sb->add_label(" px at ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_QUALITY);
|
||||
$sb->add_label(" % quality ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_WIDTH, "Max Width", true);
|
||||
$sb->add_int_option(ImageConfig::THUMB_HEIGHT, "Max Height", true);
|
||||
|
||||
$sb->add_label("<br>High-DPI scaling ");
|
||||
$sb->add_int_option(ImageConfig::THUMB_SCALING);
|
||||
$sb->add_label("%");
|
||||
$options = [];
|
||||
foreach (MediaEngine::RESIZE_TYPE_SUPPORT[$config->get_string(ImageConfig::THUMB_ENGINE)] as $type) {
|
||||
$options[$type] = $type;
|
||||
}
|
||||
|
||||
$sb->add_choice_option(ImageConfig::THUMB_FIT, $options, "Fit", true);
|
||||
|
||||
$sb->add_int_option(ImageConfig::THUMB_QUALITY, "Quality", true);
|
||||
$sb->add_int_option(ImageConfig::THUMB_SCALING, "High-DPI Scale %", true);
|
||||
|
||||
$sb->end_table();
|
||||
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
@ -275,11 +278,7 @@ class ImageIO extends Extension
|
||||
if (!is_null($image)) {
|
||||
if ($type == "thumb") {
|
||||
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
||||
$page->set_type(MIME_TYPE_MAP[$ext]);
|
||||
} else {
|
||||
$page->set_type("image/jpeg");
|
||||
}
|
||||
$page->set_type(get_mime_for_extension($ext));
|
||||
|
||||
$file = $image->get_thumb_filename();
|
||||
} else {
|
||||
@ -287,6 +286,12 @@ class ImageIO extends Extension
|
||||
$file = $image->get_image_filename();
|
||||
}
|
||||
|
||||
if (!file_exists($file)) {
|
||||
http_response_code(404);
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) {
|
||||
$if_modified_since = preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]);
|
||||
} else {
|
||||
|
@ -20,4 +20,23 @@ class ImageIOTest extends ShimmiePHPUnitTestCase
|
||||
$page = $this->get_page("thumb/$image_id/moo.jpg");
|
||||
$this->assertEquals(200, $page->code);
|
||||
}
|
||||
|
||||
public function testDeleteRequest()
|
||||
{
|
||||
$this->log_in_as_admin();
|
||||
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
|
||||
$_POST['image_id'] = "$image_id";
|
||||
send_event(new PageRequestEvent("image/delete"));
|
||||
$this->assertTrue(true); // FIXME: assert image was deleted?
|
||||
}
|
||||
|
||||
public function testReplaceRequest()
|
||||
{
|
||||
global $page;
|
||||
$this->log_in_as_admin();
|
||||
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
|
||||
$_POST['image_id'] = "$image_id";
|
||||
send_event(new PageRequestEvent("image/replace"));
|
||||
$this->assertEquals("redirect", $page->mode);
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ class ImageBan extends Extension
|
||||
}
|
||||
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
}
|
||||
} elseif ($event->get_arg(0) == "remove") {
|
||||
$user->ensure_authed();
|
||||
@ -113,7 +113,7 @@ class ImageBan extends Extension
|
||||
send_event(new RemoveImageHashBanEvent($input['d_hash']));
|
||||
$page->flash("Image ban removed");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
} elseif ($event->get_arg(0) == "list") {
|
||||
$t = new HashBanTable($database->raw_db());
|
||||
$t->token = $user->get_auth_token();
|
||||
|
@ -3,6 +3,13 @@ class ImageBanTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
private $hash = "feb01bab5698a11dd87416724c7a89e3";
|
||||
|
||||
public function testPages()
|
||||
{
|
||||
$this->log_in_as_admin();
|
||||
$page = $this->get_page("image_hash_ban/list");
|
||||
$this->assertEquals(200, $page->code);
|
||||
}
|
||||
|
||||
public function testBan()
|
||||
{
|
||||
$this->log_in_as_admin();
|
||||
|
@ -245,11 +245,18 @@ class Index extends Extension
|
||||
Image::$order_sql = "images.$ord $sort";
|
||||
$event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag
|
||||
} elseif (preg_match("/^order[=|:]random[_]([0-9]{1,4})$/i", $event->term, $matches)) {
|
||||
//order[=|:]random requires a seed to avoid duplicates
|
||||
//since the tag can't be changed during the parseevent, we instead generate the seed during submit using js
|
||||
// requires a seed to avoid duplicates
|
||||
// since the tag can't be changed during the parseevent, we instead generate the seed during submit using js
|
||||
$seed = $matches[1];
|
||||
Image::$order_sql = "RAND($seed)";
|
||||
$event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag
|
||||
} elseif (preg_match("/^order[=|:]dailyshuffle$/i", $event->term, $matches)) {
|
||||
// will use today's date as seed, thus allowing for a dynamic randomized list without outside intervention.
|
||||
// This way the list will change every day, giving a more dynamic feel to the imageboard.
|
||||
// recommended to change homepage to "post/list/order:dailyshuffle/1"
|
||||
$seed = date("Ymd");
|
||||
Image::$order_sql = "RAND($seed)";
|
||||
$event->add_querylet(new Querylet("1=1"));
|
||||
}
|
||||
|
||||
$this->stpen++;
|
||||
|
@ -197,7 +197,10 @@ class IndexTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
send_event(new UserLoginEvent(User::by_name(self::$user_name)));
|
||||
send_event(new PageNavBuildingEvent());
|
||||
send_event(new PageSubNavBuildingEvent("parent"));
|
||||
// just a few common parents
|
||||
foreach (["help", "posts", "system", "user"] as $parent) {
|
||||
send_event(new PageSubNavBuildingEvent($parent));
|
||||
}
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
@ -234,11 +234,11 @@ class LogDatabase extends Extension
|
||||
{
|
||||
$sb = new SetupBlock("Logging (Database)");
|
||||
$sb->add_choice_option("log_db_priority", [
|
||||
"Debug" => SCORE_LOG_DEBUG,
|
||||
"Info" => SCORE_LOG_INFO,
|
||||
"Warning" => SCORE_LOG_WARNING,
|
||||
"Error" => SCORE_LOG_ERROR,
|
||||
"Critical" => SCORE_LOG_CRITICAL,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_INFO] => SCORE_LOG_INFO,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_WARNING] => SCORE_LOG_WARNING,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_ERROR] => SCORE_LOG_ERROR,
|
||||
LOGGING_LEVEL_NAMES[SCORE_LOG_CRITICAL] => SCORE_LOG_CRITICAL,
|
||||
], "Debug Level: ");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ class MediaResizeEvent extends Event
|
||||
public $target_height;
|
||||
public $target_quality;
|
||||
public $minimize;
|
||||
public $ignore_aspect_ratio;
|
||||
public $allow_upscale;
|
||||
public $resize_type;
|
||||
|
||||
public function __construct(
|
||||
String $engine,
|
||||
@ -21,7 +21,7 @@ class MediaResizeEvent extends Event
|
||||
string $output_path,
|
||||
int $target_width,
|
||||
int $target_height,
|
||||
bool $ignore_aspect_ratio = false,
|
||||
string $resize_type = Media::RESIZE_TYPE_FIT,
|
||||
string $target_format = null,
|
||||
int $target_quality = 80,
|
||||
bool $minimize = false,
|
||||
@ -38,8 +38,8 @@ class MediaResizeEvent extends Event
|
||||
$this->target_format = $target_format;
|
||||
$this->target_quality = $target_quality;
|
||||
$this->minimize = $minimize;
|
||||
$this->ignore_aspect_ratio = $ignore_aspect_ratio;
|
||||
$this->allow_upscale = $allow_upscale;
|
||||
$this->resize_type = $resize_type;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,28 +26,32 @@ class Media extends Extension
|
||||
|
||||
const LOSSLESS_FORMATS = [
|
||||
self::WEBP_LOSSLESS,
|
||||
"png",
|
||||
"psd",
|
||||
"bmp",
|
||||
"ico",
|
||||
"cur",
|
||||
"ani",
|
||||
"gif"
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_PSD,
|
||||
EXTENSION_BMP,
|
||||
EXTENSION_ICO,
|
||||
EXTENSION_CUR,
|
||||
EXTENSION_ANI,
|
||||
EXTENSION_GIF
|
||||
|
||||
];
|
||||
|
||||
const ALPHA_FORMATS = [
|
||||
self::WEBP_LOSSLESS,
|
||||
self::WEBP_LOSSY,
|
||||
"webp",
|
||||
"png",
|
||||
EXTENSION_WEBP,
|
||||
EXTENSION_PNG,
|
||||
];
|
||||
|
||||
const FORMAT_ALIASES = [
|
||||
"tif" => "tiff",
|
||||
"jpeg" => "jpg",
|
||||
EXTENSION_TIF => EXTENSION_TIFF,
|
||||
EXTENSION_JPEG => EXTENSION_JPG,
|
||||
];
|
||||
|
||||
const RESIZE_TYPE_FIT = "Fit";
|
||||
const RESIZE_TYPE_FIT_BLUR = "Fit Blur";
|
||||
const RESIZE_TYPE_FILL = "Fill";
|
||||
const RESIZE_TYPE_STRETCH = "Stretch";
|
||||
|
||||
//RIFF####WEBPVP8?..............ANIM
|
||||
private const WEBP_ANIMATION_HEADER =
|
||||
@ -118,7 +122,7 @@ class Media extends Extension
|
||||
$sb->add_text_option(MediaConfig::FFMPEG_PATH, "ffmpeg", true);
|
||||
$sb->add_text_option(MediaConfig::FFPROBE_PATH, "ffprobe", true);
|
||||
|
||||
$sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "Mem limit: ", true);
|
||||
$sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "Mem limit", true);
|
||||
$sb->end_table();
|
||||
|
||||
$event->panel->add_block($sb);
|
||||
@ -206,6 +210,10 @@ class Media extends Extension
|
||||
*/
|
||||
public function onMediaResize(MediaResizeEvent $event)
|
||||
{
|
||||
if (!in_array($event->resize_type, MediaEngine::RESIZE_TYPE_SUPPORT[MediaEngine::IMAGICK])) {
|
||||
throw new MediaException("Resize type $event->resize_type not supported by selected media engine $event->engine");
|
||||
}
|
||||
|
||||
switch ($event->engine) {
|
||||
case MediaEngine::GD:
|
||||
$info = getimagesize($event->input_path);
|
||||
@ -220,7 +228,7 @@ class Media extends Extension
|
||||
$event->target_height,
|
||||
$event->output_path,
|
||||
$event->target_format,
|
||||
$event->ignore_aspect_ratio,
|
||||
$event->resize_type,
|
||||
$event->target_quality,
|
||||
$event->allow_upscale
|
||||
);
|
||||
@ -236,7 +244,7 @@ class Media extends Extension
|
||||
$event->target_height,
|
||||
$event->output_path,
|
||||
$event->target_format,
|
||||
$event->ignore_aspect_ratio,
|
||||
$event->resize_type,
|
||||
$event->target_quality,
|
||||
$event->minimize,
|
||||
$event->allow_upscale
|
||||
@ -356,6 +364,7 @@ class Media extends Extension
|
||||
}
|
||||
|
||||
$inname = warehouse_path(Image::IMAGE_DIR, $hash);
|
||||
$tmpname = tempnam("/tmp", "shimmie_ffmpeg_thumb");
|
||||
$outname = warehouse_path(Image::THUMBNAIL_DIR, $hash);
|
||||
|
||||
$orig_size = self::video_size($inname);
|
||||
@ -363,7 +372,7 @@ class Media extends Extension
|
||||
|
||||
$codec = "mjpeg";
|
||||
$quality = $config->get_int(ImageConfig::THUMB_QUALITY);
|
||||
if ($config->get_string(ImageConfig::THUMB_TYPE) == "webp") {
|
||||
if ($config->get_string(ImageConfig::THUMB_TYPE) == EXTENSION_WEBP) {
|
||||
$codec = "libwebp";
|
||||
} else {
|
||||
// mjpeg quality ranges from 2-31, with 2 being the best quality.
|
||||
@ -376,12 +385,11 @@ class Media extends Extension
|
||||
$args = [
|
||||
escapeshellarg($ffmpeg),
|
||||
"-y", "-i", escapeshellarg($inname),
|
||||
"-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}",
|
||||
"-vf", "thumbnail",
|
||||
"-f", "image2",
|
||||
"-vframes", "1",
|
||||
"-c:v", $codec,
|
||||
"-q:v", $quality,
|
||||
escapeshellarg($outname),
|
||||
"-c:v", "png",
|
||||
escapeshellarg($tmpname),
|
||||
];
|
||||
|
||||
$cmd = escapeshellcmd(implode(" ", $args));
|
||||
@ -390,6 +398,10 @@ class Media extends Extension
|
||||
|
||||
if ((int)$ret == (int)0) {
|
||||
log_debug('media', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
|
||||
create_scaled_image($tmpname, $outname, $scaled_size, "png");
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
log_error('media', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
@ -436,7 +448,7 @@ class Media extends Extension
|
||||
switch ($format) {
|
||||
case self::WEBP_LOSSLESS:
|
||||
case self::WEBP_LOSSY:
|
||||
return "webp";
|
||||
return EXTENSION_WEBP;
|
||||
default:
|
||||
return $format;
|
||||
}
|
||||
@ -445,7 +457,7 @@ class Media extends Extension
|
||||
// private static function image_save_imagick(Imagick $image, string $path, string $format, int $output_quality = 80, bool $minimize)
|
||||
// {
|
||||
// switch ($format) {
|
||||
// case "png":
|
||||
// case EXTENSION_PNG:
|
||||
// $result = $image->setOption('png:compression-level', 9);
|
||||
// if ($result !== true) {
|
||||
// throw new GraphicsException("Could not set png compression option");
|
||||
@ -555,7 +567,7 @@ class Media extends Extension
|
||||
return true;
|
||||
}
|
||||
switch ($format) {
|
||||
case "webp":
|
||||
case EXTENSION_WEBP:
|
||||
return self::is_lossless_webp($filename);
|
||||
break;
|
||||
}
|
||||
@ -569,7 +581,7 @@ class Media extends Extension
|
||||
int $new_height,
|
||||
string $output_filename,
|
||||
string $output_type = null,
|
||||
bool $ignore_aspect_ratio = false,
|
||||
string $resize_type = self::RESIZE_TYPE_FIT,
|
||||
int $output_quality = 80,
|
||||
bool $minimize = false,
|
||||
bool $allow_upscale = true
|
||||
@ -586,7 +598,7 @@ class Media extends Extension
|
||||
$output_type = $input_type;
|
||||
}
|
||||
|
||||
if ($output_type=="webp" && self::is_lossless($input_path, $input_type)) {
|
||||
if ($output_type==EXTENSION_WEBP && self::is_lossless($input_path, $input_type)) {
|
||||
$output_type = self::WEBP_LOSSLESS;
|
||||
}
|
||||
|
||||
@ -598,36 +610,58 @@ class Media extends Extension
|
||||
$input_type = $input_type . ":";
|
||||
}
|
||||
|
||||
|
||||
$resize_args = "";
|
||||
$resize_suffix = "";
|
||||
if (!$allow_upscale) {
|
||||
$resize_args .= "\>";
|
||||
$resize_suffix .= "\>";
|
||||
}
|
||||
if ($ignore_aspect_ratio) {
|
||||
$resize_args .= "\!";
|
||||
if ($resize_type==Media::RESIZE_TYPE_STRETCH) {
|
||||
$resize_suffix .= "\!";
|
||||
}
|
||||
|
||||
$args = "";
|
||||
$resize_arg = "-resize";
|
||||
if ($minimize) {
|
||||
$args .= "-strip ";
|
||||
$resize_arg = "-thumbnail";
|
||||
}
|
||||
|
||||
$file_arg = "${input_type}\"${input_path}[0]\"";
|
||||
|
||||
switch ($resize_type) {
|
||||
case Media::RESIZE_TYPE_FIT:
|
||||
case Media::RESIZE_TYPE_STRETCH:
|
||||
$args .= "${resize_arg} ${new_width}x${new_height}${resize_suffix} ${file_arg}";
|
||||
break;
|
||||
case Media::RESIZE_TYPE_FILL:
|
||||
$args .= "${resize_arg} ${new_width}x${new_height}\^ -gravity center -extent ${new_width}x${new_height} ${file_arg}";
|
||||
break;
|
||||
case Media::RESIZE_TYPE_FIT_BLUR:
|
||||
$blur_size = max(ceil(max($new_width, $new_height) / 25), 5);
|
||||
$args .= "${file_arg} ".
|
||||
"\( -clone 0 -resize ${new_width}x${new_height}\^ -gravity center -fill black -colorize 50% -extent ${new_width}x${new_height} -blur 0x${blur_size} \) ".
|
||||
"\( -clone 0 -resize ${new_width}x${new_height} \) ".
|
||||
"-delete 0 -gravity center -compose over -composite";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
switch ($output_type) {
|
||||
case Media::WEBP_LOSSLESS:
|
||||
$args .= '-define webp:lossless=true';
|
||||
break;
|
||||
case "png":
|
||||
case EXTENSION_PNG:
|
||||
$args .= '-define png:compression-level=9';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($minimize) {
|
||||
$args .= " -strip -thumbnail";
|
||||
} else {
|
||||
$args .= " -resize";
|
||||
}
|
||||
|
||||
$args .= " -quality ${output_quality} -background ${bg}";
|
||||
|
||||
|
||||
$output_ext = self::determine_ext($output_type);
|
||||
|
||||
$format = '"%s" %s %ux%u%s -quality %u -background %s %s"%s[0]" %s:"%s" 2>&1';
|
||||
$cmd = sprintf($format, $convert, $args, $new_width, $new_height, $resize_args, $output_quality, $bg, $input_type, $input_path, $output_ext, $output_filename);
|
||||
$format = '"%s" %s %s:"%s" 2>&1';
|
||||
$cmd = sprintf($format, $convert, $args, $output_ext, $output_filename);
|
||||
$cmd = str_replace("\"convert\"", "convert", $cmd); // quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27
|
||||
exec($cmd, $output, $ret);
|
||||
if ($ret != 0) {
|
||||
@ -657,7 +691,7 @@ class Media extends Extension
|
||||
int $new_height,
|
||||
string $output_filename,
|
||||
string $output_type = null,
|
||||
bool $ignore_aspect_ratio = false,
|
||||
string $resize_type = self::RESIZE_TYPE_FIT,
|
||||
int $output_quality = 80,
|
||||
bool $allow_upscale = true
|
||||
) {
|
||||
@ -668,19 +702,19 @@ class Media extends Extension
|
||||
/* If not specified, output to the same format as the original image */
|
||||
switch ($info[2]) {
|
||||
case IMAGETYPE_GIF:
|
||||
$output_type = "gif";
|
||||
$output_type = EXTENSION_GIF;
|
||||
break;
|
||||
case IMAGETYPE_JPEG:
|
||||
$output_type = "jpeg";
|
||||
$output_type = EXTENSION_JPEG;
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
$output_type = "png";
|
||||
$output_type = EXTENSION_PNG;
|
||||
break;
|
||||
case IMAGETYPE_WEBP:
|
||||
$output_type = "webp";
|
||||
$output_type = EXTENSION_WEBP;
|
||||
break;
|
||||
case IMAGETYPE_BMP:
|
||||
$output_type = "bmp";
|
||||
$output_type = EXTENSION_BMP;
|
||||
break;
|
||||
default:
|
||||
throw new MediaException("Failed to save the new image - Unsupported image type.");
|
||||
@ -693,7 +727,7 @@ class Media extends Extension
|
||||
throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)");
|
||||
}
|
||||
|
||||
if (!$ignore_aspect_ratio) {
|
||||
if ($resize_type==Media::RESIZE_TYPE_FIT) {
|
||||
list($new_width, $new_height) = get_scaled_by_aspect_ratio($width, $height, $new_width, $new_height);
|
||||
}
|
||||
if (!$allow_upscale &&
|
||||
@ -776,21 +810,21 @@ class Media extends Extension
|
||||
}
|
||||
|
||||
switch ($output_type) {
|
||||
case "bmp":
|
||||
case EXTENSION_BMP:
|
||||
$result = imagebmp($image_resized, $output_filename, true);
|
||||
break;
|
||||
case "webp":
|
||||
case EXTENSION_WEBP:
|
||||
case Media::WEBP_LOSSY:
|
||||
$result = imagewebp($image_resized, $output_filename, $output_quality);
|
||||
break;
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case EXTENSION_JPG:
|
||||
case EXTENSION_JPEG:
|
||||
$result = imagejpeg($image_resized, $output_filename, $output_quality);
|
||||
break;
|
||||
case "png":
|
||||
case EXTENSION_PNG:
|
||||
$result = imagepng($image_resized, $output_filename, 9);
|
||||
break;
|
||||
case "gif":
|
||||
case EXTENSION_GIF:
|
||||
$result = imagegif($image_resized, $output_filename);
|
||||
break;
|
||||
default:
|
||||
@ -904,7 +938,7 @@ class Media extends Extension
|
||||
*/
|
||||
public static function normalize_format(string $format, ?bool $lossless = null): ?string
|
||||
{
|
||||
if ($format == "webp") {
|
||||
if ($format == EXTENSION_WEBP) {
|
||||
if ($lossless === true) {
|
||||
$format = Media::WEBP_LOSSLESS;
|
||||
} else {
|
||||
@ -1016,7 +1050,7 @@ class Media extends Extension
|
||||
|
||||
$this->set_version(MediaConfig::VERSION, 2);
|
||||
|
||||
$database->beginTransaction();
|
||||
$database->begin_transaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,63 +15,81 @@ abstract class MediaEngine
|
||||
];
|
||||
public const OUTPUT_SUPPORT = [
|
||||
MediaEngine::GD => [
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"webp",
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_WEBP,
|
||||
Media::WEBP_LOSSY,
|
||||
],
|
||||
MediaEngine::IMAGICK => [
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"webp",
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_WEBP,
|
||||
Media::WEBP_LOSSY,
|
||||
Media::WEBP_LOSSLESS,
|
||||
],
|
||||
MediaEngine::FFMPEG => [
|
||||
"jpg",
|
||||
"webp",
|
||||
"png",
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_WEBP,
|
||||
EXTENSION_PNG,
|
||||
],
|
||||
MediaEngine::STATIC => [
|
||||
"jpg",
|
||||
EXTENSION_JPG,
|
||||
],
|
||||
];
|
||||
public const INPUT_SUPPORT = [
|
||||
MediaEngine::GD => [
|
||||
"bmp",
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"webp",
|
||||
EXTENSION_BMP,
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_WEBP,
|
||||
Media::WEBP_LOSSY,
|
||||
Media::WEBP_LOSSLESS,
|
||||
],
|
||||
MediaEngine::IMAGICK => [
|
||||
"bmp",
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"psd",
|
||||
"tiff",
|
||||
"webp",
|
||||
EXTENSION_BMP,
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_PNG,
|
||||
EXTENSION_PSD,
|
||||
EXTENSION_TIFF,
|
||||
EXTENSION_WEBP,
|
||||
Media::WEBP_LOSSY,
|
||||
Media::WEBP_LOSSLESS,
|
||||
"ico",
|
||||
EXTENSION_ICO,
|
||||
],
|
||||
MediaEngine::FFMPEG => [
|
||||
"avi",
|
||||
"mkv",
|
||||
"webm",
|
||||
"mp4",
|
||||
"mov",
|
||||
"flv",
|
||||
EXTENSION_AVI,
|
||||
EXTENSION_MKV,
|
||||
EXTENSION_WEBM,
|
||||
EXTENSION_MP4,
|
||||
EXTENSION_MOV,
|
||||
EXTENSION_FLASH_VIDEO,
|
||||
],
|
||||
MediaEngine::STATIC => [
|
||||
"jpg",
|
||||
"gif",
|
||||
"png",
|
||||
EXTENSION_JPG,
|
||||
EXTENSION_GIF,
|
||||
EXTENSION_PNG,
|
||||
],
|
||||
];
|
||||
public const RESIZE_TYPE_SUPPORT = [
|
||||
MediaEngine::GD => [
|
||||
Media::RESIZE_TYPE_FIT,
|
||||
Media::RESIZE_TYPE_STRETCH
|
||||
],
|
||||
MediaEngine::IMAGICK => [
|
||||
Media::RESIZE_TYPE_FIT,
|
||||
Media::RESIZE_TYPE_FIT_BLUR,
|
||||
Media::RESIZE_TYPE_FILL,
|
||||
Media::RESIZE_TYPE_STRETCH,
|
||||
],
|
||||
MediaEngine::FFMPEG => [
|
||||
Media::RESIZE_TYPE_FIT
|
||||
],
|
||||
MediaEngine::STATIC => [
|
||||
Media::RESIZE_TYPE_FIT
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ class NotATag extends Extension
|
||||
["tag"=>$input['c_tag'], "redirect"=>$input['c_redirect']]
|
||||
);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
} elseif ($event->get_arg(0) == "remove") {
|
||||
$user->ensure_authed();
|
||||
$input = validate_input(["d_tag"=>"string"]);
|
||||
@ -123,7 +123,7 @@ class NotATag extends Extension
|
||||
);
|
||||
$page->flash("Image ban removed");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
} elseif ($event->get_arg(0) == "list") {
|
||||
$t = new NotATagTable($database->raw_db());
|
||||
$t->token = $user->get_auth_token();
|
||||
|
@ -76,8 +76,8 @@ class NumericScoreTheme extends Themelet
|
||||
$pop_images .= $this->build_thumb_html($image)."\n";
|
||||
}
|
||||
|
||||
$b_dte = make_link("popular_by_".$dte[3]."?".date($dte[2], (strtotime('-1 '.$dte[3], strtotime($dte[0])))));
|
||||
$f_dte = make_link("popular_by_".$dte[3]."?".date($dte[2], (strtotime('+1 '.$dte[3], strtotime($dte[0])))));
|
||||
$b_dte = make_link("popular_by_".$dte[3], date($dte[2], (strtotime('-1 '.$dte[3], strtotime($dte[0])))));
|
||||
$f_dte = make_link("popular_by_".$dte[3], date($dte[2], (strtotime('+1 '.$dte[3], strtotime($dte[0])))));
|
||||
|
||||
$html = "\n".
|
||||
"<h3 style='text-align: center;'>\n".
|
||||
|
@ -114,7 +114,7 @@ class PrivMsg extends Extension
|
||||
if ($user->can(Permissions::READ_PM)) {
|
||||
$count = $this->count_pms($user);
|
||||
$h_count = $count > 0 ? " <span class='unread'>($count)</span>" : "";
|
||||
$event->add_link("Private Messages$h_count", make_link("user#private-messages"));
|
||||
$event->add_link("Private Messages$h_count", make_link("user", null, "private-messages"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@ class PrivMsg extends Extension
|
||||
$cache->delete("pm-count-{$user->id}");
|
||||
log_info("pm", "Deleted PM #$pm_id", "PM deleted");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER["HTTP_REFERER"]);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -182,7 +182,7 @@ class PrivMsg extends Extension
|
||||
send_event(new SendPMEvent(new PM($from_id, $_SERVER["REMOTE_ADDR"], $to_id, $subject, $message)));
|
||||
$page->flash("PM sent");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER["HTTP_REFERER"]);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -47,8 +47,7 @@ class PoolCreationEvent extends Event
|
||||
User $pool_user = null,
|
||||
bool $public = false,
|
||||
string $description = ""
|
||||
)
|
||||
{
|
||||
) {
|
||||
parent::__construct();
|
||||
global $user;
|
||||
|
||||
@ -59,7 +58,8 @@ class PoolCreationEvent extends Event
|
||||
}
|
||||
}
|
||||
|
||||
class Pool {
|
||||
class Pool
|
||||
{
|
||||
public $id;
|
||||
public $user_id;
|
||||
public $user_name;
|
||||
@ -81,7 +81,8 @@ class Pool {
|
||||
$this->posts = (int)$row['posts'];
|
||||
}
|
||||
|
||||
public static function makePool(array $row): Pool {
|
||||
public static function makePool(array $row): Pool
|
||||
{
|
||||
return new Pool($row);
|
||||
}
|
||||
}
|
||||
@ -316,7 +317,8 @@ class Pools extends Extension
|
||||
case "import":
|
||||
if ($this->have_permission($user, $pool)) {
|
||||
$images = Image::find_images(
|
||||
0, $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
|
||||
0,
|
||||
$config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
|
||||
Tag::explode($_POST["pool_tag"])
|
||||
);
|
||||
$this->theme->pool_result($page, $images, $pool);
|
||||
@ -703,7 +705,7 @@ class Pools extends Extension
|
||||
*
|
||||
* #return int[] Array returning two elements (prev, next) in 1 dimension. Each returns ImageID or NULL if none.
|
||||
*/
|
||||
private function get_nav_posts(Pool $pool, int $imageID): array
|
||||
private function get_nav_posts(Pool $pool, int $imageID): ?array
|
||||
{
|
||||
global $database;
|
||||
|
||||
|
@ -33,10 +33,10 @@ class PostTitles extends Extension
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
{
|
||||
global $config;
|
||||
global $config, $page;
|
||||
|
||||
if ($config->get_bool(PostTitlesConfig::SHOW_IN_WINDOW_TITLE)) {
|
||||
$event->set_title(self::get_title($event->get_image()));
|
||||
$page->set_title(self::get_title($event->get_image()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +73,16 @@ class PostTitles extends Extension
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
|
||||
public function onBulkExport(BulkExportEvent $event)
|
||||
{
|
||||
$event->fields["title"] = $event->image->title;
|
||||
}
|
||||
public function onBulkImport(BulkImportEvent $event)
|
||||
{
|
||||
if (property_exists($event->fields, "title") && $event->fields->title!=null) {
|
||||
$this->set_title($event->image->id, $event->fields->title);
|
||||
}
|
||||
}
|
||||
|
||||
private function set_title(int $image_id, string $title)
|
||||
{
|
||||
|
@ -9,9 +9,7 @@ class PostTitlesTheme extends Themelet
|
||||
<td>
|
||||
".($can_set ? "
|
||||
<span class='view'>".html_escape($title)."</span>
|
||||
<span class='edit'>
|
||||
<input class='edit' type='text' name='post_title' value='".html_escape($title)."' />
|
||||
</span>
|
||||
" : html_escape("
|
||||
$title
|
||||
"))."
|
||||
|
12
ext/private_image/info.php
Normal file
12
ext/private_image/info.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
class PrivateImageInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "private_image";
|
||||
|
||||
public $key = self::KEY;
|
||||
public $name = "Private Image";
|
||||
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
|
||||
public $license = self::LICENSE_WTFPL;
|
||||
public $description = "Allows users to mark images as private, which prevents other users from seeing them.";
|
||||
}
|
292
ext/private_image/main.php
Normal file
292
ext/private_image/main.php
Normal file
@ -0,0 +1,292 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
abstract class PrivateImageConfig
|
||||
{
|
||||
const VERSION = "ext_private_image_version";
|
||||
const USER_SET_DEFAULT = "user_private_image_set_default";
|
||||
const USER_VIEW_DEFAULT = "user_private_image_view_default";
|
||||
}
|
||||
|
||||
class PrivateImage extends Extension
|
||||
{
|
||||
/** @var PrivateImageTheme */
|
||||
protected $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
global $config;
|
||||
|
||||
Image::$bool_props[] = "private ";
|
||||
}
|
||||
|
||||
public function onInitUserConfig(InitUserConfigEvent $event)
|
||||
{
|
||||
$event->user_config->set_default_bool(PrivateImageConfig::USER_SET_DEFAULT, false);
|
||||
$event->user_config->set_default_bool(PrivateImageConfig::USER_VIEW_DEFAULT, true);
|
||||
}
|
||||
|
||||
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
|
||||
{
|
||||
global $user, $user_config;
|
||||
|
||||
$event->add_html(
|
||||
$this->theme->get_user_options(
|
||||
$user,
|
||||
$user_config->get_bool(PrivateImageConfig::USER_SET_DEFAULT),
|
||||
$user_config->get_bool(PrivateImageConfig::USER_VIEW_DEFAULT),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $page, $user, $user_config;
|
||||
|
||||
if ($event->page_matches("privatize_image") && $user->can(Permissions::SET_PRIVATE_IMAGE)) {
|
||||
// Try to get the image ID
|
||||
$image_id = int_escape($event->get_arg(0));
|
||||
if (empty($image_id)) {
|
||||
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
|
||||
}
|
||||
if (empty($image_id)) {
|
||||
throw new SCoreException("Can not make image private: No valid Image ID given.");
|
||||
}
|
||||
$image = Image::by_id($image_id);
|
||||
if ($image==null) {
|
||||
throw new SCoreException("Image not found.");
|
||||
}
|
||||
if ($image->owner_id!=$user->can(Permissions::SET_OTHERS_PRIVATE_IMAGES)) {
|
||||
throw new SCoreException("Cannot set another user's image to private.");
|
||||
}
|
||||
|
||||
self::privatize_image($image_id);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/" . $image_id));
|
||||
}
|
||||
|
||||
if ($event->page_matches("publicize_image")) {
|
||||
// Try to get the image ID
|
||||
$image_id = int_escape($event->get_arg(0));
|
||||
if (empty($image_id)) {
|
||||
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
|
||||
}
|
||||
if (empty($image_id)) {
|
||||
throw new SCoreException("Can not make image public: No valid Image ID given.");
|
||||
}
|
||||
$image = Image::by_id($image_id);
|
||||
if ($image==null) {
|
||||
throw new SCoreException("Image not found.");
|
||||
}
|
||||
if ($image->owner_id!=$user->can(Permissions::SET_OTHERS_PRIVATE_IMAGES)) {
|
||||
throw new SCoreException("Cannot set another user's image to private.");
|
||||
}
|
||||
|
||||
self::publicize_image($image_id);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/".$image_id));
|
||||
}
|
||||
|
||||
if ($event->page_matches("user_admin")) {
|
||||
if (!$user->check_auth_token()) {
|
||||
return;
|
||||
}
|
||||
switch ($event->get_arg(0)) {
|
||||
case "private_image":
|
||||
if (!array_key_exists("id", $_POST) || empty($_POST["id"])) {
|
||||
return;
|
||||
}
|
||||
$id = intval($_POST["id"]);
|
||||
if ($id != $user->id) {
|
||||
throw new SCoreException("Cannot change another user's settings");
|
||||
}
|
||||
$set_default = array_key_exists("set_default", $_POST);
|
||||
$view_default = array_key_exists("view_default", $_POST);
|
||||
|
||||
$user_config->set_bool(PrivateImageConfig::USER_SET_DEFAULT, $set_default);
|
||||
$user_config->set_bool(PrivateImageConfig::USER_VIEW_DEFAULT, $view_default);
|
||||
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("user"));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
{
|
||||
global $user, $page, $config;
|
||||
|
||||
if ($event->image->private===true && $event->image->owner_id!=$user->id && !$user->can(Permissions::SET_OTHERS_PRIVATE_IMAGES)) {
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/list"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const SEARCH_REGEXP = "/^private:(yes|no|any)/";
|
||||
public function onSearchTermParse(SearchTermParseEvent $event)
|
||||
{
|
||||
global $user, $database, $user_config;
|
||||
$show_private = $user_config->get_bool(PrivateImageConfig::USER_VIEW_DEFAULT);
|
||||
|
||||
$matches = [];
|
||||
|
||||
if (is_null($event->term) && $this->no_private_query($event->context)) {
|
||||
if ($show_private) {
|
||||
$event->add_querylet(
|
||||
new Querylet(
|
||||
$database->scoreql_to_sql("private = SCORE_BOOL_N OR owner_id = :private_owner_id"),
|
||||
["private_owner_id"=>$user->id]
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$event->add_querylet(
|
||||
new Querylet(
|
||||
$database->scoreql_to_sql("private = SCORE_BOOL_N")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($event->term)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preg_match(self::SEARCH_REGEXP, strtolower($event->term), $matches)) {
|
||||
$params = [];
|
||||
$query = "";
|
||||
switch ($matches[1]) {
|
||||
case "no":
|
||||
$query .= "private = SCORE_BOOL_N";
|
||||
break;
|
||||
case "yes":
|
||||
$query .= "private = SCORE_BOOL_Y";
|
||||
|
||||
// Admins can view others private images, but they have to specify the user
|
||||
if (!$user->can(Permissions::SET_OTHERS_PRIVATE_IMAGES) ||
|
||||
!UserPage::has_user_query($event->context)) {
|
||||
$query .= " AND owner_id = :private_owner_id";
|
||||
$params["private_owner_id"] = $user->id;
|
||||
}
|
||||
break;
|
||||
case "any":
|
||||
$query .= "private = SCORE_BOOL_N OR owner_id = :private_owner_id";
|
||||
$params["private_owner_id"] = $user->id;
|
||||
break;
|
||||
}
|
||||
$event->add_querylet(new Querylet($database->scoreql_to_sql($query), $params));
|
||||
}
|
||||
}
|
||||
|
||||
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||
{
|
||||
if ($event->key===HelpPages::SEARCH) {
|
||||
$block = new Block();
|
||||
$block->header = "Private Images";
|
||||
$block->body = $this->theme->get_help_html();
|
||||
$event->add_block($block);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function no_private_query(array $context): bool
|
||||
{
|
||||
foreach ($context as $term) {
|
||||
if (preg_match(self::SEARCH_REGEXP, $term)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function privatize_image($image_id)
|
||||
{
|
||||
global $database, $user;
|
||||
|
||||
$database->execute(
|
||||
"UPDATE images SET private = :true WHERE id = :id AND private = :false",
|
||||
["id"=>$image_id, "true"=>true, "false"=>$database->scoresql_value_prepare(false)]
|
||||
);
|
||||
}
|
||||
|
||||
public static function publicize_image($image_id)
|
||||
{
|
||||
global $database;
|
||||
|
||||
$database->execute(
|
||||
"UPDATE images SET private = :false WHERE id = :id AND private = :true",
|
||||
["id"=>$image_id, "true"=>true, "false"=>$database->scoresql_value_prepare(false)]
|
||||
);
|
||||
}
|
||||
|
||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
if ($user->can(Permissions::SET_PRIVATE_IMAGE) && $user->id==$event->image->owner_id) {
|
||||
$event->add_part($this->theme->get_image_admin_html($event->image));
|
||||
}
|
||||
}
|
||||
|
||||
public function onImageAddition(ImageAdditionEvent $event)
|
||||
{
|
||||
global $user_config;
|
||||
if ($user_config->get_bool(PrivateImageConfig::USER_SET_DEFAULT)) {
|
||||
self::privatize_image($event->image->id);
|
||||
}
|
||||
}
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
|
||||
if ($user->can(Permissions::SET_PRIVATE_IMAGE)) {
|
||||
$event->add_action("bulk_privatize_image", "Make Private");
|
||||
$event->add_action("bulk_publicize_image", "Make Public");
|
||||
}
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $page, $user;
|
||||
|
||||
switch ($event->action) {
|
||||
case "bulk_privatize_image":
|
||||
if ($user->can(Permissions::SET_PRIVATE_IMAGE)) {
|
||||
$total = 0;
|
||||
foreach ($event->items as $image) {
|
||||
if ($image->owner_id==$user->id ||
|
||||
$user->can(Permissions::SET_OTHERS_PRIVATE_IMAGES)) {
|
||||
self::privatize_image($image->id);
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
$page->flash("Made $total items private");
|
||||
}
|
||||
break;
|
||||
case "bulk_publicize_image":
|
||||
$total = 0;
|
||||
foreach ($event->items as $image) {
|
||||
if ($image->owner_id==$user->id ||
|
||||
$user->can(Permissions::SET_OTHERS_PRIVATE_IMAGES)) {
|
||||
self::publicize_image($image->id);
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
$page->flash("Made $total items public");
|
||||
break;
|
||||
}
|
||||
}
|
||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||
{
|
||||
global $database;
|
||||
|
||||
if ($this->get_version(PrivateImageConfig::VERSION) < 1) {
|
||||
$database->execute($database->scoreql_to_sql(
|
||||
"ALTER TABLE images ADD COLUMN private SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N"
|
||||
));
|
||||
|
||||
$database->execute("CREATE INDEX images_private_idx ON images(private)");
|
||||
$this->set_version(PrivateImageConfig::VERSION, 1);
|
||||
}
|
||||
}
|
||||
}
|
68
ext/private_image/theme.php
Normal file
68
ext/private_image/theme.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php declare(strict_types=1);
|
||||
use function MicroHTML\BR;
|
||||
use function MicroHTML\BUTTON;
|
||||
use function MicroHTML\INPUT;
|
||||
|
||||
class PrivateImageTheme extends Themelet
|
||||
{
|
||||
public function get_image_admin_html(Image $image)
|
||||
{
|
||||
if ($image->private===false) {
|
||||
$html = SHM_SIMPLE_FORM(
|
||||
'privatize_image/'.$image->id,
|
||||
INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image->id]),
|
||||
SHM_SUBMIT("Make Private")
|
||||
);
|
||||
} else {
|
||||
$html = SHM_SIMPLE_FORM(
|
||||
'publicize_image/'.$image->id,
|
||||
INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image->id]),
|
||||
SHM_SUBMIT("Make Public")
|
||||
);
|
||||
}
|
||||
|
||||
return (string)$html;
|
||||
}
|
||||
|
||||
|
||||
public function get_help_html()
|
||||
{
|
||||
return '<p>Search for images that are private/public.</p>
|
||||
<div class="command_example">
|
||||
<pre>private:yes</pre>
|
||||
<p>Returns images that are private, restricted to yourself if you are not an admin.</p>
|
||||
</div>
|
||||
<div class="command_example">
|
||||
<pre>private:no</pre>
|
||||
<p>Returns images that are public.</p>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
public function get_user_options(User $user, bool $set_by_default, bool $view_by_default): string
|
||||
{
|
||||
$html = "
|
||||
<p>".make_form(make_link("user_admin/private_image"))."
|
||||
<input type='hidden' name='id' value='$user->id'>
|
||||
<table style='width: 300px;'>
|
||||
<tbody>
|
||||
<tr><th colspan='2'>Private Images</th></tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label><input type='checkbox' name='set_default' value='true' " .($set_by_default ? 'checked=checked': ''). " />Mark images private by default</label>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td>
|
||||
<label><input type='checkbox' name='view_default' value='true' " .($view_by_default ? 'checked=checked': ''). " />View private images by default</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr><td><input type='submit' value='Save'></td></tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</form>
|
||||
";
|
||||
return $html;
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ class RandomImage extends Extension
|
||||
send_event(new DisplayingImageEvent($image));
|
||||
} elseif ($action === "widget") {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/html");
|
||||
$page->set_type(MIME_TYPE_HTML);
|
||||
$page->set_data($this->theme->build_thumb_html($image));
|
||||
}
|
||||
}
|
||||
|
@ -17,16 +17,13 @@ class RandomImageTheme extends Themelet
|
||||
return (string)DIV(
|
||||
["style"=>"text-align: center;"],
|
||||
A(
|
||||
[
|
||||
"href"=>make_link("post/view/{$image->id}", $query),
|
||||
"style"=>"position: relative; height: {$tsize[1]}px; width: {$tsize[0]}px;"
|
||||
],
|
||||
["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"=>"height: {$tsize[1]}px; width: {$tsize[0]}px;",
|
||||
"style"=>"max-height: {$tsize[1]}px; max-width: 100%;",
|
||||
"src"=>$image->get_thumb_link()
|
||||
])
|
||||
)
|
||||
|
@ -170,6 +170,19 @@ class Ratings extends Extension
|
||||
}
|
||||
}
|
||||
|
||||
public function onBulkExport(BulkExportEvent $event)
|
||||
{
|
||||
$event->fields["rating"] = $event->image->rating;
|
||||
}
|
||||
public function onBulkImport(BulkImportEvent $event)
|
||||
{
|
||||
if (property_exists($event->fields, "rating")
|
||||
&& $event->fields->rating != null
|
||||
&& Ratings::rating_is_valid($event->fields->rating)) {
|
||||
$this->set_rating($event->image->id, $event->fields->rating, "");
|
||||
}
|
||||
}
|
||||
|
||||
public function onRatingSet(RatingSetEvent $event)
|
||||
{
|
||||
if (empty($event->image->rating)) {
|
||||
|
@ -84,7 +84,7 @@ class ResizeImage extends Extension
|
||||
$height = $config->get_int(ResizeConfig::DEFAULT_HEIGHT);
|
||||
}
|
||||
$isanigif = 0;
|
||||
if ($image_obj->ext == "gif") {
|
||||
if ($image_obj->ext == EXTENSION_GIF) {
|
||||
$image_filename = warehouse_path(Image::IMAGE_DIR, $image_obj->hash);
|
||||
if (($fh = @fopen($image_filename, 'rb'))) {
|
||||
//check if gif is animated (via https://www.php.net/manual/en/function.imagecreatefromgif.php#104473)
|
||||
|
@ -15,7 +15,7 @@ class RotateImage extends Extension
|
||||
/** @var RotateImageTheme */
|
||||
protected $theme;
|
||||
|
||||
const SUPPORTED_EXT = ["jpg","jpeg","png","gif","webp"];
|
||||
const SUPPORTED_MIME = [MIME_TYPE_JPEG, MIME_TYPE_PNG, MIME_TYPE_GIF, MIME_TYPE_WEBP];
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
@ -28,7 +28,7 @@ class RotateImage extends Extension
|
||||
{
|
||||
global $user, $config;
|
||||
if ($user->can(Permissions::EDIT_FILES) && $config->get_bool("rotate_enabled")
|
||||
&& in_array($event->image->ext, self::SUPPORTED_EXT)) {
|
||||
&& in_array(get_mime_for_extension($event->image->ext), self::SUPPORTED_MIME)) {
|
||||
/* Add a link to rotate the image */
|
||||
$event->add_part($this->theme->get_rotate_html($event->image->id));
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class RSSComments extends Extension
|
||||
global $config, $database, $page;
|
||||
if ($event->page_matches("rss/comments")) {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("application/rss+xml");
|
||||
$page->set_type(MIME_TYPE_RSS);
|
||||
|
||||
$comments = $database->get_all("
|
||||
SELECT
|
||||
|
@ -9,7 +9,7 @@ class RSSCommentsTest extends ShimmiePHPUnitTestCase
|
||||
send_event(new CommentPostingEvent($image_id, $user, "ASDFASDF"));
|
||||
|
||||
$this->get_page('rss/comments');
|
||||
//$this->assert_mime("application/rss+xml");
|
||||
//$this->assert_mime(MIME_TYPE_RSS);
|
||||
$this->assert_no_content("Exception");
|
||||
$this->assert_content("ASDFASDF");
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class RSSImages extends Extension
|
||||
global $page;
|
||||
global $config;
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("application/rss+xml");
|
||||
$page->set_type(MIME_TYPE_RSS);
|
||||
|
||||
$data = "";
|
||||
foreach ($images as $image) {
|
||||
@ -115,7 +115,7 @@ class RSSImages extends Extension
|
||||
</item>
|
||||
";
|
||||
|
||||
$cache->set("rss-item-image:{$image->id}", $data, 86400);
|
||||
$cache->set("rss-item-image:{$image->id}", $data, rand(43200, 86400));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -8,26 +8,26 @@ class RSSImagesTest extends ShimmiePHPUnitTestCase
|
||||
$this->log_out();
|
||||
|
||||
$this->get_page('rss/images');
|
||||
//$this->assert_mime("application/rss+xml");
|
||||
//$this->assert_mime(MIME_TYPE_RSS);
|
||||
$this->assert_no_content("Exception");
|
||||
|
||||
$this->get_page('rss/images/1');
|
||||
//$this->assert_mime("application/rss+xml");
|
||||
//$this->assert_mime(MIME_TYPE_RSS);
|
||||
$this->assert_no_content("Exception");
|
||||
|
||||
# FIXME: test that the image is actually found
|
||||
$this->get_page('rss/images/computer/1');
|
||||
//$this->assert_mime("application/rss+xml");
|
||||
//$this->assert_mime(MIME_TYPE_RSS);
|
||||
$this->assert_no_content("Exception");
|
||||
|
||||
# valid tag, invalid page
|
||||
$this->get_page('rss/images/computer/2');
|
||||
//$this->assert_mime("application/rss+xml");
|
||||
//$this->assert_mime(MIME_TYPE_RSS);
|
||||
$this->assert_no_content("Exception");
|
||||
|
||||
# not found
|
||||
$this->get_page('rss/images/waffle/2');
|
||||
//$this->assert_mime("application/rss+xml");
|
||||
//$this->assert_mime(MIME_TYPE_RSS);
|
||||
$this->assert_no_content("Exception");
|
||||
}
|
||||
}
|
||||
|
@ -118,14 +118,14 @@ class Rule34 extends Extension
|
||||
['is_admin'=>$input['is_admin'] ? 't' : 'f', 'id'=>$input['user_id']]
|
||||
);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(@$_SERVER['HTTP_REFERER']);
|
||||
$page->set_redirect(referer_or(make_link()));
|
||||
}
|
||||
}
|
||||
|
||||
if ($event->page_matches("tnc_agreed")) {
|
||||
setcookie("ui-tnc-agreed", "true", 0, "/");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(@$_SERVER['HTTP_REFERER'] ?? "/");
|
||||
$page->set_redirect(referer_or("/"));
|
||||
}
|
||||
|
||||
if ($event->page_matches("admin/cache_purge")) {
|
||||
|
@ -10,4 +10,5 @@ class SetupInfo extends ExtensionInfo
|
||||
public $authors = self::SHISH_AUTHOR;
|
||||
public $description = "Allows the site admin to configure the board to his or her taste";
|
||||
public $core = true;
|
||||
public $visibility = self::VISIBLE_HIDDEN;
|
||||
}
|
||||
|
@ -111,9 +111,9 @@ class SetupBlock extends Block
|
||||
$this->body .= $content;
|
||||
$this->end_table_cell();
|
||||
}
|
||||
public function start_table_header_cell(int $colspan = 1)
|
||||
public function start_table_header_cell(int $colspan = 1, string $align = 'right')
|
||||
{
|
||||
$this->body .= "<th colspan='$colspan'>";
|
||||
$this->body .= "<th colspan='$colspan' style='text-align: $align'>";
|
||||
}
|
||||
public function end_table_header_cell()
|
||||
{
|
||||
@ -126,23 +126,34 @@ class SetupBlock extends Block
|
||||
$this->end_table_header_cell();
|
||||
}
|
||||
|
||||
private function format_option(string $name, $html, ?string $label, bool $table_row)
|
||||
{
|
||||
private function format_option(
|
||||
string $name,
|
||||
$html,
|
||||
?string $label,
|
||||
bool $table_row,
|
||||
bool $label_row = false
|
||||
) {
|
||||
if ($table_row) {
|
||||
$this->start_table_row();
|
||||
}
|
||||
if ($table_row) {
|
||||
$this->start_table_header_cell();
|
||||
$this->start_table_header_cell($label_row ? 2 : 1, $label_row ? 'center' : 'right');
|
||||
}
|
||||
if (!is_null($label)) {
|
||||
$this->body .= "<label for='{$name}'>{$label}</label>";
|
||||
}
|
||||
|
||||
if ($table_row) {
|
||||
$this->end_table_header_cell();
|
||||
}
|
||||
|
||||
if ($table_row && $label_row) {
|
||||
$this->end_table_row();
|
||||
$this->start_table_row();
|
||||
}
|
||||
|
||||
if ($table_row) {
|
||||
$this->start_table_cell();
|
||||
$this->start_table_cell($label_row ? 2 : 1);
|
||||
}
|
||||
$this->body .= $html;
|
||||
if ($table_row) {
|
||||
@ -173,7 +184,7 @@ class SetupBlock extends Block
|
||||
$html = "<textarea rows='$rows' id='$name' name='_config_$name'>$val</textarea>\n";
|
||||
$html .= "<input type='hidden' name='_type_$name' value='string'>\n";
|
||||
|
||||
$this->format_option($name, $html, $label, $table_row);
|
||||
$this->format_option($name, $html, $label, $table_row, true);
|
||||
}
|
||||
|
||||
public function add_bool_option(string $name, string $label=null, bool $table_row = false)
|
||||
@ -207,8 +218,8 @@ class SetupBlock extends Block
|
||||
global $config;
|
||||
$val = $config->get_int($name);
|
||||
|
||||
$html = "<input type='text' id='$name' name='_config_$name' value='$val' size='4' style='text-align: center;'>\n";
|
||||
$html .= "<input type='hidden' name='_type_$name' value='int'>\n";
|
||||
$html = "<input type='number' id='$name' name='_config_$name' value='$val' size='4' style='text-align: center;' step='1' />\n";
|
||||
$html .= "<input type='hidden' name='_type_$name' value='int' />\n";
|
||||
|
||||
$this->format_option($name, $html, $label, $table_row);
|
||||
}
|
||||
@ -324,27 +335,9 @@ class Setup extends Extension
|
||||
$themes[$human] = $name;
|
||||
}
|
||||
|
||||
if (isset($_SERVER["HTTP_HOST"])) {
|
||||
$host = $_SERVER["HTTP_HOST"];
|
||||
} else {
|
||||
$host = $_SERVER["SERVER_NAME"];
|
||||
if ($_SERVER["SERVER_PORT"] != "80") {
|
||||
$host .= ":" . $_SERVER["SERVER_PORT"];
|
||||
}
|
||||
}
|
||||
$full = "//" . $host . $_SERVER["PHP_SELF"];
|
||||
$test_url = str_replace("/index.php", "/nicetest", $full);
|
||||
$test_url = str_replace("/index.php", "/nicetest", $_SERVER["SCRIPT_NAME"]);
|
||||
|
||||
$nicescript = "<script type='text/javascript'>
|
||||
function getHTTPObject() {
|
||||
if (window.XMLHttpRequest){
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
else if(window.ActiveXObject){
|
||||
return new ActiveXObject('Microsoft.XMLHTTP');
|
||||
}
|
||||
}
|
||||
|
||||
checkbox = document.getElementById('nice_urls');
|
||||
out_span = document.getElementById('nicetest');
|
||||
|
||||
@ -352,11 +345,11 @@ class Setup extends Extension
|
||||
out_span.innerHTML = '(testing...)';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
var http_request = getHTTPObject();
|
||||
var http_request = new XMLHttpRequest();
|
||||
http_request.open('GET', '$test_url', false);
|
||||
http_request.send(null);
|
||||
|
||||
if(http_request.status == 200 && http_request.responseText == 'ok') {
|
||||
if(http_request.status === 200 && http_request.responseText === 'ok') {
|
||||
checkbox.disabled = false;
|
||||
out_span.innerHTML = '(tested ok)';
|
||||
}
|
||||
@ -375,7 +368,7 @@ class Setup extends Extension
|
||||
$sb->add_choice_option(SetupConfig::THEME, $themes, "<br>Theme: ");
|
||||
//$sb->add_multichoice_option("testarray", array("a" => "b", "c" => "d"), "<br>Test Array: ");
|
||||
$sb->add_bool_option("nice_urls", "<br>Nice URLs: ");
|
||||
$sb->add_label("<span id='nicetest'>(Javascript inactive, can't test!)</span>$nicescript");
|
||||
$sb->add_label("<span title='$test_url' id='nicetest'>(Javascript inactive, can't test!)</span>$nicescript");
|
||||
$event->panel->add_block($sb);
|
||||
|
||||
$sb = new SetupBlock("Remote API Integration");
|
||||
|
@ -36,7 +36,7 @@ class ShimmieApi extends Extension
|
||||
|
||||
if ($event->page_matches("api/shimmie")) {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/plain");
|
||||
$page->set_type(MIME_TYPE_TEXT);
|
||||
|
||||
if ($event->page_matches("api/shimmie/get_tags")) {
|
||||
if ($event->count_args() > 0) {
|
||||
|
@ -155,7 +155,7 @@ class XMLSitemap extends Extension
|
||||
// Generate new sitemap
|
||||
file_put_contents($this->sitemap_filepath, $xml);
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("application/xml");
|
||||
$page->set_type(MIME_TYPE_XML_APPLICATION);
|
||||
$page->set_data($xml);
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ class XMLSitemap extends Extension
|
||||
$xml = file_get_contents($this->sitemap_filepath);
|
||||
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("application/xml");
|
||||
$page->set_type(MIME_TYPE_XML_APPLICATION);
|
||||
$page->set_data($xml);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user