From 1257d87e511682376d3e414fbcff7a48e3b91b0d Mon Sep 17 00:00:00 2001
From: Michael Yick
Date: Sun, 10 Sep 2023 09:16:26 -0500
Subject: [PATCH] Merge and integrate upstream fixes
---
.github/workflows/main.yml | 26 +
.github/workflows/publish.yml | 10 +-
.scrutinizer.yml | 20 -
Dockerfile | 42 +-
core/basepage.php | 16 +-
core/basethemelet.php | 87 +++-
core/block.php | 2 +-
core/cacheengine.php | 3 +-
core/database.php | 74 +--
core/extension.php | 6 +-
core/imageboard/image.php | 96 +---
core/imageboard/misc.php | 2 +-
core/imageboard/tag.php | 38 +-
core/microhtml.php | 153 ++++++
core/permissions.php | 31 +-
core/polyfills.php | 9 +-
core/send_event.php | 26 +-
core/tests/polyfills.test.php | 13 +-
core/user.php | 10 +
core/userclass.php | 1 +
core/util.php | 83 +--
ext/admin/main.php | 4 +-
ext/alias_editor/main.php | 2 +-
ext/alias_editor/theme.php | 31 +-
ext/approval/main.php | 18 +-
ext/approval/theme.php | 40 +-
ext/artists/main.php | 9 +-
ext/artists/theme.php | 30 +-
ext/auto_tagger/main.php | 2 +-
ext/autocomplete/main.php | 2 +-
ext/biography/main.php | 2 +-
.../script.js => ext/biography/main.php: | 0
ext/blocks/main.php | 28 +-
ext/blocks/theme.php | 12 +-
ext/blotter/main.php | 2 +-
ext/bulk_actions/main.php | 10 +-
ext/bulk_actions/script.js | 2 +-
ext/bulk_actions/theme.php | 4 +-
ext/bulk_add/main.php | 4 +-
ext/bulk_add_csv/main.php | 4 +-
ext/bulk_parent_child/main.php | 2 +-
ext/comment/main.php | 2 +-
ext/cron_uploader/info.php | 2 +-
ext/cron_uploader/main.php | 4 +-
ext/cron_uploader/theme.php | 18 +-
ext/danbooru_api/main.php | 4 +
ext/danbooru_api/test.php | 1 +
ext/downtime/main.php | 2 +-
ext/emoticons_list/main.php | 2 +-
ext/et/main.php | 2 +-
ext/ext_manager/main.php | 2 +-
ext/favorites/main.php | 2 +-
ext/featured/main.php | 2 +-
ext/forum/main.php | 2 +-
ext/handle_svg/main.php | 2 +-
ext/handle_video/main.php | 82 ++-
ext/help_pages/main.php | 16 +-
ext/help_pages/theme.php | 1 +
ext/holiday/main.php | 2 +-
ext/home/counters/cavemanon/Ϫ.gif | Bin 3327 -> 0 bytes
ext/home/counters/cavemanon/Ϫ.png | Bin 8018 -> 0 bytes
ext/home/main.php | 33 +-
ext/home/theme.php | 2 +-
ext/image/main.php | 2 +-
ext/image_hash_ban/main.php | 2 +-
ext/image_view_counter/main.php | 2 +-
ext/index/main.php | 11 +-
ext/index/script.js | 2 +-
ext/index/theme.php | 317 ++++--------
ext/ipban/main.php | 2 +-
ext/link_image/main.php | 2 +-
ext/link_image/theme.php | 2 +-
ext/log_db/main.php | 10 +-
ext/media/main.php | 11 +-
ext/mime/main.php | 2 +-
ext/mime/mime_type.php | 2 +-
ext/not_a_tag/main.php | 2 +-
ext/notes/main.php | 2 +-
ext/numeric_score/main.php | 2 +-
ext/pm/main.php | 12 +-
ext/pools/main.php | 32 +-
ext/pools/theme.php | 471 +++++++++---------
ext/post_titles/main.php | 2 +-
ext/private_image/main.php | 15 +-
ext/private_image/theme.php | 2 +-
ext/qr_code/main.php | 2 +-
ext/random_image/main.php | 2 +-
ext/random_list/main.php | 2 +-
ext/rating/main.php | 32 +-
ext/rating/theme.php | 137 +++--
ext/regen_thumb/main.php | 4 +-
ext/relationships/main.php | 2 +-
ext/report_image/main.php | 2 +-
ext/resize/main.php | 2 +-
ext/rotate/main.php | 2 +-
ext/rss_images/theme.php | 10 -
ext/rule34/main.php | 4 +-
ext/setup/main.php | 2 +-
ext/shimmie_api/main.php | 2 +-
ext/sitemap/main.php | 6 +-
ext/source_history/main.php | 4 +-
ext/tag_categories/main.php | 2 +-
ext/tag_edit/main.php | 48 +-
ext/tag_editcloud/info.php | 2 +-
ext/tag_editcloud/main.php | 2 +-
ext/tag_history/main.php | 4 +-
ext/tag_history/theme.php | 2 +-
ext/tag_list/main.php | 2 +-
ext/tag_list/theme.php | 2 +-
ext/tag_tools/main.php | 2 +-
ext/tag_tools/theme.php | 2 +-
ext/tips/main.php | 2 +-
ext/transcode/main.php | 2 +-
ext/transcode_video/main.php | 2 +-
ext/trash/main.php | 10 +-
ext/update/main.php | 2 +-
ext/upload/main.php | 2 +-
ext/upload/theme.php | 82 ++-
ext/user/events.php | 4 +-
ext/user/main.php | 15 +-
ext/user/test.php | 16 +-
ext/user/theme.php | 60 ++-
ext/user_config/main.php | 10 +-
ext/user_config/theme.php | 1 +
ext/view/main.php | 2 +-
ext/wiki/info.php | 2 +-
ext/wiki/main.php | 2 +-
index.php | 1 +
tests/docker-init.sh | 10 +-
tests/router.php | 4 +-
themes/danbooru/themelet.class.php | 30 +-
themes/danbooru/view.theme.php | 2 +-
themes/danbooru2/themelet.class.php | 30 +-
themes/danbooru2/view.theme.php | 2 +-
themes/default/background.svg | 222 ---------
themes/default/style.css | 108 ++--
themes/futaba/style.css | 45 +-
themes/lite/page.class.php | 25 +-
themes/lite/style.css | 25 +-
themes/lite/themelet.class.php | 50 +-
themes/lite/view.theme.php | 2 +-
themes/material/home.theme.php | 77 ---
themes/material/material.min.css | 9 -
themes/material/material.min.js | 10 -
themes/material/mdl-LICENSE | 212 --------
themes/material/page.class.php | 278 -----------
themes/material/script0.js | 95 ----
themes/material/style.css | 17 -
themes/material/themelet.class.php | 9 -
themes/material/upload.theme.php | 16 -
themes/material/user.theme.php | 36 --
themes/material/view.theme.php | 75 ---
themes/rule34v2/header.inc | 2 +-
themes/rule34v2/tag_edit.theme.php | 15 +
themes/rule34v2/themelet.class.php | 39 +-
themes/rule34v2/upload.theme.php | 78 ++-
themes/rule34v2/user.theme.php | 5 +-
157 files changed, 1684 insertions(+), 2388 deletions(-)
create mode 100644 .github/workflows/main.yml
delete mode 100644 .scrutinizer.yml
create mode 100644 core/microhtml.php
rename themes/material/script.js => ext/biography/main.php: (100%)
delete mode 100644 ext/home/counters/cavemanon/Ϫ.gif
delete mode 100644 ext/home/counters/cavemanon/Ϫ.png
delete mode 100644 ext/rss_images/theme.php
delete mode 100644 themes/default/background.svg
delete mode 100644 themes/material/home.theme.php
delete mode 100644 themes/material/material.min.css
delete mode 100644 themes/material/material.min.js
delete mode 100644 themes/material/mdl-LICENSE
delete mode 100644 themes/material/page.class.php
delete mode 100644 themes/material/script0.js
delete mode 100644 themes/material/style.css
delete mode 100644 themes/material/themelet.class.php
delete mode 100644 themes/material/upload.theme.php
delete mode 100644 themes/material/user.theme.php
delete mode 100644 themes/material/view.theme.php
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..5d471d6f
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -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
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 89f1fb79..2e8fd122 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -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
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
deleted file mode 100644
index 3f425840..00000000
--- a/.scrutinizer.yml
+++ /dev/null
@@ -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
diff --git a/Dockerfile b/Dockerfile
index 944d0439..75508f59 100644
--- a/Dockerfile
+++ b/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"]
diff --git a/core/basepage.php b/core/basepage.php
index 410ae35b..2acdb953 100644
--- a/core/basepage.php
+++ b/core/basepage.php
@@ -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;
Shimmie ©
Shish &
The Team
- 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;
diff --git a/core/basethemelet.php b/core/basethemelet.php
index 2338d34f..d646e851 100644
--- a/core/basethemelet.php
+++ b/core/basethemelet.php
@@ -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 "".
- " ".
- " \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(" ");
}
- 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 ''.$name.' ';
+ 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 .= "";
- }
- $paginator .= $this->gen_page_link($base_url, $query, $page, $name);
- if ($page == $current_page) {
- $paginator .= " ";
+ $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
- .' << '.$pages_html.' >>';
+ return emptyHTML(
+ $this->implode(" | ", [
+ $first_html,
+ $prev_html,
+ $random_html,
+ $next_html,
+ $last_html,
+ ]),
+ BR(),
+ '<< ',
+ $pages_html,
+ ' >>'
+ );
}
}
diff --git a/core/block.php b/core/block.php
index 8cf31329..0242656a 100644
--- a/core/block.php
+++ b/core/block.php
@@ -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));
diff --git a/core/cacheengine.php b/core/cacheengine.php
index 18c379cc..01a66f90 100644
--- a/core/cacheengine.php
+++ b/core/cacheengine.php
@@ -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;
diff --git a/core/database.php b/core/database.php
index 78f8cf80..72dd60a9 100644
--- a/core/database.php
+++ b/core/database.php
@@ -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
diff --git a/core/extension.php b/core/extension.php
index 7ef28fb2..c5b7edec 100644
--- a/core/extension.php
+++ b/core/extension.php
@@ -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();
}
}
diff --git a/core/imageboard/image.php b/core/imageboard/image.php
index af2483df..df4e5de7 100644
--- a/core/imageboard/image.php
+++ b/core/imageboard/image.php
@@ -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");
diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php
index e9944606..09def27e 100644
--- a/core/imageboard/misc.php
+++ b/core/imageboard/misc.php
@@ -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);
diff --git a/core/imageboard/tag.php b/core/imageboard/tag.php
index c5997586..0780aaaa 100644
--- a/core/imageboard/tag.php
+++ b/core/imageboard/tag.php
@@ -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);
}
/**
diff --git a/core/microhtml.php b/core/microhtml.php
new file mode 100644
index 00000000..4cb1a943
--- /dev/null
+++ b/core/microhtml.php
@@ -0,0 +1,153 @@
+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 element and sets up the given options.
+ *
+ * @param string $name The name attribute of .
+ * @param array $options An array of pairs of parameters for 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 element is required.
+ * @param bool $multiple Wether the element is multiple-choice.
+ * @param bool $empty_option Whether the first option should be an empty one.
+ * @param array $attrs Additional attributes dict for . 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);
+}
diff --git a/core/permissions.php b/core/permissions.php
index 0cf1d9c4..08b2556a 100644
--- a/core/permissions.php
+++ b/core/permissions.php
@@ -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";
diff --git a/core/polyfills.php b/core/polyfills.php
index 1dfee641..1b6b9cfc 100644
--- a/core/polyfills.php
+++ b/core/polyfills.php
@@ -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) {
diff --git a/core/send_event.php b/core/send_event.php
index 9eacf5cc..33eed65f 100644
--- a/core/send_event.php
+++ b/core/send_event.php
@@ -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));
}
diff --git a/core/tests/polyfills.test.php b/core/tests/polyfills.test.php
index edae790a..a196da3b 100644
--- a/core/tests/polyfills.test.php
+++ b/core/tests/polyfills.test.php
@@ -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()
diff --git a/core/user.php b/core/user.php
index 0d553017..4692699d 100644
--- a/core/user.php
+++ b/core/user.php
@@ -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 ' ';
}
+ // 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")) {
diff --git a/core/userclass.php b/core/userclass.php
index 22d2f698..8677cc78 100644
--- a/core/userclass.php
+++ b/core/userclass.php
@@ -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,
diff --git a/core/util.php b/core/util.php
index f761a599..25d83ae4 100644
--- a/core/util.php
+++ b/core/util.php
@@ -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 '
- ";
+ $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));
}
}
diff --git a/ext/approval/main.php b/ext/approval/main.php
index 90158ff8..935091b9 100644
--- a/ext/approval/main.php
+++ b/ext/approval/main.php
@@ -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));
}
}
diff --git a/ext/approval/theme.php b/ext/approval/theme.php
index a9dde60d..07c063bd 100644
--- a/ext/approval/theme.php
+++ b/ext/approval/theme.php
@@ -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 'Search for posts that are approved/not approved.
-
-
approved:yes
-
Returns posts that have been approved.
-
-
-
approved:no
-
Returns posts that have not been approved.
-
- ';
+ 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));
}
}
diff --git a/ext/artists/main.php b/ext/artists/main.php
index 7f03941c..0c1cb158 100644
--- a/ext/artists/main.php
+++ b/ext/artists/main.php
@@ -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);
/*
diff --git a/ext/artists/theme.php b/ext/artists/theme.php
index 51f6eff3..c367518f 100644
--- a/ext/artists/theme.php
+++ b/ext/artists/theme.php
@@ -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 "
-
- Author
-
- $h_author
-
-
-
- ";
+ 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 'Search for posts with a particular artist.
-
-
artist=leonardo
-
Returns posts with the artist "leonardo".
-
- ';
+ return emptyHTML(
+ P("Search for posts with a particular artist."),
+ SHM_COMMAND_EXAMPLE("artist=leonardo", "Returns posts with the artist \"leonardo\".")
+ );
}
}
diff --git a/ext/auto_tagger/main.php b/ext/auto_tagger/main.php
index 370cdac5..d67c3cf3 100644
--- a/ext/auto_tagger/main.php
+++ b/ext/auto_tagger/main.php
@@ -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)
{
diff --git a/ext/autocomplete/main.php b/ext/autocomplete/main.php
index 006dddf0..35b379c8 100644
--- a/ext/autocomplete/main.php
+++ b/ext/autocomplete/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class AutoComplete extends Extension
{
/** @var AutoCompleteTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function get_priority(): int
{
diff --git a/ext/biography/main.php b/ext/biography/main.php
index 1c9816df..fac41f5f 100644
--- a/ext/biography/main.php
+++ b/ext/biography/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class Biography extends Extension
{
/** @var BiographyTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onUserPageBuilding(UserPageBuildingEvent $event)
{
diff --git a/themes/material/script.js b/ext/biography/main.php:
similarity index 100%
rename from themes/material/script.js
rename to ext/biography/main.php:
diff --git a/ext/blocks/main.php b/ext/blocks/main.php
index ccf71069..e864b311 100644
--- a/ext/blocks/main.php
+++ b/ext/blocks/main.php
@@ -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");
diff --git a/ext/blocks/theme.php b/ext/blocks/theme.php
index 95073e68..4f296140 100644
--- a/ext/blocks/theme.php
+++ b/ext/blocks/theme.php
@@ -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));
}
}
diff --git a/ext/blotter/main.php b/ext/blotter/main.php
index c62eb92a..7c7d5451 100644
--- a/ext/blotter/main.php
+++ b/ext/blotter/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class Blotter extends Extension
{
/** @var BlotterTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
{
diff --git a/ext/bulk_actions/main.php b/ext/bulk_actions/main.php
index 4b45a08e..19ce61ad 100644
--- a/ext/bulk_actions/main.php
+++ b/ext/bulk_actions/main.php
@@ -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 {
diff --git a/ext/bulk_actions/script.js b/ext/bulk_actions/script.js
index 4fccb5f8..831c0087 100644
--- a/ext/bulk_actions/script.js
+++ b/ext/bulk_actions/script.js
@@ -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;
diff --git a/ext/bulk_actions/theme.php b/ext/bulk_actions/theme.php
index 777a7606..608b7778 100644
--- a/ext/bulk_actions/theme.php
+++ b/ext/bulk_actions/theme.php
@@ -26,7 +26,7 @@ class BulkActionsTheme extends Themelet
";
- $hasQuery = ($query != null && $query != "");
+ $hasQuery = !empty($query);
if ($hasQuery) {
$body .= "";
@@ -61,7 +61,7 @@ class BulkActionsTheme extends Themelet
public function render_tag_input(): string
{
return " Replace tags " .
- " ";
+ " ";
}
public function render_source_input(): string
diff --git a/ext/bulk_add/main.php b/ext/bulk_add/main.php
index 9aed87dd..83d84d9c 100644
--- a/ext/bulk_add/main.php
+++ b/ext/bulk_add/main.php
@@ -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);
diff --git a/ext/bulk_add_csv/main.php b/ext/bulk_add_csv/main.php
index 6dfcb488..868ae5ac 100644
--- a/ext/bulk_add_csv/main.php
+++ b/ext/bulk_add_csv/main.php
@@ -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);
}
diff --git a/ext/bulk_parent_child/main.php b/ext/bulk_parent_child/main.php
index b7eda773..3e3c58bd 100644
--- a/ext/bulk_parent_child/main.php
+++ b/ext/bulk_parent_child/main.php
@@ -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;
diff --git a/ext/comment/main.php b/ext/comment/main.php
index bf588318..113161fb 100644
--- a/ext/comment/main.php
+++ b/ext/comment/main.php
@@ -114,7 +114,7 @@ class Comment
class CommentList extends Extension
{
/** @var CommentListTheme $theme */
- public ?Themelet $theme;
+ public Themelet $theme;
public function onInitExt(InitExtEvent $event)
{
diff --git a/ext/cron_uploader/info.php b/ext/cron_uploader/info.php
index 45d1635f..2113f504 100644
--- a/ext/cron_uploader/info.php
+++ b/ext/cron_uploader/info.php
@@ -26,7 +26,7 @@ class CronUploaderInfo extends ExtensionInfo
public function __construct()
{
- $this->documentation = "Installation guide: activate this extension and navigate to System Config screen.";
+ $this->documentation = "Installation guide: activate this extension and navigate to Board Config screen.";
parent::__construct();
}
}
diff --git a/ext/cron_uploader/main.php b/ext/cron_uploader/main.php
index 8da3c467..8690ce48 100644
--- a/ext/cron_uploader/main.php
+++ b/ext/cron_uploader/main.php
@@ -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();
diff --git a/ext/cron_uploader/theme.php b/ext/cron_uploader/theme.php
index 64b43c7c..cc293f59 100644
--- a/ext/cron_uploader/theme.php
+++ b/ext/cron_uploader/theme.php
@@ -74,10 +74,10 @@ class CronUploaderTheme extends Themelet
$install_html = "
This cron uploader is fairly easy to use but has to be configured first.
- Install & activate this plugin.
- Go to the Board Config and change any settings to match your preference.
- Copy the cron command above.
- Create a cron job or something else that can open a url on specified times.
+ Install & activate this plugin.
+ Go to the Board Config and change any settings to match your preference.
+ Copy the cron command above.
+ Create a cron job or something else that can open a url on specified times.
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.
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.
When you create the cron job, you choose when to upload new posts.
@@ -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.
({$queue_dirinfo['path']} )
- Any sub-folders will be turned into tags.
- If the file name matches \"## - tag1 tag2.png\" the tags will be used.
- If both are found, they will all be used.
- The character \";\" will be changed into \":\" in any tags.
- 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.
+ Any sub-folders will be turned into tags.
+ If the file name matches \"## - tag1 tag2.png\" the tags will be used.
+ If both are found, they will all be used.
+ The character \";\" will be changed into \":\" in any tags.
+ 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.
The cron uploader works by importing files from the queue folder whenever this url is visited:
$cron_url
diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php
index 6b46ef20..95b56e77 100644
--- a/ext/danbooru_api/main.php
+++ b/ext/danbooru_api/main.php
@@ -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);
}
diff --git a/ext/danbooru_api/test.php b/ext/danbooru_api/test.php
index 5a9da84a..258ad855 100644
--- a/ext/danbooru_api/test.php
+++ b/ext/danbooru_api/test.php
@@ -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]);
diff --git a/ext/downtime/main.php b/ext/downtime/main.php
index dec32985..28d6aeef 100644
--- a/ext/downtime/main.php
+++ b/ext/downtime/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class Downtime extends Extension
{
/** @var DowntimeTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function get_priority(): int
{
diff --git a/ext/emoticons_list/main.php b/ext/emoticons_list/main.php
index 12d660a4..07bd6f80 100644
--- a/ext/emoticons_list/main.php
+++ b/ext/emoticons_list/main.php
@@ -10,7 +10,7 @@ namespace Shimmie2;
class EmoticonList extends Extension
{
/** @var EmoticonListTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
diff --git a/ext/et/main.php b/ext/et/main.php
index 00d74a4e..0d919f92 100644
--- a/ext/et/main.php
+++ b/ext/et/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class ET extends Extension
{
/** @var ETTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
diff --git a/ext/ext_manager/main.php b/ext/ext_manager/main.php
index 2fa7d5db..0eeee676 100644
--- a/ext/ext_manager/main.php
+++ b/ext/ext_manager/main.php
@@ -37,7 +37,7 @@ class ExtensionAuthor
class ExtManager extends Extension
{
/** @var ExtManagerTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
diff --git a/ext/favorites/main.php b/ext/favorites/main.php
index 8a6e72f9..eb94d62c 100644
--- a/ext/favorites/main.php
+++ b/ext/favorites/main.php
@@ -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)
{
diff --git a/ext/featured/main.php b/ext/featured/main.php
index c0c221c7..11cba6ec 100644
--- a/ext/featured/main.php
+++ b/ext/featured/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class Featured extends Extension
{
/** @var FeaturedTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
{
diff --git a/ext/forum/main.php b/ext/forum/main.php
index 443af4ac..f7b70b5a 100644
--- a/ext/forum/main.php
+++ b/ext/forum/main.php
@@ -15,7 +15,7 @@ Todo:
class Forum extends Extension
{
/** @var ForumTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
{
diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php
index e4b214a4..be79cbb6 100644
--- a/ext/handle_svg/main.php
+++ b/ext/handle_svg/main.php
@@ -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)
{
diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php
index 2ae4f627..9d195b9d 100644
--- a/ext/handle_video/main.php
+++ b/ext/handle_video/main.php
@@ -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) {
diff --git a/ext/help_pages/main.php b/ext/help_pages/main.php
index 2a208c46..770b0cb1 100644
--- a/ext/help_pages/main.php
+++ b/ext/help_pages/main.php
@@ -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);
}
diff --git a/ext/help_pages/theme.php b/ext/help_pages/theme.php
index 6e946e9e..669d378f 100644
--- a/ext/help_pages/theme.php
+++ b/ext/help_pages/theme.php
@@ -29,5 +29,6 @@ class HelpPagesTheme extends Themelet
$page->set_title("Help - $title");
$page->set_heading("Help - $title");
+ $page->add_block(new NavBlock());
}
}
diff --git a/ext/holiday/main.php b/ext/holiday/main.php
index 8dd450c2..4386fe5c 100644
--- a/ext/holiday/main.php
+++ b/ext/holiday/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class Holiday extends Extension
{
/** @var HolidayTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
{
diff --git a/ext/home/counters/cavemanon/Ϫ.gif b/ext/home/counters/cavemanon/Ϫ.gif
deleted file mode 100644
index cf1f0b434dab8939df35e338f6eaf6a2a5436d4d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3327
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
WC!z&XEWz*L5DgWbCnZYOZ~q6!0J6UT
diff --git a/ext/home/main.php b/ext/home/main.php
index 0556c4e1..9ee91b60 100644
--- a/ext/home/main.php
+++ b/ext/home/main.php
@@ -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 .= " ";
- } elseif ($cur == 7 && rand(0, 2500) == 0) {
- $counter_text .= " ";
- } else {
- $counter_text .= " ";
- }
+ 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 .= " ";
+ $counter_text .= " ";
+ }
+ }
}
// get the homelinks and process them
diff --git a/ext/home/theme.php b/ext/home/theme.php
index 009783d6..a0b37efe 100644
--- a/ext/home/theme.php
+++ b/ext/home/theme.php
@@ -53,7 +53,7 @@ EOD
$counter_html
diff --git a/ext/image/main.php b/ext/image/main.php
index 3871799e..94effd32 100644
--- a/ext/image/main.php
+++ b/ext/image/main.php
@@ -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,
diff --git a/ext/image_hash_ban/main.php b/ext/image_hash_ban/main.php
index 2b3d302d..c50604cf 100644
--- a/ext/image_hash_ban/main.php
+++ b/ext/image_hash_ban/main.php
@@ -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)
{
diff --git a/ext/image_view_counter/main.php b/ext/image_view_counter/main.php
index 41e8c200..4b79d50e 100644
--- a/ext/image_view_counter/main.php
+++ b/ext/image_view_counter/main.php
@@ -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
diff --git a/ext/index/main.php b/ext/index/main.php
index e28c091f..a851fddf 100644
--- a/ext/index/main.php
+++ b/ext/index/main.php
@@ -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));
}
}
diff --git a/ext/index/script.js b/ext/index/script.js
index f9b05b33..0f17ce93 100644
--- a/ext/index/script.js
+++ b/ext/index/script.js
@@ -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);
diff --git a/ext/index/theme.php b/ext/index/theme.php
index 411a11a2..f6bd16dc 100644
--- a/ext/index/theme.php
+++ b/ext/index/theme.php
@@ -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" : 'Next ';
$h_search_string = html_escape(Tag::implode($search_terms));
- $h_search_link = make_link();
+ $h_search_link = make_link("post/list");
$h_search = "
- ';
+ 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."),
+ );
}
}
diff --git a/ext/ipban/main.php b/ext/ipban/main.php
index e8c6c33b..ce97c6af 100644
--- a/ext/ipban/main.php
+++ b/ext/ipban/main.php
@@ -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
{
diff --git a/ext/link_image/main.php b/ext/link_image/main.php
index 315ed858..cb56b787 100644
--- a/ext/link_image/main.php
+++ b/ext/link_image/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class LinkImage extends Extension
{
/** @var LinkImageTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onDisplayingImage(DisplayingImageEvent $event)
{
diff --git a/ext/link_image/theme.php b/ext/link_image/theme.php
index 6491de24..fe4a7b68 100644
--- a/ext/link_image/theme.php
+++ b/ext/link_image/theme.php
@@ -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;
}
diff --git a/ext/log_db/main.php b/ext/log_db/main.php
index e7848631..fc7cea05 100644
--- a/ext/log_db/main.php
+++ b/ext/log_db/main.php
@@ -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)
{
diff --git a/ext/media/main.php b/ext/media/main.php
index 46966016..3af4cbed 100644
--- a/ext/media/main.php
+++ b/ext/media/main.php
@@ -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:
diff --git a/ext/mime/main.php b/ext/mime/main.php
index 1e1380fe..d5fe4402 100644
--- a/ext/mime/main.php
+++ b/ext/mime/main.php
@@ -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";
diff --git a/ext/mime/mime_type.php b/ext/mime/mime_type.php
index b600ec13..fbb0906f 100644
--- a/ext/mime/mime_type.php
+++ b/ext/mime/mime_type.php
@@ -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];
diff --git a/ext/not_a_tag/main.php b/ext/not_a_tag/main.php
index 167cff8d..74cb064e 100644
--- a/ext/not_a_tag/main.php
+++ b/ext/not_a_tag/main.php
@@ -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
{
diff --git a/ext/notes/main.php b/ext/notes/main.php
index 67df6f02..2b1c7b8c 100644
--- a/ext/notes/main.php
+++ b/ext/notes/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class Notes extends Extension
{
/** @var NotesTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
{
diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php
index 0fff5e68..fbdc4e3e 100644
--- a/ext/numeric_score/main.php
+++ b/ext/numeric_score/main.php
@@ -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)
{
diff --git a/ext/pm/main.php b/ext/pm/main.php
index 6d022eb3..1755e839 100644
--- a/ext/pm/main.php
+++ b/ext/pm/main.php
@@ -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 ? " ($count) " : "";
- $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 ? " ($count) " : "";
- $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"));
}
}
diff --git a/ext/pools/main.php b/ext/pools/main.php
index 85fff391..f25bd1ff 100644
--- a/ext/pools/main.php
+++ b/ext/pools/main.php
@@ -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)
diff --git a/ext/pools/theme.php b/ext/pools/theme.php
index 79f8c2ec..cbe04adc 100644
--- a/ext/pools/theme.php
+++ b/ext/pools/theme.php
@@ -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[] = "" . html_escape($poolInfo['info']->title) . " ";
+ $div = DIV(SHM_A("pool/view/" . $poolID, $poolInfo["info"]->title));
- if (!empty($poolInfo['nav'])) {
- $navlinks = "";
- if (!empty($poolInfo['nav']['prev'])) {
- $navlinks .= 'Prev ';
+ 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 .= 'Next ';
- }
- if (!empty($navlinks)) {
- $navlinks .= "
";
- $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(" ", $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 .= "" . html_escape($pool->title) . " ";
- }
- return "\n" . make_form(make_link("pool/add_post")) . "
-
- $h
-
-
-
-
- ";
+ 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 = '
-
-
- Name
- Creator
- Posts
- Public
- ';
-
// Build up the list of pools.
+ $pool_rows = [];
foreach ($pools as $pool) {
- $pool_link = 'id) . '">' . html_escape($pool->title) . " ";
- $user_link = 'user_name)) . '">' . html_escape($pool->user_name) . " ";
- $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 .= "" .
- "" . $pool_link . " " .
- "" . $user_link . " " .
- "" . $pool->posts . " " .
- "" . $public . " " .
- " ";
+ $pool_rows[] = TR(
+ TD(["class"=>"left"], $pool_link),
+ TD($user_link),
+ TD($pool->posts),
+ TD($pool->public ? "Yes" : "No")
+ );
}
- $html .= "
";
+ $table = TABLE(
+ ["id"=>"poolsList", "class"=>"zebra"],
+ THEAD(TR(TH("Name"), TH("Creator"), TH("Posts"), TH("Public"))),
+ TBODY(...$pool_rows)
+ );
- $order_html = '';
- $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 .= "{$text} \n";
- }
- $order_html .= ' ';
+ $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")) . "
-
-
- ";
+ $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 = '
- Pool Index
- Create Pool
- Pool Changes
- ';
+ $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')) . '
-
-
-
-
+ // 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_id = $_hidden("pool_id", $pool->id);
- ' . make_form(make_link('pool/order')) . '
-
-
-
-
- ' . make_form(make_link('pool/reverse')) . '
-
-
-
-
- ' . make_form(make_link('post/list/pool_id%3A' . $pool->id . '/1')) . '
-
-
- ';
+ $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 .= "
-
-
- " . make_form(make_link("pool/nuke")) . "
-
-
-
- ";
+ $editor->appendChild(
+ SCRIPT(
+ ["type"=>"text/javascript"],
+ rawHTML("")
+ ),
+ 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 .= "
-
-
-
- ";
+ $editor->appendChild(
+ SCRIPT(
+ ["type"=>"text/javascript"],
+ rawHTML("")
+ ),
+ 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 = "
-
- ";
- $pool_images .= "";
+ $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->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")) . "
-
-
-
-
- ";
+ $_input_id = INPUT(["type"=>"hidden", "name"=>"pool_id", "value"=>$pool->id]);
- /* REMOVE POOLS */
- $pool_images = "\n";
+ $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 = '
-
-
- Pool
- Post Count
- Changes
- Updater
- Date
- Action
- ';
+ $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 = "" . html_escape($history['title']) . " ";
- $user_link = "" . html_escape($history['user_name']) . " ";
- $revert_link = "Revert ";
+ $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 .= "" . $prefix . $image . " ";
+ $image_links->appendChild(" ", SHM_A("post/view/" . $image, $prefix . $image));
}
- $html .= "" .
- "" . $pool_link . " " .
- "" . $history['count'] . " " .
- "" . $image_link . " " .
- "" . $user_link . " " .
- "" . $history['date'] . " " .
- "" . $revert_link . " " .
- " ";
+ $body[] = TR(
+ TD(["class"=>"left"], $pool_link),
+ TD($history["count"]),
+ TD($image_links),
+ TD($user_link),
+ TD($history["date"]),
+ TD($revert_link)
+ );
}
- $html .= "
";
+ $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 = " ";
- foreach ($pools as $pool) {
- $output .= "" . $pool->title . " ";
- }
- return $output . " ";
+ 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 " ";
+ 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 'Search for posts that are in a pool.
-
-
pool=1
-
Returns posts in pool #1.
-
-
-
pool=any
-
Returns posts in any pool.
-
-
-
pool=none
-
Returns posts not in any pool.
-
-
-
pool_by_name=swimming
-
Returns posts in the "swimming" pool.
-
-
-
pool_by_name=swimming_pool
-
Returns posts in the "swimming pool" pool. Note that the underscore becomes a space
-
- ';
+ 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."
+ )
+ );
}
}
diff --git a/ext/post_titles/main.php b/ext/post_titles/main.php
index 0088964f..ff3ef241 100644
--- a/ext/post_titles/main.php
+++ b/ext/post_titles/main.php
@@ -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
{
diff --git a/ext/private_image/main.php b/ext/private_image/main.php
index a0768dd2..88f06d35 100644
--- a/ext/private_image/main.php
+++ b/ext/private_image/main.php
@@ -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);
}
}
diff --git a/ext/private_image/theme.php b/ext/private_image/theme.php
index 9f63c7d2..a06a2239 100644
--- a/ext/private_image/theme.php
+++ b/ext/private_image/theme.php
@@ -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]),
diff --git a/ext/qr_code/main.php b/ext/qr_code/main.php
index 405d1cfb..22468c8c 100644
--- a/ext/qr_code/main.php
+++ b/ext/qr_code/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class QRImage extends Extension
{
/** @var QRImageTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onDisplayingImage(DisplayingImageEvent $event)
{
diff --git a/ext/random_image/main.php b/ext/random_image/main.php
index d835e926..2733708c 100644
--- a/ext/random_image/main.php
+++ b/ext/random_image/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class RandomImage extends Extension
{
/** @var RandomImageTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
diff --git a/ext/random_list/main.php b/ext/random_list/main.php
index 83584801..18b077d1 100644
--- a/ext/random_list/main.php
+++ b/ext/random_list/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class RandomList extends Extension
{
/** @var RandomListTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
diff --git a/ext/rating/main.php b/ext/rating/main.php
index 4f7bbed0..eab9ae0e 100644
--- a/ext/rating/main.php
+++ b/ext/rating/main.php
@@ -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;
diff --git a/ext/rating/theme.php b/ext/rating/theme.php
index 5d514bc9..929a5288 100644
--- a/ext/rating/theme.php
+++ b/ext/rating/theme.php
@@ -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 = "
-
- Rating
-
- ".($can_rate ? "
- $human_rating
-
- ".$this->get_selection_rater_html([$rating])."
-
- " : "
- $human_rating
- ")."
-
-
- ";
+
+ $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"))."
- \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 = "";
-
- $options = Ratings::get_sorted_ratings();
-
- foreach ($options as $option) {
- if ($available_options!=null && !in_array($option->code, $available_options)) {
- continue;
- }
-
- $output .= "code, $selected_options) ? "selected='selected'" : "")
- .">".$option->name." ";
- }
- return $output." ";
- }
-
- public function get_help_html(array $ratings): string
- {
- $output = 'Search for posts with one or more possible ratings.
-
-
rating:'.$ratings[0]->search_term.'
-
Returns posts with the '.$ratings[0]->name.' rating.
-
- Ratings can be abbreviated to a single letter as well
-
-
rating:'.$ratings[0]->code.'
-
Returns posts with the '.$ratings[0]->name.' rating.
-
- If abbreviations are used, multiple ratings can be searched for.
-
-
rating:'.$ratings[0]->code.$ratings[1]->code.'
-
Returns posts with the '.$ratings[0]->name.' or '.$ratings[1]->name.' rating.
-
- Available ratings:
-
- Name Search Term Abbreviation
- ';
+ $rating_rows = [TR(TH("Name"), TH("Search Term"), TH("Abbreviation"))];
foreach ($ratings as $rating) {
- $output .= "{$rating->name} {$rating->search_term} {$rating->code} ";
+ $rating_rows[] = TR(TD($rating->name), TD($rating->search_term), TD($rating->code));
}
- $output .= "
";
- 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 = "
".make_form(make_link("user_admin/default_ratings"))."
@@ -105,7 +92,7 @@ class RatingsTheme extends Themelet
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.
- ".$this->get_selection_rater_html($selected_ratings, true, $available_ratings)."
+ ".SHM_SELECT("ratings", selected_options: $selected_ratings, multiple: true, options: $available_ratings)."
@@ -115,5 +102,5 @@ class RatingsTheme extends Themelet
";
return $html;
- }
+ } */
}
diff --git a/ext/regen_thumb/main.php b/ext/regen_thumb/main.php
index 76327f29..b66389ee 100644
--- a/ext/regen_thumb/main.php
+++ b/ext/regen_thumb/main.php
@@ -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);
diff --git a/ext/relationships/main.php b/ext/relationships/main.php
index f1bc7db1..e6684cb8 100644
--- a/ext/relationships/main.php
+++ b/ext/relationships/main.php
@@ -21,7 +21,7 @@ class ImageRelationshipSetEvent extends Event
class Relationships extends Extension
{
/** @var RelationshipsTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public const NAME = "Relationships";
diff --git a/ext/report_image/main.php b/ext/report_image/main.php
index a3629a35..81ee53d8 100644
--- a/ext/report_image/main.php
+++ b/ext/report_image/main.php
@@ -43,7 +43,7 @@ class ImageReport
class ReportImage extends Extension
{
/** @var ReportImageTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
diff --git a/ext/resize/main.php b/ext/resize/main.php
index 96ca6ec3..543c2437 100644
--- a/ext/resize/main.php
+++ b/ext/resize/main.php
@@ -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
diff --git a/ext/rotate/main.php b/ext/rotate/main.php
index 030a2ac2..f8dcfa45 100644
--- a/ext/rotate/main.php
+++ b/ext/rotate/main.php
@@ -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];
diff --git a/ext/rss_images/theme.php b/ext/rss_images/theme.php
deleted file mode 100644
index 2affd486..00000000
--- a/ext/rss_images/theme.php
+++ /dev/null
@@ -1,10 +0,0 @@
-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();
diff --git a/ext/setup/main.php b/ext/setup/main.php
index 169db3a8..8684c7a0 100644
--- a/ext/setup/main.php
+++ b/ext/setup/main.php
@@ -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)
{
diff --git a/ext/shimmie_api/main.php b/ext/shimmie_api/main.php
index 835f5f6f..7d5e1262 100644
--- a/ext/shimmie_api/main.php
+++ b/ext/shimmie_api/main.php
@@ -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;
diff --git a/ext/sitemap/main.php b/ext/sitemap/main.php
index 3843adc7..c3fefe18 100644
--- a/ext/sitemap/main.php
+++ b/ext/sitemap/main.php
@@ -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
diff --git a/ext/source_history/main.php b/ext/source_history/main.php
index db98272e..b83c29c3 100644
--- a/ext/source_history/main.php
+++ b/ext/source_history/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class SourceHistory extends Extension
{
/** @var SourceHistoryTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
// in before source are actually set, so that "get current source" works
public function get_priority(): int
@@ -209,7 +209,7 @@ class SourceHistory extends Extension
$revert_date = null;
}
- set_time_limit(0); // reverting changes can take a long time, disable php's timelimit if possible.
+ shm_set_timeout(null); // reverting changes can take a long time, disable php's timelimit if possible.
// Call the revert function.
$this->process_revert_all_changes($revert_name, $revert_ip, $revert_date);
diff --git a/ext/tag_categories/main.php b/ext/tag_categories/main.php
index 8829a41b..0a31a7a8 100644
--- a/ext/tag_categories/main.php
+++ b/ext/tag_categories/main.php
@@ -9,7 +9,7 @@ require_once "config.php";
class TagCategories extends Extension
{
/** @var TagCategoriesTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
{
diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php
index b19e1dd0..dc510a20 100644
--- a/ext/tag_edit/main.php
+++ b/ext/tag_edit/main.php
@@ -133,7 +133,7 @@ class TagTermParseEvent extends Event
class TagEdit extends Extension
{
/** @var TagEditTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
@@ -143,7 +143,7 @@ class TagEdit extends Extension
if ($user->can(Permissions::MASS_TAG_EDIT) && isset($_POST['search']) && isset($_POST['replace'])) {
$search = $_POST['search'];
$replace = $_POST['replace'];
- $this->mass_tag_edit($search, $replace);
+ $this->mass_tag_edit($search, $replace, true);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("admin"));
}
@@ -256,7 +256,7 @@ class TagEdit extends Extension
*/
public function onAddAlias(AddAliasEvent $event)
{
- $this->mass_tag_edit($event->oldtag, $event->newtag);
+ $this->mass_tag_edit($event->oldtag, $event->newtag, false);
}
public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event)
@@ -290,9 +290,9 @@ class TagEdit extends Extension
$event->replace('$tags', $tags);
}
- private function mass_tag_edit(string $search, string $replace)
+ private function mass_tag_edit(string $search, string $replace, bool $commit)
{
- global $database;
+ global $database, $tracer_enabled, $_tracer;
$search_set = Tag::explode(strtolower($search), false);
$replace_set = Tag::explode(strtolower($replace), false);
@@ -300,7 +300,7 @@ class TagEdit extends Extension
log_info("tag_edit", "Mass editing tags: '$search' -> '$replace'");
if (count($search_set) == 1 && count($replace_set) == 1) {
- $images = Image::find_images(0, 10, $replace_set);
+ $images = Image::find_images(limit: 10, tags: $replace_set);
if (count($images) == 0) {
log_info("tag_edit", "No images found with target tag, doing in-place rename");
$database->execute(
@@ -317,6 +317,9 @@ class TagEdit extends Extension
$last_id = -1;
while (true) {
+ if ($tracer_enabled) {
+ $_tracer->begin("Batch starting with $last_id");
+ }
// make sure we don't look at the same images twice.
// search returns high-ids first, so we want to look
// at images with lower IDs than the previous.
@@ -326,33 +329,24 @@ class TagEdit extends Extension
$search_forward[] = "id<$last_id";
}
- $images = Image::find_images(0, 100, $search_forward);
+ $images = Image::find_images(limit: 100, tags: $search_forward);
if (count($images) == 0) {
break;
}
foreach ($images as $image) {
- // remove the search'ed tags
$before = array_map('strtolower', $image->get_tag_array());
- $after = [];
- foreach ($before as $tag) {
- if (!in_array($tag, $search_set)) {
- $after[] = $tag;
- }
- }
-
- // add the replace'd tags
- foreach ($replace_set as $tag) {
- $after[] = $tag;
- }
-
- // replace'd tag may already exist in tag set, so remove dupes to avoid integrity constraint violations.
- $after = array_unique($after);
-
- $image->set_tags($after);
-
+ $after = array_merge(array_diff($before, $search_set), $replace_set);
+ send_event(new TagSetEvent($image, $after));
$last_id = $image->id;
}
+ if ($commit) {
+ $database->commit();
+ $database->begin_transaction();
+ }
+ if ($tracer_enabled) {
+ $_tracer->end();
+ }
}
}
@@ -370,13 +364,13 @@ class TagEdit extends Extension
$search_forward[] = "id<$last_id";
}
- $images = Image::find_images(0, 100, $search_forward);
+ $images = Image::find_images(limit: 100, tags: $search_forward);
if (count($images) == 0) {
break;
}
foreach ($images as $image) {
- $image->set_source($source);
+ send_event(new SourceSetEvent($image, $source));
$last_id = $image->id;
}
}
diff --git a/ext/tag_editcloud/info.php b/ext/tag_editcloud/info.php
index 943e1e75..315c1bc4 100644
--- a/ext/tag_editcloud/info.php
+++ b/ext/tag_editcloud/info.php
@@ -10,6 +10,6 @@ class TagEditCloudInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Tag EditCloud";
- public array $authors = ["AtomicDryad", "LaureeGrd"];
+ public array $authors = ["AtomicDryad", "Luana Latte"=>"luana.latte.cat@gmail.com"];
public string $description = "Add or remove tags to the editor via clicking.";
}
diff --git a/ext/tag_editcloud/main.php b/ext/tag_editcloud/main.php
index 1b17bf3d..98396b7f 100644
--- a/ext/tag_editcloud/main.php
+++ b/ext/tag_editcloud/main.php
@@ -175,7 +175,7 @@ class TagEditCloud extends Extension
}
if ($counter++ <= $def_count) {
- if ($last_cat !== $current_cat && $last_cat != null) {
+ if ($last_cat !== $current_cat && $last_cat !== null) {
$cloud .= "\n";
} //TODO: Maybe add a title for the category after the span opens?
$cloud .= $entry;
diff --git a/ext/tag_history/main.php b/ext/tag_history/main.php
index 8b9d2a79..1384f3db 100644
--- a/ext/tag_history/main.php
+++ b/ext/tag_history/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class TagHistory extends Extension
{
/** @var TagHistoryTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
// in before tags are actually set, so that "get current tags" works
public function get_priority(): int
@@ -207,7 +207,7 @@ class TagHistory extends Extension
$revert_date = null;
}
- set_time_limit(0); // reverting changes can take a long time, disable php's timelimit if possible.
+ shm_set_timeout(null); // reverting changes can take a long time, disable php's timelimit if possible.
// Call the revert function.
$this->process_revert_all_changes($revert_name, $revert_ip, $revert_date);
diff --git a/ext/tag_history/theme.php b/ext/tag_history/theme.php
index a11b97d2..c6ea43b4 100644
--- a/ext/tag_history/theme.php
+++ b/ext/tag_history/theme.php
@@ -39,7 +39,7 @@ class TagHistoryTheme extends Themelet
$h_next = 'Next ';
$nav = $h_prev.' | '.$h_index.' | '.$h_next;
- $page->add_block(new Block("Navigation", $nav, "left"));
+ $page->add_block(new Block("Navigation", $nav, "left", 0));
}
/**
diff --git a/ext/tag_list/main.php b/ext/tag_list/main.php
index ec598443..14dc67d4 100644
--- a/ext/tag_list/main.php
+++ b/ext/tag_list/main.php
@@ -9,7 +9,7 @@ require_once "config.php";
class TagList extends Extension
{
/** @var TagListTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
private $tagcategories = null;
diff --git a/ext/tag_list/theme.php b/ext/tag_list/theme.php
index 08f053a4..27cc8e54 100644
--- a/ext/tag_list/theme.php
+++ b/ext/tag_list/theme.php
@@ -119,7 +119,7 @@ class TagListTheme extends Themelet
$page->add_block(new Block($category_display_name, $tag_categories_html[$category], "left", 9));
}
- if ($main_html != null) {
+ if ($main_html !== null) {
$page->add_block(new Block("Tags", $main_html, "left", 10));
}
}
diff --git a/ext/tag_tools/main.php b/ext/tag_tools/main.php
index 02278056..2743f1f6 100644
--- a/ext/tag_tools/main.php
+++ b/ext/tag_tools/main.php
@@ -8,7 +8,7 @@ namespace Shimmie2;
class TagTools extends Extension
{
/** @var TagToolsTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onAdminBuilding(AdminBuildingEvent $event)
{
diff --git a/ext/tag_tools/theme.php b/ext/tag_tools/theme.php
index 11994b7d..2f52c6b4 100644
--- a/ext/tag_tools/theme.php
+++ b/ext/tag_tools/theme.php
@@ -39,7 +39,7 @@ class TagToolsTheme extends Themelet
$html = (string)SHM_SIMPLE_FORM(
"admin/set_tag_case",
- INPUT(["type"=>'text', "name"=>'tag', "placeholder"=>'Enter tag with correct case', "class"=>'autocomplete_tags', "autocomplete"=>'off']),
+ INPUT(["type"=>'text', "name"=>'tag', "placeholder"=>'Enter tag with correct case', "autocomplete"=>'off']),
SHM_SUBMIT('Set Tag Case'),
);
$page->add_block(new Block("Set Tag Case", $html));
diff --git a/ext/tips/main.php b/ext/tips/main.php
index 646b39c2..172184a6 100644
--- a/ext/tips/main.php
+++ b/ext/tips/main.php
@@ -32,7 +32,7 @@ class DeleteTipEvent extends Event
class Tips extends Extension
{
/** @var TipsTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
{
diff --git a/ext/transcode/main.php b/ext/transcode/main.php
index bc6abe30..5150eeda 100644
--- a/ext/transcode/main.php
+++ b/ext/transcode/main.php
@@ -16,7 +16,7 @@ class ImageTranscodeException extends SCoreException
class TranscodeImage extends Extension
{
/** @var TranscodeImageTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public const ACTION_BULK_TRANSCODE = "bulk_transcode";
diff --git a/ext/transcode_video/main.php b/ext/transcode_video/main.php
index 8165f0f1..ad8b698e 100644
--- a/ext/transcode_video/main.php
+++ b/ext/transcode_video/main.php
@@ -16,7 +16,7 @@ class VideoTranscodeException extends SCoreException
class TranscodeVideo extends Extension
{
/** @var TranscodeVideoTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public const ACTION_BULK_TRANSCODE = "bulk_transcode_video";
diff --git a/ext/trash/main.php b/ext/trash/main.php
index 52e0b75a..5667fb0a 100644
--- a/ext/trash/main.php
+++ b/ext/trash/main.php
@@ -12,7 +12,7 @@ abstract class TrashConfig
class Trash extends Extension
{
/** @var TrashTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function get_priority(): int
{
@@ -93,6 +93,14 @@ class Trash extends Extension
}
}
+ public function onUserBlockBuilding(UserBlockBuildingEvent $event)
+ {
+ global $user;
+ if ($user->can(Permissions::VIEW_TRASH)) {
+ $event->add_link("Trash", make_link("/post/list/in%3Atrash/1"), 60);
+ }
+ }
+
public const SEARCH_REGEXP = "/^in:trash$/";
public function onSearchTermParse(SearchTermParseEvent $event)
{
diff --git a/ext/update/main.php b/ext/update/main.php
index 88ac382b..7491f660 100644
--- a/ext/update/main.php
+++ b/ext/update/main.php
@@ -7,7 +7,7 @@ namespace Shimmie2;
class Update extends Extension
{
/** @var UpdateTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
{
diff --git a/ext/upload/main.php b/ext/upload/main.php
index 29e9015b..0efd239c 100644
--- a/ext/upload/main.php
+++ b/ext/upload/main.php
@@ -66,7 +66,7 @@ class UploadException extends SCoreException
class Upload extends Extension
{
/** @var UploadTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public bool $is_full;
/**
diff --git a/ext/upload/theme.php b/ext/upload/theme.php
index 7e1421df..e30b7248 100644
--- a/ext/upload/theme.php
+++ b/ext/upload/theme.php
@@ -45,6 +45,8 @@ class UploadTheme extends Themelet
$tl_enabled = ($config->get_string(UploadConfig::TRANSLOAD_ENGINE, "none") != "none");
$max_size = $config->get_int(UploadConfig::SIZE);
$max_kb = to_shorthand_int($max_size);
+ $max_total_size = parse_shorthand_int(ini_get('post_max_size')) - 102400; //leave room for http request data
+ $max_total_kb = to_shorthand_int($max_total_size);
$upload_list = $this->h_upload_list_1();
$form = SHM_FORM("upload", "POST", true, "file_upload");
@@ -61,19 +63,79 @@ class UploadTheme extends Themelet
),
$upload_list,
TR(
- TD(["colspan"=>"8"], INPUT(["id"=>"uploadbutton", "type"=>"submit", "value"=>"Post"]))
+ TD(["colspan"=>"8"], INPUT(["id"=>"uploadbutton", "type"=>"submit", "value"=>"Post"])),
+ TD(["colspan"=>$tl_enabled ? 2 : 4,"id"=>"upload_size_tracker"], ""),
+ TD(["colspan"=>2], ""),
+ ),
+ TR(
+ TD(["colspan"=>"6"], INPUT(["id"=>"uploadbutton", "type"=>"submit", "value"=>"Post"]))
),
)
);
$html = emptyHTML(
$form,
- SMALL("(Max file size is $max_kb)")
+ SMALL("(Max file size is $max_kb)"),
+ SMALL(BR(), "(Max total size is $max_total_kb)"),
+ rawHTML("")
);
$page->set_title("Upload");
$page->set_heading("Upload");
$page->add_block(new NavBlock());
- $page->add_block(new Block("Upload", (string)$html, "main", 20));
+ $page->add_block(new Block("Upload", $html, "main", 20));
if ($tl_enabled) {
$page->add_block(new Block("Bookmarklets", (string)$this->h_bookmarklets(), "left", 20));
}
@@ -99,7 +161,7 @@ class UploadTheme extends Themelet
for ($i=0; $i<$upload_count; $i++) {
$upload_list->appendChild(
TR(
- TD(["colspan"=>$tl_enabled ? 2 : 2], INPUT(["type"=>"file", "name"=>"data${i}[]", "accept"=>$accept, "multiple"=>true])),
+ TD(["colspan"=>$tl_enabled ? 2 : 4], DIV(["name"=>"canceldata{$i}[]","style"=>"display:inline;margin-right:5px;font-size:15px;visibility:hidden;","onclick"=>"$(\"input[name='data{$i}[]']\")[0].value='';updateTracker();"], "✖"), INPUT(["type"=>"file", "name"=>"data{$i}[]", "accept"=>$accept, "multiple"=>true])),
$tl_enabled ? TD(["colspan"=>"2"], INPUT(["type"=>"text", "name"=>"url${i}"])) : emptyHTML(),
TD(["colspan"=>"4"],
SELECT(["name"=>"rating${i}"],
@@ -108,12 +170,9 @@ class UploadTheme extends Themelet
OPTION(["value"=>"rating:explicit"], "Explicit"),
)
),
-
- DIV(["class"=>'edit'],
- TD(["colspan"=>"2"], INPUT(["type"=>"text", "name"=>"tags${i}", "id"=>"tag_editor", "class"=>"autocomplete_tags", "autocomplete"=>"off"])),
+ TD(["colspan"=>"2"], INPUT(["type"=>"text", "name"=>"tags{$i}", "class"=>"autocomplete_tags", "autocomplete"=>"off"])),
)
- )
- );
+ );
}
return $upload_list;
@@ -231,7 +290,7 @@ else {
$page->set_title("Replace Post");
$page->set_heading("Replace Post");
$page->add_block(new NavBlock());
- $page->add_block(new Block("Upload Replacement Post", (string)$html, "main", 20));
+ $page->add_block(new Block("Upload Replacement Post", $html, "main", 20));
}
public function display_upload_status(Page $page, array $image_ids): void
@@ -273,6 +332,8 @@ else {
$max_size = $config->get_int(UploadConfig::SIZE);
$max_kb = to_shorthand_int($max_size);
+ $max_total_size = parse_shorthand_int(ini_get('post_max_size')) - 102400; //leave room for http request data
+ $max_total_kb = to_shorthand_int($max_total_size);
//
$form = SHM_FORM("upload", "POST", true);
@@ -288,6 +349,7 @@ else {
["class"=>'mini_upload'],
$form,
SMALL("(Max file size is $max_kb)"),
+ SMALL(BR(), "(Max total size is $max_total_kb)"),
NOSCRIPT(BR(), A(["href"=>make_link("upload")], "Larger Form"))
);
}
diff --git a/ext/user/events.php b/ext/user/events.php
index e93391af..aacf7fa1 100644
--- a/ext/user/events.php
+++ b/ext/user/events.php
@@ -4,11 +4,13 @@ declare(strict_types=1);
namespace Shimmie2;
+use MicroHTML\HTMLElement;
+
class UserBlockBuildingEvent extends Event
{
public array $parts = [];
- public function add_link(string $name, string $link, int $position=50): void
+ public function add_link(string|HTMLElement $name, string $link, int $position=50): void
{
while (isset($this->parts[$position])) {
$position++;
diff --git a/ext/user/main.php b/ext/user/main.php
index 6bfb7451..7b8679d2 100644
--- a/ext/user/main.php
+++ b/ext/user/main.php
@@ -137,7 +137,7 @@ class LoginResult
class UserPage extends Extension
{
/** @var UserPageTheme $theme */
- public ?Themelet $theme;
+ public Themelet $theme;
public function onInitExt(InitExtEvent $event)
{
@@ -159,7 +159,7 @@ class UserPage extends Extension
public function onPageRequest(PageRequestEvent $event)
{
- global $config, $database, $page, $user;
+ global $config, $database, $page, $user, $_shm_user_classes;
$this->show_user_info();
@@ -189,6 +189,12 @@ class UserPage extends Extension
array_splice($t->columns, 2, 0, [$col]);
}
$this->theme->display_user_list($page, $t->table($t->query()), $t->paginator());
+ } elseif ($event->get_arg(0) == "classes") {
+ $this->theme->display_user_classes(
+ $page,
+ $_shm_user_classes,
+ (new \ReflectionClass('\Shimmie2\Permissions'))->getReflectionConstants()
+ );
} elseif ($event->get_arg(0) == "logout") {
$this->page_logout();
}
@@ -399,7 +405,10 @@ class UserPage extends Extension
global $user;
$event->add_link("My Profile", make_link("user"));
if ($user->can(Permissions::EDIT_USER_PASSWORD)) {
- $event->add_link("User List", make_link("user_admin/list"), 98);
+ $event->add_link("User List", make_link("user_admin/list"), 97);
+ }
+ if ($user->can(Permissions::EDIT_USER_CLASS)) {
+ $event->add_link("User Classes", make_link("user_admin/classes"), 98);
}
$event->add_link("Log Out", make_link("user_admin/logout"), 99);
}
diff --git a/ext/user/test.php b/ext/user/test.php
index 1479b720..1aa90f2a 100644
--- a/ext/user/test.php
+++ b/ext/user/test.php
@@ -37,12 +37,20 @@ class UserPageTest extends ShimmiePHPUnitTestCase
// FIXME: check class
//$this->assert_text("Admin:");
$this->log_out();
+ }
- # FIXME: test user creation
- # FIXME: test adminifying
- # FIXME: test password reset
-
+ # FIXME: test user creation
+ # FIXME: test adminifying
+ # FIXME: test password reset
+ public function testUserList()
+ {
$this->get_page('user_admin/list');
$this->assert_text("demo");
}
+
+ public function testUserClasses()
+ {
+ $this->get_page('user_admin/classes');
+ $this->assert_text("admin");
+ }
}
diff --git a/ext/user/theme.php b/ext/user/theme.php
index 2a400765..33d8a5fb 100644
--- a/ext/user/theme.php
+++ b/ext/user/theme.php
@@ -56,7 +56,7 @@ class UserPageTheme extends Themelet
$html->appendChild(BR());
$html->appendChild(A(["href"=>$part["link"]], $part["name"]));
}
- $b = new Block("User Links", (string)$html, "left", 90);
+ $b = new Block("User Links", $html, "left", 90);
$b->is_content = false;
$page->add_block($b);
}
@@ -110,7 +110,7 @@ class UserPageTheme extends Themelet
$page->set_title("Create Account");
$page->set_heading("Create Account");
$page->add_block(new NavBlock());
- $page->add_block(new Block("Signup", (string)$html));
+ $page->add_block(new Block("Signup", $html));
}
public function display_user_creator()
@@ -187,7 +187,7 @@ class UserPageTheme extends Themelet
$html->appendChild(SMALL(A(["href"=>make_link("user_admin/create")], "Create Account")));
}
- $page->add_block(new Block("Login", (string)$html, "left", 90));
+ $page->add_block(new Block("Login", $html, "left", 90));
}
private function _ip_list(string $name, array $ips): HTMLElement
@@ -220,7 +220,7 @@ class UserPageTheme extends Themelet
)
);
- $page->add_block(new Block("IPs", (string)$html, "main", 70));
+ $page->add_block(new Block("IPs", $html, "main", 70));
}
public function display_user_page(User $duser, $stats)
@@ -345,4 +345,56 @@ class UserPageTheme extends Themelet
}
return $output;
}
+
+ /**
+ * @param Page $page
+ * @param UserClass[] $classes
+ * @param \ReflectionClassConstant[] $permissions
+ */
+ public function display_user_classes(Page $page, array $classes, array $permissions): void
+ {
+ $table = TABLE(["class"=>"zebra"]);
+
+ $row = TR();
+ $row->appendChild(TH("Permission"));
+ foreach ($classes as $class) {
+ $n = $class->name;
+ if ($class->parent) {
+ $n .= " ({$class->parent->name})";
+ }
+ $row->appendChild(TH($n));
+ }
+ $row->appendChild(TH("Description"));
+ $table->appendChild($row);
+
+ foreach ($permissions as $perm) {
+ $row = TR();
+ $row->appendChild(TH($perm->getName()));
+
+ foreach ($classes as $class) {
+ $opacity = array_key_exists($perm->getValue(), $class->abilities) ? 1 : 0.2;
+ if ($class->can($perm->getValue())) {
+ $cell = TD(["style"=>"color: green; opacity: $opacity;"], "✔");
+ } else {
+ $cell = TD(["style"=>"color: red; opacity: $opacity;"], "✘");
+ }
+ $row->appendChild($cell);
+ }
+
+ $doc = $perm->getDocComment();
+ if ($doc) {
+ $doc = preg_replace('/\/\*\*|\n\s*\*\s*|\*\//', '', $doc);
+ $row->appendChild(TD(["style"=>"text-align: left;"], $doc));
+ } else {
+ $row->appendChild(TD(""));
+ }
+
+ $table->appendChild($row);
+ }
+
+ $page->set_title("User Classes");
+ $page->set_heading("User Classes");
+ $page->add_block(new NavBlock());
+ $page->add_block(new Block("Classes", $table, "main", 10));
+ }
}
diff --git a/ext/user_config/main.php b/ext/user_config/main.php
index 150b9567..cf53dbb0 100644
--- a/ext/user_config/main.php
+++ b/ext/user_config/main.php
@@ -42,7 +42,7 @@ class UserOptionsBuildingEvent extends Event
class UserConfig extends Extension
{
/** @var UserConfigTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public const VERSION = "ext_user_config_version";
public const ENABLE_API_KEYS = "ext_user_config_enable_api_keys";
@@ -98,6 +98,14 @@ class UserConfig extends Extension
}
}
+ public function onUserBlockBuilding(UserBlockBuildingEvent $event)
+ {
+ global $user;
+ if (!$user->is_anonymous()) {
+ $event->add_link("User Options", make_link("user_config"), 40);
+ }
+ }
+
public function onPageRequest(PageRequestEvent $event)
{
global $user, $database, $config, $page, $user_config;
diff --git a/ext/user_config/theme.php b/ext/user_config/theme.php
index 16fbc21c..7d5209da 100644
--- a/ext/user_config/theme.php
+++ b/ext/user_config/theme.php
@@ -62,6 +62,7 @@ class UserConfigTheme extends Themelet
$page->set_title("User Options");
$page->set_heading("User Options");
+ $page->add_block(new NavBlock());
$page->add_block(new Block("User Options", $table));
$page->set_mode(PageMode::PAGE);
}
diff --git a/ext/view/main.php b/ext/view/main.php
index 24700253..73ae1394 100644
--- a/ext/view/main.php
+++ b/ext/view/main.php
@@ -16,7 +16,7 @@ use function MicroHTML\TD;
class ViewImage extends Extension
{
/** @var ViewImageTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
diff --git a/ext/wiki/info.php b/ext/wiki/info.php
index d4cbdca5..8c2583a1 100644
--- a/ext/wiki/info.php
+++ b/ext/wiki/info.php
@@ -11,7 +11,7 @@ class WikiInfo extends ExtensionInfo
public string $key = self::KEY;
public string $name = "Simple Wiki";
public string $url = self::SHIMMIE_URL;
- public array $authors = [self::SHISH_NAME=>self::SHISH_EMAIL, "LaureeGrd"=>"laureegrd@gmail.com"];
+ public array $authors = [self::SHISH_NAME=>self::SHISH_EMAIL, "Luana Latte"=>"luana.latte.cat@gmail.com"];
public string $license = self::LICENSE_GPLV2;
public string $description = "A simple wiki, for those who don't want the hugeness of mediawiki";
public ?string $documentation = "Standard formatting APIs are used (This will be BBCode by default)";
diff --git a/ext/wiki/main.php b/ext/wiki/main.php
index 5fc72acc..c115512a 100644
--- a/ext/wiki/main.php
+++ b/ext/wiki/main.php
@@ -113,7 +113,7 @@ abstract class WikiConfig
class Wiki extends Extension
{
/** @var WikiTheme */
- protected ?Themelet $theme;
+ protected Themelet $theme;
public function onInitExt(InitExtEvent $event)
{
diff --git a/index.php b/index.php
index c80883b6..61b176ca 100644
--- a/index.php
+++ b/index.php
@@ -40,6 +40,7 @@ require_once "vendor/autoload.php";
@include_once "data/config/extensions.conf.php";
require_once "core/sys_config.php";
require_once "core/util.php";
+require_once "core/microhtml.php";
global $cache, $config, $database, $user, $page, $_tracer;
_set_up_shimmie_environment();
diff --git a/tests/docker-init.sh b/tests/docker-init.sh
index 05add5c8..e8385443 100644
--- a/tests/docker-init.sh
+++ b/tests/docker-init.sh
@@ -4,9 +4,9 @@ useradd -ms /bin/bash -u $UID -g $GID shimmie
mkdir -p /app/data
chown $UID:$GID /app/data
export PHP_CLI_SERVER_WORKERS=8
-exec /usr/local/bin/su-exec shimmie:shimmie \
+exec gosu shimmie:shimmie \
/usr/bin/php \
- -d upload_max_filesize=50M \
- -d post_max_size=50M \
- -S 0.0.0.0:8000 -q \
- tests/router.php
+ -d upload_max_filesize=$UPLOAD_MAX_FILESIZE \
+ -d post_max_size=$UPLOAD_MAX_FILESIZE \
+ -S 0.0.0.0:8000 \
+ tests/router.php 2>&1 | grep --line-buffered -vE " (Accepted|Closing)"
diff --git a/tests/router.php b/tests/router.php
index 7b9ec264..63a522b4 100644
--- a/tests/router.php
+++ b/tests/router.php
@@ -31,8 +31,6 @@ else {
// website subdirectory we're installed in - if we're using router.php, then
// let's blindly assume that we're in the root directory.
$_SERVER["PHP_SELF"] = "/index.php";
- $_GET['q'] = explode("?", $_SERVER["REQUEST_URI"])[0];
- // if we use a custom handler, we need to do our own access log
- error_log("{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} [???]: {$_GET['q']}");
+ $_GET['q'] = urldecode(explode("?", $_SERVER["REQUEST_URI"])[0]);
require_once "index.php";
}
diff --git a/themes/danbooru/themelet.class.php b/themes/danbooru/themelet.class.php
index c1858f21..de10b659 100644
--- a/themes/danbooru/themelet.class.php
+++ b/themes/danbooru/themelet.class.php
@@ -4,6 +4,10 @@ declare(strict_types=1);
namespace Shimmie2;
+use MicroHTML\HTMLElement;
+
+use function MicroHTML\{A, B, DIV};
+
class Themelet extends BaseThemelet
{
public function display_paginator(Page $page, string $base, ?string $query, int $page_number, int $total_pages, bool $show_random = false)
@@ -15,24 +19,22 @@ class Themelet extends BaseThemelet
$page->add_block(new Block(null, $body, "main", 90));
}
- 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 "$name ";
+ 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 = "";
if ($page == $current_page) {
- $paginator .= "$page ";
+ $paginator = B($page);
} else {
- $paginator .= $this->gen_page_link($base_url, $query, $page, $name);
+ $paginator = $this->gen_page_link($base_url, $query, $page, $name);
}
return $paginator;
}
- private function build_paginator(int $current_page, int $total_pages, string $base_url, ?string $query): string
+ private function build_paginator(int $current_page, int $total_pages, string $base_url, ?string $query): HTMLElement
{
$next = $current_page + 1;
$prev = $current_page - 1;
@@ -41,8 +43,8 @@ class Themelet extends BaseThemelet
$at_end = ($current_page >= $total_pages -2);
$first_html = $at_start ? "" : $this->gen_page_link($base_url, $query, 1, "1");
- $prev_html = $at_start ? "" : $this->gen_page_link($base_url, $query, $prev, "<<");
- $next_html = $at_end ? "" : $this->gen_page_link($base_url, $query, $next, ">>");
+ $prev_html = $at_start ? "" : $this->gen_page_link($base_url, $query, $prev, "<<");
+ $next_html = $at_end ? "" : $this->gen_page_link($base_url, $query, $next, ">>");
$last_html = $at_end ? "" : $this->gen_page_link($base_url, $query, $total_pages, "$total_pages");
$start = $current_page-2 > 1 ? $current_page-2 : 1;
@@ -52,20 +54,20 @@ class Themelet extends 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);
- if (strlen($first_html) > 0) {
+ if ($first_html) {
$pdots = "...";
} else {
$pdots = "";
}
- if (strlen($last_html) > 0) {
+ if ($last_html) {
$ndots = "...";
} else {
$ndots = "";
}
- return "$prev_html $first_html $pdots $pages_html $ndots $last_html $next_html
";
+ return DIV(["id"=>'paginator'], $this->implode(" ", [$prev_html, $first_html, $pdots, $pages_html, $ndots, $last_html, $next_html]));
}
}
diff --git a/themes/danbooru/view.theme.php b/themes/danbooru/view.theme.php
index 5135e41b..7314829e 100644
--- a/themes/danbooru/view.theme.php
+++ b/themes/danbooru/view.theme.php
@@ -51,7 +51,7 @@ class CustomViewImageTheme extends ViewImageTheme
}
if (Extension::is_enabled(RatingsInfo::KEY)) {
- if ($image->rating == null || $image->rating == "?") {
+ if ($image->rating === null || $image->rating == "?") {
$image->rating = "?";
}
$h_rating = Ratings::rating_to_human($image->rating);
diff --git a/themes/danbooru2/themelet.class.php b/themes/danbooru2/themelet.class.php
index c1858f21..7ba357ba 100644
--- a/themes/danbooru2/themelet.class.php
+++ b/themes/danbooru2/themelet.class.php
@@ -4,6 +4,10 @@ declare(strict_types=1);
namespace Shimmie2;
+use MicroHTML\HTMLElement;
+
+use function MicroHTML\{A,B,DIV};
+
class Themelet extends BaseThemelet
{
public function display_paginator(Page $page, string $base, ?string $query, int $page_number, int $total_pages, bool $show_random = false)
@@ -15,24 +19,22 @@ class Themelet extends BaseThemelet
$page->add_block(new Block(null, $body, "main", 90));
}
- 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 "$name ";
+ 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 = "";
if ($page == $current_page) {
- $paginator .= "$page ";
+ $paginator = B($page);
} else {
- $paginator .= $this->gen_page_link($base_url, $query, $page, $name);
+ $paginator = $this->gen_page_link($base_url, $query, $page, $name);
}
return $paginator;
}
- private function build_paginator(int $current_page, int $total_pages, string $base_url, ?string $query): string
+ private function build_paginator(int $current_page, int $total_pages, string $base_url, ?string $query): HTMLElement
{
$next = $current_page + 1;
$prev = $current_page - 1;
@@ -41,8 +43,8 @@ class Themelet extends BaseThemelet
$at_end = ($current_page >= $total_pages -2);
$first_html = $at_start ? "" : $this->gen_page_link($base_url, $query, 1, "1");
- $prev_html = $at_start ? "" : $this->gen_page_link($base_url, $query, $prev, "<<");
- $next_html = $at_end ? "" : $this->gen_page_link($base_url, $query, $next, ">>");
+ $prev_html = $at_start ? "" : $this->gen_page_link($base_url, $query, $prev, "<<");
+ $next_html = $at_end ? "" : $this->gen_page_link($base_url, $query, $next, ">>");
$last_html = $at_end ? "" : $this->gen_page_link($base_url, $query, $total_pages, "$total_pages");
$start = $current_page-2 > 1 ? $current_page-2 : 1;
@@ -52,20 +54,20 @@ class Themelet extends 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);
- if (strlen($first_html) > 0) {
+ if ($first_html) {
$pdots = "...";
} else {
$pdots = "";
}
- if (strlen($last_html) > 0) {
+ if ($last_html) {
$ndots = "...";
} else {
$ndots = "";
}
- return "$prev_html $first_html $pdots $pages_html $ndots $last_html $next_html
";
+ return DIV(["id"=>"paginator"], $this->implode(" ", [$prev_html, $first_html, $pdots, $pages_html, $ndots, $last_html, $next_html]));
}
}
diff --git a/themes/danbooru2/view.theme.php b/themes/danbooru2/view.theme.php
index bb211947..4a68b7a3 100644
--- a/themes/danbooru2/view.theme.php
+++ b/themes/danbooru2/view.theme.php
@@ -52,7 +52,7 @@ class CustomViewImageTheme extends ViewImageTheme
}
if (Extension::is_enabled(RatingsInfo::KEY)) {
- if ($image->rating == null || $image->rating == "?") {
+ if ($image->rating === null || $image->rating == "?") {
$image->rating = "?";
}
if (Extension::is_enabled(RatingsInfo::KEY)) {
diff --git a/themes/default/background.svg b/themes/default/background.svg
deleted file mode 100644
index 2a85bc72..00000000
--- a/themes/default/background.svg
+++ /dev/null
@@ -1,222 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/themes/default/style.css b/themes/default/style.css
index 7e963c99..3ff7a787 100644
--- a/themes/default/style.css
+++ b/themes/default/style.css
@@ -1,42 +1,83 @@
+:root {
+ --page: #EEE;
+ --text: #000;
+ --link-default: #0000EE;
+ --link-visited: #551A8B;
+
+ --title: #BBB;
+ --title-border: #AAA;
+
+ --header: #CCC;
+ --header-border: #BBB;
+
+ --block: #DDD;
+ --block-border: #CCC;
+
+ --zebra-border: #CCC;
+ --zebra-header: #DDD;
+ --zebra-odd: #EFEFEF;
+ --zebra-even: #E0E0E0;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --page: #222;
+ --text: #CCC;
+ --link-default: #BBF;
+ --link-visited: #EAF;
+
+ --title: #333;
+ --title-border: #555;
+
+ --header: #444;
+ --header-border: #666;
+
+ --block: #555;
+ --block-border: #777;
+
+ --zebra-border: #777;
+ --zebra-header: #444;
+ --zebra-odd: #555;
+ --zebra-even: #666;
+ }
+}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* things common to all pages *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BODY {
- background: #EEE;
+ background: var(--page);
+ color: var(--text);
font-family: sans-serif;
font-size: 14px;
margin: 0;
}
H1 {
- background: #BBB;
+ background: var(--title);
+ border: 1px solid var(--title-border);
text-align: center;
- border: 1px solid #AAA;
}
H1 A {
- color: black;
+ color: var(--text);
}
H3 {
text-align: center;
margin: 0;
}
SECTION>H3 {
- background: #CCC;
- border: 1px solid #BBB;
+ background: var(--header);
+ border: 1px solid var(--header-border);
}
SECTION>.blockbody, .comment, .setupblock {
- background: #DDD;
- border: 1px solid #CCC;
+ background: var(--block);
+ border: 1px solid var(--block-border);
}
SECTION>.blockbody, H1, SECTION>H3, FOOTER, .comment, .setupblock {
margin: 8px;
padding: 8px;
- border-radius: 12px; /* standard, webkit, opera */
- -moz-border-radius: 12px; /* mozilla haven't committed yet */
- box-shadow: 2px 2px 6px rgba(0,0,0,0.6); /* standard, opera */
- -webkit-box-shadow: 2px 2px 6px rgba(0,0,0,0.6); /* webkit haven't committed yet */
- -moz-box-shadow: 2px 2px 6px rgba(0,0,0,0.6); /* mozilla haven't committed yet */
+ border-radius: 12px;
+ box-shadow: 2px 2px 6px rgba(0,0,0,0.6);
}
THEAD {
font-weight: bold;
@@ -46,24 +87,32 @@ TD {
text-align: center;
}
-TABLE.zebra {border-spacing: 0; border: 2px solid #CCC;}
+TABLE.zebra {border-spacing: 0; border: 2px solid var(--zebra-border);}
TABLE.zebra TD, TABLE.zebra TH {vertical-align: middle; padding: 4px;}
-TABLE.zebra THEAD TD, TABLE.zebra THEAD TH {border-bottom: 2px solid #CCC;}
-TABLE.zebra TFOOT TD, TABLE.zebra TFOOT TH {border-top: 2px solid #CCC;}
-TABLE.zebra TR TD {border-bottom: 1px solid #DDD;}
-TABLE.zebra TR:nth-child(odd) {background: #EFEFEF;}
-TABLE.zebra TR:nth-child(even) {background: #E0E0E0;}
+TABLE.zebra THEAD TD, TABLE.zebra THEAD TH {border-bottom: 2px solid var(--zebra-border);}
+TABLE.zebra TFOOT TD, TABLE.zebra TFOOT TH {border-top: 2px solid var(--zebra-border);}
+TABLE.zebra TR TD {border-bottom: 1px solid var(--zebra-header);}
+TABLE.zebra TR:nth-child(odd) {background: var(--zebra-odd);}
+TABLE.zebra TR:nth-child(even) {background: var(--zebra-even);}
FOOTER {
clear: both;
font-size: 0.7em;
text-align: center;
- background: #BBB;
- border: 1px solid #AAA;
+ background: var(--title);
+ border: 1px solid var(--title-border);
}
-A {text-decoration: none;}
-A:hover {text-decoration: underline;}
+A {
+ text-decoration: none;
+ color: var(--link-default);
+}
+A:hover {
+ text-decoration: underline;
+}
+A:visited {
+ color: var(--link-visited);
+}
UL {
text-align: left;
@@ -162,19 +211,14 @@ ARTICLE TABLE {
.thumb {
width: 226px;
display: inline-block;
- zoom: 1; /* ie6 */
- *display: inline; /* ie6 */
text-align: center;
margin-bottom: 8px;
}
.thumb IMG {
- border: 1px solid #CCC;
- background: #DDD;
+ border: 1px solid var(--block-border);
+ background: var(--block);
padding: 8px;
- border-radius: 12px; /* standard, webkit, opera */
- -moz-border-radius: 12px; /* mozilla haven't committed yet */
- box-shadow: 2px 2px 6px rgba(0,0,0,0.6); /* standard, opera */
- -webkit-box-shadow: 2px 2px 6px rgba(0,0,0,0.6); /* webkit haven't committed yet */
- -moz-box-shadow: 2px 2px 6px rgba(0,0,0,0.6); /* mozilla haven't committed yet */
+ border-radius: 12px;
+ box-shadow: 2px 2px 6px rgba(0,0,0,0.6);
}
diff --git a/themes/futaba/style.css b/themes/futaba/style.css
index a9738444..e0cf705d 100644
--- a/themes/futaba/style.css
+++ b/themes/futaba/style.css
@@ -32,16 +32,23 @@ A, A:visited {text-decoration: none; color: #0000EE;}
A:hover {text-decoration: underline; color: #DD0000;}
HR {border: none; border-top: 1px solid #D9BFB7; height: 0; clear: both;}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+* the navigation bar, and all its blocks *
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
NAV {
- width: 150px;
+ width: 200px;
float: left;
- text-align:left;
+ text-align: center;
+ margin-left: 16px;
+}
+NAV .blockbody {
+ font-size: 0.85em;
+ text-align: center;
+ overflow: hidden;
}
NAV TABLE {
- width: 150px;
-}
-NAV TD {
- vertical-align: middle;
+ width: 100%;
}
NAV INPUT {
width: 100%;
@@ -51,14 +58,34 @@ NAV SELECT {
width: 100%;
padding: 0;
}
+
NAV H3 {
- text-align: left;
+ text-align: center;
}
.withleft {
margin-left: 160px;
}
-TD {
- vertical-align: top;
+
+#paginator .blockbody {
+ background: none;
+ border: none;
+ box-shadow: none;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+* the main part of each page *
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ARTICLE {
+ margin-left: 226px;
+ margin-right: 16px;
+ margin-top: 16px;
+ text-align: center;
+ height: 1%;
+}
+ARTICLE TABLE {
+ width: 90%;
+ margin: auto;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
diff --git a/themes/lite/page.class.php b/themes/lite/page.class.php
index dbd0b549..7abecb7c 100644
--- a/themes/lite/page.class.php
+++ b/themes/lite/page.class.php
@@ -77,15 +77,16 @@ class Page extends BasePage
$custom_sublinks .= "";
}
+ $flash_html = $this->flash ? "".nl2br(html_escape(implode("\n", $this->flash)))." " : "";
+
if (!$this->left_enabled) {
$left_block_html = "";
$main_block_html = "{$main_block_html} ";
} else {
$left_block_html = "{$left_block_html} ";
- $main_block_html = "{$main_block_html} ";
+ $main_block_html = "$flash_html{$main_block_html} ";
}
- $flash_html = $this->flash ? "".nl2br(html_escape(implode("\n", $this->flash)))." " : "";
$head_html = $this->head_html();
$footer_html = $this->footer_html();
@@ -100,7 +101,6 @@ class Page extends BasePage
$sub_block_html
$left_block_html
- $flash_html
$main_block_html
$footer_html
@@ -115,16 +115,17 @@ EOD;
$h = $block->header;
$b = $block->body;
$i = $block->id;
- $html = "";
- if (!is_null($h)) {
- $html .= "{$h}
";
+ $html = $b;
+ if ($h != "Paginator") {
+ $html = "";
+ if (!is_null($h)) {
+ $html .= "{$h}
";
+ }
+ if (!is_null($b)) {
+ $html .= "$b
";
+ }
+ $html .= " ";
}
- if (!is_null($b)) {
- $html .= "
- {$b}
- ";
- }
- $html .= " ";
return $html;
}
diff --git a/themes/lite/style.css b/themes/lite/style.css
index c585dfcf..0d8fced9 100644
--- a/themes/lite/style.css
+++ b/themes/lite/style.css
@@ -35,6 +35,7 @@ a.tab:hover, a.tab:active, .tab-selected {
cursor:default;
margin-right:2px;
padding:2px;
+ break-inside: avoid;
}
.tframe {
margin:4px;
@@ -57,11 +58,10 @@ a.tab:hover, a.tab:active, .tab-selected {
margin-bottom: 0;
padding: 4px;
background: #CEDFF0;
- text-align: right;
+ text-align: center;
}
-.navside {
+.navside, #comment-list-recent .blockbody {
background:none repeat scroll 0 0 #CEDFF0;
- text-align:left;
padding:4px;
font-size:85%;
border:1px solid #C3D2E0;
@@ -69,6 +69,12 @@ a.tab:hover, a.tab:active, .tab-selected {
-moz-border-radius:4px;
-webkit-border-radius:4px;
}
+.navside.navtop, .shm-image-list, #Statsmain {
+ text-align:left;
+}
+.shm-image-list {
+ margin-top: 10px;
+}
.lazy{
background:none repeat scroll 0 0 #CEDFF0;
padding:4px;
@@ -139,6 +145,7 @@ TABLE.zebra TFOOT TD, TABLE.zebra TFOOT TH {border-top: 2px solid #C3D2E0;}
TABLE.zebra TR TD {border-bottom: 1px solid #C3D2E0;}
TABLE.zebra TR:nth-child(odd) {background: #CEDFF0;}
TABLE.zebra TR:nth-child(even) {background: #F0F7FF;}
+TABLE.image_info {margin: auto;}
INPUT, TEXTAREA, button {
-moz-border-radius:4px;
@@ -158,7 +165,7 @@ FOOTER {
clear: both;
padding: 8px;
font-size: 0.7em;
- text-align: right;
+ text-align: center;
border-top: 1px solid #C3D2E0;
background: #E3EFFA;
}
@@ -242,7 +249,6 @@ TABLE.tag_list>TBODY>TR>TD:after {
}
.paginator {
- clear: both;
padding: 4px;
border-right: 1px solid #C3D2E0;
border-left: 1px solid #C3D2E0;
@@ -268,7 +274,7 @@ TABLE.tag_list>TBODY>TR>TD:after {
ARTICLE {
margin-left: 226px;
margin-right: 16px;
- text-align: left;
+ text-align: center;
height: 1%;
}
ARTICLE.body_noleft {
@@ -284,7 +290,6 @@ ARTICLE TABLE {
width: 90%;
}
-
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* specific page types *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@@ -336,11 +341,9 @@ NAV .thumbblock {
NAV .thumb {
margin-bottom: 0;
}
-.thumbblock {
- width: 220px;
- height: 220px;
+.shm-thumb {
+ width: 200px;
display: inline-block;
- float: left;
}
.thumb {
display: inline-block;
diff --git a/themes/lite/themelet.class.php b/themes/lite/themelet.class.php
index c3a0fa3d..cd0aed79 100644
--- a/themes/lite/themelet.class.php
+++ b/themes/lite/themelet.class.php
@@ -4,6 +4,10 @@ declare(strict_types=1);
namespace Shimmie2;
+use MicroHTML\HTMLElement;
+
+use function MicroHTML\{A,DIV,SPAN};
+
/**
* Class Themelet
*/
@@ -27,30 +31,25 @@ class Themelet extends BaseThemelet
$total_pages = 1;
}
$body = $this->litetheme_build_paginator($page_number, $total_pages, $base, $query, $show_random);
- $page->add_block(new Block(null, $body, "main", 90));
+ $page->add_block(new Block("Paginator", $body, "main", 90));
}
- public function litetheme_gen_page_link(string $base_url, ?string $query, int $page, string $name, ?string $link_class=null): string
+ public function litetheme_gen_page_link(string $base_url, ?string $query, int $page, string $name, ?string $link_class=null): HTMLElement
{
- $link = make_link("$base_url/$page", $query);
- return "$name ";
+ return A(["href" => make_link("$base_url/$page", $query), "class" => $link_class], $name);
}
- public function litetheme_gen_page_link_block(string $base_url, ?string $query, int $page, int $current_page, string $name): string
+ public function litetheme_gen_page_link_block(string $base_url, ?string $query, int $page, int $current_page, string $name): HTMLElement
{
- $paginator = "";
-
if ($page == $current_page) {
$link_class = "tab-selected";
} else {
$link_class = "";
}
- $paginator .= $this->litetheme_gen_page_link($base_url, $query, $page, $name, $link_class);
-
- return $paginator;
+ return $this->litetheme_gen_page_link($base_url, $query, $page, $name, $link_class);
}
- public function litetheme_build_paginator(int $current_page, int $total_pages, string $base_url, ?string $query, bool $show_random): string
+ public function litetheme_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;
@@ -58,17 +57,17 @@ class Themelet extends BaseThemelet
$at_start = ($current_page <= 1 || $total_pages <= 1);
$at_end = ($current_page >= $total_pages);
- $first_html = $at_start ? "First " : $this->litetheme_gen_page_link($base_url, $query, 1, "First");
- $prev_html = $at_start ? "Prev " : $this->litetheme_gen_page_link($base_url, $query, $prev, "Prev");
+ $first_html = $at_start ? SPAN(["class"=>"tab"], "First") : $this->litetheme_gen_page_link($base_url, $query, 1, "First");
+ $prev_html = $at_start ? SPAN(["class"=>"tab"], "Prev") : $this->litetheme_gen_page_link($base_url, $query, $prev, "Prev");
$random_html = "";
if ($show_random) {
$rand = mt_rand(1, $total_pages);
- $random_html = $this->litetheme_gen_page_link($base_url, $query, $rand, "Random");
+ $random_html = $this->litetheme_gen_page_link($base_url, $query, $rand, "Random");
}
- $next_html = $at_end ? "Next " : $this->litetheme_gen_page_link($base_url, $query, $next, "Next");
- $last_html = $at_end ? "Last " : $this->litetheme_gen_page_link($base_url, $query, $total_pages, "Last");
+ $next_html = $at_end ? SPAN(["class"=>"tab"], "Next") : $this->litetheme_gen_page_link($base_url, $query, $next, "Next");
+ $last_html = $at_end ? SPAN(["class"=>"tab"], "Last") : $this->litetheme_gen_page_link($base_url, $query, $total_pages, "Last");
$start = $current_page-5 > 1 ? $current_page-5 : 1;
$end = $start+10 < $total_pages ? $start+10 : $total_pages;
@@ -77,14 +76,17 @@ class Themelet extends BaseThemelet
foreach (range($start, $end) as $i) {
$pages[] = $this->litetheme_gen_page_link_block($base_url, $query, $i, $current_page, strval($i));
}
- $pages_html = implode(" ", $pages);
- return "";
+ return DIV(
+ ["class"=>"paginator sfoot"],
+ $first_html,
+ $prev_html,
+ $random_html,
+ "<< ",
+ $this->implode(" ", $pages),
+ " >>",
+ $next_html,
+ $last_html
+ );
}
}
diff --git a/themes/lite/view.theme.php b/themes/lite/view.theme.php
index 4688b7cb..76f95336 100644
--- a/themes/lite/view.theme.php
+++ b/themes/lite/view.theme.php
@@ -56,7 +56,7 @@ class CustomViewImageTheme extends ViewImageTheme
}
if (Extension::is_enabled(RatingsInfo::KEY)) {
- if ($image->rating == null || $image->rating == "?") {
+ if ($image->rating === null || $image->rating == "?") {
$image->rating = "?";
}
$h_rating = Ratings::rating_to_human($image->rating);
diff --git a/themes/material/home.theme.php b/themes/material/home.theme.php
deleted file mode 100644
index 55522fca..00000000
--- a/themes/material/home.theme.php
+++ /dev/null
@@ -1,77 +0,0 @@
-set_mode(PageMode::DATA);
- $page->add_auto_html_headers();
- $hh = $page->get_all_html_headers();
- $page->set_data(
- <<
-
- $sitename
-
-
-
-
-
- $hh
-
-
-
- $body
-
-