forked from Cavemanon/cavepaintings
Merge and integrate upstream fixes
This commit is contained in:
parent
f2ee60fac5
commit
1257d87e51
|
@ -0,0 +1,26 @@
|
|||
name: Master to Main
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: Tests
|
||||
branches: master
|
||||
types: completed
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
merge-master-to-main:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set Git config
|
||||
run: |
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "Github Actions"
|
||||
- name: Merge master back to dev
|
||||
run: |
|
||||
git fetch --unshallow
|
||||
git checkout main
|
||||
git pull
|
||||
git merge --ff origin/master -m "Auto-merge master to main"
|
||||
git push
|
|
@ -3,22 +3,26 @@ name: Publish
|
|||
on:
|
||||
workflow_run:
|
||||
workflows: Tests
|
||||
branches: master
|
||||
branches: main
|
||||
types: completed
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Publish to Registry
|
||||
uses: elgohr/Publish-Docker-Github-Action@master
|
||||
uses: elgohr/Publish-Docker-Github-Action@main
|
||||
with:
|
||||
name: shish2k/shimmie2
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
cache: ${{ github.event_name != 'schedule' }}
|
||||
buildoptions: "--build-arg RUN_TESTS=false"
|
||||
tag_semver: true
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
imports:
|
||||
- javascript
|
||||
- php
|
||||
|
||||
filter:
|
||||
excluded_paths: [ext/*/lib/*,ext/tagger/script.js,tests/*]
|
||||
|
||||
build:
|
||||
image: default-bionic
|
||||
nodes:
|
||||
analysis:
|
||||
tests:
|
||||
before:
|
||||
- mkdir -p data/config
|
||||
- cp tests/defines.php data/config/shimmie.conf.php
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
|
||||
tools:
|
||||
external_code_coverage: true
|
42
Dockerfile
42
Dockerfile
|
@ -1,10 +1,20 @@
|
|||
ARG PHP_VERSION=8.2
|
||||
|
||||
# Install base packages which all stages (build, test, run) need
|
||||
FROM debian:bookworm AS base
|
||||
RUN apt update && apt upgrade -y && apt install -y \
|
||||
php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \
|
||||
php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 \
|
||||
gosu curl imagemagick ffmpeg zip unzip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Composer has 100MB of dependencies, and we only need that during build and test
|
||||
FROM base AS composer
|
||||
RUN apt update && apt upgrade -y && apt install -y composer php${PHP_VERSION}-xdebug && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# "Build" shimmie (composer install - done in its own stage so that we don't
|
||||
# need to include all the composer fluff in the final image)
|
||||
FROM debian:unstable AS app
|
||||
RUN apt update && apt upgrade -y
|
||||
RUN apt install -y composer php${PHP_VERSION}-gd php${PHP_VERSION}-xml php${PHP_VERSION}-sqlite3 php${PHP_VERSION}-xdebug imagemagick
|
||||
FROM composer AS app
|
||||
COPY composer.json composer.lock /app/
|
||||
WORKDIR /app
|
||||
RUN composer install --no-dev
|
||||
|
@ -13,9 +23,7 @@ COPY . /app/
|
|||
# Tests in their own image. Really we should inherit from app and then
|
||||
# `composer install` phpunit on top of that; but for some reason
|
||||
# `composer install --no-dev && composer install` doesn't install dev
|
||||
FROM debian:unstable AS tests
|
||||
RUN apt update && apt upgrade -y
|
||||
RUN apt install -y composer php${PHP_VERSION}-gd php${PHP_VERSION}-xml php${PHP_VERSION}-sqlite3 php${PHP_VERSION}-xdebug imagemagick
|
||||
FROM composer AS tests
|
||||
COPY composer.json composer.lock /app/
|
||||
WORKDIR /app
|
||||
RUN composer install
|
||||
|
@ -25,31 +33,17 @@ RUN [ $RUN_TESTS = false ] || (\
|
|||
echo '=== Installing ===' && mkdir -p data/config && INSTALL_DSN="sqlite:data/shimmie.sqlite" php index.php && \
|
||||
echo '=== Smoke Test ===' && php index.php get-page /post/list && \
|
||||
echo '=== Unit Tests ===' && ./vendor/bin/phpunit --configuration tests/phpunit.xml && \
|
||||
echo '=== Coverage ===' && ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \
|
||||
echo '=== Coverage ===' && XDEBUG_MODE=coverage ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \
|
||||
echo '=== Cleaning ===' && rm -rf data)
|
||||
|
||||
# Build su-exec so that our final image can be nicer
|
||||
FROM debian:unstable AS suexec
|
||||
RUN apt update && apt upgrade -y
|
||||
RUN apt install -y --no-install-recommends gcc libc-dev curl
|
||||
RUN curl -k -o /usr/local/bin/su-exec.c https://raw.githubusercontent.com/ncopa/su-exec/master/su-exec.c; \
|
||||
gcc -Wall /usr/local/bin/su-exec.c -o/usr/local/bin/su-exec; \
|
||||
chown root:root /usr/local/bin/su-exec; \
|
||||
chmod 0755 /usr/local/bin/su-exec;
|
||||
|
||||
# Actually run shimmie
|
||||
FROM debian:unstable
|
||||
FROM base
|
||||
EXPOSE 8000
|
||||
HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1
|
||||
ENV UID=1000 \
|
||||
GID=1000
|
||||
RUN apt update && apt upgrade -y && apt install -y \
|
||||
php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \
|
||||
php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 \
|
||||
curl imagemagick ffmpeg zip unzip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
GID=1000 \
|
||||
UPLOAD_MAX_FILESIZE=50M
|
||||
COPY --from=app /app /app
|
||||
COPY --from=suexec /usr/local/bin/su-exec /usr/local/bin/su-exec
|
||||
|
||||
WORKDIR /app
|
||||
CMD ["/bin/sh", "/app/tests/docker-init.sh"]
|
||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
require_once "core/event.php";
|
||||
|
||||
enum PageMode: string
|
||||
|
@ -80,6 +82,12 @@ class BasePage
|
|||
*/
|
||||
public function set_filename(string $filename, string $disposition = "attachment"): void
|
||||
{
|
||||
$max_len = 250;
|
||||
if(strlen($filename) > $max_len) {
|
||||
// remove extension, truncate filename, apply extension
|
||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
$filename = substr($filename, 0, $max_len - strlen($ext) - 1) . '.' . $ext;
|
||||
}
|
||||
$this->filename = $filename;
|
||||
$this->disposition = $disposition;
|
||||
}
|
||||
|
@ -568,7 +576,7 @@ EOD;
|
|||
<a href=\"https://code.shishnet.org/shimmie2/\">Shimmie</a> ©
|
||||
<a href=\"https://www.shishnet.org/\">Shish</a> &
|
||||
<a href=\"https://github.com/shish/shimmie2/graphs/contributors\">The Team</a>
|
||||
2007-2020,
|
||||
2007-2023,
|
||||
based on the Danbooru concept.
|
||||
$debug
|
||||
$contact
|
||||
|
@ -598,7 +606,7 @@ class PageSubNavBuildingEvent extends Event
|
|||
$this->parent= $parent;
|
||||
}
|
||||
|
||||
public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
|
||||
public function add_nav_link(string $name, Link $link, string|HTMLElement $desc, ?bool $active = null, int $order = 50)
|
||||
{
|
||||
$this->links[] = new NavLink($name, $link, $desc, $active, $order);
|
||||
}
|
||||
|
@ -608,11 +616,11 @@ class NavLink
|
|||
{
|
||||
public string $name;
|
||||
public Link $link;
|
||||
public string $description;
|
||||
public string|HTMLElement $description;
|
||||
public int $order;
|
||||
public bool $active = false;
|
||||
|
||||
public function __construct(String $name, Link $link, String $description, ?bool $active = null, int $order = 50)
|
||||
public function __construct(string $name, Link $link, string|HTMLElement $description, ?bool $active = null, int $order = 50)
|
||||
{
|
||||
global $config;
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\{A,B,BR,IMG,OPTION,SELECT,emptyHTML};
|
||||
|
||||
/**
|
||||
* Class BaseThemelet
|
||||
*
|
||||
|
@ -46,15 +50,15 @@ class BaseThemelet
|
|||
* Generic thumbnail code; returns HTML rather than adding
|
||||
* a block since thumbs tend to go inside blocks...
|
||||
*/
|
||||
public function build_thumb_html(Image $image): string
|
||||
public function build_thumb_html(Image $image): HTMLElement
|
||||
{
|
||||
global $config;
|
||||
|
||||
$i_id = (int) $image->id;
|
||||
$h_view_link = make_link('post/view/'.$i_id);
|
||||
$h_thumb_link = $image->get_thumb_link();
|
||||
$h_tip = html_escape($image->get_tooltip());
|
||||
$h_tags = html_escape(strtolower($image->get_tag_list()));
|
||||
$id = $image->id;
|
||||
$view_link = make_link('post/view/'.$id);
|
||||
$thumb_link = $image->get_thumb_link();
|
||||
$tip = $image->get_tooltip();
|
||||
$tags = strtolower($image->get_tag_list());
|
||||
|
||||
// TODO: Set up a function for fetching what kind of files are currently thumbnailable
|
||||
$mimeArr = array_flip([MimeType::MP3]); //List of thumbless filetypes
|
||||
|
@ -75,9 +79,27 @@ 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'>".
|
||||
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>".
|
||||
"</a>\n";
|
||||
return A(
|
||||
[
|
||||
"href"=>$view_link,
|
||||
"class"=>"thumb shm-thumb shm-thumb-link $custom_classes",
|
||||
"data-tags"=>$tags,
|
||||
"data-height"=>$image->height,
|
||||
"data-width"=>$image->width,
|
||||
"data-mime"=>$image->get_mime(),
|
||||
"data-post-id"=>$id,
|
||||
],
|
||||
IMG(
|
||||
[
|
||||
"id"=>"thumb_$id",
|
||||
"title"=>$tip,
|
||||
"alt"=>$tip,
|
||||
"height"=>$tsize[1],
|
||||
"width"=>$tsize[0],
|
||||
"src"=>$thumb_link,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function display_paginator(Page $page, string $base, ?string $query, int $page_number, int $total_pages, bool $show_random = false)
|
||||
|
@ -99,26 +121,34 @@ class BaseThemelet
|
|||
$page->add_html_header("<link rel='last' href='".make_http(make_link($base.'/'.$total_pages, $query))."'>");
|
||||
}
|
||||
|
||||
private function gen_page_link(string $base_url, ?string $query, int $page, string $name): string
|
||||
private function gen_page_link(string $base_url, ?string $query, int $page, string $name): HTMLElement
|
||||
{
|
||||
$link = make_link($base_url.'/'.$page, $query);
|
||||
return '<a href="'.$link.'">'.$name.'</a>';
|
||||
return A(["href"=>make_link($base_url.'/'.$page, $query)], $name);
|
||||
}
|
||||
|
||||
private function gen_page_link_block(string $base_url, ?string $query, int $page, int $current_page, string $name): string
|
||||
private function gen_page_link_block(string $base_url, ?string $query, int $page, int $current_page, string $name): HTMLElement
|
||||
{
|
||||
$paginator = "";
|
||||
$paginator = $this->gen_page_link($base_url, $query, $page, $name);
|
||||
if ($page == $current_page) {
|
||||
$paginator .= "<b>";
|
||||
}
|
||||
$paginator .= $this->gen_page_link($base_url, $query, $page, $name);
|
||||
if ($page == $current_page) {
|
||||
$paginator .= "</b>";
|
||||
$paginator = B($paginator);
|
||||
}
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
private function build_paginator(int $current_page, int $total_pages, string $base_url, ?string $query, bool $show_random): string
|
||||
protected function implode(string|HTMLElement $glue, array $pieces): HTMLElement
|
||||
{
|
||||
$out = emptyHTML();
|
||||
$n = 0;
|
||||
foreach ($pieces as $piece) {
|
||||
if ($n++ > 0) {
|
||||
$out->appendChild($glue);
|
||||
}
|
||||
$out->appendChild($piece);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function build_paginator(int $current_page, int $total_pages, string $base_url, ?string $query, bool $show_random): HTMLElement
|
||||
{
|
||||
$next = $current_page + 1;
|
||||
$prev = $current_page - 1;
|
||||
|
@ -145,9 +175,20 @@ class BaseThemelet
|
|||
foreach (range($start, $end) as $i) {
|
||||
$pages[] = $this->gen_page_link_block($base_url, $query, $i, $current_page, (string)$i);
|
||||
}
|
||||
$pages_html = implode(" | ", $pages);
|
||||
$pages_html = $this->implode(" | ", $pages);
|
||||
|
||||
return $first_html.' | '.$prev_html.' | '.$random_html.' | '.$next_html.' | '.$last_html
|
||||
.'<br><< '.$pages_html.' >>';
|
||||
return emptyHTML(
|
||||
$this->implode(" | ", [
|
||||
$first_html,
|
||||
$prev_html,
|
||||
$random_html,
|
||||
$next_html,
|
||||
$last_html,
|
||||
]),
|
||||
BR(),
|
||||
'<< ',
|
||||
$pages_html,
|
||||
' >>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class Block
|
|||
$this->position = $position;
|
||||
|
||||
if (is_null($id)) {
|
||||
$id = (empty($header) ? md5($body ?? '') : $header) . $section;
|
||||
$id = (empty($header) ? md5($this->body ?? '') : $header) . $section;
|
||||
}
|
||||
$str_id = preg_replace('/[^\w-]/', '', str_replace(' ', '_', $id));
|
||||
assert(is_string($str_id));
|
||||
|
|
|
@ -121,7 +121,8 @@ function loadCache(?string $dsn): CacheInterface
|
|||
], ['prefix' => 'shm:']);
|
||||
$c = new \Naroga\RedisCache\Redis($redis);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if(is_null($c)) {
|
||||
$c = new \Sabre\Cache\Memory();
|
||||
}
|
||||
global $_tracer;
|
||||
|
|
|
@ -43,12 +43,15 @@ class Database
|
|||
$this->dsn = $dsn;
|
||||
}
|
||||
|
||||
private function connect_db(): void
|
||||
private function get_db(): PDO
|
||||
{
|
||||
$this->db = new PDO($this->dsn);
|
||||
$this->connect_engine();
|
||||
$this->get_engine()->init($this->db);
|
||||
$this->begin_transaction();
|
||||
if(is_null($this->db)) {
|
||||
$this->db = new PDO($this->dsn);
|
||||
$this->connect_engine();
|
||||
$this->get_engine()->init($this->db);
|
||||
$this->begin_transaction();
|
||||
}
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
private function connect_engine(): void
|
||||
|
@ -76,7 +79,7 @@ class Database
|
|||
public function begin_transaction(): void
|
||||
{
|
||||
if ($this->is_transaction_open() === false) {
|
||||
$this->db->beginTransaction();
|
||||
$this->get_db()->beginTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +91,7 @@ class Database
|
|||
public function commit(): bool
|
||||
{
|
||||
if ($this->is_transaction_open()) {
|
||||
return $this->db->commit();
|
||||
return $this->get_db()->commit();
|
||||
} else {
|
||||
throw new SCoreException("Unable to call commit() as there is no transaction currently open.");
|
||||
}
|
||||
|
@ -97,7 +100,7 @@ class Database
|
|||
public function rollback(): bool
|
||||
{
|
||||
if ($this->is_transaction_open()) {
|
||||
return $this->db->rollback();
|
||||
return $this->get_db()->rollback();
|
||||
} else {
|
||||
throw new SCoreException("Unable to call rollback() as there is no transaction currently open.");
|
||||
}
|
||||
|
@ -123,7 +126,7 @@ class Database
|
|||
|
||||
public function get_version(): string
|
||||
{
|
||||
return $this->get_engine()->get_version($this->db);
|
||||
return $this->get_engine()->get_version($this->get_db());
|
||||
}
|
||||
|
||||
private function count_time(string $method, float $start, string $query, ?array $args): void
|
||||
|
@ -144,21 +147,18 @@ class Database
|
|||
|
||||
public function set_timeout(?int $time): void
|
||||
{
|
||||
$this->get_engine()->set_timeout($this->db, $time);
|
||||
$this->get_engine()->set_timeout($this->get_db(), $time);
|
||||
}
|
||||
|
||||
public function notify(string $channel, ?string $data=null): void
|
||||
{
|
||||
$this->get_engine()->notify($this->db, $channel, $data);
|
||||
$this->get_engine()->notify($this->get_db(), $channel, $data);
|
||||
}
|
||||
|
||||
public function execute(string $query, array $args = []): PDOStatement
|
||||
public function _execute(string $query, array $args = []): PDOStatement
|
||||
{
|
||||
try {
|
||||
if (is_null($this->db)) {
|
||||
$this->connect_db();
|
||||
}
|
||||
$ret = $this->db->execute(
|
||||
$ret = $this->get_db()->execute(
|
||||
"-- " . str_replace("%2F", "/", urlencode($_GET['q'] ?? '')). "\n" .
|
||||
$query,
|
||||
$args
|
||||
|
@ -173,13 +173,24 @@ class Database
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an SQL query with no return
|
||||
*/
|
||||
public function execute(string $query, array $args = []): PDOStatement
|
||||
{
|
||||
$_start = ftime();
|
||||
$st = $this->_execute($query, $args);
|
||||
$this->count_time("execute", $_start, $query, $args);
|
||||
return $st;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an SQL query and return a 2D array.
|
||||
*/
|
||||
public function get_all(string $query, array $args = []): array
|
||||
{
|
||||
$_start = ftime();
|
||||
$data = $this->execute($query, $args)->fetchAll();
|
||||
$data = $this->_execute($query, $args)->fetchAll();
|
||||
$this->count_time("get_all", $_start, $query, $args);
|
||||
return $data;
|
||||
}
|
||||
|
@ -190,7 +201,7 @@ class Database
|
|||
public function get_all_iterable(string $query, array $args = []): PDOStatement
|
||||
{
|
||||
$_start = ftime();
|
||||
$data = $this->execute($query, $args);
|
||||
$data = $this->_execute($query, $args);
|
||||
$this->count_time("get_all_iterable", $_start, $query, $args);
|
||||
return $data;
|
||||
}
|
||||
|
@ -201,7 +212,7 @@ class Database
|
|||
public function get_row(string $query, array $args = []): ?array
|
||||
{
|
||||
$_start = ftime();
|
||||
$row = $this->execute($query, $args)->fetch();
|
||||
$row = $this->_execute($query, $args)->fetch();
|
||||
$this->count_time("get_row", $_start, $query, $args);
|
||||
return $row ? $row : null;
|
||||
}
|
||||
|
@ -212,7 +223,7 @@ class Database
|
|||
public function get_col(string $query, array $args = []): array
|
||||
{
|
||||
$_start = ftime();
|
||||
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_COLUMN);
|
||||
$res = $this->_execute($query, $args)->fetchAll(PDO::FETCH_COLUMN);
|
||||
$this->count_time("get_col", $_start, $query, $args);
|
||||
return $res;
|
||||
}
|
||||
|
@ -223,7 +234,7 @@ class Database
|
|||
public function get_col_iterable(string $query, array $args = []): \Generator
|
||||
{
|
||||
$_start = ftime();
|
||||
$stmt = $this->execute($query, $args);
|
||||
$stmt = $this->_execute($query, $args);
|
||||
$this->count_time("get_col_iterable", $_start, $query, $args);
|
||||
foreach ($stmt as $row) {
|
||||
yield $row[0];
|
||||
|
@ -236,7 +247,7 @@ class Database
|
|||
public function get_pairs(string $query, array $args = []): array
|
||||
{
|
||||
$_start = ftime();
|
||||
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$res = $this->_execute($query, $args)->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$this->count_time("get_pairs", $_start, $query, $args);
|
||||
return $res;
|
||||
}
|
||||
|
@ -248,7 +259,7 @@ class Database
|
|||
public function get_pairs_iterable(string $query, array $args = []): \Generator
|
||||
{
|
||||
$_start = ftime();
|
||||
$stmt = $this->execute($query, $args);
|
||||
$stmt = $this->_execute($query, $args);
|
||||
$this->count_time("get_pairs_iterable", $_start, $query, $args);
|
||||
foreach ($stmt as $row) {
|
||||
yield $row[0] => $row[1];
|
||||
|
@ -261,7 +272,7 @@ class Database
|
|||
public function get_one(string $query, array $args = [])
|
||||
{
|
||||
$_start = ftime();
|
||||
$row = $this->execute($query, $args)->fetch();
|
||||
$row = $this->_execute($query, $args)->fetch();
|
||||
$this->count_time("get_one", $_start, $query, $args);
|
||||
return $row ? $row[0] : null;
|
||||
}
|
||||
|
@ -272,7 +283,7 @@ class Database
|
|||
public function exists(string $query, array $args = []): bool
|
||||
{
|
||||
$_start = ftime();
|
||||
$row = $this->execute($query, $args)->fetch();
|
||||
$row = $this->_execute($query, $args)->fetch();
|
||||
$this->count_time("exists", $_start, $query, $args);
|
||||
if ($row==null) {
|
||||
return false;
|
||||
|
@ -286,9 +297,9 @@ class Database
|
|||
public function get_last_insert_id(string $seq): int
|
||||
{
|
||||
if ($this->get_engine()->id == DatabaseDriverID::PGSQL) {
|
||||
$id = $this->db->lastInsertId($seq);
|
||||
$id = $this->get_db()->lastInsertId($seq);
|
||||
} else {
|
||||
$id = $this->db->lastInsertId();
|
||||
$id = $this->get_db()->lastInsertId();
|
||||
}
|
||||
assert(is_numeric($id));
|
||||
return (int)$id;
|
||||
|
@ -313,10 +324,6 @@ class Database
|
|||
*/
|
||||
public function count_tables(): int
|
||||
{
|
||||
if (is_null($this->db) || is_null($this->engine)) {
|
||||
$this->connect_db();
|
||||
}
|
||||
|
||||
if ($this->get_engine()->id === DatabaseDriverID::MYSQL) {
|
||||
return count(
|
||||
$this->get_all("SHOW TABLES")
|
||||
|
@ -336,10 +343,7 @@ class Database
|
|||
|
||||
public function raw_db(): PDO
|
||||
{
|
||||
if (is_null($this->db)) {
|
||||
$this->connect_db();
|
||||
}
|
||||
return $this->db;
|
||||
return $this->get_db();
|
||||
}
|
||||
|
||||
public function standardise_boolean(string $table, string $column, bool $include_postgres=false): void
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Shimmie2;
|
|||
abstract class Extension
|
||||
{
|
||||
public string $key;
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
public ExtensionInfo $info;
|
||||
|
||||
private static array $enabled_extensions = [];
|
||||
|
@ -35,7 +35,7 @@ abstract class Extension
|
|||
/**
|
||||
* Find the theme object for a given extension.
|
||||
*/
|
||||
private function get_theme_object(string $base): ?Themelet
|
||||
private function get_theme_object(string $base): Themelet
|
||||
{
|
||||
$base = str_replace("Shimmie2\\", "", $base);
|
||||
$custom = "Shimmie2\Custom{$base}Theme";
|
||||
|
@ -46,7 +46,7 @@ abstract class Extension
|
|||
} elseif (class_exists($normal)) {
|
||||
return new $normal();
|
||||
} else {
|
||||
return null;
|
||||
return new Themelet();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ class Image
|
|||
if ($start < 0) {
|
||||
$start = 0;
|
||||
}
|
||||
if ($limit!=null && $limit < 1) {
|
||||
if ($limit !== null && $limit < 1) {
|
||||
$limit = 1;
|
||||
}
|
||||
|
||||
|
@ -166,11 +166,11 @@ class Image
|
|||
/**
|
||||
* Search for an array of images
|
||||
*
|
||||
* @param String[] $tags
|
||||
* @param string[] $tags
|
||||
* @return Image[]
|
||||
*/
|
||||
#[Query(name: "posts", type: "[Post!]!", args: ["tags" => "[string!]"])]
|
||||
public static function find_images(?int $offset = 0, ?int $limit = null, array $tags=[]): array
|
||||
public static function find_images(int $offset = 0, ?int $limit = null, array $tags=[]): array
|
||||
{
|
||||
$result = self::find_images_internal($offset, $limit, $tags);
|
||||
|
||||
|
@ -586,7 +586,7 @@ class Image
|
|||
{
|
||||
$this->mime = $mime;
|
||||
$ext = FileExtension::get_for_mime($this->get_mime());
|
||||
assert($ext != null);
|
||||
assert($ext !== null);
|
||||
$this->ext = $ext;
|
||||
}
|
||||
|
||||
|
@ -640,27 +640,15 @@ class Image
|
|||
public function delete_tags_from_image(): void
|
||||
{
|
||||
global $database;
|
||||
if ($database->get_driver_id() == DatabaseDriverID::MYSQL) {
|
||||
//mysql < 5.6 has terrible subquery optimization, using EXISTS / JOIN fixes this
|
||||
$database->execute(
|
||||
"
|
||||
UPDATE tags t
|
||||
INNER JOIN image_tags it ON t.id = it.tag_id
|
||||
SET count = count - 1
|
||||
WHERE it.image_id = :id",
|
||||
["id"=>$this->id]
|
||||
);
|
||||
} else {
|
||||
$database->execute("
|
||||
UPDATE tags
|
||||
SET count = count - 1
|
||||
WHERE id IN (
|
||||
SELECT tag_id
|
||||
FROM image_tags
|
||||
WHERE image_id = :id
|
||||
)
|
||||
", ["id"=>$this->id]);
|
||||
}
|
||||
$database->execute("
|
||||
UPDATE tags
|
||||
SET count = count - 1
|
||||
WHERE id IN (
|
||||
SELECT tag_id
|
||||
FROM image_tags
|
||||
WHERE image_id = :id
|
||||
)
|
||||
", ["id"=>$this->id]);
|
||||
$database->execute("
|
||||
DELETE
|
||||
FROM image_tags
|
||||
|
@ -695,55 +683,23 @@ class Image
|
|||
throw new SCoreException('Tried to set zero tags');
|
||||
}
|
||||
|
||||
if (Tag::implode($tags) != $this->get_tag_list()) {
|
||||
if (strtolower(Tag::implode($tags)) != strtolower($this->get_tag_list())) {
|
||||
// delete old
|
||||
$this->delete_tags_from_image();
|
||||
|
||||
$written_tags = [];
|
||||
|
||||
// insert each new tags
|
||||
foreach ($tags as $tag) {
|
||||
$id = $database->get_one(
|
||||
"
|
||||
SELECT id
|
||||
FROM tags
|
||||
WHERE LOWER(tag) = LOWER(:tag)
|
||||
",
|
||||
["tag"=>$tag]
|
||||
);
|
||||
if (empty($id)) {
|
||||
// a new tag
|
||||
$database->execute(
|
||||
"INSERT INTO tags(tag) VALUES (:tag)",
|
||||
["tag"=>$tag]
|
||||
);
|
||||
$database->execute(
|
||||
"INSERT INTO image_tags(image_id, tag_id)
|
||||
VALUES(:id, (SELECT id FROM tags WHERE LOWER(tag) = LOWER(:tag)))",
|
||||
["id"=>$this->id, "tag"=>$tag]
|
||||
);
|
||||
} else {
|
||||
// check if tag has already been written
|
||||
if (in_array($id, $written_tags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$database->execute("
|
||||
INSERT INTO image_tags(image_id, tag_id)
|
||||
VALUES(:iid, :tid)
|
||||
", ["iid"=>$this->id, "tid"=>$id]);
|
||||
|
||||
$written_tags[] = $id;
|
||||
}
|
||||
$database->execute(
|
||||
"
|
||||
UPDATE tags
|
||||
SET count = count + 1
|
||||
WHERE LOWER(tag) = LOWER(:tag)
|
||||
",
|
||||
["tag"=>$tag]
|
||||
);
|
||||
}
|
||||
$ids = array_map(fn ($tag) => Tag::get_or_create_id($tag), $tags);
|
||||
$values = implode(", ", array_map(fn ($id) => "({$this->id}, $id)", $ids));
|
||||
$database->execute("INSERT INTO image_tags(image_id, tag_id) VALUES $values");
|
||||
$database->execute("
|
||||
UPDATE tags
|
||||
SET count = count + 1
|
||||
WHERE id IN (
|
||||
SELECT tag_id
|
||||
FROM image_tags
|
||||
WHERE image_id = :id
|
||||
)
|
||||
", ["id"=>$this->id]);
|
||||
|
||||
log_info("core_image", "Tags for Post #{$this->id} set to: ".Tag::implode($tags));
|
||||
$cache->delete("image-{$this->id}-tags");
|
||||
|
|
|
@ -196,7 +196,7 @@ function redirect_to_next_image(Image $image): void
|
|||
|
||||
$target_image = $image->get_next($search_terms);
|
||||
|
||||
if ($target_image == null) {
|
||||
if ($target_image === null) {
|
||||
$redirect_target = referer_or(make_link("post/list"), ['post/view']);
|
||||
} else {
|
||||
$redirect_target = make_link("post/view/{$target_image->id}", null, $query);
|
||||
|
|
|
@ -90,12 +90,42 @@ class TagUsage
|
|||
*/
|
||||
class Tag
|
||||
{
|
||||
private static $tag_id_cache = [];
|
||||
|
||||
public static function get_or_create_id(string $tag): int
|
||||
{
|
||||
global $database;
|
||||
|
||||
// don't cache in unit tests, because the test suite doesn't
|
||||
// reset static variables but it does reset the database
|
||||
if (!defined("UNITTEST") && array_key_exists($tag, self::$tag_id_cache)) {
|
||||
return self::$tag_id_cache[$tag];
|
||||
}
|
||||
|
||||
$id = $database->get_one(
|
||||
"SELECT id FROM tags WHERE LOWER(tag) = LOWER(:tag)",
|
||||
["tag"=>$tag]
|
||||
);
|
||||
if (empty($id)) {
|
||||
// a new tag
|
||||
$database->execute(
|
||||
"INSERT INTO tags(tag) VALUES (:tag)",
|
||||
["tag"=>$tag]
|
||||
);
|
||||
$id = $database->get_one(
|
||||
"SELECT id FROM tags WHERE LOWER(tag) = LOWER(:tag)",
|
||||
["tag"=>$tag]
|
||||
);
|
||||
}
|
||||
|
||||
self::$tag_id_cache[$tag] = $id;
|
||||
return $id;
|
||||
}
|
||||
|
||||
public static function implode(array $tags): string
|
||||
{
|
||||
sort($tags);
|
||||
$tags = implode(' ', $tags);
|
||||
|
||||
return $tags;
|
||||
sort($tags, SORT_FLAG_CASE|SORT_STRING);
|
||||
return implode(' ', $tags);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
use function MicroHTML\A;
|
||||
use function MicroHTML\FORM;
|
||||
use function MicroHTML\INPUT;
|
||||
use function MicroHTML\DIV;
|
||||
use function MicroHTML\OPTION;
|
||||
use function MicroHTML\PRE;
|
||||
use function MicroHTML\P;
|
||||
use function MicroHTML\SELECT;
|
||||
use function MicroHTML\TABLE;
|
||||
use function MicroHTML\THEAD;
|
||||
use function MicroHTML\TFOOT;
|
||||
use function MicroHTML\TR;
|
||||
use function MicroHTML\TH;
|
||||
use function MicroHTML\TD;
|
||||
|
||||
function SHM_FORM(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit="", string $name=""): HTMLElement
|
||||
{
|
||||
global $user;
|
||||
|
||||
$attrs = [
|
||||
"action"=>make_link($target),
|
||||
"method"=>$method
|
||||
];
|
||||
|
||||
if ($form_id) {
|
||||
$attrs["id"] = $form_id;
|
||||
}
|
||||
if ($multipart) {
|
||||
$attrs["enctype"] = 'multipart/form-data';
|
||||
}
|
||||
if ($onsubmit) {
|
||||
$attrs["onsubmit"] = $onsubmit;
|
||||
}
|
||||
if ($name) {
|
||||
$attrs["name"] = $name;
|
||||
}
|
||||
return FORM(
|
||||
$attrs,
|
||||
INPUT(["type"=>"hidden", "name"=>"q", "value"=>$target]),
|
||||
$method == "GET" ? "" : $user->get_auth_microhtml()
|
||||
);
|
||||
}
|
||||
|
||||
function SHM_SIMPLE_FORM($target, ...$children): HTMLElement
|
||||
{
|
||||
$form = SHM_FORM($target);
|
||||
$form->appendChild(emptyHTML(...$children));
|
||||
return $form;
|
||||
}
|
||||
|
||||
function SHM_SUBMIT(string $text, array $args=[]): HTMLElement
|
||||
{
|
||||
$args["type"] = "submit";
|
||||
$args["value"] = $text;
|
||||
return INPUT($args);
|
||||
}
|
||||
|
||||
function SHM_A(string $href, string|HTMLElement $text, string $id="", string $class="", array $args=[]): HTMLElement
|
||||
{
|
||||
$args["href"] = make_link($href);
|
||||
|
||||
if ($id) {
|
||||
$args["id"] = $id;
|
||||
}
|
||||
if ($class) {
|
||||
$args["class"] = $class;
|
||||
}
|
||||
|
||||
return A($args, $text);
|
||||
}
|
||||
|
||||
function SHM_COMMAND_EXAMPLE(string $ex, string $desc): HTMLElement
|
||||
{
|
||||
return DIV(
|
||||
["class"=>"command_example"],
|
||||
PRE($ex),
|
||||
P($desc)
|
||||
);
|
||||
}
|
||||
|
||||
function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot): HTMLElement
|
||||
{
|
||||
if (is_string($foot)) {
|
||||
$foot = TFOOT(TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>$foot]))));
|
||||
}
|
||||
return SHM_SIMPLE_FORM(
|
||||
$target,
|
||||
P(
|
||||
INPUT(["type"=>'hidden', "name"=>'id', "value"=>$duser->id]),
|
||||
TABLE(
|
||||
["class"=>"form"],
|
||||
THEAD(TR(TH(["colspan"=>"2"], $title))),
|
||||
$body,
|
||||
$foot
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a <select> element and sets up the given options.
|
||||
*
|
||||
* @param string $name The name attribute of <select>.
|
||||
* @param array $options An array of pairs of parameters for <option> tags. First one is value, second one is text. Example: ('optionA', 'Choose Option A').
|
||||
* @param array $selected_options The values of options that should be pre-selected.
|
||||
* @param bool $required Wether the <select> element is required.
|
||||
* @param bool $multiple Wether the <select> element is multiple-choice.
|
||||
* @param bool $empty_option Whether the first option should be an empty one.
|
||||
* @param array $attrs Additional attributes dict for <select>. Example: ["id"=>"some_id", "class"=>"some_class"].
|
||||
*/
|
||||
function SHM_SELECT(string $name, array $options, array $selected_options=[], bool $required=false, bool $multiple=false, bool $empty_option=false, array $attrs=[]): HTMLElement
|
||||
{
|
||||
if ($required) {
|
||||
$attrs["required"] = "";
|
||||
}
|
||||
if ($multiple) {
|
||||
if (!str_ends_with($name, "[]")) {
|
||||
$name = $name . "[]";
|
||||
}
|
||||
$attrs["multiple"] = "";
|
||||
}
|
||||
|
||||
$attrs["name"] = $name;
|
||||
|
||||
$_options = [];
|
||||
if ($empty_option) {
|
||||
$_options[] = OPTION();
|
||||
}
|
||||
|
||||
foreach ($options as $value => $text) {
|
||||
$_options[] = SHM_OPTION((string)$value, (string)$text, in_array($value, $selected_options));
|
||||
}
|
||||
|
||||
return SELECT($attrs, ...$_options);
|
||||
}
|
||||
|
||||
function SHM_OPTION(string $value, string $text, bool $selected=false): HTMLElement
|
||||
{
|
||||
if ($selected) {
|
||||
return OPTION(["value"=>$value, "selected"=>""], $text);
|
||||
}
|
||||
|
||||
return OPTION(["value"=>$value], $text);
|
||||
}
|
|
@ -12,32 +12,40 @@ use GQLA\Enum;
|
|||
#[Enum(name: "Permission")]
|
||||
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
|
||||
/** modify web-level settings, eg the config table */
|
||||
public const CHANGE_SETTING = "change_setting";
|
||||
/** modify sys-level settings, eg shimmie.conf.php */
|
||||
public const OVERRIDE_CONFIG = "override_config";
|
||||
/** modify own user-level settings */
|
||||
public const CHANGE_USER_SETTING = "change_user_setting";
|
||||
public const CHANGE_OTHER_USER_SETTING = "change_other_user_setting";
|
||||
|
||||
public const BIG_SEARCH = "big_search"; # search for more than 3 tags at once (speed mode only)
|
||||
/** search for more than 3 tags at once (only applies if SPEED_HAX is active) */
|
||||
public const BIG_SEARCH = "big_search";
|
||||
|
||||
/** enable or disable extensions */
|
||||
public const MANAGE_EXTENSION_LIST = "manage_extension_list";
|
||||
public const MANAGE_ALIAS_LIST = "manage_alias_list";
|
||||
public const MANAGE_AUTO_TAG = "manage_auto_tag";
|
||||
public const MASS_TAG_EDIT = "mass_tag_edit";
|
||||
|
||||
public const VIEW_IP = "view_ip"; # view IP addresses associated with things
|
||||
/** View which IP address posted a comment / image / etc */
|
||||
public const VIEW_IP = "view_ip";
|
||||
public const BAN_IP = "ban_ip";
|
||||
|
||||
public const CREATE_USER = "create_user";
|
||||
public const CREATE_OTHER_USER = "create_other_user";
|
||||
public const EDIT_USER_NAME = "edit_user_name";
|
||||
public const EDIT_USER_PASSWORD = "edit_user_password";
|
||||
public const EDIT_USER_INFO = "edit_user_info"; # email address, etc
|
||||
/** Edit metadata about a user (eg email address) */
|
||||
public const EDIT_USER_INFO = "edit_user_info";
|
||||
public const EDIT_USER_CLASS = "edit_user_class";
|
||||
public const DELETE_USER = "delete_user";
|
||||
|
||||
public const CREATE_COMMENT = "create_comment";
|
||||
public const DELETE_COMMENT = "delete_comment";
|
||||
public const BYPASS_COMMENT_CHECKS = "bypass_comment_checks"; # spam etc
|
||||
/** Allow a user to make comments even if the spam-detector disapproves */
|
||||
public const BYPASS_COMMENT_CHECKS = "bypass_comment_checks";
|
||||
|
||||
public const REPLACE_IMAGE = "replace_image";
|
||||
public const CREATE_IMAGE = "create_image";
|
||||
|
@ -59,7 +67,8 @@ abstract class Permissions
|
|||
public const VIEW_REGISTRATIONS = "view_registrations";
|
||||
|
||||
public const CREATE_IMAGE_REPORT = "create_image_report";
|
||||
public const VIEW_IMAGE_REPORT = "view_image_report"; # deal with reported images
|
||||
/** deal with reported images */
|
||||
public const VIEW_IMAGE_REPORT = "view_image_report";
|
||||
|
||||
public const WIKI_ADMIN = "wiki_admin";
|
||||
public const EDIT_WIKI_PAGE = "edit_wiki_page";
|
||||
|
@ -84,7 +93,8 @@ abstract class Permissions
|
|||
public const HELLBANNED = "hellbanned";
|
||||
public const VIEW_HELLBANNED = "view_hellbanned";
|
||||
|
||||
public const PROTECTED = "protected"; # only admins can modify protected users (stops a moderator changing an admin's password)
|
||||
/** only admins can modify protected users (stops a moderator from changing an admin's password) */
|
||||
public const PROTECTED = "protected";
|
||||
|
||||
public const EDIT_IMAGE_RATING = "edit_image_rating";
|
||||
public const BULK_EDIT_IMAGE_RATING = "bulk_edit_image_rating";
|
||||
|
@ -110,6 +120,7 @@ abstract class Permissions
|
|||
public const CRON_ADMIN = "cron_admin";
|
||||
public const APPROVE_IMAGE = "approve_image";
|
||||
public const APPROVE_COMMENT = "approve_comment";
|
||||
public const BYPASS_IMAGE_APPROVAL = "bypass_image_approval";
|
||||
|
||||
public const SET_PRIVATE_IMAGE = "set_private_image";
|
||||
public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images";
|
||||
|
|
|
@ -173,7 +173,6 @@ function stream_file(string $file, int $start, int $end): void
|
|||
{
|
||||
$fp = fopen($file, 'r');
|
||||
try {
|
||||
set_time_limit(0);
|
||||
fseek($fp, $start);
|
||||
$buffer = 1024 * 1024;
|
||||
while (!feof($fp) && ($p = ftell($fp)) <= $end) {
|
||||
|
@ -229,10 +228,6 @@ if (!function_exists('http_parse_headers')) {
|
|||
*/
|
||||
function find_header(array $headers, string $name): ?string
|
||||
{
|
||||
if (!is_array($headers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$header = null;
|
||||
|
||||
if (array_key_exists($name, $headers)) {
|
||||
|
@ -458,9 +453,9 @@ function page_number(string $input, ?int $max=null): int
|
|||
return $pageNumber;
|
||||
}
|
||||
|
||||
function clamp(?int $val, ?int $min=null, ?int $max=null): int
|
||||
function clamp(int $val, ?int $min=null, ?int $max=null): int
|
||||
{
|
||||
if (!is_numeric($val) || (!is_null($min) && $val < $min)) {
|
||||
if (!is_null($min) && $val < $min) {
|
||||
$val = $min;
|
||||
}
|
||||
if (!is_null($max) && $val > $max) {
|
||||
|
|
|
@ -4,6 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
class TimeoutException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Event API *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
@ -93,6 +98,22 @@ function _dump_event_listeners(array $event_listeners, string $path): void
|
|||
/** @private */
|
||||
global $_shm_event_count;
|
||||
$_shm_event_count = 0;
|
||||
$_shm_timeout = null;
|
||||
|
||||
function shm_set_timeout(?int $timeout=null): void
|
||||
{
|
||||
global $_shm_timeout;
|
||||
if ($timeout) {
|
||||
$_shm_timeout = ftime() + $timeout;
|
||||
} else {
|
||||
$_shm_timeout = null;
|
||||
}
|
||||
set_time_limit(is_null($timeout) ? 0 : $timeout);
|
||||
}
|
||||
|
||||
if (ini_get('max_execution_time')) {
|
||||
shm_set_timeout((int)ini_get('max_execution_time') - 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event to all registered Extensions.
|
||||
|
@ -105,7 +126,7 @@ function send_event(Event $event): Event
|
|||
{
|
||||
global $tracer_enabled;
|
||||
|
||||
global $_shm_event_listeners, $_shm_event_count, $_tracer;
|
||||
global $_shm_event_listeners, $_shm_event_count, $_tracer, $_shm_timeout;
|
||||
$event_name = _namespaced_class_name(get_class($event));
|
||||
if (!isset($_shm_event_listeners[$event_name])) {
|
||||
return $event;
|
||||
|
@ -122,6 +143,9 @@ function send_event(Event $event): Event
|
|||
ksort($my_event_listeners);
|
||||
|
||||
foreach ($my_event_listeners as $listener) {
|
||||
if ($_shm_timeout && ftime() > $_shm_timeout) {
|
||||
throw new TimeoutException("Timeout while sending $event_name");
|
||||
}
|
||||
if ($tracer_enabled) {
|
||||
$_tracer->begin(get_class($listener));
|
||||
}
|
||||
|
|
|
@ -73,11 +73,14 @@ class PolyfillsTest extends TestCase
|
|||
|
||||
public function test_clamp()
|
||||
{
|
||||
$this->assertEquals(5, clamp(0, 5, 10));
|
||||
$this->assertEquals(5, clamp(5, 5, 10));
|
||||
$this->assertEquals(7, clamp(7, 5, 10));
|
||||
$this->assertEquals(10, clamp(10, 5, 10));
|
||||
$this->assertEquals(10, clamp(15, 5, 10));
|
||||
$this->assertEquals(5, clamp(0, 5, 10)); // too small
|
||||
$this->assertEquals(5, clamp(5, 5, 10)); // lower limit
|
||||
$this->assertEquals(7, clamp(7, 5, 10)); // ok
|
||||
$this->assertEquals(10, clamp(10, 5, 10)); // upper limit
|
||||
$this->assertEquals(10, clamp(15, 5, 10)); // too large
|
||||
$this->assertEquals(0, clamp(0, null, 10)); // no lower limit
|
||||
$this->assertEquals(10, clamp(10, 0, null)); // no upper limit
|
||||
$this->assertEquals(42, clamp(42, null, null)); // no limit
|
||||
}
|
||||
|
||||
public function test_truncate()
|
||||
|
|
|
@ -7,6 +7,9 @@ namespace Shimmie2;
|
|||
use GQLA\Type;
|
||||
use GQLA\Field;
|
||||
use GQLA\Query;
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\INPUT;
|
||||
|
||||
function _new_user(array $row): User
|
||||
{
|
||||
|
@ -277,6 +280,13 @@ class User
|
|||
return '<input type="hidden" name="auth_token" value="'.$at.'">';
|
||||
}
|
||||
|
||||
// Temporary? This should eventually become get_auth_html (probably with a different name?).
|
||||
public function get_auth_microhtml(): HTMLElement
|
||||
{
|
||||
$at = $this->get_auth_token();
|
||||
return INPUT(["type"=>"hidden", "name"=>"auth_token", "value"=>$at]);
|
||||
}
|
||||
|
||||
public function check_auth_token(): bool
|
||||
{
|
||||
if (defined("UNITTEST")) {
|
||||
|
|
|
@ -217,6 +217,7 @@ new UserClass("admin", "base", [
|
|||
|
||||
Permissions::APPROVE_IMAGE => true,
|
||||
Permissions::APPROVE_COMMENT => true,
|
||||
Permissions::BYPASS_IMAGE_APPROVAL => true,
|
||||
|
||||
Permissions::CRON_RUN =>true,
|
||||
|
||||
|
|
|
@ -4,22 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
use function MicroHTML\rawHTML;
|
||||
use function MicroHTML\FORM;
|
||||
use function MicroHTML\INPUT;
|
||||
use function MicroHTML\DIV;
|
||||
use function MicroHTML\PRE;
|
||||
use function MicroHTML\P;
|
||||
use function MicroHTML\TABLE;
|
||||
use function MicroHTML\THEAD;
|
||||
use function MicroHTML\TFOOT;
|
||||
use function MicroHTML\TR;
|
||||
use function MicroHTML\TH;
|
||||
use function MicroHTML\TD;
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Misc *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
@ -270,7 +254,7 @@ function load_balance_url(string $tmpl, string $hash, int $n=0): string
|
|||
$opt_weight = 0;
|
||||
if ($parts_count === 2) {
|
||||
$opt_val = $parts[0];
|
||||
$opt_weight = $parts[1];
|
||||
$opt_weight = (int)$parts[1];
|
||||
} elseif ($parts_count === 1) {
|
||||
$opt_val = $parts[0];
|
||||
$opt_weight = 1;
|
||||
|
@ -754,71 +738,6 @@ 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
|
||||
{
|
||||
global $user;
|
||||
|
||||
$attrs = [
|
||||
"action"=>make_link($target),
|
||||
"method"=>$method
|
||||
];
|
||||
|
||||
if ($form_id) {
|
||||
$attrs["id"] = $form_id;
|
||||
}
|
||||
if ($multipart) {
|
||||
$attrs["enctype"] = 'multipart/form-data';
|
||||
}
|
||||
if ($onsubmit) {
|
||||
$attrs["onsubmit"] = $onsubmit;
|
||||
}
|
||||
return FORM(
|
||||
$attrs,
|
||||
INPUT(["type"=>"hidden", "name"=>"q", "value"=>$target]),
|
||||
$method == "GET" ? "" : rawHTML($user->get_auth_html())
|
||||
);
|
||||
}
|
||||
|
||||
function SHM_SIMPLE_FORM($target, ...$children): HTMLElement
|
||||
{
|
||||
$form = SHM_FORM($target);
|
||||
$form->appendChild(emptyHTML(...$children));
|
||||
return $form;
|
||||
}
|
||||
|
||||
function SHM_SUBMIT(string $text): HTMLElement
|
||||
{
|
||||
return INPUT(["type"=>"submit", "value"=>$text]);
|
||||
}
|
||||
|
||||
function SHM_COMMAND_EXAMPLE(string $ex, string $desc): HTMLElement
|
||||
{
|
||||
return DIV(
|
||||
["class"=>"command_example"],
|
||||
PRE($ex),
|
||||
P($desc)
|
||||
);
|
||||
}
|
||||
|
||||
function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot): HTMLElement
|
||||
{
|
||||
if (is_string($foot)) {
|
||||
$foot = TFOOT(TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>$foot]))));
|
||||
}
|
||||
return SHM_SIMPLE_FORM(
|
||||
$target,
|
||||
P(
|
||||
INPUT(["type"=>'hidden', "name"=>'id', "value"=>$duser->id]),
|
||||
TABLE(
|
||||
["class"=>"form"],
|
||||
THEAD(TR(TH(["colspan"=>"2"], $title))),
|
||||
$body,
|
||||
$foot
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const BYTE_DENOMINATIONS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
function human_filesize(int $bytes, $decimals = 2): string
|
||||
{
|
||||
|
|
|
@ -33,7 +33,7 @@ class AdminActionEvent extends Event
|
|||
class AdminPage extends Extension
|
||||
{
|
||||
/** @var AdminPageTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
@ -51,7 +51,7 @@ class AdminPage extends Extension
|
|||
|
||||
if ($user->check_auth_token()) {
|
||||
log_info("admin", "Util: $action");
|
||||
set_time_limit(0);
|
||||
shm_set_timeout(null);
|
||||
$database->set_timeout(null);
|
||||
send_event($aae);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ class AddAliasException extends SCoreException
|
|||
class AliasEditor extends Extension
|
||||
{
|
||||
/** @var AliasEditorTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -4,36 +4,35 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
use function MicroHTML\{BR,INPUT};
|
||||
|
||||
class AliasEditorTheme extends Themelet
|
||||
{
|
||||
/**
|
||||
* Show a page of aliases.
|
||||
*
|
||||
* Note: $can_manage = whether things like "add new alias" should be shown
|
||||
*/
|
||||
public function display_aliases($table, $paginator): void
|
||||
public function display_aliases(HTMLElement $table, HTMLElement $paginator): void
|
||||
{
|
||||
global $page, $user;
|
||||
|
||||
$can_manage = $user->can(Permissions::MANAGE_ALIAS_LIST);
|
||||
$html = "
|
||||
$table
|
||||
$paginator
|
||||
<p><a href='".make_link("alias/export/aliases.csv")."' download='aliases.csv'>Download as CSV</a></p>
|
||||
";
|
||||
$html = emptyHTML($table, BR(), $paginator, BR(), SHM_A("alias/export/aliases.csv", "Download as CSV", args: ["download"=>"aliases.csv"]));
|
||||
|
||||
$bulk_html = "
|
||||
".make_form(make_link("alias/import"), 'post', true)."
|
||||
<input type='file' name='alias_file'>
|
||||
<input type='submit' value='Upload List'>
|
||||
</form>
|
||||
";
|
||||
$bulk_form = SHM_FORM("alias/import", multipart: true);
|
||||
$bulk_form->appendChild(
|
||||
INPUT(["type"=>"file", "name"=>"alias_file"]),
|
||||
SHM_SUBMIT("Upload List")
|
||||
);
|
||||
$bulk_html = emptyHTML($bulk_form);
|
||||
|
||||
$page->set_title("Alias List");
|
||||
$page->set_heading("Alias List");
|
||||
$page->add_block(new NavBlock());
|
||||
$page->add_block(new Block("Aliases", $html));
|
||||
if ($can_manage) {
|
||||
|
||||
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
|
||||
$page->add_block(new Block("Bulk Upload", $bulk_html, "main", 51));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ abstract class ApprovalConfig
|
|||
class Approval extends Extension
|
||||
{
|
||||
/** @var ApprovalTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
@ -26,6 +26,15 @@ class Approval extends Extension
|
|||
Image::$bool_props[] = "approved";
|
||||
}
|
||||
|
||||
public function onImageAddition(ImageAdditionEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
|
||||
if ($config->get_bool(ApprovalConfig::IMAGES) && $user->can(Permissions::BYPASS_IMAGE_APPROVAL)) {
|
||||
self::approve_image($event->image->id);
|
||||
}
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $page, $user;
|
||||
|
@ -159,10 +168,7 @@ class Approval extends Extension
|
|||
global $user, $config;
|
||||
if ($event->key===HelpPages::SEARCH) {
|
||||
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
|
||||
$block = new Block();
|
||||
$block->header = "Approval";
|
||||
$block->body = $this->theme->get_help_html();
|
||||
$event->add_block($block);
|
||||
$event->add_block(new Block("Approval", $this->theme->get_help_html()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +228,7 @@ class Approval extends Extension
|
|||
{
|
||||
global $user, $config;
|
||||
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
|
||||
$event->add_part($this->theme->get_image_admin_html($event->image));
|
||||
$event->add_part((string)$this->theme->get_image_admin_html($event->image));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,43 +4,40 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use function MicroHTML\BR;
|
||||
use function MicroHTML\BUTTON;
|
||||
use function MicroHTML\INPUT;
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
|
||||
use function MicroHTML\{BUTTON,INPUT,P};
|
||||
|
||||
class ApprovalTheme extends Themelet
|
||||
{
|
||||
public function get_image_admin_html(Image $image): string
|
||||
public function get_image_admin_html(Image $image): HTMLElement
|
||||
{
|
||||
if ($image->approved===true) {
|
||||
$html = SHM_SIMPLE_FORM(
|
||||
$form = SHM_SIMPLE_FORM(
|
||||
'disapprove_image/'.$image->id,
|
||||
INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image->id]),
|
||||
SHM_SUBMIT("Disapprove")
|
||||
);
|
||||
} else {
|
||||
$html = SHM_SIMPLE_FORM(
|
||||
$form = SHM_SIMPLE_FORM(
|
||||
'approve_image/'.$image->id,
|
||||
INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image->id]),
|
||||
SHM_SUBMIT("Approve")
|
||||
);
|
||||
}
|
||||
|
||||
return (string)$html;
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function get_help_html(): string
|
||||
public function get_help_html(): HTMLElement
|
||||
{
|
||||
return '<p>Search for posts that are approved/not approved.</p>
|
||||
<div class="command_example">
|
||||
<pre>approved:yes</pre>
|
||||
<p>Returns posts that have been approved.</p>
|
||||
</div>
|
||||
<div class="command_example">
|
||||
<pre>approved:no</pre>
|
||||
<p>Returns posts that have not been approved.</p>
|
||||
</div>
|
||||
';
|
||||
return emptyHTML(
|
||||
P("Search for posts that are approved/not approved."),
|
||||
SHM_COMMAND_EXAMPLE("approved:yes", "Returns posts that have been approved."),
|
||||
SHM_COMMAND_EXAMPLE("approved:no", "Returns posts that have not been approved.")
|
||||
);
|
||||
}
|
||||
|
||||
public function display_admin_block(SetupBuildingEvent $event)
|
||||
|
@ -53,12 +50,13 @@ class ApprovalTheme extends Themelet
|
|||
{
|
||||
global $page;
|
||||
|
||||
$html = (string)SHM_SIMPLE_FORM(
|
||||
$form = SHM_SIMPLE_FORM(
|
||||
"admin/approval",
|
||||
BUTTON(["name"=>'approval_action', "value"=>'approve_all'], "Approve All Posts"),
|
||||
BR(),
|
||||
" ",
|
||||
BUTTON(["name"=>'approval_action', "value"=>'disapprove_all'], "Disapprove All Posts"),
|
||||
);
|
||||
$page->add_block(new Block("Approval", $html));
|
||||
|
||||
$page->add_block(new Block("Approval", $form));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class AuthorSetEvent extends Event
|
|||
class Artists extends Extension
|
||||
{
|
||||
/** @var ArtistsTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onImageInfoSet(ImageInfoSetEvent $event)
|
||||
{
|
||||
|
@ -57,10 +57,7 @@ class Artists extends Extension
|
|||
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||
{
|
||||
if ($event->key===HelpPages::SEARCH) {
|
||||
$block = new Block();
|
||||
$block->header = "Artist";
|
||||
$block->body = $this->theme->get_help_html();
|
||||
$event->add_block($block);
|
||||
$event->add_block(new Block("Artist", $this->theme->get_help_html()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,7 +209,7 @@ class Artists extends Extension
|
|||
$userIsLogged = !$user->is_anonymous();
|
||||
$userIsAdmin = $user->can(Permissions::ARTISTS_ADMIN);
|
||||
|
||||
$images = Image::find_images(0, 4, Tag::explode($artist['name']));
|
||||
$images = Image::find_images(limit: 4, tags: Tag::explode($artist['name']));
|
||||
|
||||
$this->theme->show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin);
|
||||
/*
|
||||
|
|
|
@ -4,20 +4,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
use function MicroHTML\{INPUT,P,SPAN,TD,TH,TR};
|
||||
|
||||
class ArtistsTheme extends Themelet
|
||||
{
|
||||
public function get_author_editor_html(string $author): string
|
||||
{
|
||||
$h_author = html_escape($author);
|
||||
return "
|
||||
<tr>
|
||||
<th>Author</th>
|
||||
<td>
|
||||
<span class='view'>$h_author</span>
|
||||
<input class='edit' type='text' name='tag_edit__author' value='$h_author'>
|
||||
</td>
|
||||
</tr>
|
||||
";
|
||||
return (string)TR(TH("Author", TD(
|
||||
SPAN(["class"=>"view"], $h_author),
|
||||
INPUT(["class"=>"edit", "type"=>"text", "name"=>"tag_edit__author", "value"=>$h_author])
|
||||
)));
|
||||
}
|
||||
|
||||
public function sidebar_options(string $mode, ?int $artistID=null, $is_admin=false): void
|
||||
|
@ -554,13 +554,11 @@ class ArtistsTheme extends Themelet
|
|||
return $html;
|
||||
}
|
||||
|
||||
public function get_help_html(): string
|
||||
public function get_help_html(): HTMLElement
|
||||
{
|
||||
return '<p>Search for posts with a particular artist.</p>
|
||||
<div class="command_example">
|
||||
<pre>artist=leonardo</pre>
|
||||
<p>Returns posts with the artist "leonardo".</p>
|
||||
</div>
|
||||
';
|
||||
return emptyHTML(
|
||||
P("Search for posts with a particular artist."),
|
||||
SHM_COMMAND_EXAMPLE("artist=leonardo", "Returns posts with the artist \"leonardo\".")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class AddAutoTagException extends SCoreException
|
|||
class AutoTagger extends Extension
|
||||
{
|
||||
/** @var AutoTaggerTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class AutoComplete extends Extension
|
||||
{
|
||||
/** @var AutoCompleteTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function get_priority(): int
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class Biography extends Extension
|
||||
{
|
||||
/** @var BiographyTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onUserPageBuilding(UserPageBuildingEvent $event)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class Blocks extends Extension
|
||||
{
|
||||
/** @var BlocksTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||
{
|
||||
|
@ -19,10 +19,15 @@ class Blocks extends Extension
|
|||
title VARCHAR(128) NOT NULL,
|
||||
area VARCHAR(16) NOT NULL,
|
||||
priority INTEGER NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
content TEXT NOT NULL,
|
||||
userclass TEXT
|
||||
");
|
||||
$database->execute("CREATE INDEX blocks_pages_idx ON blocks(pages)", []);
|
||||
$this->set_version("ext_blocks_version", 1);
|
||||
$this->set_version("ext_blocks_version", 2);
|
||||
}
|
||||
if ($this->get_version("ext_blocks_version") < 2) {
|
||||
$database->execute("ALTER TABLE blocks ADD COLUMN userclass TEXT");
|
||||
$this->set_version("ext_blocks_version", 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +63,12 @@ class Blocks extends Extension
|
|||
if (strlen($path) < 4000 && fnmatch($block['pages'], $path)) {
|
||||
$b = new Block($block['title'], $block['content'], $block['area'], (int)$block['priority']);
|
||||
$b->is_content = false;
|
||||
$page->add_block($b);
|
||||
|
||||
# Split by comma, trimming whitespaces, and not allowing empty elements.
|
||||
$userclasses = preg_split('/\s*,+\s*/', strtolower($block['userclass'] ?? ""), 0, PREG_SPLIT_NO_EMPTY);
|
||||
if (empty($userclasses) || in_array(strtolower($user->class->name), $userclasses)) {
|
||||
$page->add_block($b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,9 +76,9 @@ class Blocks extends Extension
|
|||
if ($event->get_arg(0) == "add") {
|
||||
if ($user->check_auth_token()) {
|
||||
$database->execute("
|
||||
INSERT INTO blocks (pages, title, area, priority, content)
|
||||
VALUES (:pages, :title, :area, :priority, :content)
|
||||
", ['pages'=>$_POST['pages'], 'title'=>$_POST['title'], 'area'=>$_POST['area'], 'priority'=>(int)$_POST['priority'], 'content'=>$_POST['content']]);
|
||||
INSERT INTO blocks (pages, title, area, priority, content, userclass)
|
||||
VALUES (:pages, :title, :area, :priority, :content, :userclass)
|
||||
", ['pages'=>$_POST['pages'], 'title'=>$_POST['title'], 'area'=>$_POST['area'], 'priority'=>(int)$_POST['priority'], 'content'=>$_POST['content'], 'userclass'=>$_POST['userclass']]);
|
||||
log_info("blocks", "Added Block #".($database->get_last_insert_id('blocks_id_seq'))." (".$_POST['title'].")");
|
||||
$cache->delete("blocks");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
|
@ -85,9 +95,9 @@ class Blocks extends Extension
|
|||
log_info("blocks", "Deleted Block #".$_POST['id']);
|
||||
} else {
|
||||
$database->execute("
|
||||
UPDATE blocks SET pages=:pages, title=:title, area=:area, priority=:priority, content=:content
|
||||
UPDATE blocks SET pages=:pages, title=:title, area=:area, priority=:priority, content=:content, userclass=:userclass
|
||||
WHERE id=:id
|
||||
", ['pages'=>$_POST['pages'], 'title'=>$_POST['title'], 'area'=>$_POST['area'], 'priority'=>(int)$_POST['priority'], 'content'=>$_POST['content'], 'id'=>$_POST['id']]);
|
||||
", ['pages'=>$_POST['pages'], 'title'=>$_POST['title'], 'area'=>$_POST['area'], 'priority'=>(int)$_POST['priority'], 'content'=>$_POST['content'], 'userclass'=>$_POST['userclass'], 'id'=>$_POST['id']]);
|
||||
log_info("blocks", "Updated Block #".$_POST['id']." (".$_POST['title'].")");
|
||||
}
|
||||
$cache->delete("blocks");
|
||||
|
|
|
@ -32,6 +32,8 @@ class BlocksTheme extends Themelet
|
|||
TD(INPUT(["type"=>"text", "name"=>"area", "value"=>$block['area']])),
|
||||
TH("Priority"),
|
||||
TD(INPUT(["type"=>"text", "name"=>"priority", "value"=>$block['priority']])),
|
||||
TH("User Class"),
|
||||
TD(INPUT(["type"=>"text", "name"=>"userclass", "value"=>$block['userclass']])),
|
||||
TH("Pages"),
|
||||
TD(INPUT(["type"=>"text", "name"=>"pages", "value"=>$block['pages']])),
|
||||
TH("Delete"),
|
||||
|
@ -39,10 +41,10 @@ class BlocksTheme extends Themelet
|
|||
TD(INPUT(["type"=>"submit", "value"=>"Save"]))
|
||||
),
|
||||
TR(
|
||||
TD(["colspan"=>"11"], TEXTAREA(["rows"=>"5", "name"=>"content"], $block['content']))
|
||||
TD(["colspan"=>"13"], TEXTAREA(["rows"=>"5", "name"=>"content"], $block['content']))
|
||||
),
|
||||
TR(
|
||||
TD(["colspan"=>"11"], rawHTML(" "))
|
||||
TD(["colspan"=>"13"], rawHTML(" "))
|
||||
),
|
||||
));
|
||||
}
|
||||
|
@ -56,18 +58,20 @@ class BlocksTheme extends Themelet
|
|||
TD(SELECT(["name"=>"area"], OPTION("left"), OPTION("main"))),
|
||||
TH("Priority"),
|
||||
TD(INPUT(["type"=>"text", "name"=>"priority", "value"=>'50'])),
|
||||
TH("User Class"),
|
||||
TD(INPUT(["type"=>"text", "name"=>"userclass", "value"=>""])),
|
||||
TH("Pages"),
|
||||
TD(INPUT(["type"=>"text", "name"=>"pages", "value"=>'post/list*'])),
|
||||
TD(["colspan"=>'3'], INPUT(["type"=>"submit", "value"=>"Add"]))
|
||||
),
|
||||
TR(
|
||||
TD(["colspan"=>"11"], TEXTAREA(["rows"=>"5", "name"=>"content"]))
|
||||
TD(["colspan"=>"13"], TEXTAREA(["rows"=>"5", "name"=>"content"]))
|
||||
),
|
||||
));
|
||||
|
||||
$page->set_title("Blocks");
|
||||
$page->set_heading("Blocks");
|
||||
$page->add_block(new NavBlock());
|
||||
$page->add_block(new Block("Block Editor", (string)$html));
|
||||
$page->add_block(new Block("Block Editor", $html));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class Blotter extends Extension
|
||||
{
|
||||
/** @var BlotterTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
|
|
@ -12,12 +12,8 @@ class BulkActionBlockBuildingEvent extends Event
|
|||
public array $actions = [];
|
||||
public array $search_terms = [];
|
||||
|
||||
public function add_action(String $action, string $button_text, string $access_key = null, String $confirmation_message = "", String $block = "", int $position = 40)
|
||||
public function add_action(String $action, string $button_text, string $access_key = null, string $confirmation_message = "", string $block = "", int $position = 40)
|
||||
{
|
||||
if ($block == null) {
|
||||
$block = "";
|
||||
}
|
||||
|
||||
if (!empty($access_key)) {
|
||||
assert(strlen($access_key)==1);
|
||||
foreach ($this->actions as $existing) {
|
||||
|
@ -55,7 +51,7 @@ class BulkActionEvent extends Event
|
|||
class BulkActions extends Extension
|
||||
{
|
||||
/** @var BulkActionsTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
{
|
||||
|
@ -180,7 +176,7 @@ class BulkActions extends Extension
|
|||
}
|
||||
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||
$query = $_POST['bulk_query'];
|
||||
if ($query != null && $query != "") {
|
||||
if (!empty($query)) {
|
||||
$items = $this->yield_search_results($query);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -14,7 +14,7 @@ function validate_selections(form, confirmationMessage) {
|
|||
} else {
|
||||
var query = $(form).find('input[name="bulk_query"]').val();
|
||||
|
||||
if (query == null || query === "") {
|
||||
if (query === null || query === "") {
|
||||
return false;
|
||||
} else {
|
||||
queryOnly = true;
|
||||
|
|
|
@ -26,7 +26,7 @@ class BulkActionsTheme extends Themelet
|
|||
</td></tr></table>
|
||||
";
|
||||
|
||||
$hasQuery = ($query != null && $query != "");
|
||||
$hasQuery = !empty($query);
|
||||
|
||||
if ($hasQuery) {
|
||||
$body .= "</div>";
|
||||
|
@ -61,7 +61,7 @@ class BulkActionsTheme extends Themelet
|
|||
public function render_tag_input(): string
|
||||
{
|
||||
return "<label><input type='checkbox' style='width:13px;' name='bulk_tags_replace' value='true'/>Replace tags</label>" .
|
||||
"<input type='text' name='bulk_tags' required='required' placeholder='Enter tags here' />";
|
||||
"<input type='text' name='bulk_tags' class='autocomplete_tags' required='required' placeholder='Enter tags here' />";
|
||||
}
|
||||
|
||||
public function render_source_input(): string
|
||||
|
|
|
@ -20,14 +20,14 @@ class BulkAddEvent extends Event
|
|||
class BulkAdd extends Extension
|
||||
{
|
||||
/** @var BulkAddTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $page, $user;
|
||||
if ($event->page_matches("bulk_add")) {
|
||||
if ($user->can(Permissions::BULK_ADD) && $user->check_auth_token() && isset($_POST['dir'])) {
|
||||
set_time_limit(0);
|
||||
shm_set_timeout(null);
|
||||
$bae = send_event(new BulkAddEvent($_POST['dir']));
|
||||
foreach ($bae->results as $result) {
|
||||
$this->theme->add_status("Adding files", $result);
|
||||
|
|
|
@ -7,14 +7,14 @@ namespace Shimmie2;
|
|||
class BulkAddCSV extends Extension
|
||||
{
|
||||
/** @var BulkAddCSVTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $page, $user;
|
||||
if ($event->page_matches("bulk_add_csv")) {
|
||||
if ($user->can(Permissions::BULK_ADD) && $user->check_auth_token() && isset($_POST['csv'])) {
|
||||
set_time_limit(0);
|
||||
shm_set_timeout(null);
|
||||
$this->add_csv($_POST['csv']);
|
||||
$this->theme->display_upload_results($page);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class BulkParentChild extends Extension
|
|||
($event->action == BulkParentChild::PARENT_CHILD_ACTION_NAME)) {
|
||||
$prev_id = null;
|
||||
foreach ($event->items as $image) {
|
||||
if ($prev_id != null) {
|
||||
if ($prev_id !== null) {
|
||||
send_event(new ImageRelationshipSetEvent($image->id, $prev_id));
|
||||
}
|
||||
$prev_id = $image->id;
|
||||
|
|
|
@ -114,7 +114,7 @@ class Comment
|
|||
class CommentList extends Extension
|
||||
{
|
||||
/** @var CommentListTheme $theme */
|
||||
public ?Themelet $theme;
|
||||
public Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
|
|
@ -26,7 +26,7 @@ class CronUploaderInfo extends ExtensionInfo
|
|||
|
||||
public function __construct()
|
||||
{
|
||||
$this->documentation = "Installation guide: activate this extension and navigate to System Config screen.</a>";
|
||||
$this->documentation = "Installation guide: activate this extension and navigate to Board Config screen.</a>";
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ require_once "config.php";
|
|||
class CronUploader extends Extension
|
||||
{
|
||||
/** @var CronUploaderTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public const NAME = "cron_uploader";
|
||||
|
||||
|
@ -350,7 +350,7 @@ class CronUploader extends Extension
|
|||
|
||||
self::$IMPORT_RUNNING = true;
|
||||
try {
|
||||
//set_time_limit(0);
|
||||
//shm_set_timeout(null);
|
||||
|
||||
$output_subdir = date('Ymd-His', time());
|
||||
$image_queue = $this->generate_image_queue();
|
||||
|
|
|
@ -74,10 +74,10 @@ class CronUploaderTheme extends Themelet
|
|||
$install_html = "
|
||||
This cron uploader is fairly easy to use but has to be configured first.
|
||||
<ol>
|
||||
<li>Install & activate this plugin.</li>
|
||||
<li>Go to the <a href='".make_link("setup")."'>Board Config</a> and change any settings to match your preference.</li>
|
||||
<li>Copy the cron command above.</li>
|
||||
<li>Create a cron job or something else that can open a url on specified times.
|
||||
<li style='text-align: left;'>Install & activate this plugin.</li>
|
||||
<li style='text-align: left;'>Go to the <a href='".make_link("setup")."'>Board Config</a> and change any settings to match your preference.</li>
|
||||
<li style='text-align: left;'>Copy the cron command above.</li>
|
||||
<li style='text-align: left;'>Create a cron job or something else that can open a url on specified times.
|
||||
<br/>cron is a service that runs commands over and over again on a a schedule. You can set up cron (or any similar tool) to run the command above to trigger the import on whatever schedule you desire.
|
||||
<br />If you're not sure how to do this, you can give the command to your web host and you can ask them to create the cron job for you.
|
||||
<br />When you create the cron job, you choose when to upload new posts.</li>
|
||||
|
@ -89,11 +89,11 @@ class CronUploaderTheme extends Themelet
|
|||
$usage_html = "Upload your images you want to be uploaded to the queue directory using your FTP client or other means.
|
||||
<br />(<b>{$queue_dirinfo['path']}</b>)
|
||||
<ol>
|
||||
<li>Any sub-folders will be turned into tags.</li>
|
||||
<li>If the file name matches \"## - tag1 tag2.png\" the tags will be used.</li>
|
||||
<li>If both are found, they will all be used.</li>
|
||||
<li>The character \";\" will be changed into \":\" in any tags.</li>
|
||||
<li>You can inherit categories by creating a folder that ends with \";\". For instance category;\\tag1 would result in the tag category:tag1. This allows creating a category folder, then creating many subfolders that will use that category.</li>
|
||||
<li style='text-align: left;'>Any sub-folders will be turned into tags.</li>
|
||||
<li style='text-align: left;'>If the file name matches \"## - tag1 tag2.png\" the tags will be used.</li>
|
||||
<li style='text-align: left;'>If both are found, they will all be used.</li>
|
||||
<li style='text-align: left;'>The character \";\" will be changed into \":\" in any tags.</li>
|
||||
<li style='text-align: left;'>You can inherit categories by creating a folder that ends with \";\". For instance category;\\tag1 would result in the tag category:tag1. This allows creating a category folder, then creating many subfolders that will use that category.</li>
|
||||
</ol>
|
||||
The cron uploader works by importing files from the queue folder whenever this url is visited:
|
||||
<br/><pre><a href='$cron_url'>$cron_url</a></pre>
|
||||
|
|
|
@ -195,6 +195,10 @@ class DanbooruApi extends Extension
|
|||
}
|
||||
|
||||
$tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : [];
|
||||
// danbooru API clients often set tags=*
|
||||
$tags = array_filter($tags, static function ($element) {
|
||||
return $element !== "*";
|
||||
});
|
||||
$count = Image::count_images($tags);
|
||||
$results = Image::find_images(max($start, 0), min($limit, 100), $tags);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ class DanbooruApiTest extends ShimmiePHPUnitTestCase
|
|||
$this->get_page("api/danbooru/find_posts");
|
||||
$this->get_page("api/danbooru/find_posts", ["id"=>$image_id]);
|
||||
$this->get_page("api/danbooru/find_posts", ["md5"=>"17fc89f372ed3636e28bd25cc7f3bac1"]);
|
||||
$this->get_page("api/danbooru/find_posts", ["tags"=>"*"]);
|
||||
|
||||
$this->get_page("api/danbooru/find_tags");
|
||||
$this->get_page("api/danbooru/find_tags", ["id"=>1]);
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class Downtime extends Extension
|
||||
{
|
||||
/** @var DowntimeTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function get_priority(): int
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Shimmie2;
|
|||
class EmoticonList extends Extension
|
||||
{
|
||||
/** @var EmoticonListTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class ET extends Extension
|
||||
{
|
||||
/** @var ETTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -37,7 +37,7 @@ class ExtensionAuthor
|
|||
class ExtManager extends Extension
|
||||
{
|
||||
/** @var ExtManagerTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -25,7 +25,7 @@ class FavoriteSetEvent extends Event
|
|||
class Favorites extends Extension
|
||||
{
|
||||
/** @var FavoritesTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class Featured extends Extension
|
||||
{
|
||||
/** @var FeaturedTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@ Todo:
|
|||
class Forum extends Extension
|
||||
{
|
||||
/** @var ForumTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||
{
|
||||
|
|
|
@ -11,7 +11,7 @@ class SVGFileHandler extends DataHandlerExtension
|
|||
protected array $SUPPORTED_MIME = [MimeType::SVG];
|
||||
|
||||
/** @var SVGFileHandlerTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -66,53 +66,51 @@ class VideoFileHandler extends DataHandlerExtension
|
|||
try {
|
||||
$data = Media::get_ffprobe_data($event->image->get_image_filename());
|
||||
|
||||
if (is_array($data)) {
|
||||
if (array_key_exists("streams", $data)) {
|
||||
$video = false;
|
||||
$audio = false;
|
||||
$video_codec = null;
|
||||
$streams = $data["streams"];
|
||||
if (is_array($streams)) {
|
||||
foreach ($streams as $stream) {
|
||||
if (is_array($stream)) {
|
||||
if (array_key_exists("codec_type", $stream)) {
|
||||
$type = $stream["codec_type"];
|
||||
switch ($type) {
|
||||
case "audio":
|
||||
$audio = true;
|
||||
break;
|
||||
case "video":
|
||||
$video = true;
|
||||
$video_codec = $stream["codec_name"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (array_key_exists("width", $stream) && !empty($stream["width"])
|
||||
&& is_numeric($stream["width"]) && intval($stream["width"]) > ($event->image->width) ?? 0) {
|
||||
$event->image->width = intval($stream["width"]);
|
||||
}
|
||||
if (array_key_exists("height", $stream) && !empty($stream["height"])
|
||||
&& is_numeric($stream["height"]) && intval($stream["height"]) > ($event->image->height) ?? 0) {
|
||||
$event->image->height = intval($stream["height"]);
|
||||
if (array_key_exists("streams", $data)) {
|
||||
$video = false;
|
||||
$audio = false;
|
||||
$video_codec = null;
|
||||
$streams = $data["streams"];
|
||||
if (is_array($streams)) {
|
||||
foreach ($streams as $stream) {
|
||||
if (is_array($stream)) {
|
||||
if (array_key_exists("codec_type", $stream)) {
|
||||
$type = $stream["codec_type"];
|
||||
switch ($type) {
|
||||
case "audio":
|
||||
$audio = true;
|
||||
break;
|
||||
case "video":
|
||||
$video = true;
|
||||
$video_codec = $stream["codec_name"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (array_key_exists("width", $stream) && !empty($stream["width"])
|
||||
&& is_numeric($stream["width"]) && intval($stream["width"]) > ($event->image->width) ?? 0) {
|
||||
$event->image->width = intval($stream["width"]);
|
||||
}
|
||||
if (array_key_exists("height", $stream) && !empty($stream["height"])
|
||||
&& is_numeric($stream["height"]) && intval($stream["height"]) > ($event->image->height) ?? 0) {
|
||||
$event->image->height = intval($stream["height"]);
|
||||
}
|
||||
}
|
||||
$event->image->video = $video;
|
||||
$event->image->video_codec = $video_codec;
|
||||
$event->image->audio = $audio;
|
||||
if ($event->image->get_mime()==MimeType::MKV &&
|
||||
VideoContainers::is_video_codec_supported(VideoContainers::WEBM, $event->image->video_codec)) {
|
||||
// WEBMs are MKVs with the VP9 or VP8 codec
|
||||
// For browser-friendliness, we'll just change the mime type
|
||||
$event->image->set_mime(MimeType::WEBM);
|
||||
}
|
||||
}
|
||||
$event->image->video = $video;
|
||||
$event->image->video_codec = $video_codec;
|
||||
$event->image->audio = $audio;
|
||||
if ($event->image->get_mime()==MimeType::MKV &&
|
||||
VideoContainers::is_video_codec_supported(VideoContainers::WEBM, $event->image->video_codec)) {
|
||||
// WEBMs are MKVs with the VP9 or VP8 codec
|
||||
// For browser-friendliness, we'll just change the mime type
|
||||
$event->image->set_mime(MimeType::WEBM);
|
||||
}
|
||||
}
|
||||
if (array_key_exists("format", $data)&& is_array($data["format"])) {
|
||||
$format = $data["format"];
|
||||
if (array_key_exists("duration", $format) && is_numeric($format["duration"])) {
|
||||
$event->image->length = (int)floor(floatval($format["duration"]) * 1000);
|
||||
}
|
||||
}
|
||||
if (array_key_exists("format", $data)&& is_array($data["format"])) {
|
||||
$format = $data["format"];
|
||||
if (array_key_exists("duration", $format) && is_numeric($format["duration"])) {
|
||||
$event->image->length = (int)floor(floatval($format["duration"]) * 1000);
|
||||
}
|
||||
}
|
||||
} catch (MediaException $e) {
|
||||
|
|
|
@ -37,25 +37,15 @@ class HelpPageBuildingEvent extends Event
|
|||
class HelpPages extends Extension
|
||||
{
|
||||
/** @var HelpPagesTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
public const SEARCH = "search";
|
||||
private ?array $pages = null;
|
||||
|
||||
private function get_pages(): array
|
||||
{
|
||||
if ($this->pages==null) {
|
||||
$this->pages = send_event(new HelpPageListBuildingEvent())->pages;
|
||||
}
|
||||
return $this->pages;
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $page;
|
||||
|
||||
$pages = $this->get_pages();
|
||||
|
||||
if ($event->page_matches("help")) {
|
||||
$pages = send_event(new HelpPageListBuildingEvent())->pages;
|
||||
if ($event->count_args() == 0) {
|
||||
$name = array_key_first($pages);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
|
@ -98,7 +88,7 @@ class HelpPages extends Extension
|
|||
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||
{
|
||||
if ($event->parent=="help") {
|
||||
$pages = $this->get_pages();
|
||||
$pages = send_event(new HelpPageListBuildingEvent())->pages;
|
||||
foreach ($pages as $key=>$value) {
|
||||
$event->add_nav_link("help_".$key, new Link('help/'.$key), $value);
|
||||
}
|
||||
|
|
|
@ -29,5 +29,6 @@ class HelpPagesTheme extends Themelet
|
|||
|
||||
$page->set_title("Help - $title");
|
||||
$page->set_heading("Help - $title");
|
||||
$page->add_block(new NavBlock());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class Holiday extends Extension
|
||||
{
|
||||
/** @var HolidayTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.8 KiB |
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class Home extends Extension
|
||||
{
|
||||
/** @var HomeTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
@ -26,6 +26,8 @@ class Home extends Extension
|
|||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$counters = [];
|
||||
$counters["None"] = "none";
|
||||
$counters["Text-only"] = "text-only";
|
||||
foreach (glob("ext/home/counters/*") as $counter_dirname) {
|
||||
$name = str_replace("ext/home/counters/", "", $counter_dirname);
|
||||
$counters[ucfirst($name)] = $name;
|
||||
|
@ -50,22 +52,21 @@ class Home extends Extension
|
|||
}
|
||||
$counter_dir = $config->get_string('home_counter', 'default');
|
||||
|
||||
$total = Image::count_images();
|
||||
$strtotal = "$total";
|
||||
$num_comma = number_format($total);
|
||||
|
||||
$num_comma = "";
|
||||
$counter_text = "";
|
||||
$length = strlen($strtotal);
|
||||
for ($n=0; $n<$length; $n++) {
|
||||
$cur = $strtotal[$n];
|
||||
|
||||
if ($cur == 6 && rand(0, 2500) == 0) {
|
||||
$counter_text .= "<img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/Ϫ.gif' />";
|
||||
} elseif ($cur == 7 && rand(0, 2500) == 0) {
|
||||
$counter_text .= "<img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/Ϫ.gif' />";
|
||||
} else {
|
||||
$counter_text .= "<img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' />";
|
||||
}
|
||||
if ($counter_dir != 'none') {
|
||||
$total = Image::count_images();
|
||||
$num_comma = number_format($total);
|
||||
|
||||
if ($counter_dir != 'text-only') {
|
||||
$strtotal = "$total";
|
||||
$length = strlen($strtotal);
|
||||
for ($n=0; $n<$length; $n++) {
|
||||
$cur = $strtotal[$n];
|
||||
$counter_text .= "<img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/Ϫ.gif' />";
|
||||
$counter_text .= "<img alt='$cur' src='$base_href/ext/home/counters/$counter_dir/$cur.gif' />";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the homelinks and process them
|
||||
|
|
|
@ -53,7 +53,7 @@ EOD
|
|||
$counter_html
|
||||
<div class='space' id='foot'>
|
||||
<small><small>
|
||||
$contact_link Serving $num_comma posts –
|
||||
$contact_link" . (empty($num_comma) ? "" : " Serving $num_comma posts –") . "
|
||||
Running <a href='https://code.shishnet.org/shimmie2/'>Shimmie2</a>
|
||||
</small></small>
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,7 @@ require_once "config.php";
|
|||
class ImageIO extends Extension
|
||||
{
|
||||
/** @var ImageIOTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public const COLLISION_OPTIONS = [
|
||||
'Error'=>ImageConfig::COLLISION_ERROR,
|
||||
|
|
|
@ -60,7 +60,7 @@ class AddImageHashBanEvent extends Event
|
|||
class ImageBan extends Extension
|
||||
{
|
||||
/** @var ImageBanTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class ImageViewCounter extends Extension
|
||||
{
|
||||
/** @var ImageViewCounterTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
private int $view_interval = 3600; # allows views to be added each hour
|
||||
|
||||
# Add Setup Block with options for view counter
|
||||
|
|
|
@ -10,7 +10,7 @@ require_once "events.php";
|
|||
class Index extends Extension
|
||||
{
|
||||
/** @var IndexTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
@ -150,10 +150,7 @@ class Index extends Extension
|
|||
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||
{
|
||||
if ($event->key===HelpPages::SEARCH) {
|
||||
$block = new Block();
|
||||
$block->header = "General";
|
||||
$block->body = $this->theme->get_help_html();
|
||||
$event->add_block($block, 0);
|
||||
$event->add_block(new Block("General", $this->theme->get_help_html()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +163,7 @@ class Index extends Extension
|
|||
}
|
||||
if ($event->cmd == "search") {
|
||||
$query = count($event->args) > 0 ? Tag::explode($event->args[0]) : [];
|
||||
$items = Image::find_images(0, 1000, $query);
|
||||
$items = Image::find_images(limit: 1000, tags: $query);
|
||||
foreach ($items as $item) {
|
||||
print("{$item->hash}\n");
|
||||
}
|
||||
|
@ -259,7 +256,7 @@ class Index extends Extension
|
|||
}
|
||||
|
||||
// If we've reached this far, and nobody else has done anything with this term, then treat it as a tag
|
||||
if ($event->order == null && $event->img_conditions == [] && $event->tag_conditions == []) {
|
||||
if ($event->order === null && $event->img_conditions == [] && $event->tag_conditions == []) {
|
||||
$event->add_tag_condition(new TagCondition($event->term, $event->positive));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
});
|
||||
|
||||
function select_blocked_tags() {
|
||||
var blocked_tags = prompt("Enter tags to ignore", Cookies.get("ui-blocked-tags") || "My_Little_Pony");
|
||||
var blocked_tags = prompt("Enter tags to ignore", Cookies.get("ui-blocked-tags") || "AI-generated");
|
||||
if(blocked_tags !== null) {
|
||||
Cookies.set("ui-blocked-tags", blocked_tags.toLowerCase(), {expires: 365});
|
||||
location.reload(true);
|
||||
|
|
|
@ -4,6 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
use function MicroHTML\{BR,H3,HR,P};
|
||||
|
||||
class IndexTheme extends Themelet
|
||||
{
|
||||
protected int $page_number;
|
||||
|
@ -82,7 +87,7 @@ and of course start organising your images :-)
|
|||
$h_next = ($page_number >= $total_pages) ? "Next" : '<a href="'.make_link('post/list'.$query.'/'.$next).'">Next</a>';
|
||||
|
||||
$h_search_string = html_escape(Tag::implode($search_terms));
|
||||
$h_search_link = make_link();
|
||||
$h_search_link = make_link("post/list");
|
||||
$h_search = "
|
||||
<p><form action='$h_search_link' method='GET'>
|
||||
<input type='search' name='search' value='$h_search_string' placeholder='Search' class='autocomplete_tags' autocomplete='off' />
|
||||
|
@ -182,227 +187,95 @@ and of course start organising your images :-)
|
|||
}
|
||||
}
|
||||
|
||||
public function get_help_html(): string
|
||||
public function get_help_html(): HTMLElement
|
||||
{
|
||||
return '<p>Searching is largely based on tags, with a number of special keywords available that allow searching based on properties of the posts.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagname</pre>
|
||||
<p>Returns posts that are tagged with "tagname".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagname othertagname</pre>
|
||||
<p>Returns posts that are tagged with "tagname" and "othertagname".</p>
|
||||
</div>
|
||||
|
||||
<p>Most tags and keywords can be prefaced with a negative sign (-) to indicate that you want to search for posts that do not match something.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>-tagname</pre>
|
||||
<p>Returns posts that are not tagged with "tagname".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>-tagname -othertagname</pre>
|
||||
<p>Returns posts that are not tagged with "tagname" and "othertagname". This is different than without the negative sign, as posts with "tagname" or "othertagname" can still be returned as long as the other one is not present.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagname -othertagname</pre>
|
||||
<p>Returns posts that are tagged with "tagname", but are not tagged with "othertagname".</p>
|
||||
</div>
|
||||
|
||||
<p>Wildcard searches are possible as well using * for "any one, more, or none" and ? for "any one".</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagn*</pre>
|
||||
<p>Returns posts that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagn?me</pre>
|
||||
<p>Returns posts that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tags=1</pre>
|
||||
<p>Returns posts with exactly 1 tag.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tags>0</pre>
|
||||
<p>Returns posts with 1 or more tags. </p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by aspect ratio</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>ratio=4:3</pre>
|
||||
<p>Returns posts with an aspect ratio of 4:3.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>ratio>16:9</pre>
|
||||
<p>Returns posts with an aspect ratio greater than 16:9. </p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =. The relation is calculated by dividing width by height.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by file size</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>filesize=1</pre>
|
||||
<p>Returns posts exactly 1 byte in size.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>filesize>100mb</pre>
|
||||
<p>Returns posts greater than 100 megabytes in size. </p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =. Supported suffixes are kb, mb, and gb. Uses multiples of 1024.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by MD5 hash</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>hash=0D3512CAA964B2BA5D7851AF5951F33B</pre>
|
||||
<p>Returns post with an MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B.</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by file name</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>filename=picasso.jpg</pre>
|
||||
<p>Returns posts that are named "picasso.jpg".</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by source</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>source=https:///google.com/</pre>
|
||||
<p>Returns posts with a source of "https://google.com/".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>source=any</pre>
|
||||
<p>Returns posts with a source set.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>source=none</pre>
|
||||
<p>Returns posts without a source set.</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by date posted.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>posted>=2019-07-19</pre>
|
||||
<p>Returns posts posted on or after 2019-07-19.</p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =. Date format is yyyy-mm-dd. Date posted includes time component, so = will not work unless the time is exact.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by length.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>length>=1h</pre>
|
||||
<p>Returns posts that are longer than an hour.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>length<=10h15m</pre>
|
||||
<p>Returns posts that are shorter than 10 hours and 15 minutes.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>length>=10000</pre>
|
||||
<p>Returns posts that are longer than 10,000 milliseconds, or 10 seconds.</p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =. Available suffixes are ms, s, m, h, d, and y. A number by itself will be interpreted as milliseconds. Searches using = are not likely to work unless time is specified down to the millisecond.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by dimensions</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>size=640x480</pre>
|
||||
<p>Returns posts exactly 640 pixels wide by 480 pixels high.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>size>1920x1080</pre>
|
||||
<p>Returns posts with a width larger than 1920 and a height larger than 1080.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>width=1000</pre>
|
||||
<p>Returns posts exactly 1000 pixels wide.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>height=1000</pre>
|
||||
<p>Returns posts exactly 1000 pixels high.</p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by ID</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>id=1234</pre>
|
||||
<p>Find the 1234th thing uploaded.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>id>1234</pre>
|
||||
<p>Find more recently posted things</p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Sorting search results can be done using the pattern order:field_direction. _direction can be either _asc or _desc, indicating ascending (123) or descending (321) order.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>order:id_asc</pre>
|
||||
<p>Returns posts sorted by ID, smallest first.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>order:width_desc</pre>
|
||||
<p>Returns posts sorted by width, largest first.</p>
|
||||
</div>
|
||||
|
||||
<p>These fields are supported:
|
||||
<ul>
|
||||
<li>id</li>
|
||||
<li>width</li>
|
||||
<li>height</li>
|
||||
<li>filesize</li>
|
||||
<li>filename</li>
|
||||
</ul>
|
||||
</p>
|
||||
';
|
||||
return emptyHTML(
|
||||
H3("Tag Searching"),
|
||||
P("Searching is largely based on tags, with a number of special keywords available that allow searching based on properties of the posts."),
|
||||
SHM_COMMAND_EXAMPLE("tagname", 'Returns posts that are tagged with "tagname".'),
|
||||
SHM_COMMAND_EXAMPLE("tagname othertagname", 'Returns posts that are tagged with "tagname" and "othertagme".'),
|
||||
//
|
||||
BR(),
|
||||
P("Most tags and keywords can be prefaced with a negative sign (-) to indicate that you want to search for posts that do not match something."),
|
||||
SHM_COMMAND_EXAMPLE("-tagname", 'Returns posts that are not tagged with "tagname".'),
|
||||
SHM_COMMAND_EXAMPLE("-tagname -othertagname", 'Returns posts that are not tagged with "tagname" or "othertagname".'),
|
||||
SHM_COMMAND_EXAMPLE("tagname -othertagname", 'Returns posts that are tagged with "tagname", but are not tagged with "othertagname".'),
|
||||
//
|
||||
BR(),
|
||||
P('Wildcard searches are possible as well using * for "any one, more, or none" and ? for "any one".'),
|
||||
SHM_COMMAND_EXAMPLE("tagn*", 'Returns posts that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".'),
|
||||
SHM_COMMAND_EXAMPLE("tagn?me", 'Returns posts that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".'),
|
||||
//
|
||||
//
|
||||
//
|
||||
HR(),
|
||||
H3("Comparing values (<, <=, >, >=, or =)"),
|
||||
P("For example, you can use this to count tags."),
|
||||
SHM_COMMAND_EXAMPLE("tags=1", "Returns posts with exactly 1 tag."),
|
||||
SHM_COMMAND_EXAMPLE("tags>0", "Returns posts with 1 or more tags."),
|
||||
//
|
||||
BR(),
|
||||
P("Searching for posts by aspect ratio."),
|
||||
P("The relation is calculated as: width / height."),
|
||||
SHM_COMMAND_EXAMPLE("ratio=4:3", "Returns posts with an aspect ratio of 4:3."),
|
||||
SHM_COMMAND_EXAMPLE("ratio>16:9", "Returns posts with an aspect ratio greater than 16:9."),
|
||||
//
|
||||
BR(),
|
||||
P("Searching by dimentions."),
|
||||
SHM_COMMAND_EXAMPLE("size=640x480", "Returns posts exactly 640 pixels wide by 480 pixels high."),
|
||||
SHM_COMMAND_EXAMPLE("size>1920x1080", "Returns posts with a width larger than 1920 and a height larger than 1080."),
|
||||
SHM_COMMAND_EXAMPLE("width=1000", "Returns posts exactly 1000 pixels wide."),
|
||||
SHM_COMMAND_EXAMPLE("height=1000", "Returns posts exactly 1000 pixels high."),
|
||||
//
|
||||
BR(),
|
||||
P("Searching by file size."),
|
||||
P("Supported suffixes are kb, mb, and gb. Uses multiples of 1024."),
|
||||
SHM_COMMAND_EXAMPLE("filesize=1", "Returns posts exactly 1 byte in size"),
|
||||
SHM_COMMAND_EXAMPLE("filesize>100mb", "Returns posts greater than 100 megabytes in size."),
|
||||
//
|
||||
BR(),
|
||||
P("Searching by date posted."),
|
||||
P("Date format is yyyy-mm-dd. Date posted includes time component, so = will not work unless the time is exact."),
|
||||
SHM_COMMAND_EXAMPLE("posted>=2019-07-19", "Returns posts posted on or after 2019-07-19."),
|
||||
//
|
||||
BR(),
|
||||
P("Searching posts by media length."),
|
||||
P("Available suffixes are ms, s, m, h, d, and y. A number by itself will be interpreted as milliseconds. Searches using = are not likely to work unless time is specified down to the millisecond."),
|
||||
SHM_COMMAND_EXAMPLE("length>=1h", "Returns posts that are longer than an hour."),
|
||||
SHM_COMMAND_EXAMPLE("length<=10h15m", "Returns posts that are shorter than 10 hours and 15 minutes."),
|
||||
SHM_COMMAND_EXAMPLE("length>=10000", "Returns posts that are longer than 10,000 milliseconds, or 10 seconds."),
|
||||
//
|
||||
BR(),
|
||||
P("Searching posts by ID."),
|
||||
SHM_COMMAND_EXAMPLE("id=1234", "Find the 1234th thing uploaded."),
|
||||
SHM_COMMAND_EXAMPLE("id>1234", "Find more recently posted things."),
|
||||
//
|
||||
//
|
||||
//
|
||||
HR(),
|
||||
H3("Post attributes."),
|
||||
P("Searching by MD5 hash."),
|
||||
SHM_COMMAND_EXAMPLE("hash=0D3512CAA964B2BA5D7851AF5951F33B", "Returns post with MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B."),
|
||||
//
|
||||
BR(),
|
||||
P("Searching by file name."),
|
||||
SHM_COMMAND_EXAMPLE("filename=picasso.jpg", 'Returns posts that are named "picasso.jpg".'),
|
||||
//
|
||||
BR(),
|
||||
P("Searching for posts by source."),
|
||||
SHM_COMMAND_EXAMPLE("source=https:///google.com/", 'Returns posts with a source of "https://google.com/".'),
|
||||
SHM_COMMAND_EXAMPLE("source=any", "Returns posts with a source set."),
|
||||
SHM_COMMAND_EXAMPLE("source=none", "Returns posts without a source set."),
|
||||
//
|
||||
//
|
||||
//
|
||||
HR(),
|
||||
H3("Sorting search results"),
|
||||
P("Sorting can be done using the pattern order:field_direction."),
|
||||
P("Supported fields: id, width, height, filesize, filename."),
|
||||
P("Direction can be either asc or desc, indicating ascending (123) or descending (321) order."),
|
||||
SHM_COMMAND_EXAMPLE("order:id_asc", "Returns posts sorted by ID, smallest first."),
|
||||
SHM_COMMAND_EXAMPLE("order:width_desc", "Returns posts sorted by width, largest first."),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ class AddIPBanEvent extends Event
|
|||
class IPBan extends Extension
|
||||
{
|
||||
/** @var IPBanTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function get_priority(): int
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class LinkImage extends Extension
|
||||
{
|
||||
/** @var LinkImageTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
{
|
||||
|
|
|
@ -61,7 +61,7 @@ class LinkImageTheme extends Themelet
|
|||
|
||||
protected function url(string $url, string $content, string $type): string
|
||||
{
|
||||
if ($content == null) {
|
||||
if (empty($content)) {
|
||||
$content=$url;
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,13 @@ class MessageColumn extends Column
|
|||
|
||||
public function get_sql_filter(): string
|
||||
{
|
||||
return "({$this->name} LIKE :{$this->name}_0 AND priority >= :{$this->name}_1)";
|
||||
$driver = $this->table->db->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||
switch ($driver) {
|
||||
case "pgsql":
|
||||
return "(LOWER({$this->name}) LIKE LOWER(:{$this->name}_0) AND priority >= :{$this->name}_1)";
|
||||
default:
|
||||
return "({$this->name} LIKE :{$this->name}_0 AND priority >= :{$this->name}_1)";
|
||||
}
|
||||
}
|
||||
|
||||
public function read_input(array $inputs)
|
||||
|
@ -222,7 +228,7 @@ class LogTable extends Table
|
|||
class LogDatabase extends Extension
|
||||
{
|
||||
/** @var LogDatabaseTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ class MediaException extends SCoreException
|
|||
class Media extends Extension
|
||||
{
|
||||
/** @var MediaTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
private const LOSSLESS_FORMATS = [
|
||||
MimeType::WEBP_LOSSLESS,
|
||||
|
@ -83,7 +83,7 @@ class Media extends Extension
|
|||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$sb = $event->panel->create_new_block("Media Engines");
|
||||
$sb = $event->panel->create_new_block("Media Engine Commands");
|
||||
|
||||
// if (self::imagick_available()) {
|
||||
// try {
|
||||
|
@ -95,7 +95,6 @@ class Media extends Extension
|
|||
// }
|
||||
// } else {
|
||||
$sb->start_table();
|
||||
$sb->add_table_header("Commands");
|
||||
|
||||
$sb->add_text_option(MediaConfig::CONVERT_PATH, "convert", true);
|
||||
// }
|
||||
|
@ -369,8 +368,8 @@ class Media extends Extension
|
|||
global $config;
|
||||
|
||||
$ffprobe = $config->get_string(MediaConfig::FFPROBE_PATH);
|
||||
if ($ffprobe == null || $ffprobe == "") {
|
||||
throw new MediaException("ffprobe command configured");
|
||||
if (empty($ffprobe)) {
|
||||
throw new MediaException("ffprobe command not configured");
|
||||
}
|
||||
|
||||
$args = [
|
||||
|
@ -657,7 +656,7 @@ class Media extends Extension
|
|||
$width = $info[0];
|
||||
$height = $info[1];
|
||||
|
||||
if ($output_mime == null) {
|
||||
if ($output_mime === null) {
|
||||
/* If not specified, output to the same format as the original image */
|
||||
switch ($info[2]) {
|
||||
case IMAGETYPE_GIF:
|
||||
|
|
|
@ -11,7 +11,7 @@ require_once "mime_type.php";
|
|||
class MimeSystem extends Extension
|
||||
{
|
||||
/** @var MimeSystemTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public const VERSION = "ext_mime_version";
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ class MimeType
|
|||
|
||||
for ($i = 0; $i < $cc; $i++) {
|
||||
$byte = $comparison[$i];
|
||||
if ($byte == null) {
|
||||
if ($byte === null) {
|
||||
continue;
|
||||
} else {
|
||||
$fileByte = $chunk[$i + 1];
|
||||
|
|
|
@ -33,7 +33,7 @@ class NotATagTable extends Table
|
|||
class NotATag extends Extension
|
||||
{
|
||||
/** @var NotATagTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function get_priority(): int
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class Notes extends Extension
|
||||
{
|
||||
/** @var NotesTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||
{
|
||||
|
|
|
@ -102,7 +102,7 @@ class NumericScoreSetEvent extends Event
|
|||
class NumericScore extends Extension
|
||||
{
|
||||
/** @var NumericScoreTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
{
|
||||
|
|
|
@ -9,6 +9,8 @@ use GQLA\Field;
|
|||
use GQLA\Query;
|
||||
use GQLA\Mutation;
|
||||
|
||||
use function MicroHTML\{emptyHTML, SPAN};
|
||||
|
||||
class SendPMEvent extends Event
|
||||
{
|
||||
public PM $pm;
|
||||
|
@ -139,7 +141,7 @@ class PM
|
|||
class PrivMsg extends Extension
|
||||
{
|
||||
/** @var PrivMsgTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
|
||||
{
|
||||
|
@ -185,8 +187,8 @@ class PrivMsg extends Extension
|
|||
if ($event->parent==="user") {
|
||||
if ($user->can(Permissions::READ_PM)) {
|
||||
$count = $this->count_pms($user);
|
||||
$h_count = $count > 0 ? " <span class='unread'>($count)</span>" : "";
|
||||
$event->add_nav_link("pm", new Link('user#private-messages'), "Private Messages$h_count");
|
||||
$h_count = $count > 0 ? SPAN(["class"=>'unread'], "($count)") : "";
|
||||
$event->add_nav_link("pm", new Link('user#private-messages'), emptyHTML("Private Messages", $h_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -196,8 +198,8 @@ class PrivMsg extends Extension
|
|||
global $user;
|
||||
if ($user->can(Permissions::READ_PM)) {
|
||||
$count = $this->count_pms($user);
|
||||
$h_count = $count > 0 ? " <span class='unread'>($count)</span>" : "";
|
||||
$event->add_link("Private Messages$h_count", make_link("user", null, "private-messages"));
|
||||
$h_count = $count > 0 ? SPAN(["class"=>'unread'], "($count)") : "";
|
||||
$event->add_link(emptyHTML("Private Messages", $h_count), make_link("user", null, "private-messages"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ function _image_to_id(Image $image): int
|
|||
class Pools extends Extension
|
||||
{
|
||||
/** @var PoolsTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
@ -357,9 +357,8 @@ class Pools extends Extension
|
|||
case "import":
|
||||
if ($this->have_permission($user, $pool)) {
|
||||
$images = Image::find_images(
|
||||
0,
|
||||
$config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
|
||||
Tag::explode($_POST["pool_tag"])
|
||||
limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
|
||||
tags: Tag::explode($_POST["pool_tag"])
|
||||
);
|
||||
$this->theme->pool_result($page, $images, $pool);
|
||||
} else {
|
||||
|
@ -467,14 +466,14 @@ class Pools extends Extension
|
|||
{
|
||||
global $config, $database, $user;
|
||||
if ($config->get_bool(PoolsConfig::ADDER_ON_VIEW_IMAGE) && !$user->is_anonymous()) {
|
||||
$pools = [];
|
||||
if ($user->can(Permissions::POOLS_ADMIN)) {
|
||||
$rows = $database->get_all("SELECT * FROM pools");
|
||||
$pools = $database->get_pairs("SELECT id,title FROM pools ORDER BY title");
|
||||
} else {
|
||||
$rows = $database->get_all("SELECT * FROM pools WHERE user_id=:id", ["id" => $user->id]);
|
||||
$pools = $database->get_pairs("SELECT id,title FROM pools WHERE user_id=:id ORDER BY title", ["id" => $user->id]);
|
||||
}
|
||||
if (count($rows) > 0) {
|
||||
$pools = array_map([Pool::class, "makePool"], $rows);
|
||||
$event->add_part($this->theme->get_adder_html($event->image, $pools));
|
||||
if (count($pools) > 0) {
|
||||
$event->add_part((string)$this->theme->get_adder_html($event->image, $pools));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -482,10 +481,7 @@ class Pools extends Extension
|
|||
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||
{
|
||||
if ($event->key===HelpPages::SEARCH) {
|
||||
$block = new Block();
|
||||
$block->header = "Pools";
|
||||
$block->body = $this->theme->get_help_html();
|
||||
$event->add_block($block);
|
||||
$event->add_block(new Block("Pools", $this->theme->get_help_html()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -554,13 +550,11 @@ class Pools extends Extension
|
|||
{
|
||||
global $database;
|
||||
|
||||
$pools = array_map(
|
||||
[Pool::class, "makePool"],
|
||||
$database->get_all("SELECT * FROM pools ORDER BY title ")
|
||||
);
|
||||
$options = $database->get_pairs("SELECT id,title FROM pools ORDER BY title");
|
||||
|
||||
$event->add_action("bulk_pool_add_existing", "Add To (P)ool", "p", "", $this->theme->get_bulk_pool_selector($pools));
|
||||
$event->add_action("bulk_pool_add_new", "Create Pool", "", "", $this->theme->get_bulk_pool_input($event->search_terms));
|
||||
// TODO: Don't cast into strings, make BABBE accept HTMLElement instead.
|
||||
$event->add_action("bulk_pool_add_existing", "Add To (P)ool", "p", "", (string)$this->theme->get_bulk_pool_selector($options));
|
||||
$event->add_action("bulk_pool_add_new", "Create Pool", "", "", (string)$this->theme->get_bulk_pool_input($event->search_terms));
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
|
|
|
@ -4,6 +4,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
use function MicroHTML\rawHTML;
|
||||
use function MicroHTML\{A,BR,DIV,INPUT,P,SCRIPT,SPAN,TABLE,TBODY,TD,TEXTAREA,TH,THEAD,TR};
|
||||
|
||||
class PoolsTheme extends Themelet
|
||||
{
|
||||
/**
|
||||
|
@ -14,44 +20,36 @@ class PoolsTheme extends Themelet
|
|||
{
|
||||
global $page;
|
||||
|
||||
$linksPools = [];
|
||||
//TODO: Use a 3 column table?
|
||||
$linksPools = emptyHTML();
|
||||
foreach ($navIDs as $poolID => $poolInfo) {
|
||||
$linksPools[] = "<a href='" . make_link("pool/view/" . $poolID) . "'>" . html_escape($poolInfo['info']->title) . "</a>";
|
||||
$div = DIV(SHM_A("pool/view/" . $poolID, $poolInfo["info"]->title));
|
||||
|
||||
if (!empty($poolInfo['nav'])) {
|
||||
$navlinks = "";
|
||||
if (!empty($poolInfo['nav']['prev'])) {
|
||||
$navlinks .= '<a href="' . make_link('post/view/' . $poolInfo['nav']['prev']) . '" class="pools_prev_img">Prev</a>';
|
||||
if (!empty($poolInfo["nav"])) {
|
||||
if (!empty($poolInfo["nav"]["prev"])) {
|
||||
$div->appendChild(SHM_A("post/view/" . $poolInfo["nav"]["prev"], "Prev", class: "pools_prev_img"));
|
||||
}
|
||||
if (!empty($poolInfo['nav']['next'])) {
|
||||
$navlinks .= '<a href="' . make_link('post/view/' . $poolInfo['nav']['next']) . '" class="pools_next_img">Next</a>';
|
||||
}
|
||||
if (!empty($navlinks)) {
|
||||
$navlinks .= "<div style='height: 5px'></div>";
|
||||
$linksPools[] = $navlinks;
|
||||
if (!empty($poolInfo["nav"]["next"])) {
|
||||
$div->appendChild(SHM_A("post/view/" . $poolInfo["nav"]["next"], "Next", class: "pools_next_img"));
|
||||
}
|
||||
}
|
||||
|
||||
$linksPools->appendChild($div);
|
||||
}
|
||||
|
||||
if (count($linksPools) > 0) {
|
||||
$page->add_block(new Block("Pools", implode("<br>", $linksPools), "left"));
|
||||
if (!empty($navIDs)) {
|
||||
$page->add_block(new Block("Pools", $linksPools, "left"));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_adder_html(Image $image, array $pools): string
|
||||
public function get_adder_html(Image $image, array $pools): HTMLElement
|
||||
{
|
||||
$h = "";
|
||||
foreach ($pools as $pool) {
|
||||
$h .= "<option value='" . $pool->id . "'>" . html_escape($pool->title) . "</option>";
|
||||
}
|
||||
return "\n" . make_form(make_link("pool/add_post")) . "
|
||||
<select name='pool_id'>
|
||||
$h
|
||||
</select>
|
||||
<input type='hidden' name='image_id' value='{$image->id}'>
|
||||
<input type='submit' value='Add Post to Pool'>
|
||||
</form>
|
||||
";
|
||||
return SHM_SIMPLE_FORM(
|
||||
"pool/add_post",
|
||||
SHM_SELECT("pool_id", $pools),
|
||||
INPUT(["type"=>"hidden", "name"=>"image_id", "value"=>$image->id]),
|
||||
SHM_SUBMIT("Add Post to Pool")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,45 +57,34 @@ class PoolsTheme extends Themelet
|
|||
*/
|
||||
public function list_pools(Page $page, array $pools, int $pageNumber, int $totalPages)
|
||||
{
|
||||
$html = '
|
||||
<table id="poolsList" class="zebra">
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>Creator</th>
|
||||
<th>Posts</th>
|
||||
<th>Public</th>
|
||||
</tr></thead><tbody>';
|
||||
|
||||
// Build up the list of pools.
|
||||
$pool_rows = [];
|
||||
foreach ($pools as $pool) {
|
||||
$pool_link = '<a href="' . make_link("pool/view/" . $pool->id) . '">' . html_escape($pool->title) . "</a>";
|
||||
$user_link = '<a href="' . make_link("user/" . url_escape($pool->user_name)) . '">' . html_escape($pool->user_name) . "</a>";
|
||||
$public = ($pool->public ? "Yes" : "No");
|
||||
$pool_link = SHM_A("pool/view/" . $pool->id, $pool->title);
|
||||
$user_link = SHM_A("user/" . url_escape($pool->user_name), $pool->user_name);
|
||||
|
||||
$html .= "<tr>" .
|
||||
"<td class='left'>" . $pool_link . "</td>" .
|
||||
"<td>" . $user_link . "</td>" .
|
||||
"<td>" . $pool->posts . "</td>" .
|
||||
"<td>" . $public . "</td>" .
|
||||
"</tr>";
|
||||
$pool_rows[] = TR(
|
||||
TD(["class"=>"left"], $pool_link),
|
||||
TD($user_link),
|
||||
TD($pool->posts),
|
||||
TD($pool->public ? "Yes" : "No")
|
||||
);
|
||||
}
|
||||
|
||||
$html .= "</tbody></table>";
|
||||
$table = TABLE(
|
||||
["id"=>"poolsList", "class"=>"zebra"],
|
||||
THEAD(TR(TH("Name"), TH("Creator"), TH("Posts"), TH("Public"))),
|
||||
TBODY(...$pool_rows)
|
||||
);
|
||||
|
||||
$order_html = '<select id="order_pool">';
|
||||
$order_selected = $page->get_cookie('ui-order-pool');
|
||||
$order_arr = ['created' => 'Recently created', 'updated' => 'Last updated', 'name' => 'Name', 'count' => 'Post Count'];
|
||||
foreach ($order_arr as $value => $text) {
|
||||
$selected = ($value == $order_selected ? "selected" : "");
|
||||
$order_html .= "<option value=\"{$value}\" {$selected}>{$text}</option>\n";
|
||||
}
|
||||
$order_html .= '</select>';
|
||||
$order_selected = $page->get_cookie('ui-order-pool');
|
||||
$order_sel = SHM_SELECT("order_pool", $order_arr, selected_options: [$order_selected], attrs: ["id"=>"order_pool"]);
|
||||
|
||||
$this->display_top(null, "Pools");
|
||||
$page->add_block(new Block("Order By", $order_html, "left", 15));
|
||||
|
||||
$page->add_block(new Block("Pools", $html, "main", 10));
|
||||
$page->add_block(new Block("Order By", $order_sel, "left", 15));
|
||||
|
||||
$page->add_block(new Block("Pools", $table, position: 10));
|
||||
|
||||
$this->display_paginator($page, "pool/list", null, $pageNumber, $totalPages);
|
||||
}
|
||||
|
@ -107,19 +94,15 @@ class PoolsTheme extends Themelet
|
|||
*/
|
||||
public function new_pool_composer(Page $page)
|
||||
{
|
||||
$create_html = "
|
||||
" . make_form(make_link("pool/create")) . "
|
||||
<table>
|
||||
<tr><td>Title:</td><td><input type='text' name='title'></td></tr>
|
||||
<tr><td>Public?</td><td><input name='public' type='checkbox' value='Y' checked='checked'/></td></tr>
|
||||
<tr><td>Description:</td><td><textarea name='description'></textarea></td></tr>
|
||||
<tr><td colspan='2'><input type='submit' value='Create' /></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
";
|
||||
$form = SHM_SIMPLE_FORM("pool/create", TABLE(
|
||||
TR(TD("Title:"), TD(INPUT(["type"=>"text", "name"=>"title"]))),
|
||||
TR(TD("Public?:"), TD(INPUT(["type"=>"checkbox", "name"=>"public", "value"=>"Y", "checked"=>"checked"]))),
|
||||
TR(TD("Description:"), TD(TEXTAREA(["name"=>"description"]))),
|
||||
TR(TD(["colspan"=>"2"], SHM_SUBMIT("Create")))
|
||||
));
|
||||
|
||||
$this->display_top(null, "Create Pool");
|
||||
$page->add_block(new Block("Create Pool", $create_html, "main", 20));
|
||||
$page->add_block(new Block("Create Pool", $form, position: 20));
|
||||
}
|
||||
|
||||
private function display_top(?Pool $pool, string $heading, bool $check_all = false)
|
||||
|
@ -129,14 +112,16 @@ class PoolsTheme extends Themelet
|
|||
$page->set_title($heading);
|
||||
$page->set_heading($heading);
|
||||
|
||||
$poolnav_html = '
|
||||
<a href="' . make_link("pool/list") . '">Pool Index</a>
|
||||
<br><a href="' . make_link("pool/new") . '">Create Pool</a>
|
||||
<br><a href="' . make_link("pool/updated") . '">Pool Changes</a>
|
||||
';
|
||||
$poolnav = emptyHTML(
|
||||
SHM_A("pool/list", "Pool Index"),
|
||||
BR(),
|
||||
SHM_A("pool/new", "Create Pool"),
|
||||
BR(),
|
||||
SHM_A("pool/updated", "Pool Changes")
|
||||
);
|
||||
|
||||
$page->add_block(new NavBlock());
|
||||
$page->add_block(new Block("Pool Navigation", $poolnav_html, "left", 10));
|
||||
$page->add_block(new Block("Pool Navigation", $poolnav, "left", 10));
|
||||
|
||||
if (!is_null($pool)) {
|
||||
if ($pool->public || $user->can(Permissions::POOLS_ADMIN)) {// IF THE POOL IS PUBLIC OR IS ADMIN SHOW EDIT PANEL
|
||||
|
@ -158,10 +143,9 @@ class PoolsTheme extends Themelet
|
|||
|
||||
$this->display_top($pool, "Pool: " . html_escape($pool->title));
|
||||
|
||||
$pool_images = '';
|
||||
$pool_images = emptyHTML();
|
||||
foreach ($images as $image) {
|
||||
$thumb_html = $this->build_thumb_html($image);
|
||||
$pool_images .= "\n" . $thumb_html . "\n";
|
||||
$pool_images->appendChild($this->build_thumb_html($image));
|
||||
}
|
||||
|
||||
$page->add_block(new Block("Viewing Posts", $pool_images, "main", 30));
|
||||
|
@ -176,63 +160,77 @@ class PoolsTheme extends Themelet
|
|||
{
|
||||
global $user;
|
||||
|
||||
$editor = "\n" . make_form(make_link('pool/import')) . '
|
||||
<input type="text" name="pool_tag" id="edit_pool_tag" placeholder="Please enter a tag"/>
|
||||
<input type="submit" name="edit" id="edit_pool_import_btn" value="Import"/>
|
||||
<input type="hidden" name="pool_id" value="' . $pool->id . '">
|
||||
</form>
|
||||
// This could become a SHM_INPUT function that also accepts 'type' and other attributes.
|
||||
$_hidden=function (string $name, $value) {
|
||||
return INPUT(["type"=>"hidden", "name"=>$name, "value"=>$value]);
|
||||
};
|
||||
|
||||
' . make_form(make_link('pool/edit')) . '
|
||||
<input type="submit" name="edit" id="edit_pool_btn" value="Edit Pool"/>
|
||||
<input type="hidden" name="edit_pool" value="yes">
|
||||
<input type="hidden" name="pool_id" value="' . $pool->id . '">
|
||||
</form>
|
||||
$_input_id = $_hidden("pool_id", $pool->id);
|
||||
|
||||
' . make_form(make_link('pool/order')) . '
|
||||
<input type="submit" name="edit" id="edit_pool_order_btn" value="Order Pool"/>
|
||||
<input type="hidden" name="order_view" value="yes">
|
||||
<input type="hidden" name="pool_id" value="' . $pool->id . '">
|
||||
</form>
|
||||
' . make_form(make_link('pool/reverse')) . '
|
||||
<input type="submit" name="edit" id="reverse_pool_order_btn" value="Reverse Order"/>
|
||||
<input type="hidden" name="reverse_view" value="yes">
|
||||
<input type="hidden" name="pool_id" value="' . $pool->id . '">
|
||||
</form>
|
||||
' . make_form(make_link('post/list/pool_id%3A' . $pool->id . '/1')) . '
|
||||
<input type="submit" name="edit" id="postlist_pool_btn" value="Post/List View"/>
|
||||
</form>
|
||||
';
|
||||
$editor = emptyHTML(
|
||||
SHM_SIMPLE_FORM(
|
||||
"pool/import",
|
||||
INPUT(["type"=>"text", "name"=>"pool_tag", "id"=>"edit_pool_tag", "placeholder"=>"Please enter a tag"]),
|
||||
$_input_id,
|
||||
SHM_SUBMIT("Import", ["name"=>"edit", "id"=>"edit_pool_import_btn"])
|
||||
),
|
||||
SHM_SIMPLE_FORM(
|
||||
"pool/edit",
|
||||
$_hidden("edit_pool", "yes"),
|
||||
$_input_id,
|
||||
SHM_SUBMIT("Edit Pool", ["name"=>"edit", "id"=>"edit_pool_btn"]),
|
||||
),
|
||||
SHM_SIMPLE_FORM(
|
||||
"pool/order",
|
||||
$_hidden("order_view", "yes"),
|
||||
$_input_id,
|
||||
SHM_SUBMIT("Order Pool", ["name"=>"edit", "id"=>"edit_pool_order_btn"])
|
||||
),
|
||||
SHM_SIMPLE_FORM(
|
||||
"pool/reverse",
|
||||
$_hidden("reverse_view", "yes"),
|
||||
$_input_id,
|
||||
SHM_SUBMIT("Reverse Order", ["name"=>"edit", "id"=>"reverse_pool_order_btn"])
|
||||
),
|
||||
SHM_SIMPLE_FORM(
|
||||
"pool/list/pool_id%3A" . $pool->id . "/1",
|
||||
SHM_SUBMIT("Post/List View", ["name"=>"edit", "id"=>"postlist_pool_btn"])
|
||||
)
|
||||
);
|
||||
|
||||
if ($user->id == $pool->user_id || $user->can(Permissions::POOLS_ADMIN)) {
|
||||
$editor .= "
|
||||
<script type='text/javascript'>
|
||||
<!--
|
||||
function confirm_action() {
|
||||
return confirm('Are you sure that you want to delete this pool?');
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
|
||||
" . make_form(make_link("pool/nuke")) . "
|
||||
<input type='submit' name='delete' id='delete_pool_btn' value='Delete Pool' onclick='return confirm_action()' />
|
||||
<input type='hidden' name='pool_id' value='" . $pool->id . "'>
|
||||
</form>
|
||||
";
|
||||
$editor->appendChild(
|
||||
SCRIPT(
|
||||
["type"=>"text/javascript"],
|
||||
rawHTML("<!--
|
||||
function confirm_action() {
|
||||
return confirm('Are you sure that you want to delete this pool?');
|
||||
}
|
||||
//-->")
|
||||
),
|
||||
SHM_SIMPLE_FORM(
|
||||
"pool/nuke",
|
||||
$_input_id,
|
||||
SHM_SUBMIT("Delete Pool", ["name"=>"delete", "id"=>"delete_pool_btn", "onclick"=>"return confirm_action()"])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($check_all) {
|
||||
$editor .= "
|
||||
<script type='text/javascript'>
|
||||
<!--
|
||||
function setAll(value) {
|
||||
$('[name=\"check[]\"]').attr('checked', value);
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<br><input type='button' name='CheckAll' value='Check All' onClick='setAll(true)'>
|
||||
<input type='button' name='UnCheckAll' value='Uncheck All' onClick='setAll(false)'>
|
||||
";
|
||||
$editor->appendChild(
|
||||
SCRIPT(
|
||||
["type"=>"text/javascript"],
|
||||
rawHTML("<!--
|
||||
function setAll(value) {
|
||||
$('[name=\"check[]\"]').attr('checked', value);
|
||||
}
|
||||
//-->")
|
||||
),
|
||||
INPUT(["type"=>"button", "name"=>"CheckAll", "value"=>"Check All", "onclick"=>"setAll(true)"]),
|
||||
INPUT(["type"=>"button", "name"=>"UnCheckAll", "value"=>"Uncheck All", "onclick"=>"setAll(false)"])
|
||||
);
|
||||
}
|
||||
|
||||
$page->add_block(new Block("Manage Pool", $editor, "left", 15));
|
||||
}
|
||||
|
||||
|
@ -242,30 +240,33 @@ class PoolsTheme extends Themelet
|
|||
public function pool_result(Page $page, array $images, Pool $pool)
|
||||
{
|
||||
$this->display_top($pool, "Importing Posts", true);
|
||||
$pool_images = "
|
||||
<script type='text/javascript'>
|
||||
function confirm_action() {
|
||||
return confirm('Are you sure you want to add selected posts to this pool?');
|
||||
}
|
||||
</script>
|
||||
";
|
||||
|
||||
$pool_images .= "<form action='" . make_link("pool/add_posts") . "' method='POST' name='checks'>";
|
||||
$import = emptyHTML(
|
||||
SCRIPT(
|
||||
["type"=>"text/javascript"],
|
||||
rawHTML("
|
||||
function confirm_action() {
|
||||
return confirm('Are you sure you want to add selected posts to this pool?');
|
||||
}")
|
||||
)
|
||||
);
|
||||
|
||||
$form = SHM_FORM("pool/add_posts", name: "checks");
|
||||
foreach ($images as $image) {
|
||||
$thumb_html = $this->build_thumb_html($image);
|
||||
|
||||
$pool_images .= '<span class="thumb">' . $thumb_html . '<br>' .
|
||||
'<input name="check[]" type="checkbox" value="' . $image->id . '" />' .
|
||||
'</span>';
|
||||
$form->appendChild(
|
||||
SPAN(["class"=>"thumb"], $this->build_thumb_html($image), BR(), INPUT(["type"=>"checkbox", "name"=>"check[]", "value"=>$image->id])),
|
||||
);
|
||||
}
|
||||
|
||||
$pool_images .= "<br>" .
|
||||
"<input type='submit' name='edit' id='edit_pool_add_btn' value='Add Selected' onclick='return confirm_action()'/>" .
|
||||
"<input type='hidden' name='pool_id' value='" . $pool->id . "'>" .
|
||||
"</form>";
|
||||
$form->appendChild(
|
||||
BR(),
|
||||
SHM_SUBMIT("Add Selected", ["name"=>"edit", "id"=>"edit_pool_add_btn", "onclick"=>"return confirm_action()"]),
|
||||
INPUT(["type"=>"hidden", "name"=>"pool_id", "value"=>$pool->id])
|
||||
);
|
||||
|
||||
$page->add_block(new Block("Import", $pool_images, "main", 30));
|
||||
$import->appendChild($form);
|
||||
|
||||
$page->add_block(new Block("Import", $import, "main", 30));
|
||||
}
|
||||
|
||||
|
||||
|
@ -277,23 +278,22 @@ class PoolsTheme extends Themelet
|
|||
{
|
||||
$this->display_top($pool, "Sorting Pool");
|
||||
|
||||
$pool_images = "\n<form action='" . make_link("pool/order") . "' method='POST' name='checks'>";
|
||||
$i = 0;
|
||||
foreach ($images as $image) {
|
||||
$thumb_html = $this->build_thumb_html($image);
|
||||
$pool_images .= '<span class="thumb">' . "\n" . $thumb_html . "\n" .
|
||||
'<br><input name="imgs[' . $i . '][]" type="number" style="max-width:50px;" value="' . $image->image_order . '" />' .
|
||||
'<input name="imgs[' . $i . '][]" type="hidden" value="' . $image->id . '" />' .
|
||||
'</span>';
|
||||
$i++;
|
||||
$form = SHM_FORM("pool/order", name: "checks");
|
||||
foreach ($images as $i=>$image) {
|
||||
$form->appendChild(SPAN(
|
||||
["class"=>"thumb"],
|
||||
$this->build_thumb_html($image),
|
||||
INPUT(["type"=>"number", "name"=>"imgs[$i][]", "value"=>$image->image_order, "style"=>"max-width: 50px;"]),
|
||||
INPUT(["type"=>"hidden", "name"=>"imgs[$i][]", "value"=>$image->id])
|
||||
));
|
||||
}
|
||||
|
||||
$pool_images .= "<br>" .
|
||||
"<input type='submit' name='edit' id='edit_pool_order' value='Order'/>" .
|
||||
"<input type='hidden' name='pool_id' value='" . $pool->id . "'>" .
|
||||
"</form>";
|
||||
$form->appendChild(
|
||||
INPUT(["type"=>"hidden", "name"=>"pool_id", "value"=>$pool->id]),
|
||||
SHM_SUBMIT("Order", ["name"=>"edit", "id"=>"edit_pool_order"])
|
||||
);
|
||||
|
||||
$page->add_block(new Block("Sorting Posts", $pool_images, "main", 30));
|
||||
$page->add_block(new Block("Sorting Posts", $form, position: 30));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,35 +304,35 @@ class PoolsTheme extends Themelet
|
|||
*/
|
||||
public function edit_pool(Page $page, Pool $pool, array $images)
|
||||
{
|
||||
/* EDIT POOL DESCRIPTION */
|
||||
$desc_html = "
|
||||
" . make_form(make_link("pool/edit_description")) . "
|
||||
<textarea name='description'>" . $pool->description . "</textarea><br />
|
||||
<input type='hidden' name='pool_id' value='" . $pool->id . "'>
|
||||
<input type='submit' value='Change Description' />
|
||||
</form>
|
||||
";
|
||||
$_input_id = INPUT(["type"=>"hidden", "name"=>"pool_id", "value"=>$pool->id]);
|
||||
|
||||
/* REMOVE POOLS */
|
||||
$pool_images = "\n<form action='" . make_link("pool/remove_posts") . "' method='POST' name='checks'>";
|
||||
$desc_form = SHM_SIMPLE_FORM(
|
||||
"pool/edit/description",
|
||||
TEXTAREA(["name"=>"description"], $pool->description),
|
||||
BR(),
|
||||
$_input_id,
|
||||
SHM_SUBMIT("Change Description")
|
||||
);
|
||||
|
||||
$images_form = SHM_FORM("pool/remove_posts", name: "checks");
|
||||
foreach ($images as $image) {
|
||||
$thumb_html = $this->build_thumb_html($image);
|
||||
|
||||
$pool_images .= '<span class="thumb">' . "\n" . $thumb_html . "\n" .
|
||||
'<br><input name="check[]" type="checkbox" value="' . $image->id . '" />' .
|
||||
'</span>';
|
||||
$images_form->appendChild(SPAN(
|
||||
["class"=>"thumb"],
|
||||
$this->build_thumb_html($image),
|
||||
INPUT(["type"=>"checkbox", "name"=>"check[]", "value"=>$image->id])
|
||||
));
|
||||
}
|
||||
|
||||
$pool_images .= "<br>" .
|
||||
"<input type='submit' name='edit' id='edit_pool_remove_sel' value='Remove Selected'/>" .
|
||||
"<input type='hidden' name='pool_id' value='" . $pool->id . "'>" .
|
||||
"</form>";
|
||||
$images_form->appendChild(
|
||||
BR(),
|
||||
$_input_id,
|
||||
SHM_SUBMIT("Remove Selected", ["name"=>"edit", "id"=>"edit_pool_remove_sel"])
|
||||
);
|
||||
|
||||
$pool->description = ""; //This is a rough fix to avoid showing the description twice.
|
||||
$this->display_top($pool, "Editing Pool", true);
|
||||
$page->add_block(new Block("Editing Description", $desc_html, "main", 28));
|
||||
$page->add_block(new Block("Editing Posts", $pool_images, "main", 30));
|
||||
$page->add_block(new Block("Editing Description", $desc_form, position: 28));
|
||||
$page->add_block(new Block("Editing Posts", $images_form, position: 30));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -341,21 +341,17 @@ class PoolsTheme extends Themelet
|
|||
public function show_history(array $histories, int $pageNumber, int $totalPages)
|
||||
{
|
||||
global $page;
|
||||
$html = '
|
||||
<table id="poolsList" class="zebra">
|
||||
<thead><tr>
|
||||
<th>Pool</th>
|
||||
<th>Post Count</th>
|
||||
<th>Changes</th>
|
||||
<th>Updater</th>
|
||||
<th>Date</th>
|
||||
<th>Action</th>
|
||||
</tr></thead><tbody>';
|
||||
|
||||
$table = TABLE(
|
||||
["id"=>"poolsList", "class"=>"zebra"],
|
||||
THEAD(TR(TH("Pool"), TH("Post Count"), TH("Changes"), TH("Updater"), TH("Date"), TH("Action")))
|
||||
);
|
||||
|
||||
$body = [];
|
||||
foreach ($histories as $history) {
|
||||
$pool_link = "<a href='" . make_link("pool/view/" . $history['pool_id']) . "'>" . html_escape($history['title']) . "</a>";
|
||||
$user_link = "<a href='" . make_link("user/" . url_escape($history['user_name'])) . "'>" . html_escape($history['user_name']) . "</a>";
|
||||
$revert_link = "<a href='" . make_link("pool/revert/" . $history['id']) . "'>Revert</a>";
|
||||
$pool_link = SHM_A("pool/view/" . $history["pool_id"], $history["title"]);
|
||||
$user_link = SHM_A("user/" . url_escape($history["user_name"]), $history["user_name"]);
|
||||
$revert_link = SHM_A(("pool/revert/" . $history["id"]), "Revert");
|
||||
|
||||
if ($history['action'] == 1) {
|
||||
$prefix = "+";
|
||||
|
@ -365,69 +361,74 @@ class PoolsTheme extends Themelet
|
|||
throw new \RuntimeException("history['action'] not in {0, 1}");
|
||||
}
|
||||
|
||||
$images = trim($history['images']);
|
||||
$images = trim($history["images"]);
|
||||
$images = explode(" ", $images);
|
||||
|
||||
$image_link = "";
|
||||
$image_links = emptyHTML();
|
||||
foreach ($images as $image) {
|
||||
$image_link .= "<a href='" . make_link("post/view/" . $image) . "'>" . $prefix . $image . " </a>";
|
||||
$image_links->appendChild(" ", SHM_A("post/view/" . $image, $prefix . $image));
|
||||
}
|
||||
|
||||
$html .= "<tr>" .
|
||||
"<td class='left'>" . $pool_link . "</td>" .
|
||||
"<td>" . $history['count'] . "</td>" .
|
||||
"<td>" . $image_link . "</td>" .
|
||||
"<td>" . $user_link . "</td>" .
|
||||
"<td>" . $history['date'] . "</td>" .
|
||||
"<td>" . $revert_link . "</td>" .
|
||||
"</tr>";
|
||||
$body[] = TR(
|
||||
TD(["class"=>"left"], $pool_link),
|
||||
TD($history["count"]),
|
||||
TD($image_links),
|
||||
TD($user_link),
|
||||
TD($history["date"]),
|
||||
TD($revert_link)
|
||||
);
|
||||
}
|
||||
|
||||
$html .= "</tbody></table>";
|
||||
$table->appendChild(TBODY(...$body));
|
||||
|
||||
$this->display_top(null, "Recent Changes");
|
||||
$page->add_block(new Block("Recent Changes", $html, "main", 10));
|
||||
$page->add_block(new Block("Recent Changes", $table, position: 10));
|
||||
|
||||
$this->display_paginator($page, "pool/updated", null, $pageNumber, $totalPages);
|
||||
}
|
||||
|
||||
public function get_bulk_pool_selector(array $pools): string
|
||||
public function get_bulk_pool_selector(array $options): HTMLElement
|
||||
{
|
||||
$output = "<select name='bulk_pool_select' required='required'><option></option>";
|
||||
foreach ($pools as $pool) {
|
||||
$output .= "<option value='" . $pool->id . "' >" . $pool->title . "</option>";
|
||||
}
|
||||
return $output . "</select>";
|
||||
return SHM_SELECT("bulk_pool_select", $options, required: true, empty_option: true);
|
||||
}
|
||||
|
||||
public function get_bulk_pool_input(array $search_terms): string
|
||||
public function get_bulk_pool_input(array $search_terms): HTMLElement
|
||||
{
|
||||
return "<input type='text' name='bulk_pool_new' placeholder='New pool' required='required' value='".(implode(" ", $search_terms))."' />";
|
||||
return INPUT(
|
||||
[
|
||||
"type"=>"text",
|
||||
"name"=>"bulk_pool_new",
|
||||
"placeholder"=>"New Pool",
|
||||
"required"=>"",
|
||||
"value"=>implode(" ", $search_terms)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function get_help_html(): string
|
||||
public function get_help_html(): HTMLElement
|
||||
{
|
||||
return '<p>Search for posts that are in a pool.</p>
|
||||
<div class="command_example">
|
||||
<pre>pool=1</pre>
|
||||
<p>Returns posts in pool #1.</p>
|
||||
</div>
|
||||
<div class="command_example">
|
||||
<pre>pool=any</pre>
|
||||
<p>Returns posts in any pool.</p>
|
||||
</div>
|
||||
<div class="command_example">
|
||||
<pre>pool=none</pre>
|
||||
<p>Returns posts not in any pool.</p>
|
||||
</div>
|
||||
<div class="command_example">
|
||||
<pre>pool_by_name=swimming</pre>
|
||||
<p>Returns posts in the "swimming" pool.</p>
|
||||
</div>
|
||||
<div class="command_example">
|
||||
<pre>pool_by_name=swimming_pool</pre>
|
||||
<p>Returns posts in the "swimming pool" pool. Note that the underscore becomes a space</p>
|
||||
</div>
|
||||
';
|
||||
return emptyHTML(
|
||||
P("Search for posts that are in a pool."),
|
||||
SHM_COMMAND_EXAMPLE(
|
||||
"pool=1",
|
||||
"Returns posts in pool #1."
|
||||
),
|
||||
SHM_COMMAND_EXAMPLE(
|
||||
"pool=any",
|
||||
"Returns posts in any pool."
|
||||
),
|
||||
SHM_COMMAND_EXAMPLE(
|
||||
"pool=none",
|
||||
"Returns posts not in any pool."
|
||||
),
|
||||
SHM_COMMAND_EXAMPLE(
|
||||
"pool_by_name=swimming",
|
||||
"Returns posts in the \"swimming\" pool."
|
||||
),
|
||||
SHM_COMMAND_EXAMPLE(
|
||||
"pool_by_name=swimming_pool",
|
||||
"Returns posts in the \"swimming pool\" pool. Note that the underscore becomes a space."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ require_once "events/post_title_set_event.php";
|
|||
class PostTitles extends Extension
|
||||
{
|
||||
/** @var PostTitlesTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function get_priority(): int
|
||||
{
|
||||
|
|
|
@ -14,11 +14,11 @@ abstract class PrivateImageConfig
|
|||
class PrivateImage extends Extension
|
||||
{
|
||||
/** @var PrivateImageTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
Image::$bool_props[] = "private ";
|
||||
Image::$bool_props[] = "private";
|
||||
}
|
||||
|
||||
public function onInitUserConfig(InitUserConfigEvent $event)
|
||||
|
@ -29,9 +29,12 @@ class PrivateImage extends Extension
|
|||
|
||||
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
$sb = $event->panel->create_new_block("Private Posts");
|
||||
$sb->start_table();
|
||||
$sb->add_bool_option(PrivateImageConfig::USER_SET_DEFAULT, "Mark posts private by default", true);
|
||||
if ($user->can(Permissions::SET_PRIVATE_IMAGE)) {
|
||||
$sb->add_bool_option(PrivateImageConfig::USER_SET_DEFAULT, "Mark posts private by default", true);
|
||||
}
|
||||
$sb->add_bool_option(PrivateImageConfig::USER_VIEW_DEFAULT, "View private posts by default", true);
|
||||
$sb->end_table();
|
||||
}
|
||||
|
@ -114,7 +117,7 @@ class PrivateImage extends Extension
|
|||
{
|
||||
global $user, $page;
|
||||
|
||||
if ($event->image->private==true && $event->image->owner_id!=$user->id && !$user->can(Permissions::SET_OTHERS_PRIVATE_IMAGES)) {
|
||||
if ($event->image->private===true && $event->image->owner_id!=$user->id && !$user->can(Permissions::SET_OTHERS_PRIVATE_IMAGES)) {
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/list"));
|
||||
}
|
||||
|
@ -228,8 +231,8 @@ class PrivateImage extends Extension
|
|||
|
||||
public function onImageAddition(ImageAdditionEvent $event)
|
||||
{
|
||||
global $user_config;
|
||||
if ($user_config->get_bool(PrivateImageConfig::USER_SET_DEFAULT)) {
|
||||
global $user, $user_config;
|
||||
if ($user_config->get_bool(PrivateImageConfig::USER_SET_DEFAULT) && $user->can(Permissions::SET_PRIVATE_IMAGE)) {
|
||||
self::privatize_image($event->image->id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ class PrivateImageTheme extends Themelet
|
|||
{
|
||||
public function get_image_admin_html(Image $image): string
|
||||
{
|
||||
if ($image->private==false) {
|
||||
if ($image->private===false) {
|
||||
$html = SHM_SIMPLE_FORM(
|
||||
'privatize_image/'.$image->id,
|
||||
INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image->id]),
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class QRImage extends Extension
|
||||
{
|
||||
/** @var QRImageTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class RandomImage extends Extension
|
||||
{
|
||||
/** @var RandomImageTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class RandomList extends Extension
|
||||
{
|
||||
/** @var RandomListTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -73,7 +73,7 @@ abstract class RatingsConfig
|
|||
class Ratings extends Extension
|
||||
{
|
||||
/** @var RatingsTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public const UNRATED_KEYWORDS = ["unknown", "unrated"];
|
||||
|
||||
|
@ -183,7 +183,7 @@ class Ratings extends Extension
|
|||
public function onBulkImport(BulkImportEvent $event)
|
||||
{
|
||||
if (array_key_exists("rating", $event->fields)
|
||||
&& $event->fields['rating'] != null
|
||||
&& $event->fields['rating'] !== null
|
||||
&& Ratings::rating_is_valid($event->fields['rating'])) {
|
||||
$this->set_rating($event->image->id, $event->fields['rating'], "");
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ class Ratings extends Extension
|
|||
{
|
||||
global $user;
|
||||
$event->add_part(
|
||||
$this->theme->get_rater_html(
|
||||
(string)$this->theme->get_rater_html(
|
||||
$event->image->id,
|
||||
$event->image->rating,
|
||||
$user->can(Permissions::EDIT_IMAGE_RATING)
|
||||
|
@ -231,13 +231,8 @@ class Ratings extends Extension
|
|||
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||
{
|
||||
if ($event->key===HelpPages::SEARCH) {
|
||||
$block = new Block();
|
||||
$block->header = "Ratings";
|
||||
|
||||
$ratings = self::get_sorted_ratings();
|
||||
|
||||
$block->body = $this->theme->get_help_html($ratings);
|
||||
$event->add_block($block);
|
||||
$event->add_block(new Block("Ratings", $this->theme->get_help_html($ratings)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,7 +309,7 @@ class Ratings extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
$this->theme->display_form($original_values, self::get_sorted_ratings());
|
||||
$this->theme->display_form($original_values);
|
||||
}
|
||||
|
||||
public function onAdminAction(AdminActionEvent $event)
|
||||
|
@ -346,7 +341,7 @@ class Ratings extends Extension
|
|||
global $user;
|
||||
|
||||
if ($user->can(Permissions::BULK_EDIT_IMAGE_RATING)) {
|
||||
$event->add_action("bulk_rate", "Set (R)ating", "r", "", $this->theme->get_selection_rater_html(["?"]));
|
||||
$event->add_action("bulk_rate", "Set (R)ating", "r", "", (string)$this->theme->get_selection_rater_html(selected_options: ["?"]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -416,6 +411,21 @@ class Ratings extends Extension
|
|||
return $ratings;
|
||||
}
|
||||
|
||||
public static function get_ratings_dict(array $ratings=null): array
|
||||
{
|
||||
if (!isset($ratings)) {
|
||||
$ratings = self::get_sorted_ratings();
|
||||
}
|
||||
return array_combine(
|
||||
array_map(function ($o) {
|
||||
return $o->code;
|
||||
}, $ratings),
|
||||
array_map(function ($o) {
|
||||
return $o->name;
|
||||
}, $ratings)
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_user_class_privs(User $user): array
|
||||
{
|
||||
global $config;
|
||||
|
|
|
@ -4,96 +4,83 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
use function MicroHTML\{P,SPAN,TABLE,TD,TH,TR};
|
||||
|
||||
class RatingsTheme extends Themelet
|
||||
{
|
||||
public function get_rater_html(int $image_id, string $rating, bool $can_rate): string
|
||||
public function get_selection_rater_html(string $name = "rating", array $ratings = [], array $selected_options = []): HTMLElement
|
||||
{
|
||||
return SHM_SELECT($name, !empty($ratings) ? $ratings : Ratings::get_ratings_dict(), required: true, selected_options: $selected_options);
|
||||
}
|
||||
|
||||
public function get_rater_html(int $image_id, string $rating, bool $can_rate): HTMLElement
|
||||
{
|
||||
$human_rating = Ratings::rating_to_human($rating);
|
||||
$html = "
|
||||
<tr>
|
||||
<th>Rating</th>
|
||||
<td>
|
||||
".($can_rate ? "
|
||||
<span class='view'>$human_rating</span>
|
||||
<span class='edit'>
|
||||
".$this->get_selection_rater_html([$rating])."
|
||||
</span>
|
||||
" : "
|
||||
$human_rating
|
||||
")."
|
||||
</td>
|
||||
</tr>
|
||||
";
|
||||
|
||||
$html = TR(TH("Rating"));
|
||||
|
||||
if ($can_rate) {
|
||||
$selector = $this->get_selection_rater_html(selected_options: [$rating]);
|
||||
|
||||
$html->appendChild(TD(
|
||||
SPAN(["class"=>"view"], $human_rating),
|
||||
SPAN(["class"=>"edit"], $selector)
|
||||
));
|
||||
} else {
|
||||
$html->appendChild(TD($human_rating));
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function display_form(array $current_ratings, array $available_ratings)
|
||||
public function display_form(array $current_ratings)
|
||||
{
|
||||
global $page;
|
||||
|
||||
$html = make_form(make_link("admin/update_ratings"))."<table class='form'><tr>
|
||||
<th>Change</th><td><select name='rating_old' required='required'><option></option>";
|
||||
foreach ($current_ratings as $key=>$value) {
|
||||
$html .= "<option value='$key'>$value</option>";
|
||||
}
|
||||
$html .= "</select></td></tr>
|
||||
<tr><th>To</th><td><select name='rating_new' required='required'><option></option>";
|
||||
foreach ($available_ratings as $value) {
|
||||
$html .= "<option value='$value->code'>$value->name</option>";
|
||||
}
|
||||
$html .= "</select></td></tr>
|
||||
<tr><td colspan='2'><input type='submit' value='Update'></td></tr></table>
|
||||
</form>\n";
|
||||
$page->add_block(new Block("Update Ratings", $html));
|
||||
$table = TABLE(
|
||||
["class"=>"form"],
|
||||
TR(TH("Change"), TD($this->get_selection_rater_html("rating_old", $current_ratings))),
|
||||
TR(TH("To"), TD($this->get_selection_rater_html("rating_new"))),
|
||||
TR(TD(["colspan"=>"2"], SHM_SUBMIT("Update")))
|
||||
);
|
||||
|
||||
$page->add_block(new Block("Update Ratings", SHM_SIMPLE_FORM("admin/update_ratings", $table)));
|
||||
}
|
||||
|
||||
public function get_selection_rater_html(array $selected_options, bool $multiple = false, array $available_options = null): string
|
||||
public function get_help_html(array $ratings): HTMLElement
|
||||
{
|
||||
$output = "<select name='rating".($multiple ? "[]' multiple='multiple'" : "' ")." >";
|
||||
|
||||
$options = Ratings::get_sorted_ratings();
|
||||
|
||||
foreach ($options as $option) {
|
||||
if ($available_options!=null && !in_array($option->code, $available_options)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$output .= "<option value='".$option->code."' ".
|
||||
(in_array($option->code, $selected_options) ? "selected='selected'" : "")
|
||||
.">".$option->name."</option>";
|
||||
}
|
||||
return $output."</select>";
|
||||
}
|
||||
|
||||
public function get_help_html(array $ratings): string
|
||||
{
|
||||
$output = '<p>Search for posts with one or more possible ratings.</p>
|
||||
<div class="command_example">
|
||||
<pre>rating:'.$ratings[0]->search_term.'</pre>
|
||||
<p>Returns posts with the '.$ratings[0]->name.' rating.</p>
|
||||
</div>
|
||||
<p>Ratings can be abbreviated to a single letter as well</p>
|
||||
<div class="command_example">
|
||||
<pre>rating:'.$ratings[0]->code.'</pre>
|
||||
<p>Returns posts with the '.$ratings[0]->name.' rating.</p>
|
||||
</div>
|
||||
<p>If abbreviations are used, multiple ratings can be searched for.</p>
|
||||
<div class="command_example">
|
||||
<pre>rating:'.$ratings[0]->code.$ratings[1]->code.'</pre>
|
||||
<p>Returns posts with the '.$ratings[0]->name.' or '.$ratings[1]->name.' rating.</p>
|
||||
</div>
|
||||
<p>Available ratings:</p>
|
||||
<table>
|
||||
<tr><th>Name</th><th>Search Term</th><th>Abbreviation</th></tr>
|
||||
';
|
||||
$rating_rows = [TR(TH("Name"), TH("Search Term"), TH("Abbreviation"))];
|
||||
foreach ($ratings as $rating) {
|
||||
$output .= "<tr><td>{$rating->name}</td><td>{$rating->search_term}</td><td>{$rating->code}</td></tr>";
|
||||
$rating_rows[] = TR(TD($rating->name), TD($rating->search_term), TD($rating->code));
|
||||
}
|
||||
$output .= "</table>";
|
||||
return $output;
|
||||
|
||||
return emptyHTML(
|
||||
P("Search for posts with one or more possible ratings."),
|
||||
SHM_COMMAND_EXAMPLE(
|
||||
"rating:" . $ratings[0]->search_term,
|
||||
"Returns posts with the " . $ratings[0]->name . " rating."
|
||||
),
|
||||
P("Ratings can be abbreviated to a single letter as well."),
|
||||
SHM_COMMAND_EXAMPLE(
|
||||
"rating:" . $ratings[0]->code,
|
||||
"Returns posts with the " . $ratings[0]->name . " rating."
|
||||
),
|
||||
P("If abbreviations are used, multiple ratings can be searched for."),
|
||||
SHM_COMMAND_EXAMPLE(
|
||||
"rating:" . $ratings[0]->code . $ratings[1]->code,
|
||||
"Returns posts with the " . $ratings[0]->name . " or " . $ratings[1]->name . " rating."
|
||||
),
|
||||
P("Available ratings:"),
|
||||
TABLE(...$rating_rows)
|
||||
);
|
||||
}
|
||||
|
||||
public function get_user_options(User $user, array $selected_ratings, array $available_ratings): string
|
||||
// This wasn't being used at all
|
||||
|
||||
/* public function get_user_options(User $user, array $selected_ratings, array $available_ratings): string
|
||||
{
|
||||
$html = "
|
||||
<p>".make_form(make_link("user_admin/default_ratings"))."
|
||||
|
@ -105,7 +92,7 @@ class RatingsTheme extends Themelet
|
|||
<tbody>
|
||||
<tr><td>This controls the default rating search results will be filtered by, and nothing else. To override in your search results, add rating:* to your search.</td></tr>
|
||||
<tr><td>
|
||||
".$this->get_selection_rater_html($selected_ratings, true, $available_ratings)."
|
||||
".SHM_SELECT("ratings", selected_options: $selected_ratings, multiple: true, options: $available_ratings)."
|
||||
</td></tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
|
@ -115,5 +102,5 @@ class RatingsTheme extends Themelet
|
|||
</form>
|
||||
";
|
||||
return $html;
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shimmie2;
|
|||
class RegenThumb extends Extension
|
||||
{
|
||||
/** @var RegenThumbTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function regenerate_thumbnail(Image $image, bool $force = true): bool
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ class RegenThumb extends Extension
|
|||
}
|
||||
if ($event->page_matches("regen_thumb/mass") && $user->can(Permissions::DELETE_IMAGE) && isset($_POST['tags'])) {
|
||||
$tags = Tag::explode(strtolower($_POST['tags']), false);
|
||||
$images = Image::find_images(0, 10000, $tags);
|
||||
$images = Image::find_images(limit: 10000, tags: $tags);
|
||||
|
||||
foreach ($images as $image) {
|
||||
$this->regenerate_thumbnail($image);
|
||||
|
|
|
@ -21,7 +21,7 @@ class ImageRelationshipSetEvent extends Event
|
|||
class Relationships extends Extension
|
||||
{
|
||||
/** @var RelationshipsTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public const NAME = "Relationships";
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ class ImageReport
|
|||
class ReportImage extends Extension
|
||||
{
|
||||
/** @var ReportImageTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@ abstract class ResizeConfig
|
|||
class ResizeImage extends Extension
|
||||
{
|
||||
/** @var ResizeImageTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
/**
|
||||
* Needs to be after the data processing extensions
|
||||
|
|
|
@ -19,7 +19,7 @@ class ImageRotateException extends SCoreException
|
|||
class RotateImage extends Extension
|
||||
{
|
||||
/** @var RotateImageTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public const SUPPORTED_MIME = [MimeType::JPEG, MimeType::PNG, MimeType::GIF, MimeType::WEBP];
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/* needed for access to build_thumb_html */
|
||||
class RSSImagesTheme extends Themelet
|
||||
{
|
||||
}
|
|
@ -20,7 +20,7 @@ if ( // kill these glitched requests immediately
|
|||
class Rule34 extends Extension
|
||||
{
|
||||
/** @var Rule34Theme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onImageDeletion(ImageDeletionEvent $event)
|
||||
{
|
||||
|
@ -114,7 +114,7 @@ class Rule34 extends Extension
|
|||
global $database, $page, $user;
|
||||
|
||||
# Database might not be connected at this point...
|
||||
#$database->set_timeout(DATABASE_TIMEOUT+15000); // deleting users can take a while
|
||||
#$database->set_timeout(null); // deleting users can take a while
|
||||
|
||||
if (function_exists("sd_notify_watchdog")) {
|
||||
\sd_notify_watchdog();
|
||||
|
|
|
@ -294,7 +294,7 @@ class SetupBlock extends Block
|
|||
class Setup extends Extension
|
||||
{
|
||||
/** @var SetupTheme */
|
||||
protected ?Themelet $theme;
|
||||
protected Themelet $theme;
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
|
|
|
@ -21,7 +21,7 @@ class _SafeImage
|
|||
public function __construct(Image $img)
|
||||
{
|
||||
$_id = $img->id;
|
||||
assert($_id != null);
|
||||
assert($_id !== null);
|
||||
$this->id = $_id;
|
||||
$this->height = $img->height;
|
||||
$this->width = $img->width;
|
||||
|
|
|
@ -42,7 +42,7 @@ class XMLSitemap extends Extension
|
|||
private function handle_smaller_sitemap()
|
||||
{
|
||||
/* --- Add latest images to sitemap with higher priority --- */
|
||||
$latestimages = Image::find_images(0, 50, []);
|
||||
$latestimages = Image::find_images(limit: 50);
|
||||
if (empty($latestimages)) {
|
||||
return;
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class XMLSitemap extends Extension
|
|||
$this->add_sitemap_queue($popular_tags, "monthly", "0.9" /* not sure how to deal with date here */);
|
||||
|
||||
/* --- Add latest images to sitemap with higher priority --- */
|
||||
$latestimages = Image::find_images(0, 50, []);
|
||||
$latestimages = Image::find_images(limit: 50);
|
||||
$latestimages_urllist = [];
|
||||
$latest_image = null;
|
||||
foreach ($latestimages as $arrayid => $image) {
|
||||
|
@ -107,7 +107,7 @@ class XMLSitemap extends Extension
|
|||
$this->add_sitemap_queue($other_tags, "monthly", "0.7" /* not sure how to deal with date here */);
|
||||
|
||||
/* --- Add all other images to sitemap with lower priority --- */
|
||||
$otherimages = Image::find_images(51, 10000000, []);
|
||||
$otherimages = Image::find_images(offset: 51, limit: 10000000);
|
||||
$image = null;
|
||||
foreach ($otherimages as $arrayid => $image) {
|
||||
// create url from image id's
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue