commit
d6a0d0cb8d
491 changed files with 5273 additions and 2760 deletions
|
@ -1,10 +1,3 @@
|
|||
# In retrospect I'm less of a fan of tabs for indentation, because
|
||||
# while they're better when they work, they're worse when they don't
|
||||
# work, and so many people use terrible editors when they don't work
|
||||
# that everything is inconsistent... but tabs are what Shimmie went
|
||||
# with back in the 90's, so that's what we use now, and we deal with
|
||||
# the pain of making sure everybody configures their editor properly
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
|
|
50
.github/workflows/tests.yml
vendored
50
.github/workflows/tests.yml
vendored
|
@ -12,44 +12,63 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Up Cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
vendor
|
||||
key: php-cs-fixer-${{ hashFiles('composer.lock') }}
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer update && composer install --prefer-dist --no-progress
|
||||
|
||||
run: composer install --prefer-dist --no-progress
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: 7.4
|
||||
php-version: 8.1
|
||||
- name: Format
|
||||
run: ./vendor/bin/php-cs-fixer fix && git diff --exit-code
|
||||
|
||||
- name: Check format
|
||||
run: ./vendor/bin/php-cs-fixer fix --dry-run
|
||||
static:
|
||||
name: Static Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Set Up Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
vendor
|
||||
key: phpstan-${{ hashFiles('composer.lock') }}
|
||||
- name: Install PHP dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
- name: PHPStan
|
||||
uses: php-actions/phpstan@v3
|
||||
with:
|
||||
configuration: tests/phpstan.neon
|
||||
memory_limit: 1G
|
||||
|
||||
test:
|
||||
name: PHP ${{ matrix.php }} / DB ${{ matrix.database }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ['7.4', '8.0']
|
||||
php: ['8.1', '8.2']
|
||||
database: ['pgsql', 'mysql', 'sqlite']
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set Up Cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
vendor
|
||||
|
@ -106,7 +125,6 @@ jobs:
|
|||
vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
|
||||
|
||||
- name: Upload coverage
|
||||
if: matrix.php == '7.4'
|
||||
if: matrix.php == '8.1'
|
||||
run: |
|
||||
wget https://scrutinizer-ci.com/ocular.phar
|
||||
php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover
|
||||
vendor/bin/ocular code-coverage:upload --format=php-clover data/coverage.clover
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
$_phpcs_finder = PhpCsFixer\Finder::create()
|
||||
->exclude('ext/amazon_s3/lib')
|
||||
->exclude('vendor')
|
||||
->exclude('data')
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
$config = new PhpCsFixer\Config();
|
||||
return $config->setRules([
|
||||
$_phpcs_config = new PhpCsFixer\Config();
|
||||
return $_phpcs_config->setRules([
|
||||
'@PSR12' => true,
|
||||
//'strict_param' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
||||
|
||||
?>
|
||||
->setFinder($_phpcs_finder)
|
||||
;
|
|
@ -6,6 +6,7 @@ filter:
|
|||
excluded_paths: [ext/*/lib/*,ext/tagger/script.js,tests/*]
|
||||
|
||||
build:
|
||||
image: default-bionic
|
||||
nodes:
|
||||
analysis:
|
||||
tests:
|
||||
|
|
34
Dockerfile
34
Dockerfile
|
@ -1,7 +1,10 @@
|
|||
ARG PHP_VERSION=8.2
|
||||
|
||||
# "Build" shimmie (composer install - done in its own stage so that we don't
|
||||
# need to include all the composer fluff in the final image)
|
||||
FROM debian:stable AS app
|
||||
RUN apt update && apt install -y composer php7.4-gd php7.4-dom php7.4-sqlite3 php-xdebug imagemagick
|
||||
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
|
||||
COPY composer.json composer.lock /app/
|
||||
WORKDIR /app
|
||||
RUN composer install --no-dev
|
||||
|
@ -10,8 +13,9 @@ COPY . /app/
|
|||
# Tests in their own image. Really we should inherit from app and then
|
||||
# `composer install` phpunit on top of that; but for some reason
|
||||
# `composer install --no-dev && composer install` doesn't install dev
|
||||
FROM debian:stable AS tests
|
||||
RUN apt update && apt install -y composer php7.4-gd php7.4-dom php7.4-sqlite3 php-xdebug imagemagick
|
||||
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
|
||||
COPY composer.json composer.lock /app/
|
||||
WORKDIR /app
|
||||
RUN composer install
|
||||
|
@ -25,22 +29,24 @@ RUN [ $RUN_TESTS = false ] || (\
|
|||
echo '=== Cleaning ===' && rm -rf data)
|
||||
|
||||
# Build su-exec so that our final image can be nicer
|
||||
FROM debian:stable AS suexec
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc libc-dev curl
|
||||
RUN curl -k -o /usr/local/bin/su-exec.c https://raw.githubusercontent.com/ncopa/su-exec/master/su-exec.c; \
|
||||
gcc -Wall /usr/local/bin/su-exec.c -o/usr/local/bin/su-exec; \
|
||||
chown root:root /usr/local/bin/su-exec; \
|
||||
chmod 0755 /usr/local/bin/su-exec;
|
||||
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:stable
|
||||
FROM debian:unstable
|
||||
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 install -y curl \
|
||||
php7.4-cli php7.4-gd php7.4-pgsql php7.4-mysql php7.4-sqlite3 php7.4-zip php7.4-dom php7.4-mbstring \
|
||||
imagemagick zip unzip && \
|
||||
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/*
|
||||
COPY --from=app /app /app
|
||||
COPY --from=suexec /usr/local/bin/su-exec /usr/local/bin/su-exec
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.4.0"
|
||||
"php": "8.1.0"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
|||
],
|
||||
|
||||
"require" : {
|
||||
"php" : "^7.4 | ^8.0",
|
||||
"php" : "^8.1",
|
||||
"ext-pdo": "*",
|
||||
"ext-json": "*",
|
||||
"ext-fileinfo": "*",
|
||||
|
@ -39,28 +39,33 @@
|
|||
"flexihash/flexihash" : "^2.0",
|
||||
"ifixit/php-akismet" : "^1.0",
|
||||
"google/recaptcha" : "^1.1",
|
||||
"dapphp/securimage" : "^3.6",
|
||||
"shish/eventtracer-php" : "^2.0",
|
||||
"shish/ffsphp" : "^1.0",
|
||||
"shish/microcrud" : "^2.0",
|
||||
"shish/microhtml" : "^2.0",
|
||||
"shish/gqla" : "dev-main",
|
||||
"enshrined/svg-sanitize" : "^0.15",
|
||||
|
||||
"bower-asset/jquery" : "^1.12",
|
||||
"bower-asset/jquery-timeago" : "^1.5",
|
||||
"bower-asset/js-cookie" : "^2.1"
|
||||
"bower-asset/js-cookie" : "^2.1",
|
||||
"psr/simple-cache" : "^1.0",
|
||||
"sabre/cache" : "^2.0.1",
|
||||
"naroga/redis-cache": "dev-master"
|
||||
},
|
||||
|
||||
"require-dev" : {
|
||||
"phpunit/phpunit" : "^9.0",
|
||||
"friendsofphp/php-cs-fixer" : "^3.4"
|
||||
"friendsofphp/php-cs-fixer" : "^3.12",
|
||||
"scrutinizer/ocular": "dev-master",
|
||||
"phpstan/phpstan": "1.10.x-dev"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-memcache": "memcache caching",
|
||||
"ext-memcached": "memcached caching",
|
||||
"ext-apc": "apc caching",
|
||||
"ext-apcu": "apc caching",
|
||||
"ext-redis": "redis caching",
|
||||
"ext-dom": "some extensions",
|
||||
"ext-curl": "some extensions",
|
||||
"ext-ctype": "some extensions",
|
||||
"ext-json": "some extensions",
|
||||
|
|
2713
composer.lock
generated
2713
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,15 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
require_once "core/event.php";
|
||||
|
||||
abstract class PageMode
|
||||
enum PageMode: string
|
||||
{
|
||||
public const REDIRECT = 'redirect';
|
||||
public const DATA = 'data';
|
||||
public const PAGE = 'page';
|
||||
public const FILE = 'file';
|
||||
public const MANUAL = 'manual';
|
||||
case REDIRECT = 'redirect';
|
||||
case DATA = 'data';
|
||||
case PAGE = 'page';
|
||||
case FILE = 'file';
|
||||
case MANUAL = 'manual';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,13 +25,13 @@ abstract class PageMode
|
|||
*/
|
||||
class BasePage
|
||||
{
|
||||
public string $mode = PageMode::PAGE;
|
||||
public PageMode $mode = PageMode::PAGE;
|
||||
private string $mime;
|
||||
|
||||
/**
|
||||
* Set what this page should do; "page", "data", or "redirect".
|
||||
*/
|
||||
public function set_mode(string $mode): void
|
||||
public function set_mode(PageMode $mode): void
|
||||
{
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
@ -227,7 +230,7 @@ class BasePage
|
|||
public function send_headers(): void
|
||||
{
|
||||
if (!headers_sent()) {
|
||||
header("HTTP/1.0 {$this->code} Shimmie");
|
||||
header("HTTP/1.1 {$this->code} Shimmie");
|
||||
header("Content-type: " . $this->mime);
|
||||
header("X-Powered-By: Shimmie-" . VERSION);
|
||||
|
||||
|
@ -255,7 +258,7 @@ class BasePage
|
|||
case PageMode::MANUAL:
|
||||
break;
|
||||
case PageMode::PAGE:
|
||||
usort($this->blocks, "blockcmp");
|
||||
usort($this->blocks, "Shimmie2\blockcmp");
|
||||
$this->add_auto_html_headers();
|
||||
$this->render();
|
||||
break;
|
||||
|
@ -270,6 +273,7 @@ class BasePage
|
|||
if (!is_null($this->filename)) {
|
||||
header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
|
||||
}
|
||||
assert($this->file, "file should not be null with PageMode::FILE");
|
||||
|
||||
// https://gist.github.com/codler/3906826
|
||||
$size = filesize($this->file); // File size
|
||||
|
@ -468,8 +472,8 @@ class BasePage
|
|||
}
|
||||
|
||||
$sub_links = $sub_links??[];
|
||||
usort($nav_links, "sort_nav_links");
|
||||
usort($sub_links, "sort_nav_links");
|
||||
usort($nav_links, "Shimmie2\sort_nav_links");
|
||||
usort($sub_links, "Shimmie2\sort_nav_links");
|
||||
|
||||
return [$nav_links, $sub_links];
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* Class BaseThemelet
|
||||
*
|
||||
|
@ -64,7 +66,7 @@ class BaseThemelet
|
|||
}
|
||||
|
||||
$custom_classes = "";
|
||||
if (class_exists("Relationships")) {
|
||||
if (class_exists("Shimmie2\Relationships")) {
|
||||
if (property_exists($image, 'parent_id') && $image->parent_id !== null) {
|
||||
$custom_classes .= "shm-thumb-has_parent ";
|
||||
}
|
||||
|
@ -136,8 +138,8 @@ class BaseThemelet
|
|||
$next_html = $at_end ? "Next" : $this->gen_page_link($base_url, $query, $next, "Next");
|
||||
$last_html = $at_end ? "Last" : $this->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;
|
||||
$start = max($current_page - 5, 1);
|
||||
$end = min($start + 10, $total_pages);
|
||||
|
||||
$pages = [];
|
||||
foreach (range($start, $end) as $i) {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* Class Block
|
||||
*
|
||||
|
@ -43,10 +45,10 @@ class Block
|
|||
*/
|
||||
public bool $is_content = true;
|
||||
|
||||
public function __construct(string $header=null, string $body=null, string $section="main", int $position=50, string $id=null)
|
||||
public function __construct(string $header=null, string|\MicroHTML\HTMLElement $body=null, string $section="main", int $position=50, string $id=null)
|
||||
{
|
||||
$this->header = $header;
|
||||
$this->body = $body;
|
||||
$this->body = (string)$body;
|
||||
$this->section = $section;
|
||||
$this->position = $position;
|
||||
|
||||
|
|
|
@ -1,200 +1,129 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
interface CacheEngine
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
class EventTracingCache implements CacheInterface
|
||||
{
|
||||
public function get(string $key);
|
||||
public function set(string $key, $val, int $time=0): void;
|
||||
public function delete(string $key): void;
|
||||
}
|
||||
private CacheInterface $engine;
|
||||
private \EventTracer $tracer;
|
||||
private int $hits=0;
|
||||
private int $misses=0;
|
||||
|
||||
class NoCache implements CacheEngine
|
||||
{
|
||||
public function get(string $key)
|
||||
public function __construct(CacheInterface $engine, \EventTracer $tracer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public function set(string $key, $val, int $time=0): void
|
||||
{
|
||||
}
|
||||
public function delete(string $key): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class MemcachedCache implements CacheEngine
|
||||
{
|
||||
public ?Memcached $memcache=null;
|
||||
|
||||
public function __construct(string $args)
|
||||
{
|
||||
$hp = explode(":", $args);
|
||||
$this->memcache = new Memcached();
|
||||
#$this->memcache->setOption(Memcached::OPT_COMPRESSION, False);
|
||||
#$this->memcache->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP);
|
||||
#$this->memcache->setOption(Memcached::OPT_PREFIX_KEY, phpversion());
|
||||
$this->memcache->addServer($hp[0], (int)$hp[1]);
|
||||
$this->engine = $engine;
|
||||
$this->tracer = $tracer;
|
||||
}
|
||||
|
||||
public function get(string $key)
|
||||
public function get($key, $default=null)
|
||||
{
|
||||
$key = urlencode($key);
|
||||
|
||||
$val = $this->memcache->get($key);
|
||||
$res = $this->memcache->getResultCode();
|
||||
|
||||
if ($res == Memcached::RES_SUCCESS) {
|
||||
return $val;
|
||||
} elseif ($res == Memcached::RES_NOTFOUND) {
|
||||
return false;
|
||||
} else {
|
||||
error_log("Memcached error during get($key): $res");
|
||||
return false;
|
||||
if ($key === "__etc_cache_hits") {
|
||||
return $this->hits;
|
||||
}
|
||||
}
|
||||
|
||||
public function set(string $key, $val, int $time=0): void
|
||||
{
|
||||
$key = urlencode($key);
|
||||
|
||||
$this->memcache->set($key, $val, $time);
|
||||
$res = $this->memcache->getResultCode();
|
||||
if ($res != Memcached::RES_SUCCESS) {
|
||||
error_log("Memcached error during set($key): $res");
|
||||
if ($key === "__etc_cache_misses") {
|
||||
return $this->misses;
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(string $key): void
|
||||
{
|
||||
$key = urlencode($key);
|
||||
|
||||
$this->memcache->delete($key);
|
||||
$res = $this->memcache->getResultCode();
|
||||
if ($res != Memcached::RES_SUCCESS && $res != Memcached::RES_NOTFOUND) {
|
||||
error_log("Memcached error during delete($key): $res");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class APCCache implements CacheEngine
|
||||
{
|
||||
public function __construct(string $args)
|
||||
{
|
||||
// $args is not used, but is passed in when APC cache is created.
|
||||
}
|
||||
|
||||
public function get(string $key)
|
||||
{
|
||||
return apc_fetch($key);
|
||||
}
|
||||
|
||||
public function set(string $key, $val, int $time=0): void
|
||||
{
|
||||
apc_store($key, $val, $time);
|
||||
}
|
||||
|
||||
public function delete(string $key): void
|
||||
{
|
||||
apc_delete($key);
|
||||
}
|
||||
}
|
||||
|
||||
class RedisCache implements CacheEngine
|
||||
{
|
||||
private Redis $redis;
|
||||
|
||||
public function __construct(string $args)
|
||||
{
|
||||
$this->redis = new Redis();
|
||||
$hp = explode(":", $args);
|
||||
$this->redis->pconnect($hp[0], (int)$hp[1]);
|
||||
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
|
||||
$this->redis->setOption(Redis::OPT_PREFIX, 'shm:');
|
||||
}
|
||||
|
||||
public function get(string $key)
|
||||
{
|
||||
return $this->redis->get($key);
|
||||
}
|
||||
|
||||
public function set(string $key, $val, int $time=0): void
|
||||
{
|
||||
if ($time > 0) {
|
||||
$this->redis->setEx($key, $time, $val);
|
||||
} else {
|
||||
$this->redis->set($key, $val);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(string $key): void
|
||||
{
|
||||
$this->redis->del($key);
|
||||
}
|
||||
}
|
||||
|
||||
class Cache
|
||||
{
|
||||
public $engine;
|
||||
public int $hits=0;
|
||||
public int $misses=0;
|
||||
public int $time=0;
|
||||
|
||||
public function __construct(?string $dsn)
|
||||
{
|
||||
$matches = [];
|
||||
$c = null;
|
||||
if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches) && !isset($_GET['DISABLE_CACHE'])) {
|
||||
if ($matches[1] == "memcached") {
|
||||
$c = new MemcachedCache($matches[2]);
|
||||
} elseif ($matches[1] == "apc") {
|
||||
$c = new APCCache($matches[2]);
|
||||
} elseif ($matches[1] == "redis") {
|
||||
$c = new RedisCache($matches[2]);
|
||||
}
|
||||
} else {
|
||||
$c = new NoCache();
|
||||
}
|
||||
$this->engine = $c;
|
||||
}
|
||||
|
||||
public function get(string $key)
|
||||
{
|
||||
global $_tracer;
|
||||
$_tracer->begin("Cache Query", ["key"=>$key]);
|
||||
$val = $this->engine->get($key);
|
||||
if ($val !== false) {
|
||||
$sentinel = "__etc_sentinel";
|
||||
$this->tracer->begin("Cache Get", ["key"=>$key]);
|
||||
$val = $this->engine->get($key, $sentinel);
|
||||
if ($val != $sentinel) {
|
||||
$res = "hit";
|
||||
$this->hits++;
|
||||
} else {
|
||||
$res = "miss";
|
||||
$val = $default;
|
||||
$this->misses++;
|
||||
}
|
||||
$_tracer->end(null, ["result"=>$res]);
|
||||
$this->tracer->end(null, ["result"=>$res]);
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function set(string $key, $val, int $time=0)
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
global $_tracer;
|
||||
$_tracer->begin("Cache Set", ["key"=>$key, "time"=>$time]);
|
||||
$this->engine->set($key, $val, $time);
|
||||
$_tracer->end();
|
||||
$this->tracer->begin("Cache Set", ["key"=>$key, "ttl"=>$ttl]);
|
||||
$val = $this->engine->set($key, $value, $ttl);
|
||||
$this->tracer->end();
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function delete(string $key)
|
||||
public function delete($key)
|
||||
{
|
||||
global $_tracer;
|
||||
$_tracer->begin("Cache Delete", ["key"=>$key]);
|
||||
$this->engine->delete($key);
|
||||
$_tracer->end();
|
||||
$this->tracer->begin("Cache Delete", ["key"=>$key]);
|
||||
$val = $this->engine->delete($key);
|
||||
$this->tracer->end();
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function get_hits(): int
|
||||
public function clear()
|
||||
{
|
||||
return $this->hits;
|
||||
$this->tracer->begin("Cache Clear");
|
||||
$val = $this->engine->clear();
|
||||
$this->tracer->end();
|
||||
return $val;
|
||||
}
|
||||
public function get_misses(): int
|
||||
|
||||
public function getMultiple($keys, $default = null)
|
||||
{
|
||||
return $this->misses;
|
||||
$this->tracer->begin("Cache Get Multiple", ["keys" => $keys]);
|
||||
$val = $this->engine->getMultiple($keys, $default);
|
||||
$this->tracer->end();
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function setMultiple($values, $ttl = null)
|
||||
{
|
||||
$this->tracer->begin("Cache Set Multiple", ["keys" => array_keys($values)]);
|
||||
$val = $this->engine->setMultiple($values, $ttl);
|
||||
$this->tracer->end();
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function deleteMultiple($keys)
|
||||
{
|
||||
$this->tracer->begin("Cache Delete Multiple", ["keys" => $keys]);
|
||||
$val = $this->engine->deleteMultiple($keys);
|
||||
$this->tracer->end();
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
$this->tracer->begin("Cache Has", ["key"=>$key]);
|
||||
$val = $this->engine->has($key);
|
||||
$this->tracer->end(null, ["exists"=>$val]);
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
||||
function loadCache(?string $dsn): CacheInterface
|
||||
{
|
||||
$matches = [];
|
||||
$c = null;
|
||||
if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches) && !isset($_GET['DISABLE_CACHE'])) {
|
||||
if ($matches[1] == "memcached" || $matches[1] == "memcache") {
|
||||
$hp = explode(":", $matches[2]);
|
||||
$memcache = new \Memcached();
|
||||
$memcache->addServer($hp[0], (int)$hp[1]);
|
||||
$c = new \Sabre\Cache\Memcached($memcache);
|
||||
} elseif ($matches[1] == "apc") {
|
||||
$c = new \Sabre\Cache\Apcu();
|
||||
} elseif ($matches[1] == "redis") {
|
||||
$hp = explode(":", $matches[2]);
|
||||
$redis = new \Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => $hp[0],
|
||||
'port' => (int)$hp[1]
|
||||
], ['prefix' => 'shm:']);
|
||||
$c = new \Naroga\RedisCache\Redis($redis);
|
||||
}
|
||||
} else {
|
||||
$c = new \Sabre\Cache\Memory();
|
||||
}
|
||||
global $_tracer;
|
||||
return new EventTracingCache($c, $_tracer);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* CAPTCHA abstraction *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
@ -22,10 +25,10 @@ function captcha_get_html(): string
|
|||
$captcha = "
|
||||
<div class=\"g-recaptcha\" data-sitekey=\"{$r_publickey}\"></div>
|
||||
<script type=\"text/javascript\" src=\"https://www.google.com/recaptcha/api.js\"></script>";
|
||||
} else {
|
||||
} /*else {
|
||||
session_start();
|
||||
$captcha = Securimage::getCaptchaHtml(['securimage_path' => './vendor/dapphp/securimage/']);
|
||||
}
|
||||
$captcha = \Securimage::getCaptchaHtml(['securimage_path' => './vendor/dapphp/securimage/']);
|
||||
}*/
|
||||
}
|
||||
return $captcha;
|
||||
}
|
||||
|
@ -48,14 +51,14 @@ function captcha_check(): bool
|
|||
log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes()));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
} /*else {
|
||||
session_start();
|
||||
$securimg = new Securimage();
|
||||
$securimg = new \Securimage();
|
||||
if ($securimg->check($_POST['captcha_code']) === false) {
|
||||
log_info("core", "Captcha failed (Securimage)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
// Provides mechanisms for cleanly executing command-line applications
|
||||
// Was created to try to centralize a solution for whatever caused this:
|
||||
// quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27
|
||||
|
@ -14,7 +16,7 @@ class CommandBuilder
|
|||
public function __construct(String $executable)
|
||||
{
|
||||
if (empty($executable)) {
|
||||
throw new InvalidArgumentException("executable cannot be empty");
|
||||
throw new \InvalidArgumentException("executable cannot be empty");
|
||||
}
|
||||
|
||||
$this->executable = $executable;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* Interface Config
|
||||
*
|
||||
|
@ -262,6 +264,7 @@ class DatabaseConfig extends BaseConfig
|
|||
private string $table_name;
|
||||
private ?string $sub_column;
|
||||
private ?string $sub_value;
|
||||
private string $cache_name;
|
||||
|
||||
public function __construct(
|
||||
Database $database,
|
||||
|
@ -275,14 +278,10 @@ class DatabaseConfig extends BaseConfig
|
|||
$this->table_name = $table_name;
|
||||
$this->sub_value = $sub_value;
|
||||
$this->sub_column = $sub_column;
|
||||
$this->cache_name = empty($sub_value) ? "config" : "config_{$sub_value}";
|
||||
|
||||
$cache_name = "config";
|
||||
if (!empty($sub_value)) {
|
||||
$cache_name .= "_".$sub_value;
|
||||
}
|
||||
|
||||
$cached = $cache->get($cache_name);
|
||||
if ($cached) {
|
||||
$cached = $cache->get($this->cache_name);
|
||||
if (!is_null($cached)) {
|
||||
$this->values = $cached;
|
||||
} else {
|
||||
$this->values = [];
|
||||
|
@ -298,7 +297,7 @@ class DatabaseConfig extends BaseConfig
|
|||
foreach ($this->database->get_all($query, $args) as $row) {
|
||||
$this->values[$row["name"]] = $row["value"];
|
||||
}
|
||||
$cache->set($cache_name, $this->values);
|
||||
$cache->set($this->cache_name, $this->values);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,7 +332,7 @@ class DatabaseConfig extends BaseConfig
|
|||
}
|
||||
// rather than deleting and having some other request(s) do a thundering
|
||||
// herd of race-conditioned updates, just save the updated version once here
|
||||
$cache->set("config", $this->values);
|
||||
$this->database->notify("config");
|
||||
$cache->set($this->cache_name, $this->values);
|
||||
$this->database->notify($this->cache_name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
use FFSPHP\PDO;
|
||||
|
||||
abstract class DatabaseDriver
|
||||
namespace Shimmie2;
|
||||
|
||||
use FFSPHP\PDO;
|
||||
use FFSPHP\PDOStatement;
|
||||
|
||||
enum DatabaseDriverID: string
|
||||
{
|
||||
public const MYSQL = "mysql";
|
||||
public const PGSQL = "pgsql";
|
||||
public const SQLITE = "sqlite";
|
||||
case MYSQL = "mysql";
|
||||
case PGSQL = "pgsql";
|
||||
case SQLITE = "sqlite";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,6 +36,7 @@ class Database
|
|||
* How many queries this DB object has run
|
||||
*/
|
||||
public int $query_count = 0;
|
||||
public array $queries = [];
|
||||
|
||||
public function __construct(string $dsn)
|
||||
{
|
||||
|
@ -42,7 +47,7 @@ class Database
|
|||
{
|
||||
$this->db = new PDO($this->dsn);
|
||||
$this->connect_engine();
|
||||
$this->engine->init($this->db);
|
||||
$this->get_engine()->init($this->db);
|
||||
$this->begin_transaction();
|
||||
}
|
||||
|
||||
|
@ -54,11 +59,11 @@ class Database
|
|||
throw new SCoreException("Can't figure out database engine");
|
||||
}
|
||||
|
||||
if ($db_proto === DatabaseDriver::MYSQL) {
|
||||
if ($db_proto === DatabaseDriverID::MYSQL->value) {
|
||||
$this->engine = new MySQL();
|
||||
} elseif ($db_proto === DatabaseDriver::PGSQL) {
|
||||
} elseif ($db_proto === DatabaseDriverID::PGSQL->value) {
|
||||
$this->engine = new PostgreSQL();
|
||||
} elseif ($db_proto === DatabaseDriver::SQLITE) {
|
||||
} elseif ($db_proto === DatabaseDriverID::SQLITE->value) {
|
||||
$this->engine = new SQLite();
|
||||
} else {
|
||||
die_nicely(
|
||||
|
@ -98,47 +103,53 @@ class Database
|
|||
}
|
||||
}
|
||||
|
||||
public function scoreql_to_sql(string $input): string
|
||||
private function get_engine(): DBEngine
|
||||
{
|
||||
if (is_null($this->engine)) {
|
||||
$this->connect_engine();
|
||||
}
|
||||
return $this->engine->scoreql_to_sql($input);
|
||||
return $this->engine;
|
||||
}
|
||||
|
||||
public function get_driver_name(): string
|
||||
public function scoreql_to_sql(string $input): string
|
||||
{
|
||||
if (is_null($this->engine)) {
|
||||
$this->connect_engine();
|
||||
}
|
||||
return $this->engine->name;
|
||||
return $this->get_engine()->scoreql_to_sql($input);
|
||||
}
|
||||
|
||||
public function get_driver_id(): DatabaseDriverID
|
||||
{
|
||||
return $this->get_engine()->id;
|
||||
}
|
||||
|
||||
public function get_version(): string
|
||||
{
|
||||
return $this->engine->get_version($this->db);
|
||||
return $this->get_engine()->get_version($this->db);
|
||||
}
|
||||
|
||||
private function count_time(string $method, float $start, string $query, ?array $args): void
|
||||
{
|
||||
global $_tracer, $tracer_enabled;
|
||||
$dur = microtime(true) - $start;
|
||||
$dur = ftime() - $start;
|
||||
// trim whitespace
|
||||
$query = preg_replace('/[\n\t ]/m', ' ', $query);
|
||||
$query = preg_replace('/ +/m', ' ', $query);
|
||||
$query = trim($query);
|
||||
if ($tracer_enabled) {
|
||||
$query = trim(preg_replace('/^[\t ]+/m', '', $query)); // trim leading whitespace
|
||||
$_tracer->complete($start * 1000000, $dur * 1000000, "DB Query", ["query"=>$query, "args"=>$args, "method"=>$method]);
|
||||
}
|
||||
$this->queries[] = $query;
|
||||
$this->query_count++;
|
||||
$this->dbtime += $dur;
|
||||
}
|
||||
|
||||
public function set_timeout(?int $time): void
|
||||
{
|
||||
$this->engine->set_timeout($this->db, $time);
|
||||
$this->get_engine()->set_timeout($this->db, $time);
|
||||
}
|
||||
|
||||
public function notify(string $channel, ?string $data=null): void
|
||||
{
|
||||
$this->engine->notify($this->db, $channel, $data);
|
||||
$this->get_engine()->notify($this->db, $channel, $data);
|
||||
}
|
||||
|
||||
public function execute(string $query, array $args = []): PDOStatement
|
||||
|
@ -147,12 +158,17 @@ class Database
|
|||
if (is_null($this->db)) {
|
||||
$this->connect_db();
|
||||
}
|
||||
return $this->db->execute(
|
||||
$ret = $this->db->execute(
|
||||
"-- " . str_replace("%2F", "/", urlencode($_GET['q'] ?? '')). "\n" .
|
||||
$query,
|
||||
$args
|
||||
);
|
||||
} catch (PDOException $pdoe) {
|
||||
if ($ret === false) {
|
||||
throw new SCoreException("Query failed", $query);
|
||||
}
|
||||
/** @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
return $ret;
|
||||
} catch (\PDOException $pdoe) {
|
||||
throw new SCoreException($pdoe->getMessage(), $query);
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +178,7 @@ class Database
|
|||
*/
|
||||
public function get_all(string $query, array $args = []): array
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$data = $this->execute($query, $args)->fetchAll();
|
||||
$this->count_time("get_all", $_start, $query, $args);
|
||||
return $data;
|
||||
|
@ -173,7 +189,7 @@ class Database
|
|||
*/
|
||||
public function get_all_iterable(string $query, array $args = []): PDOStatement
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$data = $this->execute($query, $args);
|
||||
$this->count_time("get_all_iterable", $_start, $query, $args);
|
||||
return $data;
|
||||
|
@ -184,7 +200,7 @@ class Database
|
|||
*/
|
||||
public function get_row(string $query, array $args = []): ?array
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$row = $this->execute($query, $args)->fetch();
|
||||
$this->count_time("get_row", $_start, $query, $args);
|
||||
return $row ? $row : null;
|
||||
|
@ -195,7 +211,7 @@ class Database
|
|||
*/
|
||||
public function get_col(string $query, array $args = []): array
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_COLUMN);
|
||||
$this->count_time("get_col", $_start, $query, $args);
|
||||
return $res;
|
||||
|
@ -204,9 +220,9 @@ class Database
|
|||
/**
|
||||
* Execute an SQL query and return the first column of each row as a single iterable object.
|
||||
*/
|
||||
public function get_col_iterable(string $query, array $args = []): Generator
|
||||
public function get_col_iterable(string $query, array $args = []): \Generator
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$stmt = $this->execute($query, $args);
|
||||
$this->count_time("get_col_iterable", $_start, $query, $args);
|
||||
foreach ($stmt as $row) {
|
||||
|
@ -219,7 +235,7 @@ class Database
|
|||
*/
|
||||
public function get_pairs(string $query, array $args = []): array
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$this->count_time("get_pairs", $_start, $query, $args);
|
||||
return $res;
|
||||
|
@ -229,9 +245,9 @@ class Database
|
|||
/**
|
||||
* Execute an SQL query and return the the first column => the second column as an iterable object.
|
||||
*/
|
||||
public function get_pairs_iterable(string $query, array $args = []): Generator
|
||||
public function get_pairs_iterable(string $query, array $args = []): \Generator
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$stmt = $this->execute($query, $args);
|
||||
$this->count_time("get_pairs_iterable", $_start, $query, $args);
|
||||
foreach ($stmt as $row) {
|
||||
|
@ -244,7 +260,7 @@ class Database
|
|||
*/
|
||||
public function get_one(string $query, array $args = [])
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$row = $this->execute($query, $args)->fetch();
|
||||
$this->count_time("get_one", $_start, $query, $args);
|
||||
return $row ? $row[0] : null;
|
||||
|
@ -255,7 +271,7 @@ class Database
|
|||
*/
|
||||
public function exists(string $query, array $args = []): bool
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$_start = ftime();
|
||||
$row = $this->execute($query, $args)->fetch();
|
||||
$this->count_time("exists", $_start, $query, $args);
|
||||
if ($row==null) {
|
||||
|
@ -269,7 +285,7 @@ class Database
|
|||
*/
|
||||
public function get_last_insert_id(string $seq): int
|
||||
{
|
||||
if ($this->engine->name == DatabaseDriver::PGSQL) {
|
||||
if ($this->get_engine()->id == DatabaseDriverID::PGSQL) {
|
||||
$id = $this->db->lastInsertId($seq);
|
||||
} else {
|
||||
$id = $this->db->lastInsertId();
|
||||
|
@ -287,7 +303,7 @@ class Database
|
|||
$this->connect_engine();
|
||||
}
|
||||
$data = trim($data, ", \t\n\r\0\x0B"); // mysql doesn't like trailing commas
|
||||
$this->execute($this->engine->create_table_sql($name, $data));
|
||||
$this->execute($this->get_engine()->create_table_sql($name, $data));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -301,32 +317,35 @@ class Database
|
|||
$this->connect_db();
|
||||
}
|
||||
|
||||
if ($this->engine->name === DatabaseDriver::MYSQL) {
|
||||
if ($this->get_engine()->id === DatabaseDriverID::MYSQL) {
|
||||
return count(
|
||||
$this->get_all("SHOW TABLES")
|
||||
);
|
||||
} elseif ($this->engine->name === DatabaseDriver::PGSQL) {
|
||||
} elseif ($this->get_engine()->id === DatabaseDriverID::PGSQL) {
|
||||
return count(
|
||||
$this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
|
||||
);
|
||||
} elseif ($this->engine->name === DatabaseDriver::SQLITE) {
|
||||
} elseif ($this->get_engine()->id === DatabaseDriverID::SQLITE) {
|
||||
return count(
|
||||
$this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'")
|
||||
);
|
||||
} else {
|
||||
throw new SCoreException("Can't count tables for database type {$this->engine->name}");
|
||||
throw new SCoreException("Can't count tables for database type {$this->get_engine()->id}");
|
||||
}
|
||||
}
|
||||
|
||||
public function raw_db(): PDO
|
||||
{
|
||||
if (is_null($this->db)) {
|
||||
$this->connect_db();
|
||||
}
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
public function standardise_boolean(string $table, string $column, bool $include_postgres=false): void
|
||||
{
|
||||
$d = $this->get_driver_name();
|
||||
if ($d == DatabaseDriver::MYSQL) {
|
||||
$d = $this->get_driver_id();
|
||||
if ($d == DatabaseDriverID::MYSQL) {
|
||||
# In mysql, ENUM('Y', 'N') is secretly INTEGER where Y=1 and N=2.
|
||||
# BOOLEAN is secretly TINYINT where true=1 and false=0.
|
||||
# So we can cast directly from ENUM to BOOLEAN which gives us a
|
||||
|
@ -335,16 +354,16 @@ class Database
|
|||
$this->execute("ALTER TABLE $table MODIFY COLUMN $column BOOLEAN;");
|
||||
$this->execute("UPDATE $table SET $column=0 WHERE $column=2;");
|
||||
}
|
||||
if ($d == DatabaseDriver::SQLITE) {
|
||||
if ($d == DatabaseDriverID::SQLITE) {
|
||||
# SQLite doesn't care about column types at all, everything is
|
||||
# text, so we can in-place replace a char with a bool
|
||||
$this->execute("UPDATE $table SET $column = ($column IN ('Y', 1))");
|
||||
}
|
||||
if ($d == DatabaseDriver::PGSQL && $include_postgres) {
|
||||
$this->execute("ALTER TABLE $table ADD COLUMN ${column}_b BOOLEAN DEFAULT FALSE NOT NULL");
|
||||
$this->execute("UPDATE $table SET ${column}_b = ($column = 'Y')");
|
||||
if ($d == DatabaseDriverID::PGSQL && $include_postgres) {
|
||||
$this->execute("ALTER TABLE $table ADD COLUMN {$column}_b BOOLEAN DEFAULT FALSE NOT NULL");
|
||||
$this->execute("UPDATE $table SET {$column}_b = ($column = 'Y')");
|
||||
$this->execute("ALTER TABLE $table DROP COLUMN $column");
|
||||
$this->execute("ALTER TABLE $table RENAME COLUMN ${column}_b TO $column");
|
||||
$this->execute("ALTER TABLE $table RENAME COLUMN {$column}_b TO $column");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use FFSPHP\PDO;
|
||||
|
||||
abstract class SCORE
|
||||
{
|
||||
public const AIPK = "SCORE_AIPK";
|
||||
|
@ -9,7 +14,7 @@ abstract class SCORE
|
|||
|
||||
abstract class DBEngine
|
||||
{
|
||||
public ?string $name = null;
|
||||
public DatabaseDriverID $id;
|
||||
|
||||
public function init(PDO $db)
|
||||
{
|
||||
|
@ -34,7 +39,7 @@ abstract class DBEngine
|
|||
|
||||
class MySQL extends DBEngine
|
||||
{
|
||||
public ?string $name = DatabaseDriver::MYSQL;
|
||||
public DatabaseDriverID $id = DatabaseDriverID::MYSQL;
|
||||
|
||||
public function init(PDO $db)
|
||||
{
|
||||
|
@ -73,7 +78,7 @@ class MySQL extends DBEngine
|
|||
|
||||
class PostgreSQL extends DBEngine
|
||||
{
|
||||
public ?string $name = DatabaseDriver::PGSQL;
|
||||
public DatabaseDriverID $id = DatabaseDriverID::PGSQL;
|
||||
|
||||
public function init(PDO $db)
|
||||
{
|
||||
|
@ -171,22 +176,22 @@ function _ln($n): float
|
|||
|
||||
class SQLite extends DBEngine
|
||||
{
|
||||
public ?string $name = DatabaseDriver::SQLITE;
|
||||
public DatabaseDriverID $id = DatabaseDriverID::SQLITE;
|
||||
|
||||
public function init(PDO $db)
|
||||
{
|
||||
ini_set('sqlite.assoc_case', '0');
|
||||
$db->exec("PRAGMA foreign_keys = ON;");
|
||||
$db->sqliteCreateFunction('UNIX_TIMESTAMP', '_unix_timestamp', 1);
|
||||
$db->sqliteCreateFunction('now', '_now', 0);
|
||||
$db->sqliteCreateFunction('floor', '_floor', 1);
|
||||
$db->sqliteCreateFunction('log', '_log');
|
||||
$db->sqliteCreateFunction('isnull', '_isnull', 1);
|
||||
$db->sqliteCreateFunction('md5', '_md5', 1);
|
||||
$db->sqliteCreateFunction('concat', '_concat', 2);
|
||||
$db->sqliteCreateFunction('lower', '_lower', 1);
|
||||
$db->sqliteCreateFunction('rand', '_rand', 0);
|
||||
$db->sqliteCreateFunction('ln', '_ln', 1);
|
||||
$db->sqliteCreateFunction('UNIX_TIMESTAMP', 'Shimmie2\_unix_timestamp', 1);
|
||||
$db->sqliteCreateFunction('now', 'Shimmie2\_now', 0);
|
||||
$db->sqliteCreateFunction('floor', 'Shimmie2\_floor', 1);
|
||||
$db->sqliteCreateFunction('log', 'Shimmie2\_log');
|
||||
$db->sqliteCreateFunction('isnull', 'Shimmie2\_isnull', 1);
|
||||
$db->sqliteCreateFunction('md5', 'Shimmie2\_md5', 1);
|
||||
$db->sqliteCreateFunction('concat', 'Shimmie2\_concat', 2);
|
||||
$db->sqliteCreateFunction('lower', 'Shimmie2\_lower', 1);
|
||||
$db->sqliteCreateFunction('rand', 'Shimmie2\_rand', 0);
|
||||
$db->sqliteCreateFunction('ln', 'Shimmie2\_ln', 1);
|
||||
}
|
||||
|
||||
public function scoreql_to_sql(string $data): string
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* Generic parent class for all events.
|
||||
*
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* A base exception to be caught by the upper levels.
|
||||
*/
|
||||
class SCoreException extends RuntimeException
|
||||
class SCoreException extends \RuntimeException
|
||||
{
|
||||
public ?string $query;
|
||||
public string $error;
|
||||
|
@ -19,7 +21,7 @@ class SCoreException extends RuntimeException
|
|||
}
|
||||
}
|
||||
|
||||
class InstallerException extends RuntimeException
|
||||
class InstallerException extends \RuntimeException
|
||||
{
|
||||
public string $title;
|
||||
public string $body;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* Class Extension
|
||||
*
|
||||
|
@ -17,7 +20,7 @@ abstract class Extension
|
|||
{
|
||||
public string $key;
|
||||
protected ?Themelet $theme;
|
||||
public ?ExtensionInfo $info;
|
||||
public ExtensionInfo $info;
|
||||
|
||||
private static array $enabled_extensions = [];
|
||||
|
||||
|
@ -26,9 +29,6 @@ abstract class Extension
|
|||
$class = $class ?? get_called_class();
|
||||
$this->theme = $this->get_theme_object($class);
|
||||
$this->info = ExtensionInfo::get_for_extension_class($class);
|
||||
if ($this->info===null) {
|
||||
throw new ScoreException("Info class not found for extension $class");
|
||||
}
|
||||
$this->key = $this->info->key;
|
||||
}
|
||||
|
||||
|
@ -37,8 +37,9 @@ abstract class Extension
|
|||
*/
|
||||
private function get_theme_object(string $base): ?Themelet
|
||||
{
|
||||
$custom = 'Custom'.$base.'Theme';
|
||||
$normal = $base.'Theme';
|
||||
$base = str_replace("Shimmie2\\", "", $base);
|
||||
$custom = "Shimmie2\Custom{$base}Theme";
|
||||
$normal = "Shimmie2\\{$base}Theme";
|
||||
|
||||
if (class_exists($custom)) {
|
||||
return new $custom();
|
||||
|
@ -61,9 +62,11 @@ abstract class Extension
|
|||
public static function determine_enabled_extensions(): void
|
||||
{
|
||||
self::$enabled_extensions = [];
|
||||
$extras = defined("EXTRA_EXTS") ? explode(",", EXTRA_EXTS) : [];
|
||||
|
||||
foreach (array_merge(
|
||||
ExtensionInfo::get_core_extensions(),
|
||||
explode(",", EXTRA_EXTS)
|
||||
$extras
|
||||
) as $key) {
|
||||
$ext = ExtensionInfo::get_by_key($key);
|
||||
if ($ext===null || !$ext->is_supported()) {
|
||||
|
@ -107,6 +110,13 @@ abstract class Extension
|
|||
}
|
||||
}
|
||||
|
||||
enum ExtensionVisibility
|
||||
{
|
||||
case DEFAULT;
|
||||
case ADMIN;
|
||||
case HIDDEN;
|
||||
}
|
||||
|
||||
abstract class ExtensionInfo
|
||||
{
|
||||
// Every credit you get costs us RAM. It stops now.
|
||||
|
@ -119,11 +129,6 @@ abstract class ExtensionInfo
|
|||
public const LICENSE_MIT = "MIT";
|
||||
public const LICENSE_WTFPL = "WTFPL";
|
||||
|
||||
public const VISIBLE_DEFAULT = "default";
|
||||
public const VISIBLE_ADMIN = "admin";
|
||||
public const VISIBLE_HIDDEN = "hidden";
|
||||
private const VALID_VISIBILITY = [self::VISIBLE_DEFAULT, self::VISIBLE_ADMIN, self::VISIBLE_HIDDEN];
|
||||
|
||||
public string $key;
|
||||
|
||||
public bool $core = false;
|
||||
|
@ -135,12 +140,12 @@ abstract class ExtensionInfo
|
|||
public array $authors = [];
|
||||
public array $dependencies = [];
|
||||
public array $conflicts = [];
|
||||
public string $visibility = self::VISIBLE_DEFAULT;
|
||||
public ExtensionVisibility $visibility = ExtensionVisibility::DEFAULT;
|
||||
public ?string $link = null;
|
||||
public ?string $version = null;
|
||||
public ?string $documentation = null;
|
||||
|
||||
/** @var string[] which DBs this ext supports (blank for 'all') */
|
||||
/** @var DatabaseDriverID[] which DBs this ext supports (blank for 'all') */
|
||||
public array $db_support = [];
|
||||
private ?bool $supported = null;
|
||||
private ?string $support_info = null;
|
||||
|
@ -169,7 +174,6 @@ abstract class ExtensionInfo
|
|||
{
|
||||
assert(!empty($this->key), "key field is required");
|
||||
assert(!empty($this->name), "name field is required for extension $this->key");
|
||||
assert(empty($this->visibility) || in_array($this->visibility, self::VALID_VISIBILITY), "Invalid visibility for extension $this->key");
|
||||
assert(is_array($this->db_support), "db_support has to be an array for extension $this->key");
|
||||
assert(is_array($this->authors), "authors has to be an array for extension $this->key");
|
||||
assert(is_array($this->dependencies), "dependencies has to be an array for extension $this->key");
|
||||
|
@ -184,7 +188,7 @@ abstract class ExtensionInfo
|
|||
{
|
||||
global $database;
|
||||
$this->support_info = "";
|
||||
if (!empty($this->db_support) && !in_array($database->get_driver_name(), $this->db_support)) {
|
||||
if (!empty($this->db_support) && !in_array($database->get_driver_id(), $this->db_support)) {
|
||||
$this->support_info .= "Database not supported. ";
|
||||
}
|
||||
if (!empty($this->conflicts)) {
|
||||
|
@ -223,23 +227,24 @@ abstract class ExtensionInfo
|
|||
}
|
||||
}
|
||||
|
||||
public static function get_for_extension_class(string $base): ?ExtensionInfo
|
||||
public static function get_for_extension_class(string $base): ExtensionInfo
|
||||
{
|
||||
$normal = $base.'Info';
|
||||
$normal = "{$base}Info";
|
||||
|
||||
if (array_key_exists($normal, self::$all_info_by_class)) {
|
||||
return self::$all_info_by_class[$normal];
|
||||
} else {
|
||||
return null;
|
||||
$infos = print_r(array_keys(self::$all_info_by_class), true);
|
||||
throw new SCoreException("$normal not found in {$infos}");
|
||||
}
|
||||
}
|
||||
|
||||
public static function load_all_extension_info()
|
||||
{
|
||||
foreach (get_subclasses_of("ExtensionInfo") as $class) {
|
||||
foreach (get_subclasses_of("Shimmie2\ExtensionInfo") as $class) {
|
||||
$extension_info = new $class();
|
||||
if (array_key_exists($extension_info->key, self::$all_info_by_key)) {
|
||||
throw new ScoreException("Extension Info $class with key $extension_info->key has already been loaded");
|
||||
throw new SCoreException("Extension Info $class with key $extension_info->key has already been loaded");
|
||||
}
|
||||
|
||||
self::$all_info_by_key[$extension_info->key] = $extension_info;
|
||||
|
@ -308,43 +313,20 @@ abstract class DataHandlerExtension extends Extension
|
|||
if (is_null($existing)) {
|
||||
throw new UploadException("Post to replace does not exist!");
|
||||
}
|
||||
if ($existing->hash === $event->metadata['hash']) {
|
||||
if ($existing->hash === $event->hash) {
|
||||
throw new UploadException("The uploaded post is the same as the one to replace.");
|
||||
}
|
||||
|
||||
// even more hax..
|
||||
$event->metadata['tags'] = $existing->get_tag_list();
|
||||
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->metadata['hash']), $event->metadata);
|
||||
if (is_null($image)) {
|
||||
throw new UploadException("Data handler failed to create post object from data");
|
||||
}
|
||||
if (empty($image->get_mime())) {
|
||||
throw new UploadException("Unable to determine MIME for ". $event->tmpname);
|
||||
}
|
||||
try {
|
||||
send_event(new MediaCheckPropertiesEvent($image));
|
||||
} catch (MediaException $e) {
|
||||
throw new UploadException("Unable to scan media properties: ".$e->getMessage());
|
||||
}
|
||||
|
||||
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata);
|
||||
send_event(new ImageReplaceEvent($event->replace_id, $image));
|
||||
$_id = $event->replace_id;
|
||||
assert(!is_null($_id));
|
||||
$event->image_id = $_id;
|
||||
} else {
|
||||
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata);
|
||||
if (is_null($image)) {
|
||||
throw new UploadException("Data handler failed to create post object from data");
|
||||
}
|
||||
if (empty($image->get_mime())) {
|
||||
throw new UploadException("Unable to determine MIME for ". $event->tmpname);
|
||||
}
|
||||
try {
|
||||
send_event(new MediaCheckPropertiesEvent($image));
|
||||
} catch (MediaException $e) {
|
||||
throw new UploadException("Unable to scan media properties: ".$e->getMessage());
|
||||
}
|
||||
|
||||
$iae = send_event(new ImageAdditionEvent($image));
|
||||
$event->image_id = $iae->image->id;
|
||||
$event->merged = $iae->merged;
|
||||
|
@ -358,7 +340,7 @@ abstract class DataHandlerExtension extends Extension
|
|||
// Locked Stuff.
|
||||
if (!empty($event->metadata['locked'])) {
|
||||
$locked = $event->metadata['locked'];
|
||||
send_event(new LockSetEvent($image, !empty($locked)));
|
||||
send_event(new LockSetEvent($image, $locked));
|
||||
}
|
||||
}
|
||||
} elseif ($supported_mime && !$check_contents) {
|
||||
|
@ -390,14 +372,14 @@ abstract class DataHandlerExtension extends Extension
|
|||
{
|
||||
global $page;
|
||||
if ($this->supported_mime($event->image->get_mime())) {
|
||||
/** @noinspection PhpPossiblePolymorphicInvocationInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
$this->theme->display_image($page, $event->image);
|
||||
}
|
||||
}
|
||||
|
||||
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
||||
{
|
||||
if ($this->supported_mime($event->mime)) {
|
||||
if ($this->supported_mime($event->image->get_mime())) {
|
||||
$this->media_check_properties($event);
|
||||
}
|
||||
}
|
||||
|
@ -406,19 +388,23 @@ abstract class DataHandlerExtension extends Extension
|
|||
{
|
||||
$image = new Image();
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
assert(is_readable($filename));
|
||||
$image->filesize = filesize($filename);
|
||||
$image->hash = md5_file($filename);
|
||||
$image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename'];
|
||||
|
||||
if (array_key_exists("extension", $metadata)) {
|
||||
$image->set_mime(MimeType::get_for_file($filename, $metadata["extension"]));
|
||||
} else {
|
||||
$image->set_mime(MimeType::get_for_file($filename));
|
||||
}
|
||||
|
||||
$image->set_mime(MimeType::get_for_file($filename, get_file_ext($metadata["filename"]) ?? null));
|
||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
if (empty($image->get_mime())) {
|
||||
throw new UploadException("Unable to determine MIME for $filename");
|
||||
}
|
||||
try {
|
||||
send_event(new MediaCheckPropertiesEvent($image));
|
||||
} catch (MediaException $e) {
|
||||
throw new UploadException("Unable to scan media properties $filename / $image->filename / $image->hash: ".$e->getMessage());
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
|
@ -434,13 +420,13 @@ abstract class DataHandlerExtension extends Extension
|
|||
public static function get_all_supported_mimes(): array
|
||||
{
|
||||
$arr = [];
|
||||
foreach (get_subclasses_of("DataHandlerExtension") as $handler) {
|
||||
foreach (get_subclasses_of("Shimmie2\DataHandlerExtension") as $handler) {
|
||||
$handler = (new $handler());
|
||||
$arr = array_merge($arr, $handler->SUPPORTED_MIME);
|
||||
}
|
||||
|
||||
// Not sure how to handle this otherwise, don't want to set up a whole other event for this one class
|
||||
if (class_exists("TranscodeImage")) {
|
||||
if (class_exists("Shimmie2\TranscodeImage")) {
|
||||
$arr = array_merge($arr, TranscodeImage::get_enabled_mimes());
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* An image is being added to the database.
|
||||
*/
|
||||
class ImageAdditionEvent extends Event
|
||||
{
|
||||
public User $user;
|
||||
public Image $image;
|
||||
public bool $merged = false;
|
||||
|
||||
/**
|
||||
|
@ -16,10 +17,10 @@ class ImageAdditionEvent extends Event
|
|||
* information. Also calls TagSetEvent to set the tags for
|
||||
* this new image.
|
||||
*/
|
||||
public function __construct(Image $image)
|
||||
{
|
||||
public function __construct(
|
||||
public Image $image,
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->image = $image;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,20 +33,17 @@ class ImageAdditionException extends SCoreException
|
|||
*/
|
||||
class ImageDeletionEvent extends Event
|
||||
{
|
||||
public Image $image;
|
||||
public bool $force = false;
|
||||
|
||||
/**
|
||||
* Deletes an image.
|
||||
*
|
||||
* Used by things like tags and comments handlers to
|
||||
* clean out related rows in their tables.
|
||||
*/
|
||||
public function __construct(Image $image, bool $force = false)
|
||||
{
|
||||
public function __construct(
|
||||
public Image $image,
|
||||
public bool $force = false,
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->image = $image;
|
||||
$this->force = $force;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,9 +52,6 @@ class ImageDeletionEvent extends Event
|
|||
*/
|
||||
class ImageReplaceEvent extends Event
|
||||
{
|
||||
public int $id;
|
||||
public Image $image;
|
||||
|
||||
/**
|
||||
* Replaces an image.
|
||||
*
|
||||
|
@ -64,11 +59,11 @@ class ImageReplaceEvent extends Event
|
|||
* file, leaving the tags and such unchanged. Also removes
|
||||
* the old image file and thumbnail from the disk.
|
||||
*/
|
||||
public function __construct(int $id, Image $image)
|
||||
{
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public Image $image
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->id = $id;
|
||||
$this->image = $image;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,20 +76,17 @@ class ImageReplaceException extends SCoreException
|
|||
*/
|
||||
class ThumbnailGenerationEvent extends Event
|
||||
{
|
||||
public string $hash;
|
||||
public string $mime;
|
||||
public bool $force;
|
||||
public bool $generated;
|
||||
|
||||
/**
|
||||
* Request a thumbnail be made for an image object
|
||||
*/
|
||||
public function __construct(string $hash, string $mime, bool $force=false)
|
||||
{
|
||||
public function __construct(
|
||||
public string $hash,
|
||||
public string $mime,
|
||||
public bool $force=false
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->hash = $hash;
|
||||
$this->mime = $mime;
|
||||
$this->force = $force;
|
||||
$this->generated = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use GQLA\Type;
|
||||
use GQLA\Field;
|
||||
use GQLA\Query;
|
||||
|
||||
/**
|
||||
* Class Image
|
||||
*
|
||||
|
@ -10,17 +17,25 @@ declare(strict_types=1);
|
|||
* image per se, but could be a video, sound file, or any
|
||||
* other supported upload type.
|
||||
*/
|
||||
#[\AllowDynamicProperties]
|
||||
#[Type(name: "Post")]
|
||||
class Image
|
||||
{
|
||||
public const IMAGE_DIR = "images";
|
||||
public const THUMBNAIL_DIR = "thumbs";
|
||||
|
||||
public ?int $id = null;
|
||||
#[Field]
|
||||
public int $height = 0;
|
||||
#[Field]
|
||||
public int $width = 0;
|
||||
#[Field]
|
||||
public string $hash;
|
||||
#[Field]
|
||||
public int $filesize;
|
||||
#[Field]
|
||||
public string $filename;
|
||||
#[Field]
|
||||
private string $ext;
|
||||
private string $mime;
|
||||
|
||||
|
@ -28,8 +43,11 @@ class Image
|
|||
public ?array $tag_array;
|
||||
public int $owner_id;
|
||||
public string $owner_ip;
|
||||
#[Field]
|
||||
public ?string $posted = null;
|
||||
#[Field]
|
||||
public ?string $source;
|
||||
#[Field]
|
||||
public bool $locked = false;
|
||||
public ?bool $lossless = null;
|
||||
public ?bool $video = null;
|
||||
|
@ -70,10 +88,26 @@ class Image
|
|||
}
|
||||
}
|
||||
|
||||
public static function by_id(int $id): ?Image
|
||||
#[Field(name: "post_id")]
|
||||
public function graphql_oid(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
#[Field(name: "id")]
|
||||
public function graphql_guid(): string
|
||||
{
|
||||
return "post:{$this->id}";
|
||||
}
|
||||
|
||||
#[Query(name: "post")]
|
||||
public static function by_id(int $post_id): ?Image
|
||||
{
|
||||
global $database;
|
||||
$row = $database->get_row("SELECT * FROM images WHERE images.id=:id", ["id"=>$id]);
|
||||
if ($post_id > 2**32) {
|
||||
// for some reason bots query huge numbers and pollute the DB error logs...
|
||||
return null;
|
||||
}
|
||||
$row = $database->get_row("SELECT * FROM images WHERE images.id=:id", ["id"=>$post_id]);
|
||||
return ($row ? new Image($row) : null);
|
||||
}
|
||||
|
||||
|
@ -132,12 +166,13 @@ class Image
|
|||
/**
|
||||
* Search for an array of images
|
||||
*
|
||||
* #param string[] $tags
|
||||
* #return Image[]
|
||||
* @param String[] $tags
|
||||
* @return Image[]
|
||||
*/
|
||||
public static function find_images(int $start, ?int $limit = null, array $tags=[]): array
|
||||
#[Query(name: "posts", type: "[Post!]!", args: ["tags" => "[string!]"])]
|
||||
public static function find_images(?int $offset = 0, ?int $limit = null, array $tags=[]): array
|
||||
{
|
||||
$result = self::find_images_internal($start, $limit, $tags);
|
||||
$result = self::find_images_internal($offset, $limit, $tags);
|
||||
|
||||
$images = [];
|
||||
foreach ($result as $row) {
|
||||
|
@ -149,7 +184,7 @@ class Image
|
|||
/**
|
||||
* Search for an array of images, returning a iterable object of Image
|
||||
*/
|
||||
public static function find_images_iterable(int $start = 0, ?int $limit = null, array $tags=[]): Generator
|
||||
public static function find_images_iterable(int $start = 0, ?int $limit = null, array $tags=[]): \Generator
|
||||
{
|
||||
$result = self::find_images_internal($start, $limit, $tags);
|
||||
foreach ($result as $row) {
|
||||
|
@ -165,7 +200,7 @@ class Image
|
|||
{
|
||||
global $cache, $database;
|
||||
$total = $cache->get("image-count");
|
||||
if (!$total) {
|
||||
if (is_null($total)) {
|
||||
$total = (int)$database->get_one("SELECT COUNT(*) FROM images");
|
||||
$cache->set("image-count", $total, 600);
|
||||
}
|
||||
|
@ -184,7 +219,7 @@ class Image
|
|||
/**
|
||||
* Count the number of image results for a given search
|
||||
*
|
||||
* #param string[] $tags
|
||||
* @param String[] $tags
|
||||
*/
|
||||
public static function count_images(array $tags=[]): int
|
||||
{
|
||||
|
@ -207,7 +242,7 @@ class Image
|
|||
// implode(tags) can be too long for memcache...
|
||||
$cache_key = "image-count:" . md5(Tag::implode($tags));
|
||||
$total = $cache->get($cache_key);
|
||||
if (!$total) {
|
||||
if (is_null($total)) {
|
||||
if (Extension::is_enabled(RatingsInfo::KEY)) {
|
||||
$tags[] = "rating:*";
|
||||
}
|
||||
|
@ -229,7 +264,7 @@ class Image
|
|||
/**
|
||||
* Count the number of pages for a given search
|
||||
*
|
||||
* #param string[] $tags
|
||||
* @param String[] $tags
|
||||
*/
|
||||
public static function count_pages(array $tags=[]): int
|
||||
{
|
||||
|
@ -248,7 +283,6 @@ class Image
|
|||
* Turn a bunch of strings into a bunch of TagCondition
|
||||
* and ImgCondition objects
|
||||
*/
|
||||
/** @var $stpe SearchTermParseEvent */
|
||||
$stpe = send_event(new SearchTermParseEvent($stpen++, null, $terms));
|
||||
if ($stpe->order) {
|
||||
$order = $stpe->order;
|
||||
|
@ -268,7 +302,6 @@ class Image
|
|||
continue;
|
||||
}
|
||||
|
||||
/** @var $stpe SearchTermParseEvent */
|
||||
$stpe = send_event(new SearchTermParseEvent($stpen++, $term, $terms));
|
||||
if ($stpe->order) {
|
||||
$order = $stpe->order;
|
||||
|
@ -296,7 +329,7 @@ class Image
|
|||
* Rather than simply $this_id + 1, one must take into account
|
||||
* deleted images and search queries
|
||||
*
|
||||
* #param string[] $tags
|
||||
* @param String[] $tags
|
||||
*/
|
||||
public function get_next(array $tags=[], bool $next=true): ?Image
|
||||
{
|
||||
|
@ -332,7 +365,7 @@ class Image
|
|||
/**
|
||||
* The reverse of get_next
|
||||
*
|
||||
* #param string[] $tags
|
||||
* @param String[] $tags
|
||||
*/
|
||||
public function get_prev(array $tags=[]): ?Image
|
||||
{
|
||||
|
@ -342,6 +375,7 @@ class Image
|
|||
/**
|
||||
* Find the User who owns this Image
|
||||
*/
|
||||
#[Field(name: "owner")]
|
||||
public function get_owner(): User
|
||||
{
|
||||
return User::by_id($this->owner_id);
|
||||
|
@ -369,7 +403,7 @@ class Image
|
|||
$cut_name = substr($this->filename, 0, 255);
|
||||
|
||||
if (is_null($this->posted) || $this->posted == "") {
|
||||
$this->posted = date('c', time());
|
||||
$this->posted = date('Y-m-d H:i:s', time());
|
||||
}
|
||||
|
||||
if (is_null($this->id)) {
|
||||
|
@ -440,8 +474,9 @@ class Image
|
|||
/**
|
||||
* Get this image's tags as an array.
|
||||
*
|
||||
* #return string[]
|
||||
* @return String[]
|
||||
*/
|
||||
#[Field(name: "tags", type: "[string!]!")]
|
||||
public function get_tag_array(): array
|
||||
{
|
||||
global $database;
|
||||
|
@ -469,6 +504,7 @@ class Image
|
|||
/**
|
||||
* Get the URL for the full size image
|
||||
*/
|
||||
#[Field(name: "image_link")]
|
||||
public function get_image_link(): string
|
||||
{
|
||||
return $this->get_link(ImageConfig::ILINK, '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext');
|
||||
|
@ -477,16 +513,16 @@ class Image
|
|||
/**
|
||||
* Get the nicely formatted version of the file name
|
||||
*/
|
||||
#[Field(name: "nice_name")]
|
||||
public function get_nice_image_name(): string
|
||||
{
|
||||
$plte = new ParseLinkTemplateEvent('$id - $tags.$ext', $this);
|
||||
send_event($plte);
|
||||
return $plte->text;
|
||||
return send_event(new ParseLinkTemplateEvent('$id - $tags.$ext', $this))->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the thumbnail
|
||||
*/
|
||||
#[Field(name: "thumb_link")]
|
||||
public function get_thumb_link(): string
|
||||
{
|
||||
global $config;
|
||||
|
@ -521,24 +557,22 @@ class Image
|
|||
* Get the tooltip for this image, formatted according to the
|
||||
* configured template.
|
||||
*/
|
||||
#[Field(name: "tooltip")]
|
||||
public function get_tooltip(): string
|
||||
{
|
||||
global $config;
|
||||
$plte = new ParseLinkTemplateEvent($config->get_string(ImageConfig::TIP), $this);
|
||||
send_event($plte);
|
||||
return $plte->text;
|
||||
return send_event(new ParseLinkTemplateEvent($config->get_string(ImageConfig::TIP), $this))->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the info for this image, formatted according to the
|
||||
* configured template.
|
||||
*/
|
||||
#[Field(name: "info")]
|
||||
public function get_info(): string
|
||||
{
|
||||
global $config;
|
||||
$plte = new ParseLinkTemplateEvent($config->get_string(ImageConfig::INFO), $this);
|
||||
send_event($plte);
|
||||
return $plte->text;
|
||||
return send_event(new ParseLinkTemplateEvent($config->get_string(ImageConfig::INFO), $this))->text;
|
||||
}
|
||||
|
||||
|
||||
|
@ -561,6 +595,7 @@ class Image
|
|||
/**
|
||||
* Get the original filename.
|
||||
*/
|
||||
#[Field(name: "filename")]
|
||||
public function get_filename(): string
|
||||
{
|
||||
return $this->filename;
|
||||
|
@ -569,6 +604,7 @@ class Image
|
|||
/**
|
||||
* Get the image's extension.
|
||||
*/
|
||||
#[Field(name: "ext")]
|
||||
public function get_ext(): string
|
||||
{
|
||||
return $this->ext;
|
||||
|
@ -577,6 +613,7 @@ class Image
|
|||
/**
|
||||
* Get the image's mime type.
|
||||
*/
|
||||
#[Field(name: "mime")]
|
||||
public function get_mime(): ?string
|
||||
{
|
||||
if ($this->mime===MimeType::WEBP&&$this->lossless) {
|
||||
|
@ -650,7 +687,7 @@ class Image
|
|||
public function delete_tags_from_image(): void
|
||||
{
|
||||
global $database;
|
||||
if ($database->get_driver_name() == DatabaseDriver::MYSQL) {
|
||||
if ($database->get_driver_id() == DatabaseDriverID::MYSQL) {
|
||||
//mysql < 5.6 has terrible subquery optimization, using EXISTS / JOIN fixes this
|
||||
$database->execute(
|
||||
"
|
||||
|
@ -743,7 +780,7 @@ class Image
|
|||
VALUES(:iid, :tid)
|
||||
", ["iid"=>$this->id, "tid"=>$id]);
|
||||
|
||||
array_push($written_tags, $id);
|
||||
$written_tags[] = $id;
|
||||
}
|
||||
$database->execute(
|
||||
"
|
||||
|
@ -796,14 +833,14 @@ class Image
|
|||
{
|
||||
global $database;
|
||||
$sq = "SELECT id FROM tags WHERE LOWER(tag) LIKE LOWER(:tag)";
|
||||
if ($database->get_driver_name() === DatabaseDriver::SQLITE) {
|
||||
if ($database->get_driver_id() === DatabaseDriverID::SQLITE) {
|
||||
$sq .= "ESCAPE '\\'";
|
||||
}
|
||||
return $database->get_col($sq, ["tag" => Tag::sqlify($tag)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* #param string[] $terms
|
||||
* @param String[] $terms
|
||||
*/
|
||||
private static function build_search_querylet(
|
||||
array $terms,
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Misc functions *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
@ -20,8 +23,6 @@ function add_dir(string $base): array
|
|||
$filename = basename($full_path);
|
||||
|
||||
$tags = path_to_tags($short_path);
|
||||
if ($tags[0] == "\\")
|
||||
$tags = "";
|
||||
$result = "$short_path (".str_replace(" ", ", ", $tags).")... ";
|
||||
try {
|
||||
add_image($full_path, $filename, $tags);
|
||||
|
@ -38,24 +39,18 @@ function add_dir(string $base): array
|
|||
/**
|
||||
* Sends a DataUploadEvent for a file.
|
||||
*/
|
||||
function add_image(string $tmpname, string $filename, string $tags): int
|
||||
function add_image(string $tmpname, string $filename, string $tags, ?string $source=null): DataUploadEvent
|
||||
{
|
||||
assert(file_exists($tmpname));
|
||||
return send_event(new DataUploadEvent($tmpname, [
|
||||
'filename' => pathinfo($filename, PATHINFO_BASENAME),
|
||||
'tags' => Tag::explode($tags),
|
||||
'source' => $source,
|
||||
]));
|
||||
}
|
||||
|
||||
$pathinfo = pathinfo($filename);
|
||||
$metadata = [];
|
||||
$metadata['filename'] = $pathinfo['basename'];
|
||||
if (array_key_exists('extension', $pathinfo)) {
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
}
|
||||
|
||||
$metadata['tags'] = Tag::explode($tags);
|
||||
$metadata['source'] = null;
|
||||
|
||||
$due = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($due);
|
||||
|
||||
return $due->image_id;
|
||||
function get_file_ext(string $filename): ?string
|
||||
{
|
||||
return pathinfo($filename)['extension'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class Querylet
|
||||
{
|
||||
public string $sql;
|
||||
public array $variables;
|
||||
|
||||
public function __construct(string $sql, array $variables=[])
|
||||
{
|
||||
$this->sql = $sql;
|
||||
$this->variables = $variables;
|
||||
public function __construct(
|
||||
public string $sql,
|
||||
public array $variables=[],
|
||||
) {
|
||||
}
|
||||
|
||||
public function append(Querylet $querylet): void
|
||||
|
@ -31,24 +31,18 @@ class Querylet
|
|||
|
||||
class TagCondition
|
||||
{
|
||||
public string $tag;
|
||||
public bool $positive;
|
||||
|
||||
public function __construct(string $tag, bool $positive)
|
||||
{
|
||||
$this->tag = $tag;
|
||||
$this->positive = $positive;
|
||||
public function __construct(
|
||||
public string $tag,
|
||||
public bool $positive,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
class ImgCondition
|
||||
{
|
||||
public Querylet $qlet;
|
||||
public bool $positive;
|
||||
|
||||
public function __construct(Querylet $qlet, bool $positive)
|
||||
{
|
||||
$this->qlet = $qlet;
|
||||
$this->positive = $positive;
|
||||
public function __construct(
|
||||
public Querylet $qlet,
|
||||
public bool $positive,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,85 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use GQLA\Type;
|
||||
use GQLA\Field;
|
||||
use GQLA\Query;
|
||||
|
||||
#[Type(name: "TagUsage")]
|
||||
class TagUsage
|
||||
{
|
||||
#[Field]
|
||||
public string $tag;
|
||||
#[Field]
|
||||
public int $uses;
|
||||
|
||||
public function __construct(string $tag, int $uses)
|
||||
{
|
||||
$this->tag = $tag;
|
||||
$this->uses = $uses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TagUsage[]
|
||||
*/
|
||||
#[Query(name: "tags", type: '[TagUsage!]!')]
|
||||
public static function tags(string $search, int $limit=10): array
|
||||
{
|
||||
global $cache, $database;
|
||||
|
||||
if (!$search) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$search = strtolower($search);
|
||||
if (
|
||||
$search == '' ||
|
||||
$search[0] == '_' ||
|
||||
$search[0] == '%' ||
|
||||
strlen($search) > 32
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cache_key = "tagusage-$search";
|
||||
$limitSQL = "";
|
||||
$search = str_replace('_', '\_', $search);
|
||||
$search = str_replace('%', '\%', $search);
|
||||
$SQLarr = ["search"=>"$search%"]; #, "cat_search"=>"%:$search%"];
|
||||
if ($limit !== 0) {
|
||||
$limitSQL = "LIMIT :limit";
|
||||
$SQLarr['limit'] = $limit;
|
||||
$cache_key .= "-" . $limit;
|
||||
}
|
||||
|
||||
$res = $cache->get($cache_key);
|
||||
if (is_null($res)) {
|
||||
$res = $database->get_pairs(
|
||||
"
|
||||
SELECT tag, count
|
||||
FROM tags
|
||||
WHERE LOWER(tag) LIKE LOWER(:search)
|
||||
-- OR LOWER(tag) LIKE LOWER(:cat_search)
|
||||
AND count > 0
|
||||
ORDER BY count DESC
|
||||
$limitSQL
|
||||
",
|
||||
$SQLarr
|
||||
);
|
||||
$cache->set($cache_key, $res, 600);
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach ($res as $k => $v) {
|
||||
$counts[] = new TagUsage($k, $v);
|
||||
}
|
||||
return $counts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Tag
|
||||
*
|
||||
|
@ -90,7 +169,7 @@ class Tag
|
|||
public static function sanitize(string $tag): string
|
||||
{
|
||||
$tag = preg_replace("/\s/", "", $tag); # whitespace
|
||||
$tag = preg_replace('/\x20[\x0e\x0f]/', '', $tag); # unicode RTL
|
||||
$tag = preg_replace('/\x20[\x0e\x0f]/', '', $tag); # unicode RTL
|
||||
$tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
|
||||
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
|
||||
$tag = trim($tag, ", \t\n\r\0\x0B");
|
||||
|
@ -100,7 +179,7 @@ class Tag
|
|||
} // hard-code one bad case...
|
||||
|
||||
if (mb_strlen($tag, 'UTF-8') > 255) {
|
||||
throw new ScoreException("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n");
|
||||
throw new SCoreException("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n");
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
|
@ -113,15 +192,10 @@ class Tag
|
|||
|
||||
$tags1 = array_map("strtolower", $tags1);
|
||||
$tags2 = array_map("strtolower", $tags2);
|
||||
natcasesort($tags1);
|
||||
natcasesort($tags2);
|
||||
sort($tags1);
|
||||
sort($tags2);
|
||||
|
||||
for ($i = 0; $i < count($tags1); $i++) {
|
||||
if ($tags1[$i]!==$tags2[$i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return $tags1 == $tags2;
|
||||
}
|
||||
|
||||
public static function get_diff_tags(array $source, array $remove): array
|
||||
|
@ -144,7 +218,7 @@ class Tag
|
|||
foreach ($tags as $tag) {
|
||||
try {
|
||||
$tag = Tag::sanitize($tag);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
$page->flash($e->getMessage());
|
||||
continue;
|
||||
}
|
||||
|
@ -159,7 +233,7 @@ class Tag
|
|||
public static function sqlify(string $term): string
|
||||
{
|
||||
global $database;
|
||||
if ($database->get_driver_name() === DatabaseDriver::SQLITE) {
|
||||
if ($database->get_driver_id() === DatabaseDriverID::SQLITE) {
|
||||
$term = str_replace('\\', '\\\\', $term);
|
||||
}
|
||||
$term = str_replace('_', '\_', $term);
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* Shimmie Installer
|
||||
*
|
||||
|
@ -29,7 +34,7 @@ function install()
|
|||
// Pull in necessary files
|
||||
require_once "vendor/autoload.php";
|
||||
global $_tracer;
|
||||
$_tracer = new EventTracer();
|
||||
$_tracer = new \EventTracer();
|
||||
|
||||
require_once "core/exceptions.php";
|
||||
require_once "core/cacheengine.php";
|
||||
|
@ -49,7 +54,7 @@ function get_dsn()
|
|||
{
|
||||
if (getenv("INSTALL_DSN")) {
|
||||
$dsn = getenv("INSTALL_DSN");
|
||||
} elseif (@$_POST["database_type"] == DatabaseDriver::SQLITE) {
|
||||
} elseif (@$_POST["database_type"] == DatabaseDriverID::SQLITE->value) {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$id = bin2hex(random_bytes(5));
|
||||
$dsn = "sqlite:data/shimmie.{$id}.sqlite";
|
||||
|
@ -97,11 +102,11 @@ function ask_questions()
|
|||
";
|
||||
}
|
||||
|
||||
$drivers = PDO::getAvailableDrivers();
|
||||
$drivers = \PDO::getAvailableDrivers();
|
||||
if (
|
||||
!in_array(DatabaseDriver::MYSQL, $drivers) &&
|
||||
!in_array(DatabaseDriver::PGSQL, $drivers) &&
|
||||
!in_array(DatabaseDriver::SQLITE, $drivers)
|
||||
!in_array(DatabaseDriverID::MYSQL->value, $drivers) &&
|
||||
!in_array(DatabaseDriverID::PGSQL->value, $drivers) &&
|
||||
!in_array(DatabaseDriverID::SQLITE->value, $drivers)
|
||||
) {
|
||||
$errors[] = "
|
||||
No database connection library could be found; shimmie needs
|
||||
|
@ -109,9 +114,9 @@ function ask_questions()
|
|||
";
|
||||
}
|
||||
|
||||
$db_m = in_array(DatabaseDriver::MYSQL, $drivers) ? '<option value="'. DatabaseDriver::MYSQL .'">MySQL</option>' : "";
|
||||
$db_p = in_array(DatabaseDriver::PGSQL, $drivers) ? '<option value="'. DatabaseDriver::PGSQL .'">PostgreSQL</option>' : "";
|
||||
$db_s = in_array(DatabaseDriver::SQLITE, $drivers) ? '<option value="'. DatabaseDriver::SQLITE .'">SQLite</option>' : "";
|
||||
$db_m = in_array(DatabaseDriverID::MYSQL->value, $drivers) ? '<option value="'. DatabaseDriverID::MYSQL->value .'">MySQL</option>' : "";
|
||||
$db_p = in_array(DatabaseDriverID::PGSQL->value, $drivers) ? '<option value="'. DatabaseDriverID::PGSQL->value .'">PostgreSQL</option>' : "";
|
||||
$db_s = in_array(DatabaseDriverID::SQLITE->value, $drivers) ? '<option value="'. DatabaseDriverID::SQLITE->value .'">SQLite</option>' : "";
|
||||
|
||||
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
|
||||
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
|
||||
|
@ -287,7 +292,7 @@ function create_tables(Database $db)
|
|||
if ($db->is_transaction_open()) {
|
||||
$db->commit();
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
} catch (\PDOException $e) {
|
||||
throw new InstallerException(
|
||||
"PDO Error:",
|
||||
"<p>An error occurred while trying to create the database tables necessary for Shimmie.</p>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Logging convenience *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
|
|
@ -2,9 +2,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use GQLA\Enum;
|
||||
|
||||
// action_object_attribute
|
||||
// action = create / view / edit / delete
|
||||
// object = image / user / tag / setting
|
||||
#[Enum(name: "Permission")]
|
||||
abstract class Permissions
|
||||
{
|
||||
public const CHANGE_SETTING = "change_setting"; # modify web-level settings, eg the config table
|
||||
|
@ -67,9 +72,13 @@ abstract class Permissions
|
|||
public const SEND_PM = "send_pm";
|
||||
public const READ_PM = "read_pm";
|
||||
public const VIEW_OTHER_PMS = "view_other_pms";
|
||||
|
||||
public const EDIT_FEATURE = "edit_feature";
|
||||
|
||||
public const CREATE_VOTE = "create_vote";
|
||||
public const BULK_EDIT_VOTE = "bulk_edit_vote";
|
||||
public const EDIT_OTHER_VOTE = "edit_other_vote";
|
||||
|
||||
public const VIEW_SYSINTO = "view_sysinfo";
|
||||
|
||||
public const HELLBANNED = "hellbanned";
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Things which should be in the core API *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
@ -36,7 +39,7 @@ function ip_in_range(string $IP, string $CIDR): bool
|
|||
list($net, $mask) = explode("/", $CIDR);
|
||||
|
||||
$ip_net = ip2long($net);
|
||||
$ip_mask = ~((1 << (32 - $mask)) - 1);
|
||||
$ip_mask = ~((1 << (32 - (int)$mask)) - 1);
|
||||
|
||||
$ip_ip = ip2long($IP);
|
||||
|
||||
|
@ -267,7 +270,7 @@ function get_subclasses_of(string $parent): array
|
|||
{
|
||||
$result = [];
|
||||
foreach (get_declared_classes() as $class) {
|
||||
$rclass = new ReflectionClass($class);
|
||||
$rclass = new \ReflectionClass($class);
|
||||
if (!$rclass->isAbstract() && is_subclass_of($class, $parent)) {
|
||||
$result[] = $class;
|
||||
}
|
||||
|
@ -333,38 +336,17 @@ function get_base_href(): string
|
|||
function unparse_url(array $parsed_url): string
|
||||
{
|
||||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
|
||||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||||
$host = $parsed_url['host'] ?? '';
|
||||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||||
$user = $parsed_url['user'] ?? '';
|
||||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
|
||||
$pass = ($user || $pass) ? "$pass@" : '';
|
||||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||||
$path = $parsed_url['path'] ?? '';
|
||||
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
|
||||
$fragment = !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||||
return "$scheme$user$pass$host$port$path$query$fragment";
|
||||
}
|
||||
|
||||
# finally in the core library starting from php8
|
||||
if (!function_exists('str_starts_with')) {
|
||||
function str_starts_with(string $haystack, string $needle): bool
|
||||
{
|
||||
return strncmp($haystack, $needle, strlen($needle)) === 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('str_ends_with')) {
|
||||
function str_ends_with(string $haystack, string $needle): bool
|
||||
{
|
||||
return $needle === '' || $needle === substr($haystack, - strlen($needle));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('str_contains')) {
|
||||
function str_contains(string $haystack, string $needle): bool
|
||||
{
|
||||
return '' === $needle || false !== strpos($haystack, $needle);
|
||||
}
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Input / Output Sanitising *
|
||||
|
@ -517,19 +499,24 @@ function truncate(string $string, int $limit, string $break=" ", string $pad="..
|
|||
function parse_shorthand_int(string $limit): int
|
||||
{
|
||||
if (preg_match('/^([\d\.]+)([tgmk])?b?$/i', (string)$limit, $m)) {
|
||||
$value = $m[1];
|
||||
$value = (float)$m[1];
|
||||
if (isset($m[2])) {
|
||||
switch (strtolower($m[2])) {
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case 't': $value *= 1024; // fall through
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
case 'g': $value *= 1024; // fall through
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
case 'm': $value *= 1024; // fall through
|
||||
// no break
|
||||
case 'k': $value *= 1024; break;
|
||||
case 't':
|
||||
$value *= 1024; // fall through
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
case 'g':
|
||||
$value *= 1024; // fall through
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
// no break
|
||||
case 'm':
|
||||
$value *= 1024; // fall through
|
||||
// no break
|
||||
case 'k':
|
||||
$value *= 1024;
|
||||
break;
|
||||
default: $value = -1;
|
||||
}
|
||||
}
|
||||
|
@ -787,7 +774,7 @@ function join_path(string ...$paths): string
|
|||
/**
|
||||
* Perform callback on each item returned by an iterator.
|
||||
*/
|
||||
function iterator_map(callable $callback, iterator $iter): Generator
|
||||
function iterator_map(callable $callback, \iterator $iter): \Generator
|
||||
{
|
||||
foreach ($iter as $i) {
|
||||
yield call_user_func($callback, $i);
|
||||
|
@ -797,7 +784,7 @@ function iterator_map(callable $callback, iterator $iter): Generator
|
|||
/**
|
||||
* Perform callback on each item returned by an iterator and combine the result into an array.
|
||||
*/
|
||||
function iterator_map_to_array(callable $callback, iterator $iter): array
|
||||
function iterator_map_to_array(callable $callback, \iterator $iter): array
|
||||
{
|
||||
return iterator_to_array(iterator_map($callback, $iter));
|
||||
}
|
||||
|
@ -806,7 +793,7 @@ function stringer($s): string
|
|||
{
|
||||
if (is_array($s)) {
|
||||
if (isset($s[0])) {
|
||||
return "[" . implode(", ", array_map("stringer", $s)) . "]";
|
||||
return "[" . implode(", ", array_map("Shimmie2\stringer", $s)) . "]";
|
||||
} else {
|
||||
$pairs = [];
|
||||
foreach ($s as $k=>$v) {
|
||||
|
@ -815,8 +802,20 @@ function stringer($s): string
|
|||
return "[" . implode(", ", $pairs) . "]";
|
||||
}
|
||||
}
|
||||
if (is_null($s)) {
|
||||
return "null";
|
||||
}
|
||||
if (is_string($s)) {
|
||||
return "\"$s\""; // FIXME: handle escaping quotes
|
||||
}
|
||||
return (string)$s;
|
||||
if (is_numeric($s)) {
|
||||
return "$s";
|
||||
}
|
||||
if (is_bool($s)) {
|
||||
return $s ? "true" : "false";
|
||||
}
|
||||
if (method_exists($s, "__toString")) {
|
||||
return $s->__toString();
|
||||
}
|
||||
return "<Unstringable>";
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/*
|
||||
* A small number of PHP-sanity things (eg don't silently ignore errors) to
|
||||
* be included right at the very start of index.php and tests/bootstrap.php
|
||||
|
@ -31,7 +34,7 @@ function die_nicely($title, $body, $code=0)
|
|||
exit($code);
|
||||
}
|
||||
|
||||
$min_php = "7.3";
|
||||
$min_php = "8.1";
|
||||
if (version_compare(phpversion(), $min_php, ">=") === false) {
|
||||
die_nicely("Not Supported", "
|
||||
Shimmie does not support versions of PHP lower than $min_php
|
||||
|
@ -45,7 +48,7 @@ set_error_handler(function ($errNo, $errStr) {
|
|||
// Should we turn ALL notices into errors? PHP allows a lot of
|
||||
// terrible things to happen by default...
|
||||
if (str_starts_with($errStr, 'Use of undefined constant ')) {
|
||||
throw new Exception("PHP Error#$errNo: $errStr");
|
||||
throw new \Exception("PHP Error#$errNo: $errStr");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Event API *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
@ -37,7 +40,7 @@ function _set_event_listeners(): void
|
|||
global $_shm_event_listeners;
|
||||
$_shm_event_listeners = [];
|
||||
|
||||
foreach (get_subclasses_of("Extension") as $class) {
|
||||
foreach (get_subclasses_of("Shimmie2\Extension") as $class) {
|
||||
/** @var Extension $extension */
|
||||
$extension = new $class();
|
||||
|
||||
|
@ -59,19 +62,25 @@ function _set_event_listeners(): void
|
|||
}
|
||||
}
|
||||
|
||||
function _namespaced_class_name(string $class): string
|
||||
{
|
||||
return str_replace("Shimmie2\\", "", $class);
|
||||
}
|
||||
|
||||
function _dump_event_listeners(array $event_listeners, string $path): void
|
||||
{
|
||||
$p = "<"."?php\n";
|
||||
$p = "<"."?php\nnamespace Shimmie2;\n";
|
||||
|
||||
foreach (get_subclasses_of("Extension") as $class) {
|
||||
$p .= "\$$class = new $class(); ";
|
||||
foreach (get_subclasses_of("Shimmie2\Extension") as $class) {
|
||||
$scn = _namespaced_class_name($class);
|
||||
$p .= "\$$scn = new $scn(); ";
|
||||
}
|
||||
|
||||
$p .= "\$_shm_event_listeners = array(\n";
|
||||
foreach ($event_listeners as $event => $listeners) {
|
||||
$p .= "\t'$event' => array(\n";
|
||||
foreach ($listeners as $id => $listener) {
|
||||
$p .= "\t\t$id => \$".get_class($listener).",\n";
|
||||
$p .= "\t\t$id => \$"._namespaced_class_name(get_class($listener)).",\n";
|
||||
}
|
||||
$p .= "\t),\n";
|
||||
}
|
||||
|
@ -87,16 +96,21 @@ $_shm_event_count = 0;
|
|||
|
||||
/**
|
||||
* Send an event to all registered Extensions.
|
||||
*
|
||||
* @template T of Event
|
||||
* @param T $event
|
||||
* @return T
|
||||
*/
|
||||
function send_event(Event $event): Event
|
||||
{
|
||||
global $tracer_enabled;
|
||||
|
||||
global $_shm_event_listeners, $_shm_event_count, $_tracer;
|
||||
if (!isset($_shm_event_listeners[get_class($event)])) {
|
||||
$event_name = _namespaced_class_name(get_class($event));
|
||||
if (!isset($_shm_event_listeners[$event_name])) {
|
||||
return $event;
|
||||
}
|
||||
$method_name = "on".str_replace("Event", "", get_class($event));
|
||||
$method_name = "on".str_replace("Event", "", $event_name);
|
||||
|
||||
// send_event() is performance sensitive, and with the number
|
||||
// of times tracer gets called the time starts to add up
|
||||
|
@ -104,7 +118,7 @@ function send_event(Event $event): Event
|
|||
$_tracer->begin(get_class($event));
|
||||
}
|
||||
// SHIT: https://bugs.php.net/bug.php?id=35106
|
||||
$my_event_listeners = $_shm_event_listeners[get_class($event)];
|
||||
$my_event_listeners = $_shm_event_listeners[$event_name];
|
||||
ksort($my_event_listeners);
|
||||
|
||||
foreach ($my_event_listeners as $listener) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* For any values that aren't defined in data/config/*.php,
|
||||
* Shimmie will set the values to their defaults
|
||||
|
@ -28,7 +31,7 @@ _d("DEBUG", false); // boolean print various debugging details
|
|||
_d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-shared logins, give them different prefixes
|
||||
_d("SPEED_HAX", false); // boolean do some questionable things in the name of performance
|
||||
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
|
||||
_d("VERSION", "2.9.1$_g"); // string shimmie version
|
||||
_d("VERSION", "2.10.0-alpha$_g"); // string shimmie version
|
||||
_d("TIMEZONE", null); // string timezone
|
||||
_d("EXTRA_EXTS", ""); // string optional extra extensions
|
||||
_d("BASE_HREF", null); // string force a specific base URL (default is auto-detect)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once "core/basepage.php";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once "core/block.php";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class TestInit extends TestCase
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once "core/polyfills.php";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once "core/imageboard/tag.php";
|
||||
|
@ -21,4 +23,13 @@ class TagTest extends TestCase
|
|||
$this->assertEquals("foo^q", Tag::caret("foo?"));
|
||||
$this->assertEquals("a^^b^sc^bd^qe^af", Tag::caret("a^b/c\\d?e&f"));
|
||||
}
|
||||
|
||||
public function test_compare()
|
||||
{
|
||||
$this->assertFalse(Tag::compare(["foo"], ["bar"]));
|
||||
$this->assertFalse(Tag::compare(["foo"], ["foo", "bar"]));
|
||||
$this->assertTrue(Tag::compare([], []));
|
||||
$this->assertTrue(Tag::compare(["foo"], ["FoO"]));
|
||||
$this->assertTrue(Tag::compare(["foo", "bar"], ["bar", "FoO"]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once "core/urls.php";
|
||||
|
|
|
@ -2,12 +2,49 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once "core/util.php";
|
||||
|
||||
class UtilTest extends TestCase
|
||||
{
|
||||
public function test_get_theme()
|
||||
{
|
||||
$this->assertEquals("default", get_theme());
|
||||
}
|
||||
|
||||
public function test_get_memory_limit()
|
||||
{
|
||||
get_memory_limit();
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function test_check_gd_version()
|
||||
{
|
||||
check_gd_version();
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function test_check_im_version()
|
||||
{
|
||||
check_im_version();
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function test_human_filesize()
|
||||
{
|
||||
$this->assertEquals("123.00B", human_filesize(123));
|
||||
$this->assertEquals("123B", human_filesize(123, 0));
|
||||
$this->assertEquals("120.56KB", human_filesize(123456));
|
||||
}
|
||||
|
||||
public function test_generate_key()
|
||||
{
|
||||
$this->assertEquals(20, strlen(generate_key()));
|
||||
}
|
||||
|
||||
public function test_warehouse_path()
|
||||
{
|
||||
$hash = "7ac19c10d6859415";
|
||||
|
@ -85,4 +122,44 @@ class UtilTest extends TestCase
|
|||
load_balance_url("https://{foo=10,bar=5,baz=5}.mycdn.com/$hash.$ext", $hash, 1)
|
||||
);
|
||||
}
|
||||
|
||||
public function test_path_to_tags()
|
||||
{
|
||||
$this->assertEquals(
|
||||
"",
|
||||
path_to_tags("nope.jpg")
|
||||
);
|
||||
$this->assertEquals(
|
||||
"",
|
||||
path_to_tags("\\")
|
||||
);
|
||||
$this->assertEquals(
|
||||
"",
|
||||
path_to_tags("/")
|
||||
);
|
||||
$this->assertEquals(
|
||||
"",
|
||||
path_to_tags("C:\\")
|
||||
);
|
||||
$this->assertEquals(
|
||||
"test tag",
|
||||
path_to_tags("123 - test tag.jpg")
|
||||
);
|
||||
$this->assertEquals(
|
||||
"foo bar",
|
||||
path_to_tags("/foo/bar/baz.jpg")
|
||||
);
|
||||
$this->assertEquals(
|
||||
"cake pie foo bar",
|
||||
path_to_tags("/foo/bar/123 - cake pie.jpg")
|
||||
);
|
||||
$this->assertEquals(
|
||||
"bacon lemon",
|
||||
path_to_tags("\\bacon\\lemon\\baz.jpg")
|
||||
);
|
||||
$this->assertEquals(
|
||||
"category:tag",
|
||||
path_to_tags("/category:/tag/baz.jpg")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use PhpParser\Node\Expr\Cast\Double;
|
||||
|
||||
class Link
|
||||
{
|
||||
public ?string $page;
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use GQLA\Type;
|
||||
use GQLA\Field;
|
||||
use GQLA\Query;
|
||||
|
||||
function _new_user(array $row): User
|
||||
{
|
||||
return new User($row);
|
||||
|
@ -15,13 +21,17 @@ function _new_user(array $row): User
|
|||
*
|
||||
* The currently logged in user will always be accessible via the global variable $user.
|
||||
*/
|
||||
#[Type(name: "User")]
|
||||
class User
|
||||
{
|
||||
public int $id;
|
||||
#[Field]
|
||||
public string $name;
|
||||
public ?string $email;
|
||||
#[Field]
|
||||
public string $join_date;
|
||||
public ?string $passhash;
|
||||
#[Field]
|
||||
public UserClass $class;
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
@ -56,12 +66,31 @@ class User
|
|||
}
|
||||
}
|
||||
|
||||
#[Query]
|
||||
public static function me(): User
|
||||
{
|
||||
global $user;
|
||||
return $user;
|
||||
}
|
||||
|
||||
#[Field(name: "user_id")]
|
||||
public function graphql_oid(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
#[Field(name: "id")]
|
||||
public function graphql_guid(): string
|
||||
{
|
||||
return "user:{$this->id}";
|
||||
}
|
||||
|
||||
|
||||
public static function by_session(string $name, string $session): ?User
|
||||
{
|
||||
global $cache, $config, $database;
|
||||
$row = $cache->get("user-session:$name-$session");
|
||||
if (!$row) {
|
||||
if ($database->get_driver_name() === DatabaseDriver::MYSQL) {
|
||||
if (is_null($row)) {
|
||||
if ($database->get_driver_id() === DatabaseDriverID::MYSQL) {
|
||||
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
|
||||
} else {
|
||||
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
|
||||
|
@ -77,7 +106,7 @@ class User
|
|||
global $cache, $database;
|
||||
if ($id === 1) {
|
||||
$cached = $cache->get('user-id:'.$id);
|
||||
if ($cached) {
|
||||
if (!is_null($cached)) {
|
||||
return new User($cached);
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +117,7 @@ class User
|
|||
return is_null($row) ? null : new User($row);
|
||||
}
|
||||
|
||||
#[Query(name: "user")]
|
||||
public static function by_name(string $name): ?User
|
||||
{
|
||||
global $database;
|
||||
|
@ -163,7 +193,7 @@ class User
|
|||
{
|
||||
global $database;
|
||||
if (User::by_name($name)) {
|
||||
throw new ScoreException("Desired username is already in use");
|
||||
throw new SCoreException("Desired username is already in use");
|
||||
}
|
||||
$old_name = $this->name;
|
||||
$this->name = $name;
|
||||
|
@ -196,6 +226,16 @@ class User
|
|||
* a local file, a remote file, a gravatar, a something else, etc.
|
||||
*/
|
||||
public function get_avatar_html(): string
|
||||
{
|
||||
$url = $this->get_avatar_url();
|
||||
if (!empty($url)) {
|
||||
return "<img alt='avatar' class=\"avatar gravatar\" src=\"$url\">";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
#[Field(name: "avatar_url")]
|
||||
public function get_avatar_url(): ?string
|
||||
{
|
||||
// FIXME: configurable
|
||||
global $config;
|
||||
|
@ -206,10 +246,10 @@ class User
|
|||
$d = urlencode($config->get_string("avatar_gravatar_default"));
|
||||
$r = $config->get_string("avatar_gravatar_rating");
|
||||
$cb = date("Y-m-d");
|
||||
return "<img alt='avatar' class=\"avatar gravatar\" src=\"https://www.gravatar.com/avatar/$hash.jpg?s=$s&d=$d&r=$r&cacheBreak=$cb\">";
|
||||
return "https://www.gravatar.com/avatar/$hash.jpg?s=$s&d=$d&r=$r&cacheBreak=$cb";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use GQLA\Type;
|
||||
use GQLA\Field;
|
||||
use GQLA\Query;
|
||||
|
||||
/**
|
||||
* @global UserClass[] $_shm_user_classes
|
||||
*/
|
||||
|
@ -10,8 +17,10 @@ $_shm_user_classes = [];
|
|||
/**
|
||||
* Class UserClass
|
||||
*/
|
||||
#[Type(name: "UserClass")]
|
||||
class UserClass
|
||||
{
|
||||
#[Field]
|
||||
public ?string $name = null;
|
||||
public ?UserClass $parent = null;
|
||||
public array $abilities = [];
|
||||
|
@ -30,6 +39,19 @@ class UserClass
|
|||
$_shm_user_classes[$name] = $this;
|
||||
}
|
||||
|
||||
#[Field(type: "[Permission!]!")]
|
||||
public function permissions(): array
|
||||
{
|
||||
global $_all_false;
|
||||
$perms = [];
|
||||
foreach ((new \ReflectionClass('\Shimmie2\Permissions'))->getConstants() as $k => $v) {
|
||||
if ($this->can($v)) {
|
||||
$perms[] = $v;
|
||||
}
|
||||
}
|
||||
return $perms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this class of user can perform an action or has ability.
|
||||
*
|
||||
|
@ -58,7 +80,7 @@ class UserClass
|
|||
}
|
||||
|
||||
$_all_false = [];
|
||||
foreach ((new ReflectionClass('Permissions'))->getConstants() as $k => $v) {
|
||||
foreach ((new \ReflectionClass('\Shimmie2\Permissions'))->getConstants() as $k => $v) {
|
||||
$_all_false[$v] = false;
|
||||
}
|
||||
new UserClass("base", null, $_all_false);
|
||||
|
@ -86,6 +108,7 @@ new UserClass("user", "base", [
|
|||
Permissions::CREATE_IMAGE_REPORT => true,
|
||||
Permissions::EDIT_IMAGE_RATING => true,
|
||||
Permissions::EDIT_FAVOURITES => true,
|
||||
Permissions::CREATE_VOTE => true,
|
||||
Permissions::SEND_PM => true,
|
||||
Permissions::READ_PM => true,
|
||||
Permissions::SET_PRIVATE_IMAGE => true,
|
||||
|
@ -161,6 +184,7 @@ new UserClass("admin", "base", [
|
|||
Permissions::EDIT_FEATURE => true,
|
||||
Permissions::BULK_EDIT_VOTE => true,
|
||||
Permissions::EDIT_OTHER_VOTE => true,
|
||||
Permissions::CREATE_VOTE => true,
|
||||
Permissions::VIEW_SYSINTO => true,
|
||||
|
||||
Permissions::HELLBANNED => false,
|
||||
|
|
124
core/util.php
124
core/util.php
|
@ -1,7 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroHTML\HTMLElement;
|
||||
|
||||
use function MicroHTML\emptyHTML;
|
||||
use function MicroHTML\rawHTML;
|
||||
use function MicroHTML\FORM;
|
||||
|
@ -203,8 +207,8 @@ function get_session_ip(Config $config): string
|
|||
*/
|
||||
function format_text(string $string): string
|
||||
{
|
||||
$tfe = send_event(new TextFormattingEvent($string));
|
||||
return $tfe->formatted;
|
||||
$event = send_event(new TextFormattingEvent($string));
|
||||
return $event->formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,7 +262,7 @@ function load_balance_url(string $tmpl, string $hash, int $n=0): string
|
|||
if (isset($flexihashes[$opts])) {
|
||||
$flexihash = $flexihashes[$opts];
|
||||
} else {
|
||||
$flexihash = new Flexihash\Flexihash();
|
||||
$flexihash = new \Flexihash\Flexihash();
|
||||
foreach (explode(",", $opts) as $opt) {
|
||||
$parts = explode("=", $opt);
|
||||
$parts_count = count($parts);
|
||||
|
@ -354,10 +358,13 @@ function path_to_tags(string $path): string
|
|||
$tags = explode(" ", $matches[1]);
|
||||
}
|
||||
|
||||
$path = dirname($path);
|
||||
$path = str_replace("\\", "/", $path);
|
||||
$path = str_replace(";", ":", $path);
|
||||
$path = str_replace("__", " ", $path);
|
||||
|
||||
$path = dirname($path);
|
||||
if ($path == "\\" || $path == "/" || $path == ".") {
|
||||
$path = "";
|
||||
}
|
||||
|
||||
$category = "";
|
||||
foreach (explode("/", $path) as $dir) {
|
||||
|
@ -390,18 +397,6 @@ function path_to_tags(string $path): string
|
|||
return implode(" ", $tags);
|
||||
}
|
||||
|
||||
|
||||
function join_url(string $base, string ...$paths): string
|
||||
{
|
||||
$output = $base;
|
||||
foreach ($paths as $path) {
|
||||
$output = rtrim($output, "/");
|
||||
$path = ltrim($path, "/");
|
||||
$output .= "/".$path;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
function get_dir_contents(string $dir): array
|
||||
{
|
||||
assert(!empty($dir));
|
||||
|
@ -486,18 +481,18 @@ function scan_dir(string $path): array
|
|||
$bytestotal = 0;
|
||||
$nbfiles = 0;
|
||||
|
||||
$ite = new RecursiveDirectoryIterator(
|
||||
$ite = new \RecursiveDirectoryIterator(
|
||||
$path,
|
||||
FilesystemIterator::KEY_AS_PATHNAME |
|
||||
FilesystemIterator::CURRENT_AS_FILEINFO |
|
||||
FilesystemIterator::SKIP_DOTS
|
||||
\FilesystemIterator::KEY_AS_PATHNAME |
|
||||
\FilesystemIterator::CURRENT_AS_FILEINFO |
|
||||
\FilesystemIterator::SKIP_DOTS
|
||||
);
|
||||
foreach (new RecursiveIteratorIterator($ite) as $filename => $cur) {
|
||||
foreach (new \RecursiveIteratorIterator($ite) as $filename => $cur) {
|
||||
try {
|
||||
$filesize = $cur->getSize();
|
||||
$bytestotal += $filesize;
|
||||
$nbfiles++;
|
||||
} catch (RuntimeException $e) {
|
||||
} catch (\RuntimeException $e) {
|
||||
// This usually just means that the file got eaten by the import
|
||||
continue;
|
||||
}
|
||||
|
@ -509,14 +504,20 @@ function scan_dir(string $path): array
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* because microtime() returns string|float, and we only ever want float
|
||||
*/
|
||||
function ftime(): float
|
||||
{
|
||||
return (float)microtime(true);
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Debugging functions *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
// SHIT by default this returns the time as a string. And it's not even a
|
||||
// string representation of a number, it's two numbers separated by a space.
|
||||
// What the fuck were the PHP developers smoking.
|
||||
$_shm_load_start = microtime(true);
|
||||
$_shm_load_start = ftime();
|
||||
|
||||
/**
|
||||
* Collects some debug information (execution time, memory usage, queries, etc)
|
||||
|
@ -524,28 +525,39 @@ $_shm_load_start = microtime(true);
|
|||
*/
|
||||
function get_debug_info(): string
|
||||
{
|
||||
global $cache, $config, $_shm_event_count, $database, $_shm_load_start;
|
||||
$d = get_debug_info_arr();
|
||||
|
||||
$i_mem = sprintf("%5.2f", ((memory_get_peak_usage(true)+512)/1024)/1024);
|
||||
$debug = "<br>Took {$d['time']} seconds (db:{$d['dbtime']}) and {$d['mem_mb']}MB of RAM";
|
||||
$debug .= "; Used {$d['files']} files and {$d['query_count']} queries";
|
||||
$debug .= "; Sent {$d['event_count']} events";
|
||||
$debug .= "; {$d['cache_hits']} cache hits and {$d['cache_misses']} misses";
|
||||
$debug .= "; Shimmie version {$d['version']}";
|
||||
|
||||
return $debug;
|
||||
}
|
||||
|
||||
function get_debug_info_arr(): array
|
||||
{
|
||||
global $cache, $config, $_shm_event_count, $database, $_shm_load_start;
|
||||
|
||||
if ($config->get_string("commit_hash", "unknown") == "unknown") {
|
||||
$commit = "";
|
||||
} else {
|
||||
$commit = " (".$config->get_string("commit_hash").")";
|
||||
}
|
||||
$time = sprintf("%.2f", microtime(true) - $_shm_load_start);
|
||||
$dbtime = sprintf("%.2f", $database->dbtime);
|
||||
$i_files = count(get_included_files());
|
||||
$hits = $cache->get_hits();
|
||||
$miss = $cache->get_misses();
|
||||
|
||||
$debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM";
|
||||
$debug .= "; Used $i_files files and {$database->query_count} queries";
|
||||
$debug .= "; Sent $_shm_event_count events";
|
||||
$debug .= "; $hits cache hits and $miss misses";
|
||||
$debug .= "; Shimmie version ". VERSION . $commit;
|
||||
|
||||
return $debug;
|
||||
return [
|
||||
"time" => round(ftime() - $_shm_load_start, 2),
|
||||
"dbtime" => round($database->dbtime, 2),
|
||||
"mem_mb" => round(((memory_get_peak_usage(true)+512)/1024)/1024, 2),
|
||||
"files" => count(get_included_files()),
|
||||
"query_count" => $database->query_count,
|
||||
// "query_log" => $database->queries,
|
||||
"event_count" => $_shm_event_count,
|
||||
"cache_hits" => $cache->get("__etc_cache_hits"),
|
||||
"cache_misses" => $cache->get("__etc_cache_misses"),
|
||||
"version" => VERSION . $commit,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
@ -553,10 +565,6 @@ function get_debug_info(): string
|
|||
* Request initialisation stuff *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/** @privatesection
|
||||
* @noinspection PhpIncludeInspection
|
||||
*/
|
||||
|
||||
function require_all(array $files): void
|
||||
{
|
||||
foreach ($files as $filename) {
|
||||
|
@ -575,7 +583,9 @@ function _load_core_files()
|
|||
|
||||
function _load_theme_files()
|
||||
{
|
||||
require_all(_get_themelet_files(get_theme()));
|
||||
$theme = get_theme();
|
||||
$files = _get_themelet_files($theme);
|
||||
require_all($files);
|
||||
}
|
||||
|
||||
function _set_up_shimmie_environment(): void
|
||||
|
@ -617,13 +627,13 @@ function _get_themelet_files(string $_theme): array
|
|||
/**
|
||||
* Used to display fatal errors to the web user.
|
||||
*/
|
||||
function _fatal_error(Exception $e): void
|
||||
function _fatal_error(\Exception $e): void
|
||||
{
|
||||
$version = VERSION;
|
||||
$message = $e->getMessage();
|
||||
$phpver = phpversion();
|
||||
$query = is_subclass_of($e, "SCoreException") ? $e->query : null;
|
||||
$code = is_subclass_of($e, "SCoreException") ? $e->http_code : 500;
|
||||
$query = is_subclass_of($e, "Shimmie2\SCoreException") ? $e->query : null;
|
||||
$code = is_subclass_of($e, "Shimmie2\SCoreException") ? $e->http_code : 500;
|
||||
|
||||
//$hash = exec("git rev-parse HEAD");
|
||||
//$h_hash = $hash ? "<p><b>Hash:</b> $hash" : "";
|
||||
|
@ -635,7 +645,7 @@ function _fatal_error(Exception $e): void
|
|||
foreach ($t as $n => $f) {
|
||||
$c = $f['class'] ?? '';
|
||||
$t = $f['type'] ?? '';
|
||||
$a = implode(", ", array_map("stringer", $f['args']));
|
||||
$a = implode(", ", array_map("Shimmie2\stringer", $f['args']));
|
||||
print("$n: {$f['file']}({$f['line']}): {$c}{$t}{$f['function']}({$a})\n");
|
||||
}
|
||||
|
||||
|
@ -674,12 +684,18 @@ function _get_user(): User
|
|||
{
|
||||
global $config, $page;
|
||||
$my_user = null;
|
||||
if ($page->get_cookie("user") && $page->get_cookie("session")) {
|
||||
$tmp_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session"));
|
||||
if (!is_null($tmp_user)) {
|
||||
$my_user = $tmp_user;
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||
$parts = explode(" ", $_SERVER['HTTP_AUTHORIZATION'], 2);
|
||||
if (count($parts) == 2 && $parts[0] == "Bearer") {
|
||||
$parts = explode(":", $parts[1], 2);
|
||||
if (count($parts) == 2) {
|
||||
$my_user = User::by_session($parts[0], $parts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($page->get_cookie("user") && $page->get_cookie("session")) {
|
||||
$my_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session"));
|
||||
}
|
||||
if (is_null($my_user)) {
|
||||
$my_user = User::by_id($config->get_int("anon_id", 0));
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AdminPageInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "admin";
|
||||
|
@ -13,5 +15,5 @@ class AdminPageInfo extends ExtensionInfo
|
|||
public string $license = self::LICENSE_GPLV2;
|
||||
public string $description = "Provides a base for various small admin functions";
|
||||
public bool $core = true;
|
||||
public string $visibility = self::VISIBLE_HIDDEN;
|
||||
public ExtensionVisibility $visibility = ExtensionVisibility::HIDDEN;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
/**
|
||||
* Sent when the admin page is ready to be added to
|
||||
*/
|
||||
|
@ -115,7 +117,7 @@ class AdminPage extends Extension
|
|||
$key = $event->args[1];
|
||||
switch ($cmd) {
|
||||
case "get":
|
||||
var_dump($cache->get($key));
|
||||
var_export($cache->get($key));
|
||||
break;
|
||||
case "set":
|
||||
$cache->set($key, $event->args[2], 60);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AdminPageTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testAuth()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AdminPageTheme extends Themelet
|
||||
{
|
||||
/*
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AliasEditorInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "alias_editor";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use MicroCRUD\ActionColumn;
|
||||
use MicroCRUD\TextColumn;
|
||||
use MicroCRUD\Table;
|
||||
|
@ -104,7 +106,7 @@ class AliasEditor extends Extension
|
|||
if (count($_FILES) > 0) {
|
||||
$tmp = $_FILES['alias_file']['tmp_name'];
|
||||
$contents = file_get_contents($tmp);
|
||||
$this->add_alias_csv($database, $contents);
|
||||
$this->add_alias_csv($contents);
|
||||
log_info("alias_editor", "Imported aliases from file", "Imported aliases"); # FIXME: how many?
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("alias/list"));
|
||||
|
@ -177,7 +179,7 @@ class AliasEditor extends Extension
|
|||
return $csv;
|
||||
}
|
||||
|
||||
private function add_alias_csv(Database $database, string $csv): int
|
||||
private function add_alias_csv(string $csv): int
|
||||
{
|
||||
$csv = str_replace("\r", "\n", $csv);
|
||||
$i = 0;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AliasEditorTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testAliasList()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AliasEditorTheme extends Themelet
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class ApprovalInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "approval";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
abstract class ApprovalConfig
|
||||
{
|
||||
public const VERSION = "ext_approval_version";
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use function MicroHTML\BR;
|
||||
use function MicroHTML\BUTTON;
|
||||
use function MicroHTML\INPUT;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class ArtistsInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "artists";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AuthorSetEvent extends Event
|
||||
{
|
||||
public Image $image;
|
||||
|
@ -163,256 +165,256 @@ class Artists extends Extension
|
|||
switch ($event->get_arg(0)) {
|
||||
//*************ARTIST SECTION**************
|
||||
case "list":
|
||||
{
|
||||
$this->get_listing($page, $event);
|
||||
$this->theme->sidebar_options("neutral");
|
||||
break;
|
||||
}
|
||||
{
|
||||
$this->get_listing($event);
|
||||
$this->theme->sidebar_options("neutral");
|
||||
break;
|
||||
}
|
||||
case "new":
|
||||
{
|
||||
if (!$user->is_anonymous()) {
|
||||
$this->theme->new_artist_composer();
|
||||
} else {
|
||||
$this->theme->display_error(401, "Error", "You must be registered and logged in to create a new artist.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "new_artist":
|
||||
{
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/new"));
|
||||
break;
|
||||
}
|
||||
case "create":
|
||||
{
|
||||
if (!$user->is_anonymous()) {
|
||||
$newArtistID = $this->add_artist();
|
||||
if ($newArtistID == -1) {
|
||||
$this->theme->display_error(400, "Error", "Error when entering artist data.");
|
||||
{
|
||||
if (!$user->is_anonymous()) {
|
||||
$this->theme->new_artist_composer();
|
||||
} else {
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$newArtistID));
|
||||
$this->theme->display_error(401, "Error", "You must be registered and logged in to create a new artist.");
|
||||
}
|
||||
} else {
|
||||
$this->theme->display_error(401, "Error", "You must be registered and logged in to create a new artist.");
|
||||
break;
|
||||
}
|
||||
case "new_artist":
|
||||
{
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/new"));
|
||||
break;
|
||||
}
|
||||
case "create":
|
||||
{
|
||||
if (!$user->is_anonymous()) {
|
||||
$newArtistID = $this->add_artist();
|
||||
if ($newArtistID == -1) {
|
||||
$this->theme->display_error(400, "Error", "Error when entering artist data.");
|
||||
} else {
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$newArtistID));
|
||||
}
|
||||
} else {
|
||||
$this->theme->display_error(401, "Error", "You must be registered and logged in to create a new artist.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "view":
|
||||
{
|
||||
$artistID = int_escape($event->get_arg(1));
|
||||
$artist = $this->get_artist($artistID);
|
||||
$aliases = $this->get_alias($artist['id']);
|
||||
$members = $this->get_members($artist['id']);
|
||||
$urls = $this->get_urls($artist['id']);
|
||||
{
|
||||
$artistID = int_escape($event->get_arg(1));
|
||||
$artist = $this->get_artist($artistID);
|
||||
$aliases = $this->get_alias($artist['id']);
|
||||
$members = $this->get_members($artist['id']);
|
||||
$urls = $this->get_urls($artist['id']);
|
||||
|
||||
$userIsLogged = !$user->is_anonymous();
|
||||
$userIsAdmin = $user->can(Permissions::ARTISTS_ADMIN);
|
||||
$userIsLogged = !$user->is_anonymous();
|
||||
$userIsAdmin = $user->can(Permissions::ARTISTS_ADMIN);
|
||||
|
||||
$images = Image::find_images(0, 4, Tag::explode($artist['name']));
|
||||
$images = Image::find_images(0, 4, Tag::explode($artist['name']));
|
||||
|
||||
$this->theme->show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin);
|
||||
/*
|
||||
if ($userIsLogged) {
|
||||
$this->theme->show_new_alias_composer($artistID);
|
||||
$this->theme->show_new_member_composer($artistID);
|
||||
$this->theme->show_new_url_composer($artistID);
|
||||
$this->theme->show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin);
|
||||
/*
|
||||
if ($userIsLogged) {
|
||||
$this->theme->show_new_alias_composer($artistID);
|
||||
$this->theme->show_new_member_composer($artistID);
|
||||
$this->theme->show_new_url_composer($artistID);
|
||||
}
|
||||
*/
|
||||
|
||||
$this->theme->sidebar_options("editor", $artistID, $userIsAdmin);
|
||||
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
$this->theme->sidebar_options("editor", $artistID, $userIsAdmin);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "edit":
|
||||
{
|
||||
$artistID = int_escape($event->get_arg(1));
|
||||
$artist = $this->get_artist($artistID);
|
||||
$aliases = $this->get_alias($artistID);
|
||||
$members = $this->get_members($artistID);
|
||||
$urls = $this->get_urls($artistID);
|
||||
{
|
||||
$artistID = int_escape($event->get_arg(1));
|
||||
$artist = $this->get_artist($artistID);
|
||||
$aliases = $this->get_alias($artistID);
|
||||
$members = $this->get_members($artistID);
|
||||
$urls = $this->get_urls($artistID);
|
||||
|
||||
if (!$user->is_anonymous()) {
|
||||
$this->theme->show_artist_editor($artist, $aliases, $members, $urls);
|
||||
if (!$user->is_anonymous()) {
|
||||
$this->theme->show_artist_editor($artist, $aliases, $members, $urls);
|
||||
|
||||
$userIsAdmin = $user->can(Permissions::ARTISTS_ADMIN);
|
||||
$this->theme->sidebar_options("editor", $artistID, $userIsAdmin);
|
||||
} else {
|
||||
$this->theme->display_error(401, "Error", "You must be registered and logged in to edit an artist.");
|
||||
$userIsAdmin = $user->can(Permissions::ARTISTS_ADMIN);
|
||||
$this->theme->sidebar_options("editor", $artistID, $userIsAdmin);
|
||||
} else {
|
||||
$this->theme->display_error(401, "Error", "You must be registered and logged in to edit an artist.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "edit_artist":
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/edit/".$artistID));
|
||||
break;
|
||||
}
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/edit/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "edited":
|
||||
{
|
||||
$artistID = int_escape($_POST['id']);
|
||||
$this->update_artist();
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
{
|
||||
$artistID = int_escape($_POST['id']);
|
||||
$this->update_artist();
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "nuke_artist":
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/nuke/".$artistID));
|
||||
break;
|
||||
}
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/nuke/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "nuke":
|
||||
{
|
||||
$artistID = int_escape($event->get_arg(1));
|
||||
$this->delete_artist($artistID); // this will delete the artist, its alias, its urls and its members
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/list"));
|
||||
break;
|
||||
}
|
||||
{
|
||||
$artistID = int_escape($event->get_arg(1));
|
||||
$this->delete_artist($artistID); // this will delete the artist, its alias, its urls and its members
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/list"));
|
||||
break;
|
||||
}
|
||||
case "add_alias":
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$this->theme->show_new_alias_composer($artistID);
|
||||
break;
|
||||
}
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$this->theme->show_new_alias_composer($artistID);
|
||||
break;
|
||||
}
|
||||
case "add_member":
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$this->theme->show_new_member_composer($artistID);
|
||||
break;
|
||||
}
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$this->theme->show_new_member_composer($artistID);
|
||||
break;
|
||||
}
|
||||
case "add_url":
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$this->theme->show_new_url_composer($artistID);
|
||||
break;
|
||||
}
|
||||
//***********ALIAS SECTION ***********************
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$this->theme->show_new_url_composer($artistID);
|
||||
break;
|
||||
}
|
||||
//***********ALIAS SECTION ***********************
|
||||
case "alias":
|
||||
{
|
||||
switch ($event->get_arg(1)) {
|
||||
case "add":
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_alias();
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "delete":
|
||||
{
|
||||
$aliasID = int_escape($event->get_arg(2));
|
||||
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
||||
$this->delete_alias($aliasID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "edit":
|
||||
{
|
||||
$aliasID = int_escape($event->get_arg(2));
|
||||
$alias = $this->get_alias_by_id($aliasID);
|
||||
$this->theme->show_alias_editor($alias);
|
||||
break;
|
||||
}
|
||||
case "edited":
|
||||
{
|
||||
$this->update_alias();
|
||||
$aliasID = int_escape($_POST['aliasID']);
|
||||
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
{
|
||||
switch ($event->get_arg(1)) {
|
||||
case "add":
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_alias();
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "delete":
|
||||
{
|
||||
$aliasID = int_escape($event->get_arg(2));
|
||||
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
||||
$this->delete_alias($aliasID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "edit":
|
||||
{
|
||||
$aliasID = int_escape($event->get_arg(2));
|
||||
$alias = $this->get_alias_by_id($aliasID);
|
||||
$this->theme->show_alias_editor($alias);
|
||||
break;
|
||||
}
|
||||
case "edited":
|
||||
{
|
||||
$this->update_alias();
|
||||
$aliasID = int_escape($_POST['aliasID']);
|
||||
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break; // case: alias
|
||||
}
|
||||
break; // case: alias
|
||||
}
|
||||
|
||||
//**************** URLS SECTION **********************
|
||||
//**************** URLS SECTION **********************
|
||||
case "url":
|
||||
{
|
||||
switch ($event->get_arg(1)) {
|
||||
case "add":
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_urls();
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "delete":
|
||||
{
|
||||
$urlID = int_escape($event->get_arg(2));
|
||||
$artistID = $this->get_artistID_by_urlID($urlID);
|
||||
$this->delete_url($urlID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "edit":
|
||||
{
|
||||
$urlID = int_escape($event->get_arg(2));
|
||||
$url = $this->get_url_by_id($urlID);
|
||||
$this->theme->show_url_editor($url);
|
||||
break;
|
||||
}
|
||||
case "edited":
|
||||
{
|
||||
$this->update_url();
|
||||
$urlID = int_escape($_POST['urlID']);
|
||||
$artistID = $this->get_artistID_by_urlID($urlID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
{
|
||||
switch ($event->get_arg(1)) {
|
||||
case "add":
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_urls();
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "delete":
|
||||
{
|
||||
$urlID = int_escape($event->get_arg(2));
|
||||
$artistID = $this->get_artistID_by_urlID($urlID);
|
||||
$this->delete_url($urlID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "edit":
|
||||
{
|
||||
$urlID = int_escape($event->get_arg(2));
|
||||
$url = $this->get_url_by_id($urlID);
|
||||
$this->theme->show_url_editor($url);
|
||||
break;
|
||||
}
|
||||
case "edited":
|
||||
{
|
||||
$this->update_url();
|
||||
$urlID = int_escape($_POST['urlID']);
|
||||
$artistID = $this->get_artistID_by_urlID($urlID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break; // case: urls
|
||||
}
|
||||
break; // case: urls
|
||||
}
|
||||
//******************* MEMBERS SECTION *********************
|
||||
//******************* MEMBERS SECTION *********************
|
||||
case "member":
|
||||
{
|
||||
switch ($event->get_arg(1)) {
|
||||
case "add":
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_members();
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "delete":
|
||||
{
|
||||
$memberID = int_escape($event->get_arg(2));
|
||||
$artistID = $this->get_artistID_by_memberID($memberID);
|
||||
$this->delete_member($memberID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "edit":
|
||||
{
|
||||
$memberID = int_escape($event->get_arg(2));
|
||||
$member = $this->get_member_by_id($memberID);
|
||||
$this->theme->show_member_editor($member);
|
||||
break;
|
||||
}
|
||||
case "edited":
|
||||
{
|
||||
$this->update_member();
|
||||
$memberID = int_escape($_POST['memberID']);
|
||||
$artistID = $this->get_artistID_by_memberID($memberID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
{
|
||||
switch ($event->get_arg(1)) {
|
||||
case "add":
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_members();
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "delete":
|
||||
{
|
||||
$memberID = int_escape($event->get_arg(2));
|
||||
$artistID = $this->get_artistID_by_memberID($memberID);
|
||||
$this->delete_member($memberID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "edit":
|
||||
{
|
||||
$memberID = int_escape($event->get_arg(2));
|
||||
$member = $this->get_member_by_id($memberID);
|
||||
$this->theme->show_member_editor($member);
|
||||
break;
|
||||
}
|
||||
case "edited":
|
||||
{
|
||||
$this->update_member();
|
||||
$memberID = int_escape($_POST['memberID']);
|
||||
$artistID = $this->get_artistID_by_memberID($memberID);
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break; //case: members
|
||||
}
|
||||
break; //case: members
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -845,7 +847,7 @@ class Artists extends Extension
|
|||
/*
|
||||
* HERE WE GET THE LIST OF ALL ARTIST WITH PAGINATION
|
||||
*/
|
||||
private function get_listing(Page $page, PageRequestEvent $event)
|
||||
private function get_listing(PageRequestEvent $event)
|
||||
{
|
||||
global $config, $database;
|
||||
|
||||
|
@ -1048,7 +1050,8 @@ class Artists extends Extension
|
|||
ORDER BY alias ASC
|
||||
", ['artist_id'=>$artistID]);
|
||||
|
||||
for ($i = 0 ; $i < count($result) ; $i++) {
|
||||
$rc = count($result);
|
||||
for ($i = 0 ; $i < $rc ; $i++) {
|
||||
$result[$i]["alias_name"] = stripslashes($result[$i]["alias_name"]);
|
||||
}
|
||||
return $result;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class ArtistsTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testSearch()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class ArtistsTheme extends Themelet
|
||||
{
|
||||
public function get_author_editor_html(string $author): string
|
||||
|
@ -436,7 +439,8 @@ class ArtistsTheme extends Themelet
|
|||
$html .= "</tr>";
|
||||
|
||||
if (count($aliases) > 1) {
|
||||
for ($i = 1; $i < count($aliases); $i++) {
|
||||
$ac = count($aliases);
|
||||
for ($i = 1; $i < $ac; $i++) {
|
||||
$aliasViewLink = str_replace("_", " ", $aliases[$i]['alias_name']); // no link anymore
|
||||
$aliasEditLink = "<a href='" . make_link("artist/alias/edit/" . $aliases[$i]['alias_id']) . "'>Edit</a>";
|
||||
$aliasDeleteLink = "<a href='" . make_link("artist/alias/delete/" . $aliases[$i]['alias_id']) . "'>Delete</a>";
|
||||
|
@ -479,7 +483,8 @@ class ArtistsTheme extends Themelet
|
|||
$html .= "</tr>";
|
||||
|
||||
if (count($members) > 1) {
|
||||
for ($i = 1; $i < count($members); $i++) {
|
||||
$mc = count($members);
|
||||
for ($i = 1; $i < $mc; $i++) {
|
||||
$memberViewLink = str_replace("_", " ", $members[$i]['name']); // no link anymore
|
||||
$memberEditLink = "<a href='" . make_link("artist/member/edit/" . $members[$i]['id']) . "'>Edit</a>";
|
||||
$memberDeleteLink = "<a href='" . make_link("artist/member/delete/" . $members[$i]['id']) . "'>Delete</a>";
|
||||
|
@ -524,7 +529,8 @@ class ArtistsTheme extends Themelet
|
|||
$html .= "</tr>";
|
||||
|
||||
if (count($urls) > 1) {
|
||||
for ($i = 1; $i < count($urls); $i++) {
|
||||
$uc = count($urls);
|
||||
for ($i = 1; $i < $uc; $i++) {
|
||||
$urlViewLink = "<a href='" . str_replace("_", " ", $urls[$i]['url']) . "' target='_blank'>" . str_replace("_", " ", $urls[$i]['url']) . "</a>";
|
||||
$urlEditLink = "<a href='" . make_link("artist/url/edit/" . $urls[$i]['id']) . "'>Edit</a>";
|
||||
$urlDeleteLink = "<a href='" . make_link("artist/url/delete/" . $urls[$i]['id']) . "'>Delete</a>";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
abstract class AutoTaggerConfig
|
||||
{
|
||||
public const VERSION = "ext_auto_tagger_ver";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AutoTaggerInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "auto_tagger";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
use MicroCRUD\ActionColumn;
|
||||
|
@ -110,7 +112,7 @@ class AutoTagger extends Extension
|
|||
if (count($_FILES) > 0) {
|
||||
$tmp = $_FILES['auto_tag_file']['tmp_name'];
|
||||
$contents = file_get_contents($tmp);
|
||||
$count = $this->add_auto_tag_csv($database, $contents);
|
||||
$count = $this->add_auto_tag_csv($contents);
|
||||
log_info(AutoTaggerInfo::KEY, "Imported $count auto-tag definitions from file from file", "Imported $count auto-tag definitions");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("auto_tag/list"));
|
||||
|
@ -142,7 +144,7 @@ class AutoTagger extends Extension
|
|||
additional_tags VARCHAR(2000) NOT NULL
|
||||
");
|
||||
|
||||
if ($database->get_driver_name() == DatabaseDriver::PGSQL) {
|
||||
if ($database->get_driver_id() == DatabaseDriverID::PGSQL) {
|
||||
$database->execute('CREATE INDEX auto_tag_lower_tag_idx ON auto_tag ((lower(tag)))');
|
||||
}
|
||||
$this->set_version(AutoTaggerConfig::VERSION, 1);
|
||||
|
@ -189,7 +191,7 @@ class AutoTagger extends Extension
|
|||
return $csv;
|
||||
}
|
||||
|
||||
private function add_auto_tag_csv(Database $database, string $csv): int
|
||||
private function add_auto_tag_csv(string $csv): int
|
||||
{
|
||||
$csv = str_replace("\r", "\n", $csv);
|
||||
$i = 0;
|
||||
|
@ -210,23 +212,23 @@ class AutoTagger extends Extension
|
|||
private function add_auto_tag(string $tag, string $additional_tags)
|
||||
{
|
||||
global $database;
|
||||
$existing_tags = $database->get_one("SELECT additional_tags FROM auto_tag WHERE LOWER(tag)=LOWER(:tag)", ["tag"=>$tag]);
|
||||
if (!is_null($existing_tags)) {
|
||||
// Auto Tags already exist, so we will append new tags to the existing one
|
||||
$tag = Tag::sanitize($tag);
|
||||
$existing_tags = $database->get_one("SELECT additional_tags FROM auto_tag WHERE LOWER(tag)=LOWER(:tag)", ["tag"=>$tag]);
|
||||
if (!is_null($existing_tags)) {
|
||||
// Auto Tags already exist, so we will append new tags to the existing one
|
||||
$tag = Tag::sanitize($tag);
|
||||
$additional_tags = Tag::explode($additional_tags);
|
||||
$existing_tags = Tag::explode($existing_tags);
|
||||
foreach ($additional_tags as $t) {
|
||||
if (!in_array(strtolower($t), $existing_tags)) {
|
||||
array_push($existing_tags, strtolower($t));
|
||||
}
|
||||
}
|
||||
|
||||
$database->execute(
|
||||
$existing_tags = Tag::explode($existing_tags);
|
||||
foreach ($additional_tags as $t) {
|
||||
if (!in_array(strtolower($t), $existing_tags)) {
|
||||
$existing_tags[] = strtolower($t);
|
||||
}
|
||||
}
|
||||
|
||||
$database->execute(
|
||||
"UPDATE auto_tag set additional_tags=:existing_tags where tag=:tag",
|
||||
["tag"=>$tag, "existing_tags"=>Tag::implode($existing_tags)]
|
||||
);
|
||||
log_info(
|
||||
log_info(
|
||||
AutoTaggerInfo::KEY,
|
||||
"Updated auto-tag for {$tag} -> {".implode(" ", $additional_tags)."}"
|
||||
);
|
||||
|
@ -244,39 +246,8 @@ class AutoTagger extends Extension
|
|||
"Added auto-tag for {$tag} -> {".implode(" ", $additional_tags)."}"
|
||||
);
|
||||
}
|
||||
// Now we apply it to existing items
|
||||
$this->apply_new_auto_tag($tag);
|
||||
}
|
||||
|
||||
private function update_auto_tag(string $tag, string $additional_tags): bool
|
||||
{
|
||||
global $database;
|
||||
$result = $database->get_row("SELECT * FROM auto_tag WHERE LOWER(tag)=LOWER(:tag)", ["tag"=>$tag]);
|
||||
|
||||
if ($result===null) {
|
||||
throw new AutoTaggerException("Auto-tag not set for $tag, can't update");
|
||||
} else {
|
||||
$additional_tags = Tag::explode($additional_tags);
|
||||
$current_additional_tags = Tag::explode($result["additional_tags"]);
|
||||
|
||||
if (!Tag::compare($additional_tags, $current_additional_tags)) {
|
||||
$database->execute(
|
||||
"UPDATE auto_tag SET additional_tags = :additional_tags WHERE LOWER(tag)=LOWER(:tag)",
|
||||
["tag"=>$tag, "additional_tags"=>Tag::implode($additional_tags)]
|
||||
);
|
||||
|
||||
log_info(
|
||||
AutoTaggerInfo::KEY,
|
||||
"Updated auto-tag for {$tag} -> {".implode(" ", $additional_tags)."}",
|
||||
"Updated Auto-Tag"
|
||||
);
|
||||
|
||||
// Now we apply it to existing items
|
||||
$this->apply_new_auto_tag($tag);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
// Now we apply it to existing items
|
||||
$this->apply_new_auto_tag($tag);
|
||||
}
|
||||
|
||||
private function apply_new_auto_tag(string $tag)
|
||||
|
@ -288,14 +259,11 @@ class AutoTagger extends Extension
|
|||
foreach ($image_ids as $image_id) {
|
||||
$image_id = (int) $image_id;
|
||||
$image = Image::by_id($image_id);
|
||||
$event = new TagSetEvent($image, $image->get_tag_array());
|
||||
send_event($event);
|
||||
send_event(new TagSetEvent($image, $image->get_tag_array()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function remove_auto_tag(String $tag)
|
||||
{
|
||||
global $database;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AutoTaggerTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testAutoTaggerList()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AutoTaggerTheme extends Themelet
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AutoCompleteInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "autocomplete";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AutoComplete extends Extension
|
||||
{
|
||||
/** @var AutoCompleteTheme */
|
||||
|
@ -48,7 +50,8 @@ class AutoComplete extends Extension
|
|||
return [];
|
||||
}
|
||||
|
||||
$cache_key = "autocomplete-$search";
|
||||
# memcache keys can't contain spaces
|
||||
$cache_key = "autocomplete:" . md5($search);
|
||||
$limitSQL = "";
|
||||
$search = str_replace('_', '\_', $search);
|
||||
$search = str_replace('%', '\%', $search);
|
||||
|
@ -60,7 +63,7 @@ class AutoComplete extends Extension
|
|||
}
|
||||
|
||||
$res = $cache->get($cache_key);
|
||||
if (!$res) {
|
||||
if (is_null($res)) {
|
||||
$res = $database->get_pairs(
|
||||
"
|
||||
SELECT tag, count
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AutoCompleteTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testAuth()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class AutoCompleteTheme extends Themelet
|
||||
{
|
||||
public function build_autocomplete(Page $page)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BanWordsInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "ban_words";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BanWords extends Extension
|
||||
{
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BanWordsTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function check_blocked($image_id, $words)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BBCodeInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "bbcode";
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BBCode extends FormatterExtension
|
||||
{
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BBCodeTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testBasics()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BiographyInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "biography";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class Biography extends Extension
|
||||
{
|
||||
/** @var BiographyTheme */
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BiographyTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testBio()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use function MicroHTML\TEXTAREA;
|
||||
|
||||
class BiographyTheme extends Themelet
|
||||
|
@ -12,16 +15,12 @@ class BiographyTheme extends Themelet
|
|||
|
||||
public function display_composer(Page $page, string $bio)
|
||||
{
|
||||
global $user;
|
||||
$post_url = make_link("biography");
|
||||
$auth = $user->get_auth_html();
|
||||
|
||||
$html = SHM_SIMPLE_FORM(
|
||||
$post_url,
|
||||
make_link("biography"),
|
||||
TEXTAREA(["style"=>"width: 100%", "rows"=>"6", "name"=>"biography"], $bio),
|
||||
SHM_SUBMIT("Save")
|
||||
);
|
||||
|
||||
$page->add_block(new Block("About Me", (string)$html, "main", 30));
|
||||
$page->add_block(new Block("About Me", $html, "main", 30));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BlocksInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "blocks";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class Blocks extends Extension
|
||||
{
|
||||
/** @var BlocksTheme */
|
||||
|
@ -47,7 +49,7 @@ class Blocks extends Extension
|
|||
global $cache, $database, $page, $user;
|
||||
|
||||
$blocks = $cache->get("blocks");
|
||||
if ($blocks === false) {
|
||||
if (is_null($blocks)) {
|
||||
$blocks = $database->get_all("SELECT * FROM blocks");
|
||||
$cache->set("blocks", $blocks, 600);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BlocksTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testBlocks()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
use function MicroHTML\TABLE;
|
||||
use function MicroHTML\TR;
|
||||
use function MicroHTML\TH;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BlotterInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "blotter";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class Blotter extends Extension
|
||||
{
|
||||
/** @var BlotterTheme */
|
||||
|
@ -113,9 +115,6 @@ class Blotter extends Extension
|
|||
$this->theme->display_permission_denied();
|
||||
} else {
|
||||
$id = int_escape($_POST['id']);
|
||||
if (!isset($id)) {
|
||||
die("No ID!");
|
||||
}
|
||||
$database->execute("DELETE FROM blotter WHERE id=:id", ["id"=>$id]);
|
||||
log_info("blotter", "Removed Entry #$id");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BlotterTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testDenial()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BlotterTheme extends Themelet
|
||||
{
|
||||
public function display_editor($entries)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BrowserSearchInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "browser_search";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BrowserSearch extends Extension
|
||||
{
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BrowserSearchTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testBasic()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkActionsInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "bulk_actions";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkActionException extends SCoreException
|
||||
{
|
||||
}
|
||||
|
@ -39,10 +41,10 @@ class BulkActionBlockBuildingEvent extends Event
|
|||
class BulkActionEvent extends Event
|
||||
{
|
||||
public string $action;
|
||||
public Generator $items;
|
||||
public \Generator $items;
|
||||
public bool $redirect = true;
|
||||
|
||||
public function __construct(String $action, Generator $items)
|
||||
public function __construct(String $action, \Generator $items)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->action = $action;
|
||||
|
@ -173,7 +175,7 @@ class BulkActions extends Extension
|
|||
if (empty($data)) {
|
||||
throw new BulkActionException("No ids specified in bulk_selected_ids");
|
||||
}
|
||||
if (is_array($data) && !empty($data)) {
|
||||
if (is_array($data)) {
|
||||
$items = $this->yield_items($data);
|
||||
}
|
||||
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||
|
@ -201,7 +203,7 @@ class BulkActions extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
private function yield_items(array $data): Generator
|
||||
private function yield_items(array $data): \Generator
|
||||
{
|
||||
foreach ($data as $id) {
|
||||
if (is_numeric($id)) {
|
||||
|
@ -213,7 +215,7 @@ class BulkActions extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
private function yield_search_results(string $query): Generator
|
||||
private function yield_search_results(string $query): \Generator
|
||||
{
|
||||
$tags = Tag::explode($query);
|
||||
return Image::find_images_iterable(0, null, $tags);
|
||||
|
@ -231,7 +233,7 @@ class BulkActions extends Extension
|
|||
$size = 0;
|
||||
foreach ($posts as $post) {
|
||||
try {
|
||||
if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) {
|
||||
if (class_exists("Shimmie2\ImageBan") && isset($_POST['bulk_ban_reason'])) {
|
||||
$reason = $_POST['bulk_ban_reason'];
|
||||
if ($reason) {
|
||||
send_event(new AddImageHashBanEvent($post->hash, $reason));
|
||||
|
@ -240,7 +242,7 @@ class BulkActions extends Extension
|
|||
send_event(new ImageDeletionEvent($post));
|
||||
$total++;
|
||||
$size += $post->filesize;
|
||||
} catch (Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
$page->flash("Error while removing {$post->id}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +297,7 @@ class BulkActions extends Extension
|
|||
try {
|
||||
send_event(new SourceSetEvent($image, $source));
|
||||
$total++;
|
||||
} catch (Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
$page->flash("Error while setting source for {$image->id}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkActionsTheme extends Themelet
|
||||
{
|
||||
public function display_selector(Page $page, array $actions, string $query)
|
||||
|
@ -49,7 +51,7 @@ class BulkActionsTheme extends Themelet
|
|||
|
||||
public function render_ban_reason_input(): string
|
||||
{
|
||||
if (class_exists("ImageBan")) {
|
||||
if (class_exists("Shimmie2\ImageBan")) {
|
||||
return "<input type='text' name='bulk_ban_reason' placeholder='Ban reason (leave blank to not ban)' />";
|
||||
} else {
|
||||
return "";
|
||||
|
@ -64,6 +66,6 @@ class BulkActionsTheme extends Themelet
|
|||
|
||||
public function render_source_input(): string
|
||||
{
|
||||
return "<input type='text' name='bulk_source' required='required' placeholder='Enter source here' />";
|
||||
return "<input type='text' name='bulk_source' placeholder='Enter source here' />";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkAddInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "bulk_add";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkAddEvent extends Event
|
||||
{
|
||||
public string $dir;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkAddTest extends ShimmiePHPUnitTestCase
|
||||
{
|
||||
public function testInvalidDir()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkAddTheme extends Themelet
|
||||
{
|
||||
private array $messages = [];
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkAddCSVInfo extends ExtensionInfo
|
||||
{
|
||||
public const KEY = "bulk_add_csv";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkAddCSV extends Extension
|
||||
{
|
||||
/** @var BulkAddCSVTheme */
|
||||
|
@ -48,21 +50,11 @@ class BulkAddCSV extends Extension
|
|||
*/
|
||||
private function add_image(string $tmpname, string $filename, string $tags, string $source, string $rating, string $thumbfile)
|
||||
{
|
||||
assert(file_exists($tmpname));
|
||||
|
||||
$pathinfo = pathinfo($filename);
|
||||
$metadata = [];
|
||||
$metadata['filename'] = $pathinfo['basename'];
|
||||
if (array_key_exists('extension', $pathinfo)) {
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
}
|
||||
$metadata['tags'] = Tag::explode($tags);
|
||||
$metadata['source'] = $source;
|
||||
$event = send_event(new DataUploadEvent($tmpname, $metadata));
|
||||
$event = add_image($tmpname, $filename, $tags, $source);
|
||||
if ($event->image_id == -1) {
|
||||
throw new UploadException("File type not recognised");
|
||||
} else {
|
||||
if (class_exists("RatingSetEvent") && in_array($rating, ["s", "q", "e"])) {
|
||||
if (class_exists("Shimmie2\RatingSetEvent") && in_array($rating, ["s", "q", "e"])) {
|
||||
send_event(new RatingSetEvent(Image::by_id($event->image_id), $rating));
|
||||
}
|
||||
if (file_exists($thumbfile)) {
|
||||
|
@ -103,14 +95,13 @@ class BulkAddCSV extends Extension
|
|||
$source = $csvdata[2];
|
||||
$rating = $csvdata[3];
|
||||
$thumbfile = $csvdata[4];
|
||||
$pathinfo = pathinfo($fullpath);
|
||||
$shortpath = $pathinfo["basename"];
|
||||
$shortpath = pathinfo($fullpath, PATHINFO_BASENAME);
|
||||
$list .= "<br>".html_escape("$shortpath (".str_replace(" ", ", ", $tags).")... ");
|
||||
if (file_exists($csvdata[0]) && is_file($csvdata[0])) {
|
||||
try {
|
||||
$this->add_image($fullpath, $pathinfo["basename"], $tags, $source, $rating, $thumbfile);
|
||||
$this->add_image($fullpath, $shortpath, $tags, $source, $rating, $thumbfile);
|
||||
$list .= "ok\n";
|
||||
} catch (Exception $ex) {
|
||||
} catch (\Exception $ex) {
|
||||
$list .= "failed:<br>". $ex->getMessage();
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkAddCSVTheme extends Themelet
|
||||
{
|
||||
private array $messages = [];
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkDownloadInfo extends ExtensionInfo
|
||||
{
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkDownloadConfig
|
||||
{
|
||||
public const SIZE_LIMIT = "bulk_download_size_limit";
|
||||
|
@ -47,11 +49,11 @@ class BulkDownload extends Extension
|
|||
($event->action == BulkDownload::DOWNLOAD_ACTION_NAME)) {
|
||||
$download_filename = $user->name . '-' . date('YmdHis') . '.zip';
|
||||
$zip_filename = tempnam(sys_get_temp_dir(), "shimmie_bulk_download");
|
||||
$zip = new ZipArchive();
|
||||
$zip = new \ZipArchive();
|
||||
$size_total = 0;
|
||||
$max_size = $config->get_int(BulkDownloadConfig::SIZE_LIMIT);
|
||||
|
||||
if ($zip->open($zip_filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
|
||||
if ($zip->open($zip_filename, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE) === true) {
|
||||
foreach ($event->items as $image) {
|
||||
$img_loc = warehouse_path(Image::IMAGE_DIR, $image->hash, false);
|
||||
$size_total += filesize($img_loc);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shimmie2;
|
||||
|
||||
class BulkExportEvent extends Event
|
||||
{
|
||||
public Image $image;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue