Compare commits

..

4 Commits

345 changed files with 2886 additions and 5501 deletions

View File

@ -7,40 +7,12 @@ on:
- cron: '0 2 * * 0' # Weekly on Sundays at 02:00
jobs:
format:
name: Format
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set Up Cache
uses: actions/cache@v2
with:
path: |
vendor
key: php-cs-fixer-${{ hashFiles('composer.lock') }}
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install PHP dependencies
run: composer update && composer install --prefer-dist --no-progress
- name: Set up PHP
uses: shivammathur/setup-php@master
with:
php-version: 7.4
- name: Check format
run: ./vendor/bin/php-cs-fixer fix --dry-run
test:
name: PHP ${{ matrix.php }} / DB ${{ matrix.database }}
strategy:
fail-fast: false
matrix:
php: ['7.4', '8.0']
php: ['7.3', '7.4', '8.0']
database: ['pgsql', 'mysql', 'sqlite']
runs-on: ubuntu-latest
@ -114,7 +86,7 @@ jobs:
publish:
name: Publish
runs-on: ubuntu-latest
needs: [format, test]
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@master

View File

@ -35,7 +35,7 @@ RUN curl -k -o /usr/local/bin/su-exec.c https://raw.githubusercontent.com/ncopa
# Actually run shimmie
FROM debian:testing-slim
EXPOSE 8000
HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1
HEALTHCHECK --interval=5m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1
ENV UID=1000 \
GID=1000
RUN apt update && apt install -y curl \

View File

@ -5,12 +5,6 @@
"license" : "GPL-2.0-or-later",
"minimum-stability" : "dev",
"config": {
"platform": {
"php": "7.4.0"
}
},
"repositories" : [
{
"type": "composer",
@ -31,7 +25,7 @@
],
"require" : {
"php" : "^7.4 | ^8.0",
"php" : "^7.3 | ^8.0",
"ext-pdo": "*",
"ext-json": "*",
"ext-fileinfo": "*",
@ -44,17 +38,18 @@
"shish/ffsphp" : "^1.0",
"shish/microcrud" : "^2.0",
"shish/microhtml" : "^2.0",
"enshrined/svg-sanitize" : "^0.14",
"enshrined/svg-sanitize" : "^0.13",
"bower-asset/jquery" : "^1.12",
"bower-asset/jquery-timeago" : "^1.5",
"bower-asset/mediaelement" : "^2.21",
"bower-asset/js-cookie" : "^2.1"
},
"require-dev" : {
"phpunit/phpunit" : "^9.0",
"friendsofphp/php-cs-fixer" : "^2.18"
"phpunit/phpunit" : "^9.0"
},
"suggest": {
"ext-memcache": "memcache caching",
"ext-memcached": "memcached caching",

2551
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -20,8 +20,10 @@ abstract class PageMode
*/
class BasePage
{
public string $mode = PageMode::PAGE;
private string $mime;
/** @var string */
public $mode = PageMode::PAGE;
/** @var string */
private $mime;
/**
* Set what this page should do; "page", "data", or "redirect".
@ -50,11 +52,19 @@ class BasePage
// ==============================================
public string $data = ""; // public only for unit test
private ?string $file = null;
private bool $file_delete = false;
private ?string $filename = null;
private ?string $disposition = null;
/** @var string; public only for unit test */
public $data = "";
/** @var string */
private $file = null;
/** @var bool */
private $file_delete = false;
/** @var string */
private $filename = null;
private $disposition = null;
/**
* Set the raw data to be sent.
@ -81,7 +91,8 @@ class BasePage
// ==============================================
public string $redirect = "";
/** @var string */
public $redirect = "";
/**
* Set the URL to redirect to (remember to use make_link() if linking
@ -94,25 +105,32 @@ class BasePage
// ==============================================
public int $code = 200;
public string $title = "";
public string $heading = "";
public string $subheading = "";
/** @var int */
public $code = 200;
/** @var string */
public $title = "";
/** @var string */
public $heading = "";
/** @var string */
public $subheading = "";
/** @var string[] */
public array $html_headers = [];
public $html_headers = [];
/** @var string[] */
public array $http_headers = [];
public $http_headers = [];
/** @var string[][] */
public array $cookies = [];
public $cookies = [];
/** @var Block[] */
public array $blocks = [];
public $blocks = [];
/** @var string[] */
public array $flash = [];
public $flash = [];
/**
* Set the HTTP status code
@ -337,6 +355,8 @@ class BasePage
* Why do this? Two reasons:
* 1. Reduces the number of files the user's browser needs to download.
* 2. Allows these cached files to be compressed/minified by the admin.
*
* TODO: This should really be configurable somehow...
*/
public function add_auto_html_headers(): void
{
@ -361,7 +381,7 @@ class BasePage
$css_latest = $config_latest;
$css_files = array_merge(
zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/style.css"),
zglob("themes/$theme_name/{" . implode(",", $this->get_theme_stylesheets()) . "}")
zglob("themes/$theme_name/style.css")
);
foreach ($css_files as $css) {
$css_latest = max($css_latest, filemtime($css));
@ -391,7 +411,7 @@ class BasePage
"ext/static_files/modernizr-3.3.1.custom.js",
],
zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/script.js"),
zglob("themes/$theme_name/{" . implode(",", $this->get_theme_scripts()) . "}")
zglob("themes/$theme_name/script.js")
);
foreach ($js_files as $js) {
$js_latest = max($js_latest, filemtime($js));
@ -408,25 +428,7 @@ class BasePage
$this->add_html_header("<script defer src='$data_href/$js_cache_file' type='text/javascript'></script>", 44);
}
/**
* @return array A list of stylesheets relative to the theme root.
*/
protected function get_theme_stylesheets(): array
{
return ["style.css"];
}
/**
* @return array A list of script files relative to the theme root.
*/
protected function get_theme_scripts(): array
{
return ["script.js"];
}
protected function get_nav_links(): array
protected function get_nav_links()
{
$pnbe = send_event(new PageNavBuildingEvent());
@ -572,7 +574,7 @@ EOD;
class PageNavBuildingEvent extends Event
{
public array $links = [];
public $links = [];
public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
{
@ -582,9 +584,9 @@ class PageNavBuildingEvent extends Event
class PageSubNavBuildingEvent extends Event
{
public string $parent;
public $parent;
public array $links = [];
public $links = [];
public function __construct(string $parent)
{
@ -600,11 +602,11 @@ class PageSubNavBuildingEvent extends Event
class NavLink
{
public string $name;
public Link $link;
public string $description;
public int $order;
public bool $active = false;
public $name;
public $link;
public $description;
public $order;
public $active = false;
public function __construct(String $name, Link $link, String $description, ?bool $active = null, int $order = 50)
{
@ -661,7 +663,7 @@ class NavLink
}
}
function sort_nav_links(NavLink $a, NavLink $b): int
function sort_nav_links(NavLink $a, NavLink $b)
{
return $a->order - $b->order;
}

View File

@ -72,7 +72,7 @@ class BaseThemelet
}
}
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-height='$image->height' data-width='$image->width' data-mime='{$image->get_mime()}' data-post-id='$i_id'>".
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>".
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>".
"</a>\n";
}

View File

@ -9,37 +9,49 @@ class Block
{
/**
* The block's title.
*
* @var string
*/
public ?string $header;
public $header;
/**
* The content of the block.
*
* @var string
*/
public ?string $body;
public $body;
/**
* Where the block should be placed. The default theme supports
* "main" and "left", other themes can add their own areas.
*
* @var string
*/
public string $section;
public $section;
/**
* How far down the section the block should appear, higher
* numbers appear lower. The scale is 0-100 by convention,
* though any number will work.
*
* @var int
*/
public int $position;
public $position;
/**
* A unique ID for the block.
*
* @var string
*/
public string $id;
public $id;
/**
* Should this block count as content for the sake of
* the 404 handler
*
* @var boolean
*/
public bool $is_content = true;
public $is_content = true;
public function __construct(string $header=null, string $body=null, string $section="main", int $position=50, string $id=null)
{
@ -51,9 +63,7 @@ class Block
if (is_null($id)) {
$id = (empty($header) ? md5($body ?? '') : $header) . $section;
}
$str_id = preg_replace('/[^\w-]/', '', str_replace(' ', '_', $id));
assert(is_string($str_id));
$this->id = $str_id;
$this->id = preg_replace('/[^\w-]/', '', str_replace(' ', '_', $id));
}
/**

View File

@ -2,8 +2,8 @@
interface CacheEngine
{
public function get(string $key);
public function set(string $key, $val, int $time=0): void;
public function delete(string $key): void;
public function set(string $key, $val, int $time=0);
public function delete(string $key);
}
class NoCache implements CacheEngine
@ -12,17 +12,18 @@ class NoCache implements CacheEngine
{
return false;
}
public function set(string $key, $val, int $time=0): void
public function set(string $key, $val, int $time=0)
{
}
public function delete(string $key): void
public function delete(string $key)
{
}
}
class MemcachedCache implements CacheEngine
{
public ?Memcached $memcache=null;
/** @var ?Memcached */
public $memcache=null;
public function __construct(string $args)
{
@ -51,7 +52,7 @@ class MemcachedCache implements CacheEngine
}
}
public function set(string $key, $val, int $time=0): void
public function set(string $key, $val, int $time=0)
{
$key = urlencode($key);
@ -62,7 +63,7 @@ class MemcachedCache implements CacheEngine
}
}
public function delete(string $key): void
public function delete(string $key)
{
$key = urlencode($key);
@ -86,12 +87,12 @@ class APCCache implements CacheEngine
return apc_fetch($key);
}
public function set(string $key, $val, int $time=0): void
public function set(string $key, $val, int $time=0)
{
apc_store($key, $val, $time);
}
public function delete(string $key): void
public function delete(string $key)
{
apc_delete($key);
}
@ -99,7 +100,7 @@ class APCCache implements CacheEngine
class RedisCache implements CacheEngine
{
private Redis $redis;
private $redis=null;
public function __construct(string $args)
{
@ -115,7 +116,7 @@ class RedisCache implements CacheEngine
return $this->redis->get($key);
}
public function set(string $key, $val, int $time=0): void
public function set(string $key, $val, int $time=0)
{
if ($time > 0) {
$this->redis->setEx($key, $time, $val);
@ -124,7 +125,7 @@ class RedisCache implements CacheEngine
}
}
public function delete(string $key): void
public function delete(string $key)
{
$this->redis->del($key);
}
@ -133,15 +134,15 @@ class RedisCache implements CacheEngine
class Cache
{
public $engine;
public int $hits=0;
public int $misses=0;
public int $time=0;
public $hits=0;
public $misses=0;
public $time=0;
public function __construct(?string $dsn)
{
$matches = [];
$c = null;
if ($dsn && preg_match('<(.*):\/\/(.*)>', $dsn, $matches) && !isset($_GET['DISABLE_CACHE'])) {
if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches) && !isset($_GET['DISABLE_CACHE'])) {
if ($matches[1] == "memcached") {
$c = new MemcachedCache($matches[2]);
} elseif ($matches[1] == "apc") {

View File

@ -22,7 +22,7 @@ function captcha_get_html(): string
<script type=\"text/javascript\" src=\"https://www.google.com/recaptcha/api.js\"></script>";
} else {
session_start();
$captcha = Securimage::getCaptchaHtml(['securimage_path' => '/vendor/dapphp/securimage/']);
$captcha = Securimage::getCaptchaHtml(['securimage_path' => './vendor/dapphp/securimage/']);
}
}
return $captcha;

View File

@ -5,9 +5,9 @@
// quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27
class CommandBuilder
{
private string $executable;
private array $args = [];
public array $output;
private $executable;
private $args = [];
public $output;
public function __construct(String $executable)
{

View File

@ -130,7 +130,7 @@ interface Config
*/
abstract class BaseConfig implements Config
{
public array $values = [];
public $values = [];
public function set_int(string $name, ?int $value): void
{
@ -256,10 +256,12 @@ abstract class BaseConfig implements Config
*/
class DatabaseConfig extends BaseConfig
{
private Database $database;
private string $table_name;
private ?string $sub_column;
private ?string $sub_value;
/** @var Database */
private $database = null;
private $table_name;
private $sub_column;
private $sub_value;
public function __construct(
Database $database,

View File

@ -13,23 +13,30 @@ abstract class DatabaseDriver
*/
class Database
{
private string $dsn;
/** @var string */
private $dsn;
/**
* The PDO database connection object, for anyone who wants direct access.
* @var null|PDO
*/
private ?PDO $db = null;
public float $dbtime = 0.0;
private $db = null;
/**
* @var float
*/
public $dbtime = 0.0;
/**
* Meta info about the database engine.
* @var DBEngine|null
*/
private ?DBEngine $engine = null;
private $engine = null;
/**
* How many queries this DB object has run
*/
public int $query_count = 0;
public $query_count = 0;
public function __construct(string $dsn)
{

View File

@ -7,15 +7,16 @@ abstract class SCORE
abstract class DBEngine
{
public ?string $name = null;
/** @var null|string */
public $name = null;
public function init(PDO $db)
{
}
public function scoreql_to_sql(string $data): string
public function scoreql_to_sql(string $scoreql): string
{
return $data;
return $scoreql;
}
public function create_table_sql(string $name, string $data): string
@ -32,7 +33,8 @@ abstract class DBEngine
class MySQL extends DBEngine
{
public ?string $name = DatabaseDriver::MYSQL;
/** @var string */
public $name = DatabaseDriver::MYSQL;
public function init(PDO $db)
{
@ -71,7 +73,8 @@ class MySQL extends DBEngine
class PostgreSQL extends DBEngine
{
public ?string $name = DatabaseDriver::PGSQL;
/** @var string */
public $name = DatabaseDriver::PGSQL;
public function init(PDO $db)
{
@ -119,19 +122,19 @@ class PostgreSQL extends DBEngine
}
// shimmie functions for export to sqlite
function _unix_timestamp($date): int
function _unix_timestamp($date)
{
return strtotime($date);
}
function _now(): string
function _now()
{
return date("Y-m-d H:i:s");
}
function _floor($a): float
function _floor($a)
{
return floor($a);
}
function _log($a, $b=null): float
function _log($a, $b=null)
{
if (is_null($b)) {
return log($a);
@ -139,34 +142,35 @@ function _log($a, $b=null): float
return log($a, $b);
}
}
function _isnull($a): bool
function _isnull($a)
{
return is_null($a);
}
function _md5($a): string
function _md5($a)
{
return md5($a);
}
function _concat($a, $b): string
function _concat($a, $b)
{
return $a . $b;
}
function _lower($a): string
function _lower($a)
{
return strtolower($a);
}
function _rand(): int
function _rand()
{
return rand();
}
function _ln($n): float
function _ln($n)
{
return log($n);
}
class SQLite extends DBEngine
{
public ?string $name = DatabaseDriver::SQLITE;
/** @var string */
public $name = DatabaseDriver::SQLITE;
public function init(PDO $db)
{

View File

@ -6,13 +6,13 @@
*/
abstract class Event
{
public bool $stop_processing = false;
public $stop_processing = false;
public function __construct()
{
}
public function __toString(): string
public function __toString()
{
return var_export($this, true);
}
@ -42,11 +42,19 @@ class InitExtEvent extends Event
class PageRequestEvent extends Event
{
/**
* @var string[]
* @var array
*/
public $args;
public int $arg_count;
public int $part_count;
/**
* @var int
*/
public $arg_count;
/**
* @var int
*/
public $part_count;
public function __construct(string $path)
{
@ -171,12 +179,15 @@ class PageRequestEvent extends Event
*/
class CommandEvent extends Event
{
public string $cmd = "help";
/**
* @var string
*/
public $cmd = "help";
/**
* @var string[]
* @var array
*/
public array $args = [];
public $args = [];
/**
* #param string[] $args
@ -245,18 +256,24 @@ class TextFormattingEvent extends Event
{
/**
* For reference
*
* @var string
*/
public string $original;
public $original;
/**
* with formatting applied
*
* @var string
*/
public string $formatted;
public $formatted;
/**
* with formatting removed
*
* @var string
*/
public string $stripped;
public $stripped;
public function __construct(string $text)
{
@ -279,30 +296,38 @@ class LogEvent extends Event
{
/**
* a category, normally the extension name
*
* @var string
*/
public string $section;
public $section;
/**
* See python...
*
* @var int
*/
public int $priority = 0;
public $priority = 0;
/**
* Free text to be logged
*
* @var string
*/
public string $message;
public $message;
/**
* The time that the event was created
*
* @var int
*/
public int $time;
public $time;
/**
* Extra data to be held separate
*
* @var string[]
* @var array
*/
public array $args;
public $args;
public function __construct(string $section, int $priority, string $message)
{

View File

@ -7,8 +7,11 @@
*/
class SCoreException extends RuntimeException
{
public ?string $query;
public string $error;
/** @var string|null */
public $query;
/** @var string */
public $error;
public function __construct(string $msg, ?string $query=null)
{
@ -20,16 +23,21 @@ class SCoreException extends RuntimeException
class InstallerException extends RuntimeException
{
public string $title;
public string $body;
public int $exit_code;
/** @var string */
public $title;
public function __construct(string $title, string $body, int $exit_code)
/** @var string */
public $body;
/** @var int */
public $code;
public function __construct(string $title, string $body, int $code)
{
parent::__construct($body);
$this->title = $title;
$this->body = $body;
$this->exit_code = $exit_code;
$this->code = $code;
}
}

View File

@ -13,11 +13,16 @@
*/
abstract class Extension
{
public string $key;
protected ?Themelet $theme;
public ?ExtensionInfo $info;
/** @var string */
public $key;
private static array $enabled_extensions = [];
/** @var Themelet */
protected $theme;
/** @var ExtensionInfo */
public $info;
private static $enabled_extensions = [];
public function __construct($class = null)
{
@ -117,31 +122,35 @@ abstract class ExtensionInfo
public const LICENSE_MIT = "MIT";
public const LICENSE_WTFPL = "WTFPL";
public const VISIBLE_DEFAULT = "default";
public const VISIBLE_ADMIN = "admin";
public const VISIBLE_HIDDEN = "hidden";
private const VALID_VISIBILITY = [self::VISIBLE_DEFAULT, self::VISIBLE_ADMIN, self::VISIBLE_HIDDEN];
private const VALID_VISIBILITY = [self::VISIBLE_ADMIN, self::VISIBLE_HIDDEN];
public string $key;
public $key;
public bool $core = false;
public bool $beta = false;
public $core = false;
public string $name;
public string $license;
public string $description;
public array $authors = [];
public array $dependencies = [];
public array $conflicts = [];
public string $visibility = self::VISIBLE_DEFAULT;
public ?string $link = null;
public ?string $version = null;
public ?string $documentation = null;
public $beta = false;
/** @var string[] which DBs this ext supports (blank for 'all') */
public array $db_support = [];
private ?bool $supported = null;
private ?string $support_info = null;
public $name;
public $authors = [];
public $link;
public $license;
public $version;
public $dependencies = [];
public $conflicts = [];
public $visibility;
public $description;
public $documentation;
/** @var array which DBs this ext supports (blank for 'all') */
public $db_support = [];
/** @var bool */
private $supported = null;
/** @var string */
private $support_info = null;
public function is_supported(): bool
{
@ -159,9 +168,9 @@ abstract class ExtensionInfo
return $this->support_info;
}
private static array $all_info_by_key = [];
private static array $all_info_by_class = [];
private static array $core_extensions = [];
private static $all_info_by_key = [];
private static $all_info_by_class = [];
private static $core_extensions = [];
protected function __construct()
{
@ -274,7 +283,7 @@ abstract class FormatterExtension extends Extension
*/
abstract class DataHandlerExtension extends Extension
{
protected array $SUPPORTED_MIME = [];
protected $SUPPORTED_MIME = [];
protected function move_upload_to_archive(DataUploadEvent $event)
{
@ -326,9 +335,7 @@ abstract class DataHandlerExtension extends Extension
}
send_event(new ImageReplaceEvent($event->replace_id, $image));
$_id = $event->replace_id;
assert(!is_null($_id));
$event->image_id = $_id;
$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)) {

View File

@ -5,9 +5,13 @@
*/
class ImageAdditionEvent extends Event
{
public User $user;
public Image $image;
public bool $merged = false;
/** @var User */
public $user;
/** @var Image */
public $image;
public $merged = false;
/**
* Inserts a new image into the database with its associated
@ -30,8 +34,11 @@ class ImageAdditionException extends SCoreException
*/
class ImageDeletionEvent extends Event
{
public Image $image;
public bool $force = false;
/** @var Image */
public $image;
/** @var bool */
public $force = false;
/**
* Deletes an image.
@ -52,8 +59,10 @@ class ImageDeletionEvent extends Event
*/
class ImageReplaceEvent extends Event
{
public int $id;
public Image $image;
/** @var int */
public $id;
/** @var Image */
public $image;
/**
* Replaces an image.
@ -79,10 +88,15 @@ class ImageReplaceException extends SCoreException
*/
class ThumbnailGenerationEvent extends Event
{
public string $hash;
public string $mime;
public bool $force;
public bool $generated;
/** @var string */
public $hash;
/** @var string */
public $mime;
/** @var bool */
public $force;
/** @var bool */
public $generated;
/**
* Request a thumbnail be made for an image object
@ -107,10 +121,14 @@ class ThumbnailGenerationEvent extends Event
*/
class ParseLinkTemplateEvent extends Event
{
public string $link;
public string $text;
public string $original;
public Image $image;
/** @var string */
public $link;
/** @var string */
public $text;
/** @var string */
public $original;
/** @var Image */
public $image;
public function __construct(string $link, Image $image)
{

View File

@ -13,31 +13,68 @@ class Image
public const IMAGE_DIR = "images";
public const THUMBNAIL_DIR = "thumbs";
public ?int $id = null;
public int $height = 0;
public int $width = 0;
public string $hash;
public int $filesize;
public string $filename;
private string $ext;
private string $mime;
/** @var null|int */
public $id = null;
/** @var int */
public $height;
/** @var int */
public $width;
/** @var string */
public $hash;
/** @var int */
public $filesize;
/** @var string */
public $filename;
/** @var string */
private $ext;
/** @var string */
private $mime;
/** @var string[]|null */
public ?array $tag_array;
public int $owner_id;
public string $owner_ip;
public string $posted;
public ?string $source;
public bool $locked = false;
public ?bool $lossless = null;
public ?bool $video = null;
public ?string $video_codec = null;
public ?bool $image = null;
public ?bool $audio = null;
public ?int $length = null;
public $tag_array;
public static array $bool_props = ["locked", "lossless", "video", "audio", "image"];
public static array $int_props = ["id", "owner_id", "height", "width", "filesize", "length"];
/** @var int */
public $owner_id;
/** @var string */
public $owner_ip;
/** @var string */
public $posted;
/** @var string */
public $source;
/** @var boolean */
public $locked = false;
/** @var boolean */
public $lossless = null;
/** @var boolean */
public $video = null;
/** @var string */
public $video_codec = null;
/** @var boolean */
public $image = null;
/** @var boolean */
public $audio = null;
/** @var int */
public $length = null;
public static $bool_props = ["locked", "lossless", "video", "audio"];
public static $int_props = ["id", "owner_id", "height", "width", "filesize", "length"];
/**
* One will very rarely construct an image directly, more common
@ -108,7 +145,7 @@ class Image
private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags=[]): iterable
{
global $database, $user;
global $database, $user, $config;
if ($start < 0) {
$start = 0;
@ -124,7 +161,9 @@ class Image
}
$querylet = Image::build_search_querylet($tags, $limit, $start);
return $database->get_all_iterable($querylet->sql, $querylet->variables);
$result = $database->get_all_iterable($querylet->sql, $querylet->variables);
return $result;
}
/**
@ -444,7 +483,6 @@ class Image
WHERE image_id=:id
ORDER BY tag
", ["id"=>$this->id]);
sort($this->tag_array);
}
return $this->tag_array;
}
@ -586,9 +624,7 @@ class Image
public function set_mime($mime): void
{
$this->mime = $mime;
$ext = FileExtension::get_for_mime($this->get_mime());
assert($ext != null);
$this->ext = $ext;
$this->ext = FileExtension::get_for_mime($this->get_mime());
}

View File

@ -1,8 +1,10 @@
<?php declare(strict_types=1);
class Querylet
{
public string $sql;
public array $variables;
/** @var string */
public $sql;
/** @var array */
public $variables;
public function __construct(string $sql, array $variables=[])
{
@ -29,8 +31,10 @@ class Querylet
class TagCondition
{
public string $tag;
public bool $positive;
/** @var string */
public $tag;
/** @var bool */
public $positive;
public function __construct(string $tag, bool $positive)
{
@ -41,8 +45,10 @@ class TagCondition
class ImgCondition
{
public Querylet $qlet;
public bool $positive;
/** @var Querylet */
public $qlet;
/** @var bool */
public $positive;
public function __construct(Querylet $qlet, bool $positive)
{

View File

@ -68,7 +68,7 @@ function do_install($dsn)
create_tables(new Database($dsn));
write_config($dsn);
} catch (InstallerException $e) {
die_nicely($e->title, $e->body, $e->exit_code);
die_nicely($e->title, $e->body, $e->code);
}
}

View File

@ -7,9 +7,6 @@ abstract class Permissions
{
public const CHANGE_SETTING = "change_setting"; # modify web-level settings, eg the config table
public const OVERRIDE_CONFIG = "override_config"; # modify sys-level settings, eg shimmie.conf.php
public const CHANGE_USER_SETTING = "change_user_setting"; # modify own user-level settings
public const CHANGE_OTHER_USER_SETTING = "change_other_user_setting"; # modify own user-level settings
public const BIG_SEARCH = "big_search"; # search for more than 3 tags at once (speed mode only)
public const MANAGE_EXTENSION_LIST = "manage_extension_list";
@ -103,7 +100,6 @@ abstract class Permissions
public const SET_PRIVATE_IMAGE = "set_private_image";
public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images";
public const CRON_RUN = "cron_run";
public const BULK_IMPORT = "bulk_import";
public const BULK_EXPORT = "bulk_export";
public const BULK_DOWNLOAD = "bulk_download";

View File

@ -246,22 +246,21 @@ function find_header(array $headers, string $name): ?string
if (!function_exists('mb_strlen')) {
// TODO: we should warn the admin that they are missing multibyte support
/** @noinspection PhpUnusedParameterInspection */
function mb_strlen($str, $encoding): int
function mb_strlen($str, $encoding)
{
return strlen($str);
}
function mb_internal_encoding($encoding): void
function mb_internal_encoding($encoding)
{
}
function mb_strtolower($str): string
function mb_strtolower($str)
{
return strtolower($str);
}
}
/** @noinspection PhpUnhandledExceptionInspection */
function get_subclasses_of(string $parent): array
function get_subclasses_of(string $parent)
{
$result = [];
foreach (get_declared_classes() as $class) {
@ -328,7 +327,7 @@ function get_base_href(): string
/**
* The opposite of the standard library's parse_url
*/
function unparse_url(array $parsed_url): string
function unparse_url($parsed_url)
{
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
@ -346,14 +345,14 @@ function unparse_url(array $parsed_url): string
if (!function_exists('str_starts_with')) {
function str_starts_with(string $haystack, string $needle): bool
{
return strncmp($haystack, $needle, strlen($needle)) === 0;
return \strncmp($haystack, $needle, \strlen($needle)) === 0;
}
}
if (!function_exists('str_ends_with')) {
function str_ends_with(string $haystack, string $needle): bool
{
return $needle === '' || $needle === substr($haystack, - strlen($needle));
return $needle === '' || $needle === \substr($haystack, - \strlen($needle));
}
}
@ -526,6 +525,7 @@ function parse_shorthand_int(string $limit): int
/** @noinspection PhpMissingBreakStatementInspection */
// no break
case 'm': $value *= 1024; // fall through
/** @noinspection PhpMissingBreakStatementInspection */
// no break
case 'k': $value *= 1024; break;
default: $value = -1;
@ -800,7 +800,7 @@ function iterator_map_to_array(callable $callback, iterator $iter): array
return iterator_to_array(iterator_map($callback, $iter));
}
function stringer($s): string
function stringer($s)
{
if (is_array($s)) {
if (isset($s[0])) {

View File

@ -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.9.1$_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)

View File

@ -2,8 +2,8 @@
class Link
{
public ?string $page;
public ?string $query;
public $page;
public $query;
public function __construct(?string $page=null, ?string $query=null)
{

View File

@ -15,12 +15,22 @@ function _new_user(array $row): User
*/
class User
{
public int $id;
public string $name;
public ?string $email;
public string $join_date;
public ?string $passhash;
public UserClass $class;
/** @var int */
public $id;
/** @var string */
public $name;
/** @var string */
public $email;
public $join_date;
/** @var string */
public $passhash;
/** @var UserClass */
public $class;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialisation *

View File

@ -10,9 +10,21 @@ $_shm_user_classes = [];
*/
class UserClass
{
public ?string $name = null;
public ?UserClass $parent = null;
public array $abilities = [];
/**
* @var ?string
*/
public $name = null;
/**
* @var ?UserClass
*/
public $parent = null;
/**
* @var array
*/
public $abilities = [];
public function __construct(string $name, string $parent = null, array $abilities = [])
{
@ -70,6 +82,7 @@ new UserClass("ghost", "base", [
// the admin might grant them some permissions
new UserClass("anonymous", "base", [
Permissions::CREATE_USER => true,
Permissions::CREATE_COMMENT => true,
]);
new UserClass("user", "base", [
@ -88,7 +101,6 @@ new UserClass("user", "base", [
Permissions::READ_PM => true,
Permissions::SET_PRIVATE_IMAGE => false,
Permissions::BULK_DOWNLOAD => true,
Permissions::CHANGE_USER_SETTING => true
]);
new UserClass("hellbanned", "user", [
@ -97,8 +109,6 @@ new UserClass("hellbanned", "user", [
new UserClass("admin", "base", [
Permissions::CHANGE_SETTING => true,
Permissions::CHANGE_USER_SETTING => true,
Permissions::CHANGE_OTHER_USER_SETTING => true,
Permissions::OVERRIDE_CONFIG => true,
Permissions::BIG_SEARCH => true,
@ -191,8 +201,6 @@ new UserClass("admin", "base", [
Permissions::APPROVE_IMAGE => true,
Permissions::APPROVE_COMMENT => true,
Permissions::CRON_RUN =>true,
Permissions::BULK_IMPORT =>true,
Permissions::BULK_EXPORT =>true,
Permissions::BULK_DOWNLOAD => true,

View File

@ -12,7 +12,6 @@ use function MicroHTML\TFOOT;
use function MicroHTML\TR;
use function MicroHTML\TH;
use function MicroHTML\TD;
use MicroHTML\HTMLElement;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Misc *
@ -63,7 +62,6 @@ function contact_link(): ?string
*/
function is_https_enabled(): bool
{
if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') $_SERVER['HTTPS']='on';
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
}
@ -361,7 +359,7 @@ function path_to_tags(string $path): string
}
function join_url(string $base, string ...$paths): string
function join_url(string $base, string ...$paths)
{
$output = $base;
foreach ($paths as $path) {
@ -412,7 +410,7 @@ function remove_empty_dirs(string $dir): bool
}
}
if ($result===true) {
$result = rmdir($dir);
$result = $result && rmdir($dir);
}
return $result;
}
@ -586,6 +584,7 @@ function _get_themelet_files(string $_theme): array
/**
* Used to display fatal errors to the web user.
* @noinspection PhpPossiblePolymorphicInvocationInspection
*/
function _fatal_error(Exception $e): void
{
@ -704,7 +703,7 @@ function make_form(string $target, string $method="POST", bool $multipart=false,
return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$extra_inputs;
}
function SHM_FORM(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit=""): HTMLElement
function SHM_FORM(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit="")
{
global $user;
@ -729,19 +728,19 @@ function SHM_FORM(string $target, string $method="POST", bool $multipart=false,
);
}
function SHM_SIMPLE_FORM($target, ...$children): HTMLElement
function SHM_SIMPLE_FORM($target, ...$children)
{
$form = SHM_FORM($target);
$form->appendChild(emptyHTML(...$children));
return $form;
}
function SHM_SUBMIT(string $text): HTMLElement
function SHM_SUBMIT(string $text)
{
return INPUT(["type"=>"submit", "value"=>$text]);
}
function SHM_COMMAND_EXAMPLE(string $ex, string $desc): HTMLElement
function SHM_COMMAND_EXAMPLE(string $ex, string $desc)
{
return DIV(
["class"=>"command_example"],
@ -750,7 +749,7 @@ function SHM_COMMAND_EXAMPLE(string $ex, string $desc): HTMLElement
);
}
function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot): HTMLElement
function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot)
{
if (is_string($foot)) {
$foot = TFOOT(TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>$foot]))));
@ -770,16 +769,16 @@ function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot)
}
const BYTE_DENOMINATIONS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
function human_filesize(int $bytes, $decimals = 2): string
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): string
function generate_key(int $length = 20)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';

View File

@ -4,12 +4,20 @@ class AdminPageInfo extends ExtensionInfo
{
public const KEY = "admin";
public string $key = self::KEY;
public string $name = "Admin Controls";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Provides a base for various small admin functions";
public bool $core = true;
public string $visibility = self::VISIBLE_HIDDEN;
public $key = self::KEY;
public $name = "Admin Controls";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Various things to make admins' lives easier";
public $documentation =
"Various moderate-level tools for admins; for advanced, obscure, and possibly dangerous tools see the shimmie2-utils script set
<p>Lowercase all tags:
<br>Set all tags to lowercase for consistency
<p>Recount tag use:
<br>If the counts of posts per tag get messed up somehow, this will reset them, and remove any unused tags
<p>Database dump:
<br>Download the contents of the database in plain text format, useful for backups.
<p>Post dump:
<br>Download all the posts as a .zip file (Requires ZipArchive)";
}

View File

@ -1,11 +1,13 @@
<?php declare(strict_types=1);
<?php /** @noinspection PhpUnusedPrivateMethodInspection */
declare(strict_types=1);
/**
* Sent when the admin page is ready to be added to
*/
class AdminBuildingEvent extends Event
{
public Page $page;
/** @var Page */
public $page;
public function __construct(Page $page)
{
@ -16,8 +18,10 @@ class AdminBuildingEvent extends Event
class AdminActionEvent extends Event
{
public string $action;
public bool $redirect = true;
/** @var string */
public $action;
/** @var bool */
public $redirect = true;
public function __construct(string $action)
{
@ -29,7 +33,7 @@ class AdminActionEvent extends Event
class AdminPage extends Extension
{
/** @var AdminPageTheme */
protected ?Themelet $theme;
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{
@ -128,6 +132,7 @@ class AdminPage extends Extension
public function onAdminBuilding(AdminBuildingEvent $event)
{
$this->theme->display_page();
$this->theme->display_form();
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
@ -147,4 +152,46 @@ class AdminPage extends Extension
$event->add_link("Board Admin", make_link("admin"));
}
}
public function onAdminAction(AdminActionEvent $event)
{
$action = $event->action;
if (method_exists($this, $action)) {
$event->redirect = $this->$action();
}
}
private function set_tag_case()
{
global $database;
$database->execute(
"UPDATE tags SET tag=:tag1 WHERE LOWER(tag) = LOWER(:tag2)",
["tag1" => $_POST['tag'], "tag2" => $_POST['tag']]
);
log_info("admin", "Fixed the case of {$_POST['tag']}", "Fixed case");
return true;
}
private function lowercase_all_tags()
{
global $database;
$database->execute("UPDATE tags SET tag=lower(tag)");
log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase");
return true;
}
private function recount_tag_use()
{
global $database;
$database->execute("
UPDATE tags
SET count = COALESCE(
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
0
)
");
$database->execute("DELETE FROM tags WHERE count=0");
log_warning("admin", "Re-counted tags", "Re-counted tags");
return true;
}
}

View File

@ -19,6 +19,59 @@ class AdminPageTest extends ShimmiePHPUnitTestCase
$this->assertEquals("Admin Tools", $page->title);
}
public function testLowercaseAndSetCase()
{
// Create a problem
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "TeStCase$ts");
// Validate problem
$page = $this->get_page("post/view/$image_id_1");
$this->assertEquals("Post $image_id_1: TeStCase$ts", $page->title);
// Fix
send_event(new AdminActionEvent('lowercase_all_tags'));
// Validate fix
$this->get_page("post/view/$image_id_1");
$this->assert_title("Post $image_id_1: testcase$ts");
// Change
$_POST["tag"] = "TestCase$ts";
send_event(new AdminActionEvent('set_tag_case'));
// Validate change
$this->get_page("post/view/$image_id_1");
$this->assert_title("Post $image_id_1: TestCase$ts");
}
# FIXME: make sure the admin tools actually work
public function testRecount()
{
global $database;
// Create a problem
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$database->execute(
"INSERT INTO tags(tag, count) VALUES(:tag, :count)",
["tag"=>"tes$ts", "count"=>42]
);
// Fix
send_event(new AdminActionEvent('recount_tag_use'));
// Validate fix
$this->assertEquals(
0,
$database->get_one(
"SELECT count FROM tags WHERE tag = :tag",
["tag"=>"tes$ts"]
)
);
}
public function testCommands()
{
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));

View File

@ -1,4 +1,5 @@
<?php declare(strict_types=1);
use function MicroHTML\INPUT;
class AdminPageTheme extends Themelet
{
@ -13,4 +14,41 @@ class AdminPageTheme extends Themelet
$page->set_heading("Admin Tools");
$page->add_block(new NavBlock());
}
protected function button(string $name, string $action, bool $protected=false): string
{
$c_protected = $protected ? " protected" : "";
$html = make_form(make_link("admin/$action"), "POST", false, "admin$c_protected");
if ($protected) {
$html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>";
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
} else {
$html .= "<input type='submit' id='$action' value='$name'>";
}
$html .= "</form>\n";
return $html;
}
/*
* Show a form which links to admin_utils with POST[action] set to one of:
* 'lowercase all tags'
* 'recount tag use'
* etc
*/
public function display_form()
{
global $page;
$html = "";
$html .= $this->button("All tags to lowercase", "lowercase_all_tags", true);
$html .= $this->button("Recount tag use", "recount_tag_use", false);
$page->add_block(new Block("Misc Admin Tools", $html));
$html = (string)SHM_SIMPLE_FORM(
"admin/set_tag_case",
INPUT(["type"=>'text', "name"=>'tag', "placeholder"=>'Enter tag with correct case', "class"=>'autocomplete_tags', "autocomplete"=>'off']),
SHM_SUBMIT('Set Tag Case'),
);
$page->add_block(new Block("Set Tag Case", $html));
}
}

View File

@ -4,12 +4,12 @@ class AliasEditorInfo extends ExtensionInfo
{
public const KEY = "alias_editor";
public string $key = self::KEY;
public string $name = "Alias Editor";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Edit the alias list";
public ?string $documentation = 'The list is visible at <a href="$site/alias/list">/alias/list</a>; only site admins can edit it, other people can view and download it';
public bool $core = true;
public $key = self::KEY;
public $name = "Alias Editor";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Edit the alias list";
public $documentation = 'The list is visible at <a href="$site/alias/list">/alias/list</a>; only site admins can edit it, other people can view and download it';
public $core = true;
}

View File

@ -26,8 +26,10 @@ class AliasTable extends Table
class AddAliasEvent extends Event
{
public string $oldtag;
public string $newtag;
/** @var string */
public $oldtag;
/** @var string */
public $newtag;
public function __construct(string $oldtag, string $newtag)
{
@ -39,7 +41,7 @@ class AddAliasEvent extends Event
class DeleteAliasEvent extends Event
{
public string $oldtag;
public $oldtag;
public function __construct(string $oldtag)
{
@ -55,7 +57,7 @@ class AddAliasException extends SCoreException
class AliasEditor extends Extension
{
/** @var AliasEditorTheme */
protected ?Themelet $theme;
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{

View File

@ -11,9 +11,9 @@ class ApiInternalInfo extends ExtensionInfo
{
public const KEY = "api_internal";
public string $key = self::KEY;
public string $name = "Internal API";
public array $authors = ["Daku"=>"admin@codeanimu.net", "Matthew Barbour"=>"matthew@darkholme.net"];
public string $description = "Dependency extension used to provide a standardized source for performing operations via an API";
public string $visibility = self::VISIBLE_HIDDEN;
public $key = self::KEY;
public $name = "Internal API";
public $authors = ["Daku"=>"admin@codeanimu.net", "Matthew Barbour"=>"matthew@darkholme.net"];
public $description = "Dependency extension used to provide a standardized source for performing operations via an API";
public $visibility = self::VISIBLE_HIDDEN;
}

View File

@ -4,9 +4,9 @@ class ApprovalInfo extends ExtensionInfo
{
public const KEY = "approval";
public string $key = self::KEY;
public string $name = "Approval";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public string $description = "Adds an approval step to the upload/import process.";
public $key = self::KEY;
public $name = "Approval";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Adds an approval step to the upload/import process.";
}

View File

@ -10,7 +10,7 @@ abstract class ApprovalConfig
class Approval extends Extension
{
/** @var ApprovalTheme */
protected ?Themelet $theme;
protected $theme;
public function onInitExt(InitExtEvent $event)
{

View File

@ -5,7 +5,7 @@ use function MicroHTML\INPUT;
class ApprovalTheme extends Themelet
{
public function get_image_admin_html(Image $image): string
public function get_image_admin_html(Image $image)
{
if ($image->approved===true) {
$html = SHM_SIMPLE_FORM(
@ -24,7 +24,8 @@ class ApprovalTheme extends Themelet
return (string)$html;
}
public function get_help_html(): string
public function get_help_html()
{
return '<p>Search for posts that are approved/not approved.</p>
<div class="command_example">
@ -40,8 +41,9 @@ class ApprovalTheme extends Themelet
public function display_admin_block(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Approval");
$sb = new SetupBlock("Approval");
$sb->add_bool_option(ApprovalConfig::IMAGES, "Posts: ");
$event->panel->add_block($sb);
}
public function display_admin_form()

View File

@ -4,11 +4,11 @@ class ArtistsInfo extends ExtensionInfo
{
public const KEY = "artists";
public string $key = self::KEY;
public string $name = "Artists System";
public string $url = self::SHIMMIE_URL;
public array $authors = ["Sein Kraft"=>"mail@seinkraft.info","Alpha"=>"alpha@furries.com.ar"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Simple artists extension";
public bool $beta = true;
public $key = self::KEY;
public $name = "Artists System";
public $url = self::SHIMMIE_URL;
public $authors = ["Sein Kraft"=>"mail@seinkraft.info","Alpha"=>"alpha@furries.com.ar"];
public $license = self::LICENSE_GPLV2;
public $description = "Simple artists extension";
public $beta = true;
}

View File

@ -2,9 +2,12 @@
class AuthorSetEvent extends Event
{
public Image $image;
public User $user;
public string $author;
/** @var Image */
public $image;
/** @var User */
public $user;
/** @var string */
public $author;
public function __construct(Image $image, User $user, string $author)
{
@ -18,7 +21,7 @@ class AuthorSetEvent extends Event
class Artists extends Extension
{
/** @var ArtistsTheme */
protected ?Themelet $theme;
protected $theme;
public function onImageInfoSet(ImageInfoSetEvent $event)
{
@ -680,7 +683,7 @@ class Artists extends Extension
);
}
private function add_artist(): int
private function add_artist()
{
global $user;
$inputs = validate_input([

View File

@ -546,7 +546,7 @@ class ArtistsTheme extends Themelet
return $html;
}
public function get_help_html(): string
public function get_help_html()
{
return '<p>Search for posts with a particular artist.</p>
<div class="command_example">

View File

@ -4,9 +4,9 @@ class AutoTaggerInfo extends ExtensionInfo
{
public const KEY = "auto_tagger";
public string $key = self::KEY;
public string $name = "Auto-Tagger";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public string $description = "Provides several automatic tagging functions";
public $key = self::KEY;
public $name = "Auto-Tagger";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Provides several automatic tagging functions";
}

View File

@ -28,8 +28,10 @@ class AutoTaggerTable extends Table
class AddAutoTagEvent extends Event
{
public string $tag;
public string $additional_tags;
/** @var string */
public $tag;
/** @var string */
public $additional_tags;
public function __construct(string $tag, string $additional_tags)
{
@ -41,7 +43,7 @@ class AddAutoTagEvent extends Event
class DeleteAutoTagEvent extends Event
{
public string $tag;
public $tag;
public function __construct(string $tag)
{
@ -61,7 +63,7 @@ class AddAutoTagException extends SCoreException
class AutoTagger extends Extension
{
/** @var AutoTaggerTheme */
protected ?Themelet $theme;
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{

View File

@ -4,9 +4,9 @@ class AutoCompleteInfo extends ExtensionInfo
{
public const KEY = "autocomplete";
public string $key = self::KEY;
public string $name = "Autocomplete";
public array $authors = ["Daku"=>"admin@codeanimu.net", "Matthew Barbour"=>"matthew@darkholme.net"];
public string $description = "Adds autocomplete to search & tagging.";
public array $dependencies = [ApiInternalInfo::KEY];
public $key = self::KEY;
public $name = "Autocomplete";
public $authors = ["Daku"=>"admin@codeanimu.net", "Matthew Barbour"=>"matthew@darkholme.net"];
public $description = "Adds autocomplete to search & tagging.";
public $dependencies = [ApiInternalInfo::KEY];
}

View File

@ -13,7 +13,7 @@ class AutoCompleteConfig
class AutoComplete extends Extension
{
/** @var AutoCompleteTheme */
protected ?Themelet $theme;
protected $theme;
public function get_priority(): int
{
@ -33,69 +33,9 @@ class AutoComplete extends Extension
{
global $page;
if ($event->page_matches("api/internal/autocomplete")) {
$limit = $_GET["limit"] ?? 0;
$s = $_GET["s"] ?? null;
$res = $this->complete($s, $limit);
$page->set_mode(PageMode::DATA);
$page->set_mime(MimeType::JSON);
$page->set_data(json_encode($res));
}
$this->theme->build_autocomplete($page);
}
private function complete(string $search, int $limit): array
{
global $cache, $database;
if (!$search) {
return [];
}
$search = strtolower($search);
if (
$search == '' ||
$search[0] == '_' ||
$search[0] == '%' ||
strlen($search) > 32
) {
return [];
}
$cache_key = "autocomplete-$search";
$limitSQL = "";
$search = str_replace('_', '\_', $search);
$search = str_replace('%', '\%', $search);
$SQLarr = ["search"=>"$search%"]; #, "cat_search"=>"%:$search%"];
if ($limit !== 0) {
$limitSQL = "LIMIT :limit";
$SQLarr['limit'] = $limit;
$cache_key .= "-" . $limit;
}
$res = $cache->get($cache_key);
if (!$res) {
$res = $database->get_pairs(
"
SELECT tag, count
FROM tags
WHERE LOWER(tag) LIKE LOWER(:search)
-- OR LOWER(tag) LIKE LOWER(:cat_search)
AND count > 0
ORDER BY count DESC
$limitSQL
",
$SQLarr
);
$cache->set($cache_key, $res, 600);
}
return $res;
}
public function onSetupBuilding(SetupBuildingEvent $event)
{
$this->theme->display_admin_block($event);

View File

@ -69,12 +69,12 @@ function enableTagAutoComplete(element, limit, search_categories) {
var keyCode = e.keyCode || e.which;
//Stop tags containing space.
if(keyCode === 32) {
if(keyCode == 32) {
e.preventDefault();
$('.autocomplete_tags').tagit('createTag', $(this).val());
$(this).autocomplete('close');
} else if (keyCode === 9) {
} else if (keyCode == 9) {
e.preventDefault();
var tag = $('.tagit-autocomplete[style*=\"display: block\"] > li:focus, .tagit-autocomplete[style*=\"display: block\"] > li:first').first();

View File

@ -23,7 +23,7 @@ class AutoCompleteTheme extends Themelet
$page->add_html_header("<script defer src='$base_href/ext/autocomplete/lib/jquery-ui.min.js' type='text/javascript'></script>");
$page->add_html_header("<script defer src='$base_href/ext/autocomplete/lib/tag-it.min.js' type='text/javascript'></script>");
$page->add_html_header("<link rel='stylesheet' type='text/css' href='$base_href/ext/autocomplete/lib/jquery-ui.min.css' />");
$page->add_html_header('<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/flick/jquery-ui.css">');
$page->add_html_header("<link rel='stylesheet' type='text/css' href='$base_href/ext/autocomplete/lib/jquery.tagit.css' />");
$scripts = "";
@ -32,7 +32,7 @@ class AutoCompleteTheme extends Themelet
$scripts .= self::generate_autocomplete_enable_script('.autocomplete_tags[name="search"]');
}
if ($config->get_bool(AutoCompleteConfig::TAGGING)) {
$scripts .= self::generate_autocomplete_enable_script('.autocomplete_tags[name^="tags"], .autocomplete_tags[name="bulk_tags"]'); //.autocomplete_tags[name="tag_edit__tags"]');
$scripts .= self::generate_autocomplete_enable_script('.autocomplete_tags[name^="tags"], .autocomplete_tags[name="bulk_tags"], .autocomplete_tags[name="tag_edit__tags"]');
}
$b = new Block(
@ -53,12 +53,13 @@ class AutoCompleteTheme extends Themelet
public function display_admin_block(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Autocomplete");
$sb = new SetupBlock("Autocomplete");
$sb->start_table();
$sb->add_int_option(AutoCompleteConfig::SEARCH_LIMIT, "Search Limit", true);
$sb->add_bool_option(AutoCompleteConfig::SEARCH_CATEGORIES, "Search Categories", true);
$sb->add_bool_option(AutoCompleteConfig::NAVIGATION, "Enable For Navigation", true);
$sb->add_bool_option(AutoCompleteConfig::TAGGING, "Enable For Tagging", true);
$sb->end_table();
$event->panel->add_block($sb);
}
}

View File

@ -4,13 +4,13 @@ class BanWordsInfo extends ExtensionInfo
{
public const KEY = "ban_words";
public string $key = self::KEY;
public string $name = "Comment Word Ban";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "For stopping spam and other comment abuse";
public ?string $documentation =
public $key = self::KEY;
public $name = "Comment Word Ban";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "For stopping spam and other comment abuse";
public $documentation =
"Allows an administrator to ban certain words
from comments. This can be a very simple but effective way
of stopping spam; just add \"viagra\", \"porn\", etc to the

View File

@ -55,7 +55,7 @@ xanax
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Banned Phrases");
$sb = new SetupBlock("Banned Phrases");
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
$sb->add_longtext_option("banned_words");
$failed = [];
@ -69,6 +69,7 @@ xanax
if ($failed) {
$sb->add_label("Failed regexes: ".join(", ", $failed));
}
$event->panel->add_block($sb);
}
/**

View File

@ -4,52 +4,29 @@ class BBCodeInfo extends ExtensionInfo
{
public const KEY = "bbcode";
public string $key = self::KEY;
public string $name = "BBCode";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public bool $core = true;
public string $description = "Turns BBCode into HTML";
public ?string $documentation =
" Basic formatting tags:
public $key = self::KEY;
public $name = "BBCode";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $core = true;
public $description = "Turns BBCode into HTML";
public $documentation =
" Supported tags:
<ul>
<li>[img]url[/img]
<li>[url]<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>[/url]
<li>[email]<a href=\"mailto:{self::SHISH_EMAIL}\">webmaster@shishnet.org</a>[/email]
<li>[b]<b>bold</b>[/b]
<li>[i]<i>italic</i>[/i]
<li>[u]<u>underline</u>[/u]
<li>[s]<s>strikethrough</s>[/s]
<li>[sup]<sup>superscript</sup>[/sup]
<li>[sub]<sub>subscript</sub>[/sub]
<li>[h1]Heading 1[/h1]
<li>[h2]Heading 2[/h2]
<li>[h3]Heading 3[/h3]
<li>[h4]Heading 4[/h4]
<li>[align=left|center|right]Aligned Text[/align]
</ul>
<br>
Link tags:
<ul>
<li>[img]url[/img]
<li>[url]<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>[/url]
<li>[url=<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>]some text[/url]
<li>[url]site://ext_doc/bbcode[/url]
<li>[url=site://ext_doc/bbcode]Link to BBCode docs[/url]
<li>[email]<a href=\"mailto:{self::SHISH_EMAIL}\">webmaster@shishnet.org</a>[/email]
<li>[[wiki article]]
<li>[[wiki article|with some text]]
<li>[quote]text[/quote]
<li>[quote=Username]text[/quote]
<li>&gt;&gt;123 (link to post #123)
<li>[anchor=target]Scroll to #bb-target[/anchor]
</ul>
<br>
More format Tags:
<ul>
<li>[list]Unordered list[/list]
<li>[ul]Unordered list[/ul]
<li>[ol]Ordered list[/ol]
<li>[li]List Item[/li]
<li>[code]<pre>print(\"Hello World!\");</pre>[/code]
<li>[spoiler]<span style=\"background-color:#000; color:#000;\">Voldemort is bad</span>[/spoiler]
<li>[quote]<blockquote><small>To be or not to be...</small></blockquote>[/quote]
<li>[quote=Shakespeare]<blockquote><em>Shakespeare said:</em><br><small>... That is the question</small></blockquote>[/quote]
</ul>";
}

View File

@ -96,13 +96,13 @@ class BBCodeTest extends ShimmiePHPUnitTestCase
);
}
private function filter($in): string
private function filter($in)
{
$bb = new BBCode();
return $bb->format($in);
}
private function strip($in): string
private function strip($in)
{
$bb = new BBCode();
return $bb->strip($in);

View File

@ -1,13 +0,0 @@
<?php declare(strict_types=1);
class BiographyInfo extends ExtensionInfo
{
public const KEY = "biography";
public string $key = self::KEY;
public string $name = "User Bios";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Allow users to write a bit about themselves";
}

View File

@ -1,34 +0,0 @@
<?php declare(strict_types=1);
class Biography extends Extension
{
/** @var BiographyTheme */
protected ?Themelet $theme;
public function onUserPageBuilding(UserPageBuildingEvent $event)
{
global $page, $user;
$duser = $event->display_user;
$duser_config = UserConfig::get_for_user($event->display_user->id);
$bio = $duser_config->get_string("biography", "");
if ($user->id == $duser->id) {
$this->theme->display_composer($page, $bio);
} else {
$this->theme->display_biography($page, $bio);
}
}
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user, $user_config;
if ($event->page_matches("biography")) {
if ($user->check_auth_token()) {
$user_config->set_string("biography", $_POST['biography']);
$page->flash("Bio Updated");
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(referer_or(make_link()));
}
}
}
}

View File

@ -1,18 +0,0 @@
<?php declare(strict_types=1);
class BiographyTest extends ShimmiePHPUnitTestCase
{
public function testBio()
{
$this->log_in_as_user();
$this->post_page("biography", ["biography"=>"My bio goes here"]);
$this->get_page("user/" . self::$user_name);
$this->assert_text("My bio goes here");
$this->log_in_as_admin();
$this->get_page("user/" . self::$user_name);
$this->assert_text("My bio goes here");
$this->get_page("user/" . self::$admin_name);
$this->assert_no_text("My bio goes here");
}
}

View File

@ -1,25 +0,0 @@
<?php declare(strict_types=1);
use function MicroHTML\TEXTAREA;
class BiographyTheme extends Themelet
{
public function display_biography(Page $page, string $bio)
{
$page->add_block(new Block("About Me", format_text($bio), "main", 30, "about-me"));
}
public function display_composer(Page $page, string $bio)
{
global $user;
$post_url = make_link("biography");
$auth = $user->get_auth_html();
$html = SHM_SIMPLE_FORM(
$post_url,
TEXTAREA(["style"=>"width: 100%", "rows"=>"6", "name"=>"biography"], $bio),
SHM_SUBMIT("Save")
);
$page->add_block(new Block("About Me", (string)$html, "main", 30));
}
}

View File

@ -4,10 +4,10 @@ class BlocksInfo extends ExtensionInfo
{
public const KEY = "blocks";
public string $key = self::KEY;
public string $name = "Generic Blocks";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Add HTML to some space (News, Ads, etc)";
public $key = self::KEY;
public $name = "Generic Blocks";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Add HTML to some space (News, Ads, etc)";
}

View File

@ -3,7 +3,7 @@
class Blocks extends Extension
{
/** @var BlocksTheme */
protected ?Themelet $theme;
protected $theme;
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
{

View File

@ -4,12 +4,12 @@ class BlotterInfo extends ExtensionInfo
{
public const KEY = "blotter";
public string $key = self::KEY;
public string $name = "Blotter";
public string $url = "http://seemslegit.com/";
public array $authors = ["Zach Hall"=>"zach@sosguy.net"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Displays brief updates about whatever you want on every page.
public $key = self::KEY;
public $name = "Blotter";
public $url = "http://seemslegit.com/";
public $authors = ["Zach Hall"=>"zach@sosguy.net"];
public $license = self::LICENSE_GPLV2;
public $description = "Displays brief updates about whatever you want on every page.
Colors and positioning can be configured to match your site's design.
Development TODO at https://github.com/zshall/shimmie2/issues";

View File

@ -3,7 +3,7 @@
class Blotter extends Extension
{
/** @var BlotterTheme */
protected ?Themelet $theme;
protected $theme;
public function onInitExt(InitExtEvent $event)
{
@ -40,10 +40,11 @@ class Blotter extends Extension
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Blotter");
$sb = new SetupBlock("Blotter");
$sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: ");
$sb->add_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) ");
$sb->add_choice_option("blotter_position", ["Top of page" => "subheading", "In navigation bar" => "left"], "<br>Position: ");
$event->panel->add_block($sb);
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)

View File

@ -20,7 +20,7 @@ class BlotterTheme extends Themelet
$page->add_block(new Block("Blotter Entries", $html, "main", 10));
}
public function display_blotter(array $entries): void
public function display_blotter($entries)
{
global $page, $config;
$html = $this->get_html_for_blotter($entries);
@ -28,7 +28,7 @@ class BlotterTheme extends Themelet
$page->add_block(new Block(null, $html, $position, 20));
}
private function get_html_for_blotter_editor(array $entries): string
private function get_html_for_blotter_editor($entries)
{
global $user;
@ -99,7 +99,7 @@ class BlotterTheme extends Themelet
return $html;
}
private function get_html_for_blotter_page(array $entries): string
private function get_html_for_blotter_page($entries)
{
/**
* This one displays a list of all blotter entries.
@ -130,7 +130,7 @@ class BlotterTheme extends Themelet
return $html;
}
private function get_html_for_blotter(array $entries): string
private function get_html_for_blotter($entries)
{
global $config;
$i_color = $config->get_string("blotter_color", "#FF0000");

View File

@ -4,14 +4,14 @@ class BrowserSearchInfo extends ExtensionInfo
{
public const KEY = "browser_search";
public string $key = self::KEY;
public string $name = "Browser Search";
public string $url = "http://atravelinggeek.com/";
public array $authors = ["ATravelingGeek"=>"atg@atravelinggeek.com"];
public string $license = self::LICENSE_GPLV2;
public ?string $version = "0.1c, October 26, 2007";
public string $description = "Allows the user to add a browser 'plugin' to search the site with real-time suggestions";
public ?string $documentation =
public $key = self::KEY;
public $name = "Browser Search";
public $url = "http://atravelinggeek.com/";
public $authors = ["ATravelingGeek"=>"atg@atravelinggeek.com"];
public $license = self::LICENSE_GPLV2;
public $version = "0.1c, October 26, 2007";
public $description = "Allows the user to add a browser 'plugin' to search the site with real-time suggestions";
public $documentation =
"Once installed, users with an opensearch compatible browser should see their search box light up with whatever \"click here to add a search engine\" notification they have
Some code (and lots of help) by Artanis (Erik Youngren <artanis.00@gmail.com>) from the 'tagger' extension - Used with permission";

View File

@ -78,7 +78,8 @@ class BrowserSearch extends Extension
$sort_by['Tag Count'] = 't';
$sort_by['Disabled'] = 'n';
$sb = $event->panel->create_new_block("Browser Search");
$sb = new SetupBlock("Browser Search");
$sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:");
$event->panel->add_block($sb);
}
}

View File

@ -4,10 +4,10 @@ class BulkActionsInfo extends ExtensionInfo
{
public const KEY = "bulk_actions";
public string $key = self::KEY;
public string $name = "Bulk Actions";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public string $description = "Provides query and selection-based bulk action support";
public ?string $documentation = "Provides bulk action section in list view. Allows performing actions against a set of posts based on query or manual selection. Based on Mass Tagger by Christian Walde <walde.christian@googlemail.com>, contributions by Shish and Agasa.";
public $key = self::KEY;
public $name = "Bulk Actions";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Provides query and selection-based bulk action support";
public $documentation = "Provides bulk action section in list view. Allows performing actions against a set of posts based on query or manual selection. Based on Mass Tagger by Christian Walde <walde.christian@googlemail.com>, contributions by Shish and Agasa.";
}

View File

@ -5,8 +5,10 @@ class BulkActionException extends SCoreException
}
class BulkActionBlockBuildingEvent extends Event
{
public array $actions = [];
public array $search_terms = [];
/** @var array */
public $actions = [];
public $search_terms = [];
public function add_action(String $action, string $button_text, string $access_key = null, String $confirmation_message = "", String $block = "", int $position = 40)
{
@ -36,9 +38,12 @@ class BulkActionBlockBuildingEvent extends Event
class BulkActionEvent extends Event
{
public string $action;
public Generator $items;
public bool $redirect = true;
/** @var string */
public $action;
/** @var array */
public $items;
/** @var bool */
public $redirect = true;
public function __construct(String $action, Generator $items)
{
@ -51,7 +56,7 @@ class BulkActionEvent extends Event
class BulkActions extends Extension
{
/** @var BulkActionsTheme */
protected ?Themelet $theme;
protected $theme;
public function onPostListBuilding(PostListBuildingEvent $event)
{
@ -122,8 +127,8 @@ class BulkActions extends Extension
switch ($event->action) {
case "bulk_delete":
if ($user->can(Permissions::DELETE_IMAGE)) {
$i = $this->delete_posts($event->items);
$page->flash("Deleted $i[0] items, totaling ".human_filesize($i[1]));
$i = $this->delete_items($event->items);
$page->flash("Deleted $i items");
}
break;
case "bulk_tag":
@ -222,27 +227,25 @@ class BulkActions extends Extension
return $a["position"] - $b["position"];
}
private function delete_posts(iterable $posts): array
private function delete_items(iterable $items): int
{
global $page;
$total = 0;
$size = 0;
foreach ($posts as $post) {
foreach ($items as $image) {
try {
if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) {
$reason = $_POST['bulk_ban_reason'];
if ($reason) {
send_event(new AddImageHashBanEvent($post->hash, $reason));
send_event(new AddImageHashBanEvent($image->hash, $reason));
}
}
send_event(new ImageDeletionEvent($post));
send_event(new ImageDeletionEvent($image));
$total++;
$size += $post->filesize;
} catch (Exception $e) {
$page->flash("Error while removing {$post->id}: " . $e->getMessage());
$page->flash("Error while removing {$image->id}: " . $e->getMessage());
}
}
return [$total, $size];
return $total;
}
private function tag_items(iterable $items, string $tags, bool $replace): int

View File

@ -8,13 +8,13 @@ function validate_selections(form, confirmationMessage) {
var queryOnly = false;
if(bulk_selector_active) {
var data = get_selected_items();
if(data.length===0) {
if(data.length==0) {
return false;
}
} else {
var query = $(form).find('input[name="bulk_query"]').val();
if (query == null || query === "") {
if (query == null || query == "") {
return false;
} else {
queryOnly = true;
@ -22,7 +22,7 @@ function validate_selections(form, confirmationMessage) {
}
if(confirmationMessage!=null&&confirmationMessage!=="") {
if(confirmationMessage!=null&&confirmationMessage!="") {
return confirm(confirmationMessage);
} else if(queryOnly) {
var action = $(form).find('input[name="submit_button"]').val();
@ -59,7 +59,7 @@ function deactivate_bulk_selector() {
function get_selected_items() {
var data = $('#bulk_selected_ids').val();
if(data===""||data==null) {
if(data==""||data==null) {
data = [];
} else {
data = JSON.parse(data);
@ -97,11 +97,11 @@ function toggle_selection( id ) {
var data = get_selected_items();
if(data.includes(id)) {
data.splice(data.indexOf(id),1);
set_selected_items(data);
set_selected_items(data);
return false;
} else {
data.push(id);
set_selected_items(data);
set_selected_items(data);
return true;
}
}
@ -116,7 +116,7 @@ function select_all() {
items.push(id);
}
);
set_selected_items(items);
set_selected_items(items);
}
function select_invert() {
@ -131,11 +131,11 @@ function select_invert() {
}
}
);
set_selected_items(items);
set_selected_items(items);
}
function select_none() {
set_selected_items([]);
set_selected_items([]);
}
function select_range(start, end) {
@ -145,7 +145,7 @@ function select_range(start, end) {
function ( index, block ) {
block = $(block);
var id = block.data("post-id");
if(id===start)
if(id==start)
selecting = true;
if(selecting) {
@ -153,7 +153,7 @@ function select_range(start, end) {
data.push(id);
}
if(id===end) {
if(id==end) {
selecting = false;
}
}
@ -163,14 +163,14 @@ function select_range(start, end) {
var last_clicked_item;
function add_selector_button($block) {
function add_selector_button($block) {
var c = function(e) {
if(!bulk_selector_active)
return true;
e.preventDefault();
e.stopPropagation();
var id = $block.data("post-id");
if(e.shiftKey) {
if(last_clicked_item<id) {
@ -182,7 +182,7 @@ function add_selector_button($block) {
last_clicked_item = id;
toggle_selection(id);
}
return false;
return false;
};
$block.find("A").click(c);

View File

@ -45,7 +45,7 @@ class BulkActionsTheme extends Themelet
$page->add_block($block);
}
public function render_ban_reason_input(): string
public function render_ban_reason_input()
{
if (class_exists("ImageBan")) {
return "<input type='text' name='bulk_ban_reason' placeholder='Ban reason (leave blank to not ban)' />";
@ -54,13 +54,13 @@ class BulkActionsTheme extends Themelet
}
}
public function render_tag_input(): string
public function render_tag_input()
{
return "<label><input type='checkbox' style='width:13px;' name='bulk_tags_replace' value='true'/>Replace tags</label>" .
"<input type='text' name='bulk_tags' class='autocomplete_tags' required='required' placeholder='Enter tags here' />";
}
public function render_source_input(): string
public function render_source_input()
{
return "<input type='text' name='bulk_source' required='required' placeholder='Enter source here' />";
}

View File

@ -4,13 +4,13 @@ class BulkAddInfo extends ExtensionInfo
{
public const KEY = "bulk_add";
public string $key = self::KEY;
public string $name = "Bulk Add";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Bulk add server-side images";
public ?string $documentation =
public $key = self::KEY;
public $name = "Bulk Add";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Bulk add server-side images";
public $documentation =
"Upload the images into a new directory via ftp or similar, go to
shimmie's admin page and put that directory in the bulk add box.
If there are subdirectories, they get used as tags (eg if you

View File

@ -2,8 +2,8 @@
class BulkAddEvent extends Event
{
public string $dir;
public array $results;
public $dir;
public $results;
public function __construct(string $dir)
{
@ -16,7 +16,7 @@ class BulkAddEvent extends Event
class BulkAdd extends Extension
{
/** @var BulkAddTheme */
protected ?Themelet $theme;
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{

View File

@ -2,7 +2,7 @@
class BulkAddTheme extends Themelet
{
private array $messages = [];
private $messages = [];
/*
* Show a standard page for results to be put into

View File

@ -4,13 +4,13 @@ class BulkAddCSVInfo extends ExtensionInfo
{
public const KEY = "bulk_add_csv";
public string $key = self::KEY;
public string $name = "Bulk Add CSV";
public string $url = self::SHIMMIE_URL;
public array $authors = ["velocity37"=>"velocity37@gmail.com"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Bulk add server-side posts with metadata from CSV file";
public ?string $documentation =
public $key = self::KEY;
public $name = "Bulk Add CSV";
public $url = self::SHIMMIE_URL;
public $authors = ["velocity37"=>"velocity37@gmail.com"];
public $license = self::LICENSE_GPLV2;
public $description = "Bulk add server-side posts with metadata from CSV file";
public $documentation =
"Modification of \"Bulk Add\" by Shish.<br><br>
Adds posts from a CSV with the five following values: <br>
\"/path/to/image.jpg\",\"spaced tags\",\"source\",\"rating s/q/e\",\"/path/thumbnail.jpg\" <br>

View File

@ -3,7 +3,7 @@
class BulkAddCSV extends Extension
{
/** @var BulkAddCSVTheme */
protected ?Themelet $theme;
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{

View File

@ -2,7 +2,7 @@
class BulkAddCSVTheme extends Themelet
{
private array $messages = [];
private $messages = [];
/*
* Show a standard page for results to be put into

View File

@ -5,10 +5,10 @@ class BulkDownloadInfo extends ExtensionInfo
{
public const KEY = "bulk_download";
public string $key = self::KEY;
public string $name = "Bulk Download";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public string $description = "Allows bulk downloading images.";
public array $dependencies = [BulkActionsInfo::KEY];
public $key = self::KEY;
public $name = "Bulk Download";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Allows bulk downloading images.";
public $dependencies = [BulkActionsInfo::KEY];
}

View File

@ -30,11 +30,13 @@ class BulkDownload extends Extension
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Bulk Download");
$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)

View File

@ -2,8 +2,8 @@
class BulkExportEvent extends Event
{
public Image $image;
public array $fields = [];
public $image;
public $fields = [];
public function __construct(Image $image)
{
@ -15,8 +15,8 @@ class BulkExportEvent extends Event
class BulkImportEvent extends Event
{
public Image $image;
public array $fields = [];
public $image;
public $fields = [];
public function __construct(Image $image, $fields)
{

View File

@ -6,10 +6,10 @@ class BulkImportExportInfo extends ExtensionInfo
{
public const KEY = "bulk_import_export";
public string $key = self::KEY;
public string $name = "Bulk Import/Export";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public string $description = "Allows bulk exporting/importing of images and associated data.";
public array $dependencies = [BulkActionsInfo::KEY];
public $key = self::KEY;
public $name = "Bulk Import/Export";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Allows bulk exporting/importing of images and associated data.";
public $dependencies = [BulkActionsInfo::KEY];
}

View File

@ -5,7 +5,7 @@ class BulkImportExport extends DataHandlerExtension
{
const EXPORT_ACTION_NAME = "bulk_export";
const EXPORT_INFO_FILE_NAME = "export.json";
protected array $SUPPORTED_MIME = [MimeType::ZIP];
protected $SUPPORTED_MIME = [MimeType::ZIP];
public function onDataUpload(DataUploadEvent $event)
@ -159,7 +159,7 @@ class BulkImportExport extends DataHandlerExtension
return false;
}
protected function create_thumb(string $hash, string $mime): bool
protected function create_thumb(string $hash, string $type): bool
{
return false;
}

View File

@ -4,12 +4,12 @@ class CommentListInfo extends ExtensionInfo
{
public const KEY = "comment";
public string $key = self::KEY;
public string $name = "Post Comments";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Allow users to make comments on images";
public ?string $documentation = "Formatting is done with the standard formatting API (normally BBCode)";
public bool $core = true;
public $key = self::KEY;
public $name = "Post Comments";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Allow users to make comments on images";
public $documentation = "Formatting is done with the standard formatting API (normally BBCode)";
public $core = true;
}

View File

@ -4,9 +4,12 @@ require_once "vendor/ifixit/php-akismet/akismet.class.php";
class CommentPostingEvent extends Event
{
public int $image_id;
public User $user;
public string $comment;
/** @var int */
public $image_id;
/** @var User */
public $user;
/** @var string */
public $comment;
public function __construct(int $image_id, User $user, string $comment)
{
@ -24,7 +27,8 @@ class CommentPostingEvent extends Event
*/
class CommentDeletionEvent extends Event
{
public int $comment_id;
/** @var int */
public $comment_id;
public function __construct(int $comment_id)
{
@ -39,27 +43,46 @@ class CommentPostingException extends SCoreException
class Comment
{
public ?User $owner;
public int $owner_id;
public string $owner_name;
public ?string $owner_email;
public string $owner_class;
public string $comment;
public int $comment_id;
public int $image_id;
public string $poster_ip;
public string $posted;
/** @var User */
public $owner;
/** @var int */
public $owner_id;
/** @var string */
public $owner_name;
/** @var string */
public $owner_email;
/** @var string */
public $owner_class;
/** @var string */
public $comment;
/** @var int */
public $comment_id;
/** @var int */
public $image_id;
/** @var string */
public $poster_ip;
/** @var string */
public $posted;
public function __construct($row)
{
$this->owner = null;
$this->owner_id = (int)$row['user_id'];
$this->owner_id = $row['user_id'];
$this->owner_name = $row['user_name'];
$this->owner_email = $row['user_email']; // deprecated
$this->owner_class = $row['user_class'];
$this->comment = $row['comment'];
$this->comment_id = (int)$row['comment_id'];
$this->image_id = (int)$row['image_id'];
$this->comment_id = $row['comment_id'];
$this->image_id = $row['image_id'];
$this->poster_ip = $row['poster_ip'];
$this->posted = $row['posted'];
}
@ -86,7 +109,7 @@ class Comment
class CommentList extends Extension
{
/** @var CommentListTheme $theme */
public ?Themelet $theme;
public $theme;
public function onInitExt(InitExtEvent $event)
{
@ -356,7 +379,7 @@ class CommentList extends Extension
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Comment Options");
$sb = new SetupBlock("Comment Options");
$sb->add_bool_option("comment_captcha", "Require CAPTCHA for anonymous comments: ");
$sb->add_label("<br>Limit to ");
$sb->add_int_option("comment_limit");
@ -371,6 +394,7 @@ class CommentList extends Extension
$sb->add_label(" comments per image on the list");
$sb->add_label("<br>Make samefags public ");
$sb->add_bool_option("comment_samefags_public");
$event->panel->add_block($sb);
}
public function onSearchTermParse(SearchTermParseEvent $event)

View File

@ -1,9 +1,9 @@
<?php declare(strict_types=1);
class CommentListTheme extends Themelet
{
private bool $show_anon_id = false;
private int $anon_id = 1;
private array $anon_map = [];
private $show_anon_id = false;
private $anon_id = 1;
private $anon_map = [];
/**
* Display a page with a list of images, and for each image, the image's comments.
@ -86,6 +86,7 @@ class CommentListTheme extends Themelet
}
}
public function display_admin_block()
{
global $page;
@ -103,6 +104,7 @@ class CommentListTheme extends Themelet
$page->add_block(new Block("Mass Comment Delete", $html));
}
/**
* Add some comments to the page, probably in a sidebar.
*
@ -120,6 +122,7 @@ class CommentListTheme extends Themelet
$page->add_block(new Block("Comments", $html, "left", 50, "comment-list-recent"));
}
/**
* Show comments for an image.
*
@ -139,6 +142,7 @@ class CommentListTheme extends Themelet
$page->add_block(new Block("Comments", $html, "main", 30, "comment-list-image"));
}
/**
* Show comments made by a user.
*
@ -283,7 +287,7 @@ class CommentListTheme extends Themelet
';
}
public function get_help_html(): string
public function get_help_html()
{
return '<p>Search for posts containing a certain number of comments, or comments by a particular individual.</p>
<div class="command_example">

View File

@ -5,8 +5,66 @@ abstract class CronUploaderConfig
{
public const DEFAULT_PATH = "cron_uploader";
public const KEY = "cron_uploader_key";
public const DIR = "cron_uploader_dir";
public const USER = "cron_uploader_user";
public const STOP_ON_ERROR = "cron_uploader_stop_on_error";
public const INCLUDE_ALL_LOGS = "cron_uploader_include_all_logs";
public const LOG_LEVEL = "cron_uploader_log_level";
public static function set_defaults(): void
{
global $config;
$config->set_default_string(self::DIR, data_path(self::DEFAULT_PATH));
$config->set_default_bool(self::INCLUDE_ALL_LOGS, false);
$config->set_default_bool(self::STOP_ON_ERROR, false);
$config->set_default_int(self::LOG_LEVEL, SCORE_LOG_INFO);
$upload_key = $config->get_string(self::KEY, "");
if (empty($upload_key)) {
$upload_key = generate_key();
$config->set_string(self::KEY, $upload_key);
}
}
public static function get_user(): int
{
global $config;
return $config->get_int(self::USER);
}
public static function set_user(int $value): void
{
global $config;
$config->set_int(self::USER, $value);
}
public static function get_key(): string
{
global $config;
return $config->get_string(self::KEY);
}
public static function set_key(string $value): void
{
global $config;
$config->set_string(self::KEY, $value);
}
public static function get_dir(): string
{
global $config;
$value = $config->get_string(self::DIR);
if (empty($value)) {
$value = data_path("cron_uploader");
self::set_dir($value);
}
return $value;
}
public static function set_dir(string $value): void
{
global $config;
$config->set_string(self::DIR, $value);
}
}

View File

@ -13,12 +13,12 @@ class CronUploaderInfo extends ExtensionInfo
{
public const KEY = "cron_uploader";
public string $key = self::KEY;
public string $name = "Cron Uploader";
public string $url = self::SHIMMIE_URL;
public array $authors = ["YaoiFox"=>"admin@yaoifox.com", "Matthew Barbour"=>"matthew@darkholme.net"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Uploads images automatically using Cron Jobs";
public $key = self::KEY;
public $name = "Cron Uploader";
public $url = self::SHIMMIE_URL;
public $authors = ["YaoiFox"=>"admin@yaoifox.com", "Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_GPLV2;
public $description = "Uploads images automatically using Cron Jobs";
public function __construct()
{

View File

@ -5,7 +5,7 @@ require_once "config.php";
class CronUploader extends Extension
{
/** @var CronUploaderTheme */
protected ?Themelet $theme;
protected $theme;
public const NAME = "cron_uploader";
@ -15,42 +15,14 @@ class CronUploader extends Extension
const UPLOADED_DIR = "uploaded";
const FAILED_DIR = "failed_to_upload";
private static bool $IMPORT_RUNNING = false;
private static $IMPORT_RUNNING = false;
public function onInitUserConfig(InitUserConfigEvent $event)
public function onInitExt(InitExtEvent $event)
{
$event->user_config->set_default_string(
CronUploaderConfig::DIR,
data_path(CronUploaderConfig::DEFAULT_PATH.DIRECTORY_SEPARATOR.$event->user->name)
);
$event->user_config->set_default_bool(CronUploaderConfig::INCLUDE_ALL_LOGS, false);
$event->user_config->set_default_bool(CronUploaderConfig::STOP_ON_ERROR, false);
$event->user_config->set_default_int(CronUploaderConfig::LOG_LEVEL, SCORE_LOG_INFO);
// Set default values
CronUploaderConfig::set_defaults();
}
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
{
if ($event->user->can(Permissions::CRON_ADMIN)) {
$documentation_link = make_http(make_link("cron_upload"));
$sb = $event->panel->create_new_block("Cron Uploader");
$sb->start_table();
$sb->add_text_option(CronUploaderConfig::DIR, "Root dir", 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.");
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
if ($event->parent=="system") {
@ -67,14 +39,42 @@ class CronUploader extends Extension
global $user;
if ($event->page_matches("cron_upload")) {
if ($event->count_args() == 1 && $event->get_arg(0) =="run") {
$this->process_upload(); // Start upload
} elseif ($user->can(Permissions::CRON_RUN)) {
if ($event->count_args() == 1) {
$this->process_upload($event->get_arg(0)); // Start upload
} elseif ($user->can(Permissions::CRON_ADMIN)) {
$this->display_documentation();
}
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
{
global $database;
$documentation_link = make_http(make_link("cron_upload"));
$users = $database->get_pairs("SELECT name, id FROM users UNION ALL SELECT '', null order by name");
$sb = new SetupBlock("Cron Uploader");
$sb->start_table();
$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.");
$event->panel->add_block($sb);
}
public function onAdminBuilding(AdminBuildingEvent $event)
{
$failed_dir = $this->get_failed_dir();
@ -118,20 +118,19 @@ class CronUploader extends Extension
public function onLog(LogEvent $event)
{
global $user_config;
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 ;
if (self::$IMPORT_RUNNING) {
$all = $user_config->get_bool(CronUploaderConfig::INCLUDE_ALL_LOGS);
if ($event->priority >= $user_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();
echo $output . "\r\n";
flush_output();
$log_path = $this->get_log_file();
file_put_contents($log_path, $output);
}
$log_path = $this->get_log_file();
file_put_contents($log_path, $output);
}
}
@ -194,28 +193,24 @@ class CronUploader extends Extension
private function clear_folder($folder)
{
global $page, $user_config;
$path = join_path($user_config->get_string(CronUploaderConfig::DIR), $folder);
global $page;
$path = join_path(CronUploaderConfig::get_dir(), $folder);
deltree($path);
$page->flash("Cleared $path");
}
private function get_cron_url(): string
private function get_cron_url()
{
global $user_config;
$user_api_key = $user_config->get_string(UserConfig::API_KEY);
return make_http(make_link("/cron_upload/run", "api_key=".urlencode($user_api_key)));
return make_http(make_link("/cron_upload/" . CronUploaderConfig::get_key()));
}
private function get_cron_cmd(): string
private function get_cron_cmd()
{
return "curl --silent " . $this->get_cron_url();
}
private function display_documentation(): void
private function display_documentation()
{
global $database;
@ -261,36 +256,28 @@ class CronUploader extends Extension
);
}
public function get_queue_dir(): string
public function get_queue_dir()
{
global $user_config;
$dir = $user_config->get_string(CronUploaderConfig::DIR);
$dir = CronUploaderConfig::get_dir();
return join_path($dir, self::QUEUE_DIR);
}
public function get_uploaded_dir(): string
public function get_uploaded_dir()
{
global $user_config;
$dir = $user_config->get_string(CronUploaderConfig::DIR);
$dir = CronUploaderConfig::get_dir();
return join_path($dir, self::UPLOADED_DIR);
}
public function get_failed_dir(): string
public function get_failed_dir()
{
global $user_config;
$dir = $user_config->get_string(CronUploaderConfig::DIR);
$dir = CronUploaderConfig::get_dir();
return join_path($dir, self::FAILED_DIR);
}
private function prep_root_dir(): string
{
global $user_config;
// Determine directory (none = default)
$dir = $user_config->get_string(CronUploaderConfig::DIR);
$dir = CronUploaderConfig::get_dir();
// Make the directory if it doesn't exist yet
if (!is_dir($this->get_queue_dir())) {
@ -308,36 +295,35 @@ class CronUploader extends Extension
private function get_lock_file(): string
{
global $user_config;
$root_dir = $user_config->get_string(CronUploaderConfig::DIR);
$root_dir = CronUploaderConfig::get_dir();
return join_path($root_dir, ".lock");
}
/**
* Uploads the image & handles everything
*/
public function process_upload(): bool
public function process_upload(string $key, ?int $upload_count = null): bool
{
global $database, $user, $user_config, $config, $_shm_load_start;
global $database, $config, $_shm_load_start;
$max_time = intval(ini_get('max_execution_time'))*.8;
$this->set_headers();
if (!$config->get_bool(UserConfig::ENABLE_API_KEYS)) {
throw new SCoreException("User API keys are note enabled. Please enable them for the cron upload functionality to work.");
if ($key!=CronUploaderConfig::get_key()) {
throw new SCoreException("Cron upload key incorrect");
}
$user_id = CronUploaderConfig::get_user();
if (empty($user_id)) {
throw new SCoreException("Cron upload user not set");
}
$my_user = User::by_id($user_id);
if ($my_user == null) {
throw new SCoreException("No user found for cron upload user $user_id");
}
if ($user->is_anonymous()) {
throw new SCoreException("User not present. Please specify the api_key for the user to run cron upload as.");
}
$this->log_message(SCORE_LOG_INFO, "Logged in as user {$user->name}");
if (!$user->can(Permissions::CRON_RUN)) {
throw new SCoreException("User does not have permission to run cron upload");
}
send_event(new UserLoginEvent($my_user));
$this->log_message(SCORE_LOG_INFO, "Logged in as user {$my_user->name}");
$lockfile = fopen($this->get_lock_file(), "w");
if (!flock($lockfile, LOCK_EX | LOCK_NB)) {
@ -349,7 +335,7 @@ class CronUploader extends Extension
//set_time_limit(0);
$output_subdir = date('Ymd-His', time());
$image_queue = $this->generate_image_queue($user_config->get_string(CronUploaderConfig::DIR));
$image_queue = $this->generate_image_queue(CronUploaderConfig::get_dir());
// Randomize Images
//shuffle($this->image_queue);
@ -363,9 +349,6 @@ class CronUploader extends Extension
$execution_time = microtime(true) - $_shm_load_start;
if ($execution_time>$max_time) {
break;
} else {
$remaining = $max_time - $execution_time;
$this->log_message(SCORE_LOG_DEBUG, "Max run time remaining: $remaining");
}
try {
$database->begin_transaction();
@ -391,7 +374,7 @@ class CronUploader extends Extension
$failed++;
$this->log_message(SCORE_LOG_ERROR, "(" . gettype($e) . ") " . $e->getMessage());
$this->log_message(SCORE_LOG_ERROR, $e->getTraceAsString());
if ($user_config->get_bool(CronUploaderConfig::STOP_ON_ERROR)) {
if ($config->get_bool(CronUploaderConfig::STOP_ON_ERROR)) {
break;
} else {
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
@ -420,9 +403,7 @@ class CronUploader extends Extension
private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false)
{
global $user_config;
$rootDir = $user_config->get_string(CronUploaderConfig::DIR);
$rootDir = CronUploaderConfig::get_dir();
$rootLength = strlen($rootDir);
if ($rootDir[$rootLength-1]=="/"||$rootDir[$rootLength-1]=="\\") {
$rootLength--;
@ -499,7 +480,7 @@ class CronUploader extends Extension
private const SKIPPABLE_FILES = ['.ds_store','thumbs.db'];
private const SKIPPABLE_DIRECTORIES = ['__macosx'];
private function is_skippable_dir(string $path): bool
private function is_skippable_dir(string $path)
{
$info = pathinfo($path);
@ -510,7 +491,7 @@ class CronUploader extends Extension
return false;
}
private function is_skippable_file(string $path): bool
private function is_skippable_file(string $path)
{
$info = pathinfo($path);
@ -559,11 +540,7 @@ class CronUploader extends Extension
private function get_log_file(): string
{
global $user_config;
$dir = $user_config->get_string(CronUploaderConfig::DIR);
return join_path($dir, "uploads.log");
return join_path(CronUploaderConfig::get_dir(), "uploads.log");
}
private function set_headers(): void

View File

@ -1,7 +0,0 @@
function copyInputToClipboard(inputId) {
// Referenced from https://www.w3schools.com/howto/howto_js_copy_clipboard.asp
let source = document.getElementById(inputId);
source.select();
source.setSelectionRange(0, 99999); /*For mobile devices*/
document.execCommand("copy");
}

View File

@ -1,16 +1,5 @@
<?php declare(strict_types=1);
use function MicroHTML\LABEL;
use function MicroHTML\TABLE;
use function MicroHTML\TBODY;
use function MicroHTML\TFOOT;
use function MicroHTML\TR;
use function MicroHTML\TH;
use function MicroHTML\TD;
use function MicroHTML\INPUT;
use function MicroHTML\rawHTML;
use function MicroHTML\emptyHTML;
class CronUploaderTheme extends Themelet
{
public function display_documentation(
@ -22,18 +11,11 @@ class CronUploaderTheme extends Themelet
string $cron_url,
?array $log_entries
) {
global $page, $config, $user_config;
global $page;
$info_html = "";
$page->set_title("Cron Uploader");
$page->set_heading("Cron Uploader");
if (!$config->get_bool(UserConfig::ENABLE_API_KEYS)) {
$info_html .= "<b style='color:red'>THIS EXTENSION REQUIRES USER API KEYS TO BE ENABLED IN <a href=''>BOARD ADMIN</a></b>";
}
$info_html .= "<b>Information</b>
$info_html = "<b>Information</b>
<br>
<table style='width:470px;'>
" . ($running ? "<tr><td colspan='4'><b style='color:red'>Cron upload is currently running</b></td></tr>" : "") . "
@ -59,13 +41,9 @@ class CronUploaderTheme extends Themelet
<td>{$failed_dirinfo['path']}</td>
</tr></table>
<div>Cron Command: <input type='text' size='60' value='$cron_cmd' id='cron_command'>
<button onclick='copyInputToClipboard(\"cron_command\")'>Copy</button></div>
<div>Create a cron job with the command above.
Read the documentation if you're not sure what to do.</div>
<div>URL: <input type='text' size='60' value='$cron_url' id='cron_url'>
<button onclick='copyInputToClipboard(\"cron_url\")'>Copy</button></div>";
<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br>
Create a cron job with the command above.<br/>
Read the documentation if you're not sure what to do.<br>";
$install_html = "
This cron uploader is fairly easy to use but has to be configured first.
@ -98,10 +76,12 @@ class CronUploaderTheme extends Themelet
<li>If an import is already running, another cannot start until it is done.</li>
<li>Each time it runs it will import for up to ".number_format($max_time)." seconds. This is controlled by the PHP max execution time.</li>
<li>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 ".$user_config->get_string(CronUploaderConfig::DIR).DIRECTORY_SEPARATOR."uploads.log</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>
";
$page->set_title("Cron Uploader");
$page->set_heading("Cron Uploader");
$block = new Block("Cron Uploader", $info_html, "main", 10);
$block_install = new Block("Setup Guide", $install_html, "main", 30);
@ -121,40 +101,6 @@ class CronUploaderTheme extends Themelet
}
}
public function get_user_options(string $dir, bool $stop_on_error, int $log_level, bool $all_logs): string
{
$form = SHM_SIMPLE_FORM(
"user_admin/cron_uploader",
TABLE(
["class"=>"form"],
TBODY(
TR(
TH("Cron Uploader")
),
TR(
TH("Root dir"),
TD(INPUT(["type"=>'text', "name"=>'name', "required"=>true]))
),
TR(
TH(),
TD(
LABEL(INPUT(["type"=>'checkbox', "name"=>'stop_on_error']), "Stop On Error")
)
),
TR(
TH(rawHTML("Repeat&nbsp;Password")),
TD(INPUT(["type"=>'password', "name"=>'pass2', "required"=>true]))
)
),
TFOOT(
TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>"Save Settings"])))
)
)
);
$html = emptyHTML($form);
return (string)$html;
}
public function display_form(array $failed_dirs)
{
global $page;

View File

@ -4,13 +4,13 @@ class CustomHtmlHeadersInfo extends ExtensionInfo
{
public const KEY = "custom_html_headers";
public string $key = self::KEY;
public string $name = "Custom HTML Headers";
public string $url = "http://www.drudexsoftware.com";
public array $authors = ["Drudex Software"=>"support@drudexsoftware.com"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Allows admins to modify & set custom &lt;head&gt; content";
public ?string $documentation =
public $key = self::KEY;
public $name = "Custom HTML Headers";
public $url = "http://www.drudexsoftware.com";
public $authors = ["Drudex Software"=>"support@drudexsoftware.com"];
public $license = self::LICENSE_GPLV2;
public $description = "Allows admins to modify & set custom &lt;head&gt; content";
public $documentation =
"When you go to board config you can find a block named Custom HTML Headers.
In that block you can simply place any thing you can place within &lt;head&gt;&lt;/head&gt;

View File

@ -5,7 +5,7 @@ class CustomHtmlHeaders extends Extension
# Adds setup block for custom <head> content
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Custom HTML Headers");
$sb = new SetupBlock("Custom HTML Headers");
// custom headers
$sb->add_longtext_option(
@ -19,6 +19,8 @@ class CustomHtmlHeaders extends Extension
"as prefix" => "prefix",
"as suffix" => "suffix"
], "<br>Add website name in title");
$event->panel->add_block($sb);
}
public function onInitExt(InitExtEvent $event)

View File

@ -4,11 +4,11 @@ class DanbooruApiInfo extends ExtensionInfo
{
public const KEY = "danbooru_api";
public string $key = self::KEY;
public string $name = "Danbooru Client API";
public array $authors = ["JJS"=>"jsutinen@gmail.com"];
public string $description = "Allow Danbooru apps like Danbooru Uploader for Firefox to communicate with Shimmie";
public ?string $documentation =
public $key = self::KEY;
public $name = "Danbooru Client API";
public $authors = ["JJS"=>"jsutinen@gmail.com"];
public $description = "Allow Danbooru apps like Danbooru Uploader for Firefox to communicate with Shimmie";
public $documentation =
"<p>Notes:
<br>danbooru API based on documentation from danbooru 1.0 -
http://attachr.com/7569

View File

@ -2,19 +2,19 @@
use \MicroHTML\HTMLElement;
function TAGS(...$args): HTMLElement
function TAGS(...$args)
{
return new HTMLElement("tags", $args);
}
function TAG(...$args): HTMLElement
function TAG(...$args)
{
return new HTMLElement("tag", $args);
}
function POSTS(...$args): HTMLElement
function POSTS(...$args)
{
return new HTMLElement("posts", $args);
}
function POST(...$args): HTMLElement
function POST(...$args)
{
return new HTMLElement("post", $args);
}
@ -114,14 +114,11 @@ class DanbooruApi extends Extension
}
}
// Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags
/*
elseif (isset($_GET['tags'])) {
elseif (false && isset($_GET['tags'])) {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$tags = Tag::explode($_GET['tags']);
assert(!is_null($start) && !is_null($tags));
}
*/
else {
} else {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE count > 0 AND id >= :id ORDER BY id DESC",

View File

@ -2,12 +2,12 @@
class ImageDownloadingEvent extends Event
{
public Image $image;
public string $mime;
public string $path;
public bool $file_modified = false;
public $image;
public $mime;
public $path;
public $file_modified = false;
public function __construct(Image $image, string $path, string $mime)
public function __construct(Image $image, String $path, string $mime)
{
parent::__construct();
$this->image = $image;

View File

@ -4,11 +4,11 @@ class DownloadInfo extends ExtensionInfo
{
public const KEY = "download";
public string $key = self::KEY;
public string $name = "Download";
public array $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public string $license = self::LICENSE_WTFPL;
public string $description = "System-wide download functions";
public bool $core = true;
public string $visibility = self::VISIBLE_HIDDEN;
public $key = self::KEY;
public $name = "Download";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "System-wide download functions";
public $core = true;
public $visibility = self::VISIBLE_HIDDEN;
}

View File

@ -4,13 +4,13 @@ class DowntimeInfo extends ExtensionInfo
{
public const KEY = "downtime";
public string $key = self::KEY;
public string $name = "Downtime";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Show a \"down for maintenance\" page";
public ?string $documentation =
public $key = self::KEY;
public $name = "Downtime";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Show a \"down for maintenance\" page";
public $documentation =
"Once installed there will be some more options on the config page --
Ticking \"disable non-admin access\" will mean that regular and anonymous
users will be blocked from accessing the site, only able to view the

View File

@ -3,7 +3,7 @@
class Downtime extends Extension
{
/** @var DowntimeTheme */
protected ?Themelet $theme;
protected $theme;
public function get_priority(): int
{
@ -12,9 +12,10 @@ class Downtime extends Extension
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Downtime");
$sb = new SetupBlock("Downtime");
$sb->add_bool_option("downtime", "Disable non-admin access: ");
$sb->add_longtext_option("downtime_message", "<br>");
$event->panel->add_block($sb);
}
public function onPageRequest(PageRequestEvent $event)
@ -35,7 +36,7 @@ class Downtime extends Extension
}
}
private function is_safe_page(PageRequestEvent $event): bool
private function is_safe_page(PageRequestEvent $event)
{
if ($event->page_matches("user_admin/login")) {
return true;

View File

@ -4,14 +4,14 @@ class EmoticonsInfo extends ExtensionInfo
{
public const KEY = "emoticons";
public string $key = self::KEY;
public string $name = "Emoticon Filter";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public array $dependencies = [EmoticonListInfo::KEY];
public string $description = "Lets users use graphical smilies";
public ?string $documentation =
public $key = self::KEY;
public $name = "Emoticon Filter";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $dependencies = [EmoticonListInfo::KEY];
public $description = "Lets users use graphical smilies";
public $documentation =
"This extension will turn colon-something-colon into a link
to an image with that something as the name, eg :smile:
becomes a link to smile.gif

View File

@ -4,12 +4,12 @@ class EmoticonListInfo extends ExtensionInfo
{
public const KEY = "emoticons_list";
public string $key = self::KEY;
public string $name = "Emoticon List";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Lists available graphical smilies";
public $key = self::KEY;
public $name = "Emoticon List";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Lists available graphical smilies";
public string $visibility = self::VISIBLE_HIDDEN;
public $visibility = self::VISIBLE_HIDDEN;
}

View File

@ -6,7 +6,7 @@
class EmoticonList extends Extension
{
/** @var EmoticonListTheme */
protected ?Themelet $theme;
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{

View File

@ -4,10 +4,10 @@ class EokmInfo extends ExtensionInfo
{
public const KEY = "eokm";
public string $key = self::KEY;
public string $name = "EOKM Filter";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public string $description = "Check uploads against the EOKM blocklist";
public $key = self::KEY;
public $name = "EOKM Filter";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Check uploads against the EOKM blocklist";
}

View File

@ -25,7 +25,6 @@ class Eokm extends Extension
$return = curl_exec($ch);
curl_close($ch);
/** @noinspection PhpStatementHasEmptyBodyInspection */
if ($return == "false") {
// all ok
} elseif ($return == "true") {
@ -39,11 +38,13 @@ class Eokm extends Extension
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("EOKM Filter");
$sb = new SetupBlock("EOKM Filter");
$sb->start_table();
$sb->add_text_option("eokm_username", "Username", true);
$sb->add_text_option("eokm_password", "Password", true);
$sb->end_table();
$event->panel->add_block($sb);
}
}

View File

@ -4,14 +4,14 @@ class ETInfo extends ExtensionInfo
{
public const KEY = "et";
public string $key = self::KEY;
public string $name = "System Info";
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public string $license = self::LICENSE_GPLV2;
public bool $core = true;
public string $description = "Show various bits of system information";
public ?string $documentation =
public $key = self::KEY;
public $name = "System Info";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $core = true;
public $description = "Show various bits of system information";
public $documentation =
"Knowing the information that this extension shows can be very useful for debugging. There's also an option to send
your stats to my database, so I can get some idea of how shimmie is used, which servers I need to support, which
versions of PHP I should test with, etc.";

View File

@ -3,7 +3,7 @@
class ET extends Extension
{
/** @var ETTheme */
protected ?Themelet $theme;
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{
@ -116,7 +116,7 @@ class ET extends Extension
return $info;
}
private function to_yaml(array $info): string
private function to_yaml($info)
{
$data = "";
foreach ($info as $title => $section) {

View File

@ -22,7 +22,7 @@ class ETTheme extends Themelet
$page->add_block(new Block("Information:", $this->build_data_form($yaml)));
}
protected function build_data_form($yaml): string
protected function build_data_form($yaml)
{
return (string)FORM(
["action"=>"https://shimmie.shishnet.org/register.php", "method"=>"POST"],

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