Merge remote-tracking branch 'upstream/develop' into custom_ratings
This commit is contained in:
commit
8e3b8a7a1b
@ -3,6 +3,7 @@
|
|||||||
$finder = PhpCsFixer\Finder::create()
|
$finder = PhpCsFixer\Finder::create()
|
||||||
->exclude('ext/amazon_s3/lib')
|
->exclude('ext/amazon_s3/lib')
|
||||||
->exclude('vendor')
|
->exclude('vendor')
|
||||||
|
->exclude('data')
|
||||||
->in(__DIR__)
|
->in(__DIR__)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -99,24 +99,24 @@ For example, one can override the default anonymous "allow nothing"
|
|||||||
permissions like so:
|
permissions like so:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
new UserClass("anonymous", "base", array(
|
new UserClass("anonymous", "base", [
|
||||||
"create_comment" => True,
|
Permissions::CREATE_COMMENT => True,
|
||||||
"edit_image_tag" => True,
|
Permissions::EDIT_IMAGE_TAG => True,
|
||||||
"edit_image_source" => True,
|
Permissions::EDIT_IMAGE_SOURCE => True,
|
||||||
"create_image_report" => True,
|
Permissions::CREATE_IMAGE_REPORT => True,
|
||||||
));
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
For a moderator class, being a regular user who can delete images and comments:
|
For a moderator class, being a regular user who can delete images and comments:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
new UserClass("moderator", "user", array(
|
new UserClass("moderator", "user", [
|
||||||
"delete_image" => True,
|
Permissions::DELETE_IMAGE => True,
|
||||||
"delete_comment" => True,
|
Permissions::DELETE_COMMENT => True,
|
||||||
));
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
For a list of permissions, see `core/userclass.php`
|
For a list of permissions, see `core/permissions.php`
|
||||||
|
|
||||||
|
|
||||||
# Development Info
|
# Development Info
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"ifixit/php-akismet" : "1.*",
|
"ifixit/php-akismet" : "1.*",
|
||||||
"google/recaptcha" : "~1.1",
|
"google/recaptcha" : "~1.1",
|
||||||
"dapphp/securimage" : "3.6.*",
|
"dapphp/securimage" : "3.6.*",
|
||||||
"shish/libcontext-php" : "dev-master",
|
"shish/eventtracer-php" : "dev-master",
|
||||||
"enshrined/svg-sanitize" : "0.8.2",
|
"enshrined/svg-sanitize" : "0.8.2",
|
||||||
|
|
||||||
"bower-asset/jquery" : "1.12.3",
|
"bower-asset/jquery" : "1.12.3",
|
||||||
@ -42,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"require-dev" : {
|
"require-dev" : {
|
||||||
"phpunit/phpunit" : "6.*"
|
"phpunit/phpunit" : "7.*"
|
||||||
},
|
},
|
||||||
|
|
||||||
"suggest": {
|
"suggest": {
|
||||||
|
560
composer.lock
generated
560
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -4,20 +4,25 @@
|
|||||||
* actually do anything as far as the app is concerned
|
* actually do anything as far as the app is concerned
|
||||||
*/
|
*/
|
||||||
|
|
||||||
global $config, $database, $user, $page, $_shm_ctx;
|
global $config, $database, $user, $page, $_tracer;
|
||||||
|
|
||||||
require_once "core/sys_config.php";
|
require_once "core/sys_config.php";
|
||||||
require_once "core/polyfills.php";
|
require_once "core/polyfills.php";
|
||||||
require_once "core/util.php";
|
require_once "core/util.php";
|
||||||
require_once "vendor/shish/libcontext-php/context.php";
|
|
||||||
require_once "vendor/autoload.php";
|
require_once "vendor/autoload.php";
|
||||||
|
|
||||||
// set up and purify the environment
|
// set up and purify the environment
|
||||||
_version_check();
|
_version_check();
|
||||||
_sanitise_environment();
|
_sanitise_environment();
|
||||||
|
|
||||||
|
// The trace system has a certain amount of memory consumption every time it is used,
|
||||||
|
// so to prevent running out of memory during complex operations code that uses it should
|
||||||
|
// check if tracer output is enabled before making use of it.
|
||||||
|
$tracer_enabled = constant('TRACE_FILE')!==null;
|
||||||
|
|
||||||
// load base files
|
// load base files
|
||||||
$_shm_ctx->log_start("Opening files");
|
$_tracer->begin("Bootstrap");
|
||||||
|
$_tracer->begin("Opening files");
|
||||||
$_shm_files = array_merge(
|
$_shm_files = array_merge(
|
||||||
zglob("core/*.php"),
|
zglob("core/*.php"),
|
||||||
zglob("core/{".ENABLED_MODS."}/*.php"),
|
zglob("core/{".ENABLED_MODS."}/*.php"),
|
||||||
@ -30,23 +35,27 @@ foreach ($_shm_files as $_shm_filename) {
|
|||||||
}
|
}
|
||||||
unset($_shm_files);
|
unset($_shm_files);
|
||||||
unset($_shm_filename);
|
unset($_shm_filename);
|
||||||
$_shm_ctx->log_endok();
|
$_tracer->end();
|
||||||
|
|
||||||
// connect to the database
|
// connect to the database
|
||||||
$_shm_ctx->log_start("Connecting to DB");
|
$_tracer->begin("Connecting to DB");
|
||||||
$database = new Database();
|
$database = new Database();
|
||||||
$config = new DatabaseConfig($database);
|
$config = new DatabaseConfig($database);
|
||||||
$_shm_ctx->log_endok();
|
$_tracer->end();
|
||||||
|
|
||||||
// load the theme parts
|
// load the theme parts
|
||||||
$_shm_ctx->log_start("Loading themelets");
|
$_tracer->begin("Loading themelets");
|
||||||
foreach (_get_themelet_files(get_theme()) as $themelet) {
|
foreach (_get_themelet_files(get_theme()) as $themelet) {
|
||||||
require_once $themelet;
|
require_once $themelet;
|
||||||
}
|
}
|
||||||
unset($themelet);
|
unset($themelet);
|
||||||
$page = class_exists("CustomPage") ? new CustomPage() : new Page();
|
$page = class_exists("CustomPage") ? new CustomPage() : new Page();
|
||||||
$_shm_ctx->log_endok();
|
$_tracer->end();
|
||||||
|
|
||||||
// hook up event handlers
|
// hook up event handlers
|
||||||
|
$_tracer->begin("Loading extensions");
|
||||||
_load_event_listeners();
|
_load_event_listeners();
|
||||||
|
$_tracer->end();
|
||||||
|
|
||||||
send_event(new InitExtEvent());
|
send_event(new InitExtEvent());
|
||||||
|
$_tracer->end();
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
date_default_timezone_set('UTC');
|
date_default_timezone_set('UTC');
|
||||||
|
define("DATABASE_TIMEOUT", 10000);
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@ -58,6 +60,9 @@ date_default_timezone_set('UTC');
|
|||||||
<?php }
|
<?php }
|
||||||
|
|
||||||
// Pull in necessary files
|
// Pull in necessary files
|
||||||
|
require_once "vendor/autoload.php";
|
||||||
|
$_tracer = new EventTracer();
|
||||||
|
|
||||||
require_once "core/exceptions.php";
|
require_once "core/exceptions.php";
|
||||||
require_once "core/cacheengine.php";
|
require_once "core/cacheengine.php";
|
||||||
require_once "core/dbengine.php";
|
require_once "core/dbengine.php";
|
||||||
@ -110,7 +115,7 @@ function do_install()
|
|||||||
{ // {{{
|
{ // {{{
|
||||||
if (file_exists("data/config/auto_install.conf.php")) {
|
if (file_exists("data/config/auto_install.conf.php")) {
|
||||||
require_once "data/config/auto_install.conf.php";
|
require_once "data/config/auto_install.conf.php";
|
||||||
} elseif (@$_POST["database_type"] == "sqlite") {
|
} elseif (@$_POST["database_type"] == DatabaseDriver::SQLITE) {
|
||||||
$id = bin2hex(random_bytes(5));
|
$id = bin2hex(random_bytes(5));
|
||||||
define('DATABASE_DSN', "sqlite:data/shimmie.{$id}.sqlite");
|
define('DATABASE_DSN', "sqlite:data/shimmie.{$id}.sqlite");
|
||||||
} elseif (isset($_POST['database_type']) && isset($_POST['database_host']) && isset($_POST['database_user']) && isset($_POST['database_name'])) {
|
} elseif (isset($_POST['database_type']) && isset($_POST['database_host']) && isset($_POST['database_user']) && isset($_POST['database_name'])) {
|
||||||
@ -121,7 +126,6 @@ function do_install()
|
|||||||
}
|
}
|
||||||
|
|
||||||
define("CACHE_DSN", null);
|
define("CACHE_DSN", null);
|
||||||
define("DEBUG_SQL", false);
|
|
||||||
define("DATABASE_KA", true);
|
define("DATABASE_KA", true);
|
||||||
install_process();
|
install_process();
|
||||||
} // }}}
|
} // }}}
|
||||||
@ -153,9 +157,9 @@ function ask_questions()
|
|||||||
|
|
||||||
$drivers = PDO::getAvailableDrivers();
|
$drivers = PDO::getAvailableDrivers();
|
||||||
if (
|
if (
|
||||||
!in_array("mysql", $drivers) &&
|
!in_array(DatabaseDriver::MYSQL, $drivers) &&
|
||||||
!in_array("pgsql", $drivers) &&
|
!in_array(DatabaseDriver::PGSQL, $drivers) &&
|
||||||
!in_array("sqlite", $drivers)
|
!in_array(DatabaseDriver::SQLITE, $drivers)
|
||||||
) {
|
) {
|
||||||
$errors[] = "
|
$errors[] = "
|
||||||
No database connection library could be found; shimmie needs
|
No database connection library could be found; shimmie needs
|
||||||
@ -163,9 +167,9 @@ function ask_questions()
|
|||||||
";
|
";
|
||||||
}
|
}
|
||||||
|
|
||||||
$db_m = in_array("mysql", $drivers) ? '<option value="mysql">MySQL</option>' : "";
|
$db_m = in_array(DatabaseDriver::MYSQL, $drivers) ? '<option value="'. DatabaseDriver::MYSQL .'">MySQL</option>' : "";
|
||||||
$db_p = in_array("pgsql", $drivers) ? '<option value="pgsql">PostgreSQL</option>' : "";
|
$db_p = in_array(DatabaseDriver::PGSQL, $drivers) ? '<option value="'. DatabaseDriver::PGSQL .'">PostgreSQL</option>' : "";
|
||||||
$db_s = in_array("sqlite", $drivers) ? '<option value="sqlite">SQLite</option>' : "";
|
$db_s = in_array(DatabaseDriver::SQLITE, $drivers) ? '<option value="'. DatabaseDriver::SQLITE .'">SQLite</option>' : "";
|
||||||
|
|
||||||
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
|
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
|
||||||
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
|
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
|
||||||
|
@ -58,7 +58,7 @@ class BaseThemelet
|
|||||||
$tsize = get_thumbnail_size($image->width, $image->height);
|
$tsize = get_thumbnail_size($image->width, $image->height);
|
||||||
} else {
|
} else {
|
||||||
//Use max thumbnail size if using thumbless filetype
|
//Use max thumbnail size if using thumbless filetype
|
||||||
$tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height'));
|
$tsize = get_thumbnail_size($config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_WIDTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
$custom_classes = "";
|
$custom_classes = "";
|
||||||
|
@ -170,7 +170,7 @@ class Cache
|
|||||||
{
|
{
|
||||||
$matches = [];
|
$matches = [];
|
||||||
$c = null;
|
$c = null;
|
||||||
if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches)) {
|
if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches) && !isset($_GET['DISABLE_CACHE'])) {
|
||||||
if ($matches[1] == "memcache") {
|
if ($matches[1] == "memcache") {
|
||||||
$c = new MemcacheCache($matches[2]);
|
$c = new MemcacheCache($matches[2]);
|
||||||
} elseif ($matches[1] == "memcached") {
|
} elseif ($matches[1] == "memcached") {
|
||||||
@ -188,34 +188,34 @@ class Cache
|
|||||||
|
|
||||||
public function get(string $key)
|
public function get(string $key)
|
||||||
{
|
{
|
||||||
|
global $_tracer;
|
||||||
|
$_tracer->begin("Cache Query", ["key"=>$key]);
|
||||||
$val = $this->engine->get($key);
|
$val = $this->engine->get($key);
|
||||||
if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
|
|
||||||
$hit = $val === false ? "hit" : "miss";
|
|
||||||
file_put_contents("data/cache.log", "Cache $hit: $key\n", FILE_APPEND);
|
|
||||||
}
|
|
||||||
if ($val !== false) {
|
if ($val !== false) {
|
||||||
|
$res = "hit";
|
||||||
$this->hits++;
|
$this->hits++;
|
||||||
return $val;
|
|
||||||
} else {
|
} else {
|
||||||
|
$res = "miss";
|
||||||
$this->misses++;
|
$this->misses++;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
$_tracer->end(null, ["result"=>$res]);
|
||||||
|
return $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set(string $key, $val, int $time=0)
|
public function set(string $key, $val, int $time=0)
|
||||||
{
|
{
|
||||||
|
global $_tracer;
|
||||||
|
$_tracer->begin("Cache Set", ["key"=>$key, "time"=>$time]);
|
||||||
$this->engine->set($key, $val, $time);
|
$this->engine->set($key, $val, $time);
|
||||||
if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
|
$_tracer->end();
|
||||||
file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(string $key)
|
public function delete(string $key)
|
||||||
{
|
{
|
||||||
|
global $_tracer;
|
||||||
|
$_tracer->begin("Cache Delete", ["key"=>$key]);
|
||||||
$this->engine->delete($key);
|
$this->engine->delete($key);
|
||||||
if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
|
$_tracer->end();
|
||||||
file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_hits(): int
|
public function get_hits(): int
|
||||||
|
@ -144,6 +144,13 @@ abstract class BaseConfig implements Config
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function set_default_float(string $name, float $value): void
|
||||||
|
{
|
||||||
|
if (is_null($this->get($name))) {
|
||||||
|
$this->values[$name] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function set_default_string(string $name, string $value): void
|
public function set_default_string(string $name, string $value): void
|
||||||
{
|
{
|
||||||
if (is_null($this->get($name))) {
|
if (is_null($this->get($name))) {
|
||||||
@ -170,6 +177,11 @@ abstract class BaseConfig implements Config
|
|||||||
return (int)($this->get($name, $default));
|
return (int)($this->get($name, $default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_float(string $name, ?float $default=null): ?float
|
||||||
|
{
|
||||||
|
return (float)($this->get($name, $default));
|
||||||
|
}
|
||||||
|
|
||||||
public function get_string(string $name, ?string $default=null): ?string
|
public function get_string(string $name, ?string $default=null): ?string
|
||||||
{
|
{
|
||||||
return $this->get($name, $default);
|
return $this->get($name, $default);
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
abstract class DatabaseDriver
|
||||||
|
{
|
||||||
|
public const MYSQL = "mysql";
|
||||||
|
public const PGSQL = "pgsql";
|
||||||
|
public const SQLITE = "sqlite";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for controlled database access
|
* A class for controlled database access
|
||||||
*/
|
*/
|
||||||
class Database
|
class Database
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PDO database connection object, for anyone who wants direct access.
|
* The PDO database connection object, for anyone who wants direct access.
|
||||||
* @var null|PDO
|
* @var null|PDO
|
||||||
@ -72,7 +80,7 @@ class Database
|
|||||||
|
|
||||||
// https://bugs.php.net/bug.php?id=70221
|
// https://bugs.php.net/bug.php?id=70221
|
||||||
$ka = DATABASE_KA;
|
$ka = DATABASE_KA;
|
||||||
if (version_compare(PHP_VERSION, "6.9.9") == 1 && $this->get_driver_name() == "sqlite") {
|
if (version_compare(PHP_VERSION, "6.9.9") == 1 && $this->get_driver_name() == DatabaseDriver::SQLITE) {
|
||||||
$ka = false;
|
$ka = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,11 +104,11 @@ class Database
|
|||||||
throw new SCoreException("Can't figure out database engine");
|
throw new SCoreException("Can't figure out database engine");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($db_proto === "mysql") {
|
if ($db_proto === DatabaseDriver::MYSQL) {
|
||||||
$this->engine = new MySQL();
|
$this->engine = new MySQL();
|
||||||
} elseif ($db_proto === "pgsql") {
|
} elseif ($db_proto === DatabaseDriver::PGSQL) {
|
||||||
$this->engine = new PostgreSQL();
|
$this->engine = new PostgreSQL();
|
||||||
} elseif ($db_proto === "sqlite") {
|
} elseif ($db_proto === DatabaseDriver::SQLITE) {
|
||||||
$this->engine = new SQLite();
|
$this->engine = new SQLite();
|
||||||
} else {
|
} else {
|
||||||
die('Unknown PDO driver: '.$db_proto);
|
die('Unknown PDO driver: '.$db_proto);
|
||||||
@ -159,6 +167,19 @@ class Database
|
|||||||
return $this->engine->scoreql_to_sql($input);
|
return $this->engine->scoreql_to_sql($input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function scoresql_value_prepare($input)
|
||||||
|
{
|
||||||
|
if (is_null($this->engine)) {
|
||||||
|
$this->connect_engine();
|
||||||
|
}
|
||||||
|
if($input===true) {
|
||||||
|
return $this->engine->BOOL_Y;
|
||||||
|
} else if ($input===false) {
|
||||||
|
return $this->engine->BOOL_N;
|
||||||
|
}
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
public function get_driver_name(): string
|
public function get_driver_name(): string
|
||||||
{
|
{
|
||||||
if (is_null($this->engine)) {
|
if (is_null($this->engine)) {
|
||||||
@ -167,35 +188,16 @@ class Database
|
|||||||
return $this->engine->name;
|
return $this->engine->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function count_execs(string $sql, array $inputarray): void
|
private function count_time(string $method, float $start, string $query, ?array $args): void
|
||||||
{
|
{
|
||||||
if ((DEBUG_SQL === true) || (is_null(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) {
|
global $_tracer, $tracer_enabled;
|
||||||
$sql = trim(preg_replace('/\s+/msi', ' ', $sql));
|
$dur = microtime(true) - $start;
|
||||||
if (isset($inputarray) && is_array($inputarray) && !empty($inputarray)) {
|
if($tracer_enabled) {
|
||||||
$text = $sql." -- ".join(", ", $inputarray)."\n";
|
$query = trim(preg_replace('/^[\t ]+/m', '', $query)); // trim leading whitespace
|
||||||
} else {
|
$_tracer->complete($start * 1000000, $dur * 1000000, "DB Query", ["query"=>$query, "args"=>$args, "method"=>$method]);
|
||||||
$text = $sql."\n";
|
|
||||||
}
|
}
|
||||||
file_put_contents("data/sql.log", $text, FILE_APPEND);
|
|
||||||
}
|
|
||||||
if (!is_array($inputarray)) {
|
|
||||||
$this->query_count++;
|
$this->query_count++;
|
||||||
}
|
$this->dbtime += $dur;
|
||||||
# handle 2-dimensional input arrays
|
|
||||||
elseif (is_array(reset($inputarray))) {
|
|
||||||
$this->query_count += sizeof($inputarray);
|
|
||||||
} else {
|
|
||||||
$this->query_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function count_time(string $method, float $start): void
|
|
||||||
{
|
|
||||||
if ((DEBUG_SQL === true) || (is_null(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) {
|
|
||||||
$text = $method.":".(microtime(true) - $start)."\n";
|
|
||||||
file_put_contents("data/sql.log", $text, FILE_APPEND);
|
|
||||||
}
|
|
||||||
$this->dbtime += microtime(true) - $start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(string $query, array $args=[]): PDOStatement
|
public function execute(string $query, array $args=[]): PDOStatement
|
||||||
@ -204,7 +206,6 @@ class Database
|
|||||||
if (is_null($this->db)) {
|
if (is_null($this->db)) {
|
||||||
$this->connect_db();
|
$this->connect_db();
|
||||||
}
|
}
|
||||||
$this->count_execs($query, $args);
|
|
||||||
$stmt = $this->db->prepare(
|
$stmt = $this->db->prepare(
|
||||||
"-- " . str_replace("%2F", "/", urlencode(@$_GET['q'])). "\n" .
|
"-- " . str_replace("%2F", "/", urlencode(@$_GET['q'])). "\n" .
|
||||||
$query
|
$query
|
||||||
@ -235,7 +236,18 @@ class Database
|
|||||||
{
|
{
|
||||||
$_start = microtime(true);
|
$_start = microtime(true);
|
||||||
$data = $this->execute($query, $args)->fetchAll();
|
$data = $this->execute($query, $args)->fetchAll();
|
||||||
$this->count_time("get_all", $_start);
|
$this->count_time("get_all", $_start, $query, $args);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an SQL query and return a iterable object for use with generators.
|
||||||
|
*/
|
||||||
|
public function get_all_iterable(string $query, array $args=[]): PDOStatement
|
||||||
|
{
|
||||||
|
$_start = microtime(true);
|
||||||
|
$data = $this->execute($query, $args);
|
||||||
|
$this->count_time("get_all_iterable", $_start, $query, $args);
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +258,7 @@ class Database
|
|||||||
{
|
{
|
||||||
$_start = microtime(true);
|
$_start = microtime(true);
|
||||||
$row = $this->execute($query, $args)->fetch();
|
$row = $this->execute($query, $args)->fetch();
|
||||||
$this->count_time("get_row", $_start);
|
$this->count_time("get_row", $_start, $query, $args);
|
||||||
return $row ? $row : null;
|
return $row ? $row : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,27 +268,32 @@ class Database
|
|||||||
public function get_col(string $query, array $args=[]): array
|
public function get_col(string $query, array $args=[]): array
|
||||||
{
|
{
|
||||||
$_start = microtime(true);
|
$_start = microtime(true);
|
||||||
$stmt = $this->execute($query, $args);
|
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_COLUMN);
|
||||||
$res = [];
|
$this->count_time("get_col", $_start, $query, $args);
|
||||||
foreach ($stmt as $row) {
|
|
||||||
$res[] = $row[0];
|
|
||||||
}
|
|
||||||
$this->count_time("get_col", $_start);
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute an SQL query and return the the first row => the second row.
|
* 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
|
||||||
|
{
|
||||||
|
$_start = microtime(true);
|
||||||
|
$stmt = $this->execute($query, $args);
|
||||||
|
$this->count_time("get_col_iterable", $_start, $query, $args);
|
||||||
|
foreach ($stmt as $row) {
|
||||||
|
yield $row[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an SQL query and return the the first column => the second column.
|
||||||
*/
|
*/
|
||||||
public function get_pairs(string $query, array $args=[]): array
|
public function get_pairs(string $query, array $args=[]): array
|
||||||
{
|
{
|
||||||
$_start = microtime(true);
|
$_start = microtime(true);
|
||||||
$stmt = $this->execute($query, $args);
|
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
$res = [];
|
$this->count_time("get_pairs", $_start, $query, $args);
|
||||||
foreach ($stmt as $row) {
|
|
||||||
$res[$row[0]] = $row[1];
|
|
||||||
}
|
|
||||||
$this->count_time("get_pairs", $_start);
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +304,7 @@ class Database
|
|||||||
{
|
{
|
||||||
$_start = microtime(true);
|
$_start = microtime(true);
|
||||||
$row = $this->execute($query, $args)->fetch();
|
$row = $this->execute($query, $args)->fetch();
|
||||||
$this->count_time("get_one", $_start);
|
$this->count_time("get_one", $_start, $query, $args);
|
||||||
return $row[0];
|
return $row[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +313,7 @@ class Database
|
|||||||
*/
|
*/
|
||||||
public function get_last_insert_id(string $seq): int
|
public function get_last_insert_id(string $seq): int
|
||||||
{
|
{
|
||||||
if ($this->engine->name == "pgsql") {
|
if ($this->engine->name == DatabaseDriver::PGSQL) {
|
||||||
return $this->db->lastInsertId($seq);
|
return $this->db->lastInsertId($seq);
|
||||||
} else {
|
} else {
|
||||||
return $this->db->lastInsertId();
|
return $this->db->lastInsertId();
|
||||||
@ -326,15 +343,15 @@ class Database
|
|||||||
$this->connect_db();
|
$this->connect_db();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->engine->name === "mysql") {
|
if ($this->engine->name === DatabaseDriver::MYSQL) {
|
||||||
return count(
|
return count(
|
||||||
$this->get_all("SHOW TABLES")
|
$this->get_all("SHOW TABLES")
|
||||||
);
|
);
|
||||||
} elseif ($this->engine->name === "pgsql") {
|
} elseif ($this->engine->name === DatabaseDriver::PGSQL) {
|
||||||
return count(
|
return count(
|
||||||
$this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
|
$this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
|
||||||
);
|
);
|
||||||
} elseif ($this->engine->name === "sqlite") {
|
} elseif ($this->engine->name === DatabaseDriver::SQLITE) {
|
||||||
return count(
|
return count(
|
||||||
$this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'")
|
$this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'")
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
abstract class SCORE {
|
||||||
|
const AIPK = "SCORE_AIPK";
|
||||||
|
const INET = "SCORE_INET";
|
||||||
|
const BOOL_Y = "SCORE_BOOL_Y";
|
||||||
|
const BOOL_N = "SCORE_BOOL_N";
|
||||||
|
const BOOL = "SCORE_BOOL";
|
||||||
|
const DATETIME = "SCORE_DATETIME";
|
||||||
|
const NOW = "SCORE_NOW";
|
||||||
|
const STRNORM = "SCORE_STRNORM";
|
||||||
|
const ILIKE = "SCORE_ILIKE";
|
||||||
|
}
|
||||||
|
|
||||||
class DBEngine
|
class DBEngine
|
||||||
{
|
{
|
||||||
/** @var null|string */
|
/** @var null|string */
|
||||||
public $name = null;
|
public $name = null;
|
||||||
|
|
||||||
|
public $BOOL_Y = null;
|
||||||
|
public $BOOL_N = null;
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -22,7 +37,10 @@ class DBEngine
|
|||||||
class MySQL extends DBEngine
|
class MySQL extends DBEngine
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $name = "mysql";
|
public $name = DatabaseDriver::MYSQL;
|
||||||
|
|
||||||
|
public $BOOL_Y = 'Y';
|
||||||
|
public $BOOL_N = 'N';
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
@ -31,15 +49,15 @@ class MySQL extends DBEngine
|
|||||||
|
|
||||||
public function scoreql_to_sql(string $data): string
|
public function scoreql_to_sql(string $data): string
|
||||||
{
|
{
|
||||||
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY auto_increment", $data);
|
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY auto_increment", $data);
|
||||||
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
|
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
|
||||||
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
|
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
|
||||||
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
|
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
|
||||||
$data = str_replace("SCORE_BOOL", "ENUM('Y', 'N')", $data);
|
$data = str_replace(SCORE::BOOL, "ENUM('Y', 'N')", $data);
|
||||||
$data = str_replace("SCORE_DATETIME", "DATETIME", $data);
|
$data = str_replace(SCORE::DATETIME, "DATETIME", $data);
|
||||||
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
|
$data = str_replace(SCORE::NOW, "\"1970-01-01\"", $data);
|
||||||
$data = str_replace("SCORE_STRNORM", "", $data);
|
$data = str_replace(SCORE::STRNORM, "", $data);
|
||||||
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
|
$data = str_replace(SCORE::ILIKE, "LIKE", $data);
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,8 +71,13 @@ class MySQL extends DBEngine
|
|||||||
|
|
||||||
class PostgreSQL extends DBEngine
|
class PostgreSQL extends DBEngine
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $name = "pgsql";
|
public $name = DatabaseDriver::PGSQL;
|
||||||
|
|
||||||
|
public $BOOL_Y = 't';
|
||||||
|
public $BOOL_N = 'f';
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
@ -63,20 +86,20 @@ class PostgreSQL extends DBEngine
|
|||||||
} else {
|
} else {
|
||||||
$db->exec("SET application_name TO 'shimmie [local]';");
|
$db->exec("SET application_name TO 'shimmie [local]';");
|
||||||
}
|
}
|
||||||
$db->exec("SET statement_timeout TO 10000;");
|
$db->exec("SET statement_timeout TO ".DATABASE_TIMEOUT.";");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scoreql_to_sql(string $data): string
|
public function scoreql_to_sql(string $data): string
|
||||||
{
|
{
|
||||||
$data = str_replace("SCORE_AIPK", "SERIAL PRIMARY KEY", $data);
|
$data = str_replace(SCORE::AIPK, "SERIAL PRIMARY KEY", $data);
|
||||||
$data = str_replace("SCORE_INET", "INET", $data);
|
$data = str_replace(SCORE::INET, "INET", $data);
|
||||||
$data = str_replace("SCORE_BOOL_Y", "'t'", $data);
|
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
|
||||||
$data = str_replace("SCORE_BOOL_N", "'f'", $data);
|
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
|
||||||
$data = str_replace("SCORE_BOOL", "BOOL", $data);
|
$data = str_replace(SCORE::BOOL, "BOOL", $data);
|
||||||
$data = str_replace("SCORE_DATETIME", "TIMESTAMP", $data);
|
$data = str_replace(SCORE::DATETIME, "TIMESTAMP", $data);
|
||||||
$data = str_replace("SCORE_NOW", "current_timestamp", $data);
|
$data = str_replace(SCORE::NOW, "current_timestamp", $data);
|
||||||
$data = str_replace("SCORE_STRNORM", "lower", $data);
|
$data = str_replace(SCORE::STRNORM, "lower", $data);
|
||||||
$data = str_replace("SCORE_ILIKE", "ILIKE", $data);
|
$data = str_replace(SCORE::ILIKE, "ILIKE", $data);
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +159,11 @@ function _ln($n)
|
|||||||
class SQLite extends DBEngine
|
class SQLite extends DBEngine
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $name = "sqlite";
|
public $name = DatabaseDriver::SQLITE;
|
||||||
|
|
||||||
|
public $BOOL_Y = 'Y';
|
||||||
|
public $BOOL_N = 'N';
|
||||||
|
|
||||||
|
|
||||||
public function init(PDO $db)
|
public function init(PDO $db)
|
||||||
{
|
{
|
||||||
@ -156,14 +183,14 @@ class SQLite extends DBEngine
|
|||||||
|
|
||||||
public function scoreql_to_sql(string $data): string
|
public function scoreql_to_sql(string $data): string
|
||||||
{
|
{
|
||||||
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY", $data);
|
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY", $data);
|
||||||
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
|
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
|
||||||
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
|
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
|
||||||
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
|
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
|
||||||
$data = str_replace("SCORE_BOOL", "CHAR(1)", $data);
|
$data = str_replace(SCORE::BOOL, "CHAR(1)", $data);
|
||||||
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
|
$data = str_replace(SCORE::NOW, "\"1970-01-01\"", $data);
|
||||||
$data = str_replace("SCORE_STRNORM", "lower", $data);
|
$data = str_replace(SCORE::STRNORM, "lower", $data);
|
||||||
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
|
$data = str_replace(SCORE::ILIKE, "LIKE", $data);
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
abstract class Event
|
abstract class Event
|
||||||
{
|
{
|
||||||
|
public $stop_processing = false;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -58,7 +60,7 @@ class PageRequestEvent extends Event
|
|||||||
|
|
||||||
// if path is not specified, use the default front page
|
// if path is not specified, use the default front page
|
||||||
if (empty($path)) { /* empty is faster than strlen */
|
if (empty($path)) { /* empty is faster than strlen */
|
||||||
$path = $config->get_string('front_page');
|
$path = $config->get_string(SetupConfig::FRONT_PAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// break the path into parts
|
// break the path into parts
|
||||||
|
@ -182,7 +182,7 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
|
|
||||||
// even more hax..
|
// even more hax..
|
||||||
$event->metadata['tags'] = $existing->get_tag_list();
|
$event->metadata['tags'] = $existing->get_tag_list();
|
||||||
$image = $this->create_image_from_data(warehouse_path("images", $event->metadata['hash']), $event->metadata);
|
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->metadata['hash']), $event->metadata);
|
||||||
|
|
||||||
if (is_null($image)) {
|
if (is_null($image)) {
|
||||||
throw new UploadException("Data handler failed to create image object from data");
|
throw new UploadException("Data handler failed to create image object from data");
|
||||||
@ -192,13 +192,14 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
send_event($ire);
|
send_event($ire);
|
||||||
$event->image_id = $image_id;
|
$event->image_id = $image_id;
|
||||||
} else {
|
} else {
|
||||||
$image = $this->create_image_from_data(warehouse_path("images", $event->hash), $event->metadata);
|
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata);
|
||||||
if (is_null($image)) {
|
if (is_null($image)) {
|
||||||
throw new UploadException("Data handler failed to create image object from data");
|
throw new UploadException("Data handler failed to create image object from data");
|
||||||
}
|
}
|
||||||
$iae = new ImageAdditionEvent($image);
|
$iae = new ImageAdditionEvent($image);
|
||||||
send_event($iae);
|
send_event($iae);
|
||||||
$event->image_id = $iae->image->id;
|
$event->image_id = $iae->image->id;
|
||||||
|
$event->merged = $iae->merged;
|
||||||
|
|
||||||
// Rating Stuff.
|
// Rating Stuff.
|
||||||
if (!empty($event->metadata['rating'])) {
|
if (!empty($event->metadata['rating'])) {
|
||||||
@ -222,13 +223,13 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
$result = false;
|
$result = false;
|
||||||
if ($this->supported_ext($event->type)) {
|
if ($this->supported_ext($event->type)) {
|
||||||
if ($event->force) {
|
if ($event->force) {
|
||||||
$result = $this->create_thumb($event->hash);
|
$result = $this->create_thumb($event->hash, $event->type);
|
||||||
} else {
|
} else {
|
||||||
$outname = warehouse_path("thumbs", $event->hash);
|
$outname = warehouse_path(Image::THUMBNAIL_DIR, $event->hash);
|
||||||
if (file_exists($outname)) {
|
if (file_exists($outname)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$result = $this->create_thumb($event->hash);
|
$result = $this->create_thumb($event->hash, $event->type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($result) {
|
if ($result) {
|
||||||
@ -256,5 +257,5 @@ abstract class DataHandlerExtension extends Extension
|
|||||||
abstract protected function supported_ext(string $ext): bool;
|
abstract protected function supported_ext(string $ext): bool;
|
||||||
abstract protected function check_contents(string $tmpname): bool;
|
abstract protected function check_contents(string $tmpname): bool;
|
||||||
abstract protected function create_image_from_data(string $filename, array $metadata);
|
abstract protected function create_image_from_data(string $filename, array $metadata);
|
||||||
abstract protected function create_thumb(string $hash): bool;
|
abstract protected function create_thumb(string $hash, string $type): bool;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ class ImageAdditionEvent extends Event
|
|||||||
/** @var Image */
|
/** @var Image */
|
||||||
public $image;
|
public $image;
|
||||||
|
|
||||||
|
public $merged = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a new image into the database with its associated
|
* Inserts a new image into the database with its associated
|
||||||
* information. Also calls TagSetEvent to set the tags for
|
* information. Also calls TagSetEvent to set the tags for
|
||||||
@ -40,15 +42,19 @@ class ImageDeletionEvent extends Event
|
|||||||
/** @var Image */
|
/** @var Image */
|
||||||
public $image;
|
public $image;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
public $force = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an image.
|
* Deletes an image.
|
||||||
*
|
*
|
||||||
* Used by things like tags and comments handlers to
|
* Used by things like tags and comments handlers to
|
||||||
* clean out related rows in their tables.
|
* clean out related rows in their tables.
|
||||||
*/
|
*/
|
||||||
public function __construct(Image $image)
|
public function __construct(Image $image, bool $force = false)
|
||||||
{
|
{
|
||||||
$this->image = $image;
|
$this->image = $image;
|
||||||
|
$this->force = $force;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
*/
|
*/
|
||||||
class Image
|
class Image
|
||||||
{
|
{
|
||||||
|
public const IMAGE_DIR = "images";
|
||||||
|
public const THUMBNAIL_DIR = "thumbs";
|
||||||
|
|
||||||
private static $tag_n = 0; // temp hack
|
private static $tag_n = 0; // temp hack
|
||||||
public static $order_sql = null; // this feels ugly
|
public static $order_sql = null; // this feels ugly
|
||||||
|
|
||||||
@ -51,6 +54,19 @@ class Image
|
|||||||
/** @var boolean */
|
/** @var boolean */
|
||||||
public $locked = false;
|
public $locked = false;
|
||||||
|
|
||||||
|
/** @var boolean */
|
||||||
|
public $lossless = null;
|
||||||
|
|
||||||
|
/** @var boolean */
|
||||||
|
public $video = null;
|
||||||
|
|
||||||
|
/** @var boolean */
|
||||||
|
public $audio = null;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
public $length = null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One will very rarely construct an image directly, more common
|
* One will very rarely construct an image directly, more common
|
||||||
* would be to use Image::by_id, Image::by_hash, etc.
|
* would be to use Image::by_id, Image::by_hash, etc.
|
||||||
@ -100,6 +116,43 @@ class Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags=[]): iterable
|
||||||
|
{
|
||||||
|
global $database, $user, $config;
|
||||||
|
|
||||||
|
if ($start < 0) {
|
||||||
|
$start = 0;
|
||||||
|
}
|
||||||
|
if ($limit!=null && $limit < 1) {
|
||||||
|
$limit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SPEED_HAX) {
|
||||||
|
if (!$user->can(Permissions::BIG_SEARCH) and count($tags) > 3) {
|
||||||
|
throw new SCoreException("Anonymous users may only search for up to 3 tags at a time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list($tag_conditions, $img_conditions) = self::terms_to_conditions($tags);
|
||||||
|
|
||||||
|
$result = Image::get_accelerated_result($tag_conditions, $img_conditions, $start, $limit);
|
||||||
|
if (!$result) {
|
||||||
|
$querylet = Image::build_search_querylet($tag_conditions, $img_conditions);
|
||||||
|
$querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order"))));
|
||||||
|
if($limit!=null) {
|
||||||
|
$querylet->append(new Querylet(" LIMIT :limit ", ["limit" => $limit]));
|
||||||
|
}
|
||||||
|
$querylet->append(new Querylet(" OFFSET :offset ", ["offset"=>$start]));
|
||||||
|
#var_dump($querylet->sql); var_dump($querylet->variables);
|
||||||
|
$result = $database->get_all_iterable($querylet->sql, $querylet->variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::$order_sql = null;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for an array of images
|
* Search for an array of images
|
||||||
*
|
*
|
||||||
@ -108,92 +161,30 @@ class Image
|
|||||||
*/
|
*/
|
||||||
public static function find_images(int $start, int $limit, array $tags=[]): array
|
public static function find_images(int $start, int $limit, array $tags=[]): array
|
||||||
{
|
{
|
||||||
global $database, $user, $config;
|
$result = self::find_images_internal($start, $limit, $tags);
|
||||||
|
|
||||||
$images = [];
|
$images = [];
|
||||||
|
foreach ($result as $row) {
|
||||||
if ($start < 0) {
|
|
||||||
$start = 0;
|
|
||||||
}
|
|
||||||
if ($limit < 1) {
|
|
||||||
$limit = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SPEED_HAX) {
|
|
||||||
if (!$user->can("big_search") and count($tags) > 3) {
|
|
||||||
throw new SCoreException("Anonymous users may only search for up to 3 tags at a time");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = null;
|
|
||||||
if (SEARCH_ACCEL) {
|
|
||||||
$result = Image::get_accelerated_result($tags, $start, $limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$result) {
|
|
||||||
$querylet = Image::build_search_querylet($tags);
|
|
||||||
$querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order"))));
|
|
||||||
$querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", ["limit"=>$limit, "offset"=>$start]));
|
|
||||||
#var_dump($querylet->sql); var_dump($querylet->variables);
|
|
||||||
$result = $database->execute($querylet->sql, $querylet->variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
while ($row = $result->fetch()) {
|
|
||||||
$images[] = new Image($row);
|
$images[] = new Image($row);
|
||||||
}
|
}
|
||||||
Image::$order_sql = null;
|
|
||||||
return $images;
|
return $images;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for an array of image IDs
|
* Search for an array of images, returning a iterable object of Image
|
||||||
*
|
|
||||||
* #param string[] $tags
|
|
||||||
* #return int[]
|
|
||||||
*/
|
*/
|
||||||
public static function find_image_ids(int $start, int $limit, array $tags=[]): array
|
public static function find_images_iterable(int $start = 0, ?int $limit = null, array $tags=[]): Generator
|
||||||
{
|
{
|
||||||
global $database, $user, $config;
|
$result = self::find_images_internal($start, $limit, $tags);
|
||||||
|
foreach ($result as $row) {
|
||||||
$images = [];
|
yield new Image($row);
|
||||||
|
|
||||||
if ($start < 0) {
|
|
||||||
$start = 0;
|
|
||||||
}
|
}
|
||||||
if ($limit < 1) {
|
|
||||||
$limit = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SPEED_HAX) {
|
|
||||||
if (!$user->can("big_search") and count($tags) > 3) {
|
|
||||||
throw new SCoreException("Anonymous users may only search for up to 3 tags at a time");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = null;
|
|
||||||
if (SEARCH_ACCEL) {
|
|
||||||
$result = Image::get_accelerated_result($tags, $start, $limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$result) {
|
|
||||||
$querylet = Image::build_search_querylet($tags);
|
|
||||||
$querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order"))));
|
|
||||||
$querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", ["limit"=>$limit, "offset"=>$start]));
|
|
||||||
#var_dump($querylet->sql); var_dump($querylet->variables);
|
|
||||||
$result = $database->execute($querylet->sql, $querylet->variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
while ($row = $result->fetch()) {
|
|
||||||
$images[] = $row["id"];
|
|
||||||
}
|
|
||||||
Image::$order_sql = null;
|
|
||||||
return $images;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Accelerator stuff
|
* Accelerator stuff
|
||||||
*/
|
*/
|
||||||
public static function get_acceleratable(array $tags): ?array
|
public static function get_acceleratable(array $tag_conditions): ?array
|
||||||
{
|
{
|
||||||
$ret = [
|
$ret = [
|
||||||
"yays" => [],
|
"yays" => [],
|
||||||
@ -201,16 +192,17 @@ class Image
|
|||||||
];
|
];
|
||||||
$yays = 0;
|
$yays = 0;
|
||||||
$nays = 0;
|
$nays = 0;
|
||||||
foreach ($tags as $tag) {
|
foreach ($tag_conditions as $tq) {
|
||||||
if (!preg_match("/^-?[a-zA-Z0-9_'-]+$/", $tag)) {
|
if (strpos($tq->tag, "*") !== false) {
|
||||||
|
// can't deal with wildcards
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if ($tag[0] == "-") {
|
if ($tq->positive) {
|
||||||
$nays++;
|
|
||||||
$ret["nays"][] = substr($tag, 1);
|
|
||||||
} else {
|
|
||||||
$yays++;
|
$yays++;
|
||||||
$ret["yays"][] = $tag;
|
$ret["yays"][] = $tq->tag;
|
||||||
|
} else {
|
||||||
|
$nays++;
|
||||||
|
$ret["nays"][] = $tq->tag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($yays > 1 || $nays > 0) {
|
if ($yays > 1 || $nays > 0) {
|
||||||
@ -219,11 +211,15 @@ class Image
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_accelerated_result(array $tags, int $offset, int $limit): ?PDOStatement
|
public static function get_accelerated_result(array $tag_conditions, array $img_conditions, int $offset, ?int $limit): ?PDOStatement
|
||||||
{
|
{
|
||||||
|
if (!SEARCH_ACCEL || !empty($img_conditions) || isset($_GET['DISABLE_ACCEL'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
global $database;
|
global $database;
|
||||||
|
|
||||||
$req = Image::get_acceleratable($tags);
|
$req = Image::get_acceleratable($tag_conditions);
|
||||||
if (!$req) {
|
if (!$req) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -231,8 +227,8 @@ class Image
|
|||||||
$req["limit"] = $limit;
|
$req["limit"] = $limit;
|
||||||
|
|
||||||
$response = Image::query_accelerator($req);
|
$response = Image::query_accelerator($req);
|
||||||
|
if ($response) {
|
||||||
$list = implode(",", $response);
|
$list = implode(",", $response);
|
||||||
if ($list) {
|
|
||||||
$result = $database->execute("SELECT * FROM images WHERE id IN ($list) ORDER BY images.id DESC");
|
$result = $database->execute("SELECT * FROM images WHERE id IN ($list) ORDER BY images.id DESC");
|
||||||
} else {
|
} else {
|
||||||
$result = $database->execute("SELECT * FROM images WHERE 1=0 ORDER BY images.id DESC");
|
$result = $database->execute("SELECT * FROM images WHERE 1=0 ORDER BY images.id DESC");
|
||||||
@ -240,9 +236,13 @@ class Image
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_accelerated_count(array $tags): ?int
|
public static function get_accelerated_count(array $tag_conditions, array $img_conditions): ?int
|
||||||
{
|
{
|
||||||
$req = Image::get_acceleratable($tags);
|
if (!SEARCH_ACCEL || !empty($img_conditions) || isset($_GET['DISABLE_ACCEL'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$req = Image::get_acceleratable($tag_conditions);
|
||||||
if (!$req) {
|
if (!$req) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -253,17 +253,21 @@ class Image
|
|||||||
|
|
||||||
public static function query_accelerator($req)
|
public static function query_accelerator($req)
|
||||||
{
|
{
|
||||||
|
global $_tracer;
|
||||||
$fp = @fsockopen("127.0.0.1", 21212);
|
$fp = @fsockopen("127.0.0.1", 21212);
|
||||||
if (!$fp) {
|
if (!$fp) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
fwrite($fp, json_encode($req));
|
$req_str = json_encode($req);
|
||||||
|
$_tracer->begin("Accelerator Query", ["req"=>$req_str]);
|
||||||
|
fwrite($fp, $req_str);
|
||||||
$data = "";
|
$data = "";
|
||||||
while (($buffer = fgets($fp, 4096)) !== false) {
|
while (($buffer = fgets($fp, 4096)) !== false) {
|
||||||
$data .= $buffer;
|
$data .= $buffer;
|
||||||
}
|
}
|
||||||
|
$_tracer->end();
|
||||||
if (!feof($fp)) {
|
if (!feof($fp)) {
|
||||||
die("Error: unexpected fgets() fail in query_accelerator($req)\n");
|
die("Error: unexpected fgets() fail in query_accelerator($req_str)\n");
|
||||||
}
|
}
|
||||||
fclose($fp);
|
fclose($fp);
|
||||||
return json_decode($data);
|
return json_decode($data);
|
||||||
@ -295,9 +299,10 @@ class Image
|
|||||||
["tag"=>$tags[0]]
|
["tag"=>$tags[0]]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$total = Image::get_accelerated_count($tags);
|
list($tag_conditions, $img_conditions) = self::terms_to_conditions($tags);
|
||||||
|
$total = Image::get_accelerated_count($tag_conditions, $img_conditions);
|
||||||
if (is_null($total)) {
|
if (is_null($total)) {
|
||||||
$querylet = Image::build_search_querylet($tags);
|
$querylet = Image::build_search_querylet($tag_conditions, $img_conditions);
|
||||||
$total = $database->get_one("SELECT COUNT(*) AS cnt FROM ($querylet->sql) AS tbl", $querylet->variables);
|
$total = $database->get_one("SELECT COUNT(*) AS cnt FROM ($querylet->sql) AS tbl", $querylet->variables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -318,6 +323,49 @@ class Image
|
|||||||
return ceil(Image::count_images($tags) / $config->get_int('index_images'));
|
return ceil(Image::count_images($tags) / $config->get_int('index_images'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function terms_to_conditions(array $terms): array
|
||||||
|
{
|
||||||
|
$tag_conditions = [];
|
||||||
|
$img_conditions = [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn a bunch of strings into a bunch of TagCondition
|
||||||
|
* and ImgCondition objects
|
||||||
|
*/
|
||||||
|
$stpe = new SearchTermParseEvent(null, $terms);
|
||||||
|
send_event($stpe);
|
||||||
|
if ($stpe->is_querylet_set()) {
|
||||||
|
foreach ($stpe->get_querylets() as $querylet) {
|
||||||
|
$img_conditions[] = new ImgCondition($querylet, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($terms as $term) {
|
||||||
|
$positive = true;
|
||||||
|
if (is_string($term) && !empty($term) && ($term[0] == '-')) {
|
||||||
|
$positive = false;
|
||||||
|
$term = substr($term, 1);
|
||||||
|
}
|
||||||
|
if (strlen($term) === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stpe = new SearchTermParseEvent($term, $terms);
|
||||||
|
send_event($stpe);
|
||||||
|
if ($stpe->is_querylet_set()) {
|
||||||
|
foreach ($stpe->get_querylets() as $querylet) {
|
||||||
|
$img_conditions[] = new ImgCondition($querylet, $positive);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the whole match is wild, skip this
|
||||||
|
if (str_replace("*", "", $term) != "") {
|
||||||
|
$tag_conditions[] = new TagCondition($term, $positive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [$tag_conditions, $img_conditions];
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Accessors & mutators
|
* Accessors & mutators
|
||||||
*/
|
*/
|
||||||
@ -352,7 +400,8 @@ class Image
|
|||||||
');
|
');
|
||||||
} else {
|
} else {
|
||||||
$tags[] = 'id'. $gtlt . $this->id;
|
$tags[] = 'id'. $gtlt . $this->id;
|
||||||
$querylet = Image::build_search_querylet($tags);
|
list($tag_conditions, $img_conditions) = self::terms_to_conditions($tags);
|
||||||
|
$querylet = Image::build_search_querylet($tag_conditions, $img_conditions);
|
||||||
$querylet->append_sql(' ORDER BY images.id '.$dir.' LIMIT 1');
|
$querylet->append_sql(' ORDER BY images.id '.$dir.' LIMIT 1');
|
||||||
$row = $database->get_row($querylet->sql, $querylet->variables);
|
$row = $database->get_row($querylet->sql, $querylet->variables);
|
||||||
}
|
}
|
||||||
@ -427,7 +476,7 @@ class Image
|
|||||||
*/
|
*/
|
||||||
public function get_image_link(): string
|
public function get_image_link(): string
|
||||||
{
|
{
|
||||||
return $this->get_link('image_ilink', '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext');
|
return $this->get_link(ImageConfig::ILINK, '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -444,8 +493,8 @@ class Image
|
|||||||
public function get_thumb_link(): string
|
public function get_thumb_link(): string
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
$ext = $config->get_string("thumb_type");
|
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||||
return $this->get_link('image_tlink', '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext);
|
return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -476,7 +525,7 @@ class Image
|
|||||||
public function get_tooltip(): string
|
public function get_tooltip(): string
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
$tt = $this->parse_link_template($config->get_string('image_tip'), "no_escape");
|
$tt = $this->parse_link_template($config->get_string(ImageConfig::TIP), "no_escape");
|
||||||
|
|
||||||
// Removes the size tag if the file is an mp3
|
// Removes the size tag if the file is an mp3
|
||||||
if ($this->ext === 'mp3') {
|
if ($this->ext === 'mp3') {
|
||||||
@ -502,7 +551,7 @@ class Image
|
|||||||
*/
|
*/
|
||||||
public function get_image_filename(): string
|
public function get_image_filename(): string
|
||||||
{
|
{
|
||||||
return warehouse_path("images", $this->hash);
|
return warehouse_path(self::IMAGE_DIR, $this->hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -510,7 +559,7 @@ class Image
|
|||||||
*/
|
*/
|
||||||
public function get_thumb_filename(): string
|
public function get_thumb_filename(): string
|
||||||
{
|
{
|
||||||
return warehouse_path("thumbs", $this->hash);
|
return warehouse_path(self::THUMBNAIL_DIR, $this->hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -590,7 +639,7 @@ class Image
|
|||||||
public function delete_tags_from_image(): void
|
public function delete_tags_from_image(): void
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
if ($database->get_driver_name() == "mysql") {
|
if ($database->get_driver_name() == DatabaseDriver::MYSQL) {
|
||||||
//mysql < 5.6 has terrible subquery optimization, using EXISTS / JOIN fixes this
|
//mysql < 5.6 has terrible subquery optimization, using EXISTS / JOIN fixes this
|
||||||
$database->execute(
|
$database->execute(
|
||||||
"
|
"
|
||||||
@ -764,7 +813,7 @@ class Image
|
|||||||
$tmpl = str_replace('$size', "{$this->width}x{$this->height}", $tmpl);
|
$tmpl = str_replace('$size', "{$this->width}x{$this->height}", $tmpl);
|
||||||
$tmpl = str_replace('$filesize', to_shorthand_int($this->filesize), $tmpl);
|
$tmpl = str_replace('$filesize', to_shorthand_int($this->filesize), $tmpl);
|
||||||
$tmpl = str_replace('$filename', $_escape($base_fname), $tmpl);
|
$tmpl = str_replace('$filename', $_escape($base_fname), $tmpl);
|
||||||
$tmpl = str_replace('$title', $_escape($config->get_string("title")), $tmpl);
|
$tmpl = str_replace('$title', $_escape($config->get_string(SetupConfig::TITLE)), $tmpl);
|
||||||
$tmpl = str_replace('$date', $_escape(autodate($this->posted, false)), $tmpl);
|
$tmpl = str_replace('$date', $_escape(autodate($this->posted, false)), $tmpl);
|
||||||
|
|
||||||
// nothing seems to use this, sending the event out to 50 exts is a lot of overhead
|
// nothing seems to use this, sending the event out to 50 exts is a lot of overhead
|
||||||
@ -774,16 +823,17 @@ class Image
|
|||||||
$tmpl = $plte->link;
|
$tmpl = $plte->link;
|
||||||
}
|
}
|
||||||
|
|
||||||
static $flexihash = null;
|
static $flexihashes = [];
|
||||||
static $fh_last_opts = null;
|
|
||||||
$matches = [];
|
$matches = [];
|
||||||
if (preg_match("/(.*){(.*)}(.*)/", $tmpl, $matches)) {
|
if (preg_match("/(.*){(.*)}(.*)/", $tmpl, $matches)) {
|
||||||
$pre = $matches[1];
|
$pre = $matches[1];
|
||||||
$opts = $matches[2];
|
$opts = $matches[2];
|
||||||
$post = $matches[3];
|
$post = $matches[3];
|
||||||
|
|
||||||
if ($opts != $fh_last_opts) {
|
if(isset($flexihashes[$opts])) {
|
||||||
$fh_last_opts = $opts;
|
$flexihash = $flexihashes[$opts];
|
||||||
|
}
|
||||||
|
else {
|
||||||
$flexihash = new Flexihash\Flexihash();
|
$flexihash = new Flexihash\Flexihash();
|
||||||
foreach (explode(",", $opts) as $opt) {
|
foreach (explode(",", $opts) as $opt) {
|
||||||
$parts = explode("=", $opt);
|
$parts = explode("=", $opt);
|
||||||
@ -799,6 +849,7 @@ class Image
|
|||||||
}
|
}
|
||||||
$flexihash->addTarget($opt_val, $opt_weight);
|
$flexihash->addTarget($opt_val, $opt_weight);
|
||||||
}
|
}
|
||||||
|
$flexihashes[$opts] = $flexihash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// $choice = $flexihash->lookup($pre.$post);
|
// $choice = $flexihash->lookup($pre.$post);
|
||||||
@ -813,59 +864,19 @@ class Image
|
|||||||
/**
|
/**
|
||||||
* #param string[] $terms
|
* #param string[] $terms
|
||||||
*/
|
*/
|
||||||
private static function build_search_querylet(array $terms): Querylet
|
private static function build_search_querylet(array $tag_conditions, array $img_conditions): Querylet
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
|
|
||||||
$tag_querylets = [];
|
|
||||||
$img_querylets = [];
|
|
||||||
$positive_tag_count = 0;
|
$positive_tag_count = 0;
|
||||||
$negative_tag_count = 0;
|
$negative_tag_count = 0;
|
||||||
|
foreach ($tag_conditions as $tq) {
|
||||||
/*
|
if ($tq->positive) {
|
||||||
* Turn a bunch of strings into a bunch of TagQuerylet
|
|
||||||
* and ImgQuerylet objects
|
|
||||||
*/
|
|
||||||
$stpe = new SearchTermParseEvent(null, $terms);
|
|
||||||
send_event($stpe);
|
|
||||||
if ($stpe->is_querylet_set()) {
|
|
||||||
foreach ($stpe->get_querylets() as $querylet) {
|
|
||||||
$img_querylets[] = new ImgQuerylet($querylet, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($terms as $term) {
|
|
||||||
$positive = true;
|
|
||||||
if (is_string($term) && !empty($term) && ($term[0] == '-')) {
|
|
||||||
$positive = false;
|
|
||||||
$term = substr($term, 1);
|
|
||||||
}
|
|
||||||
if (strlen($term) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stpe = new SearchTermParseEvent($term, $terms);
|
|
||||||
send_event($stpe);
|
|
||||||
if ($stpe->is_querylet_set()) {
|
|
||||||
foreach ($stpe->get_querylets() as $querylet) {
|
|
||||||
$img_querylets[] = new ImgQuerylet($querylet, $positive);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if the whole match is wild, skip this;
|
|
||||||
// if not, translate into SQL
|
|
||||||
if (str_replace("*", "", $term) != "") {
|
|
||||||
$term = str_replace('_', '\_', $term);
|
|
||||||
$term = str_replace('%', '\%', $term);
|
|
||||||
$term = str_replace('*', '%', $term);
|
|
||||||
$tag_querylets[] = new TagQuerylet($term, $positive);
|
|
||||||
if ($positive) {
|
|
||||||
$positive_tag_count++;
|
$positive_tag_count++;
|
||||||
} else {
|
} else {
|
||||||
$negative_tag_count++;
|
$negative_tag_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Turn a bunch of Querylet objects into a base query
|
* Turn a bunch of Querylet objects into a base query
|
||||||
@ -888,41 +899,20 @@ class Image
|
|||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
// one positive tag (a common case), do an optimised search
|
|
||||||
elseif ($positive_tag_count === 1 && $negative_tag_count === 0) {
|
|
||||||
# "LIKE" to account for wildcards
|
|
||||||
$query = new Querylet($database->scoreql_to_sql("
|
|
||||||
SELECT *
|
|
||||||
FROM (
|
|
||||||
SELECT images.*
|
|
||||||
FROM images
|
|
||||||
JOIN image_tags ON images.id=image_tags.image_id
|
|
||||||
JOIN tags ON image_tags.tag_id=tags.id
|
|
||||||
WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag)
|
|
||||||
GROUP BY images.id
|
|
||||||
) AS images
|
|
||||||
WHERE 1=1
|
|
||||||
"), ["tag"=>$tag_querylets[0]->tag]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// more than one positive tag, or more than zero negative tags
|
// more than one positive tag, or more than zero negative tags
|
||||||
else {
|
else {
|
||||||
if ($database->get_driver_name() === "mysql") {
|
$query = Image::build_accurate_search_querylet($tag_conditions);
|
||||||
$query = Image::build_ugly_search_querylet($tag_querylets);
|
|
||||||
} else {
|
|
||||||
$query = Image::build_accurate_search_querylet($tag_querylets);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Merge all the image metadata searches into one generic querylet
|
* Merge all the image metadata searches into one generic querylet
|
||||||
* and append to the base querylet with "AND blah"
|
* and append to the base querylet with "AND blah"
|
||||||
*/
|
*/
|
||||||
if (!empty($img_querylets)) {
|
if (!empty($img_conditions)) {
|
||||||
$n = 0;
|
$n = 0;
|
||||||
$img_sql = "";
|
$img_sql = "";
|
||||||
$img_vars = [];
|
$img_vars = [];
|
||||||
foreach ($img_querylets as $iq) {
|
foreach ($img_conditions as $iq) {
|
||||||
if ($n++ > 0) {
|
if ($n++ > 0) {
|
||||||
$img_sql .= " AND";
|
$img_sql .= " AND";
|
||||||
}
|
}
|
||||||
@ -940,47 +930,30 @@ class Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WARNING: this description is no longer accurate, though it does get across
|
* #param TagQuerylet[] $tag_conditions
|
||||||
* the general idea - the actual method has a few extra optimisations
|
|
||||||
*
|
|
||||||
* "foo bar -baz user=foo" becomes
|
|
||||||
*
|
|
||||||
* SELECT * FROM images WHERE
|
|
||||||
* images.id IN (SELECT image_id FROM image_tags WHERE tag='foo')
|
|
||||||
* AND images.id IN (SELECT image_id FROM image_tags WHERE tag='bar')
|
|
||||||
* AND NOT images.id IN (SELECT image_id FROM image_tags WHERE tag='baz')
|
|
||||||
* AND images.id IN (SELECT id FROM images WHERE owner_name='foo')
|
|
||||||
*
|
|
||||||
* This is:
|
|
||||||
* A) Incredibly simple:
|
|
||||||
* Each search term maps to a list of image IDs
|
|
||||||
* B) Runs really fast on a good database:
|
|
||||||
* These lists are calculated once, and the set intersection taken
|
|
||||||
* C) Runs really slow on bad databases:
|
|
||||||
* All the subqueries are executed every time for every row in the
|
|
||||||
* images table. Yes, MySQL does suck this much.
|
|
||||||
*
|
|
||||||
* #param TagQuerylet[] $tag_querylets
|
|
||||||
*/
|
*/
|
||||||
private static function build_accurate_search_querylet(array $tag_querylets): Querylet
|
private static function build_accurate_search_querylet(array $tag_conditions): Querylet
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
|
|
||||||
$positive_tag_id_array = [];
|
$positive_tag_id_array = [];
|
||||||
|
$positive_wildcard_id_array = [];
|
||||||
$negative_tag_id_array = [];
|
$negative_tag_id_array = [];
|
||||||
|
|
||||||
foreach ($tag_querylets as $tq) {
|
foreach ($tag_conditions as $tq) {
|
||||||
$tag_ids = $database->get_col(
|
$tag_ids = $database->get_col(
|
||||||
$database->scoreql_to_sql("
|
$database->scoreql_to_sql("
|
||||||
SELECT id
|
SELECT id
|
||||||
FROM tags
|
FROM tags
|
||||||
WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag)
|
WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag)
|
||||||
"),
|
"),
|
||||||
["tag" => $tq->tag]
|
["tag" => Tag::sqlify($tq->tag)]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$tag_count = count($tag_ids);
|
||||||
|
|
||||||
if ($tq->positive) {
|
if ($tq->positive) {
|
||||||
$positive_tag_id_array = array_merge($positive_tag_id_array, $tag_ids);
|
if ($tag_count== 0) {
|
||||||
if (count($tag_ids) == 0) {
|
|
||||||
# one of the positive tags had zero results, therefor there
|
# one of the positive tags had zero results, therefor there
|
||||||
# can be no results; "where 1=0" should shortcut things
|
# can be no results; "where 1=0" should shortcut things
|
||||||
return new Querylet("
|
return new Querylet("
|
||||||
@ -988,115 +961,71 @@ class Image
|
|||||||
FROM images
|
FROM images
|
||||||
WHERE 1=0
|
WHERE 1=0
|
||||||
");
|
");
|
||||||
|
} elseif($tag_count==1) {
|
||||||
|
// All wildcard terms that qualify for a single tag can be treated the same as non-wildcards
|
||||||
|
$positive_tag_id_array[] = $tag_ids[0];
|
||||||
|
} else {
|
||||||
|
// Terms that resolve to multiple tags act as an OR within themselves
|
||||||
|
// and as an AND in relation to all other terms,
|
||||||
|
$positive_wildcard_id_array[] = $tag_ids;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Unlike positive criteria, negative criteria are all handled in an OR fashion,
|
||||||
|
// so we can just compile them all into a single sub-query.
|
||||||
$negative_tag_id_array = array_merge($negative_tag_id_array, $tag_ids);
|
$negative_tag_id_array = array_merge($negative_tag_id_array, $tag_ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert($positive_tag_id_array || $negative_tag_id_array, @$_GET['q']);
|
$sql = "";
|
||||||
$wheres = [];
|
assert($positive_tag_id_array || $positive_wildcard_id_array || $negative_tag_id_array, @$_GET['q']);
|
||||||
|
if(!empty($positive_tag_id_array) || !empty($positive_wildcard_id_array)) {
|
||||||
|
$inner_joins = [];
|
||||||
if (!empty($positive_tag_id_array)) {
|
if (!empty($positive_tag_id_array)) {
|
||||||
$positive_tag_id_list = join(', ', $positive_tag_id_array);
|
foreach($positive_tag_id_array as $tag) {
|
||||||
$wheres[] = "tag_id IN ($positive_tag_id_list)";
|
$inner_joins[] = "= $tag";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!empty($positive_wildcard_id_array)) {
|
||||||
|
foreach ($positive_wildcard_id_array as $tags) {
|
||||||
|
$positive_tag_id_list = join(', ', $tags);
|
||||||
|
$inner_joins[] = "IN ($positive_tag_id_list)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = array_shift($inner_joins);
|
||||||
|
$sub_query = "SELECT it.image_id FROM image_tags it ";
|
||||||
|
$i = 0;
|
||||||
|
foreach ($inner_joins as $inner_join) {
|
||||||
|
$i++;
|
||||||
|
$sub_query .= " INNER JOIN image_tags it$i ON it$i.image_id = it.image_id AND it$i.tag_id $inner_join ";
|
||||||
}
|
}
|
||||||
if(!empty($negative_tag_id_array)) {
|
if(!empty($negative_tag_id_array)) {
|
||||||
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
||||||
$wheres[] = "tag_id NOT IN ($negative_tag_id_list)";
|
$sub_query .= " LEFT JOIN image_tags negative ON negative.image_id = it.image_id AND negative.tag_id IN ($negative_tag_id_list) ";
|
||||||
}
|
}
|
||||||
$wheres_str = join(" AND ", $wheres);
|
$sub_query .= "WHERE it.tag_id $first ";
|
||||||
return new Querylet("
|
if(!empty($negative_tag_id_array)) {
|
||||||
|
$sub_query .= " AND negative.image_id IS NULL";
|
||||||
|
}
|
||||||
|
$sub_query .= " GROUP BY it.image_id ";
|
||||||
|
|
||||||
|
$sql = "
|
||||||
SELECT images.*
|
SELECT images.*
|
||||||
FROM images
|
FROM images INNER JOIN (
|
||||||
WHERE images.id IN (
|
$sub_query
|
||||||
SELECT image_id
|
) a on a.image_id = images.id
|
||||||
FROM image_tags
|
";
|
||||||
WHERE $wheres_str
|
} elseif(!empty($negative_tag_id_array)) {
|
||||||
GROUP BY image_id
|
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
||||||
HAVING COUNT(image_id) >= :search_score
|
$sql = "
|
||||||
)
|
|
||||||
", ["search_score"=>count($positive_tag_id_array)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this function exists because mysql is a turd, see the docs for
|
|
||||||
* build_accurate_search_querylet() for a full explanation
|
|
||||||
*
|
|
||||||
* #param TagQuerylet[] $tag_querylets
|
|
||||||
*/
|
|
||||||
private static function build_ugly_search_querylet(array $tag_querylets): Querylet
|
|
||||||
{
|
|
||||||
global $database;
|
|
||||||
|
|
||||||
$positive_tag_count = 0;
|
|
||||||
foreach ($tag_querylets as $tq) {
|
|
||||||
if ($tq->positive) {
|
|
||||||
$positive_tag_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only negative tags - shortcut to fail
|
|
||||||
if ($positive_tag_count == 0) {
|
|
||||||
// TODO: This isn't currently implemented.
|
|
||||||
// SEE: https://github.com/shish/shimmie2/issues/66
|
|
||||||
return new Querylet("
|
|
||||||
SELECT images.*
|
SELECT images.*
|
||||||
FROM images
|
FROM images LEFT JOIN image_tags negative ON negative.image_id = images.id AND negative.tag_id in ($negative_tag_id_list)
|
||||||
WHERE 1=0
|
WHERE negative.image_id IS NULL
|
||||||
");
|
";
|
||||||
|
} else {
|
||||||
|
throw new SCoreException("No criteria specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge all the tag querylets into one generic one
|
return new Querylet($sql);
|
||||||
$sql = "0";
|
|
||||||
$terms = [];
|
|
||||||
foreach ($tag_querylets as $tq) {
|
|
||||||
$sign = $tq->positive ? "+" : "-";
|
|
||||||
$sql .= ' '.$sign.' IF(SUM(tag LIKE :tag'.Image::$tag_n.'), 1, 0)';
|
|
||||||
$terms['tag'.Image::$tag_n] = $tq->tag;
|
|
||||||
Image::$tag_n++;
|
|
||||||
}
|
|
||||||
$tag_search = new Querylet($sql, $terms);
|
|
||||||
|
|
||||||
$tag_id_array = [];
|
|
||||||
|
|
||||||
foreach ($tag_querylets as $tq) {
|
|
||||||
$tag_ids = $database->get_col(
|
|
||||||
$database->scoreql_to_sql("
|
|
||||||
SELECT id
|
|
||||||
FROM tags
|
|
||||||
WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag)
|
|
||||||
"),
|
|
||||||
["tag" => $tq->tag]
|
|
||||||
);
|
|
||||||
$tag_id_array = array_merge($tag_id_array, $tag_ids);
|
|
||||||
|
|
||||||
if ($tq->positive && count($tag_ids) == 0) {
|
|
||||||
# one of the positive tags had zero results, therefor there
|
|
||||||
# can be no results; "where 1=0" should shortcut things
|
|
||||||
return new Querylet("
|
|
||||||
SELECT images.*
|
|
||||||
FROM images
|
|
||||||
WHERE 1=0
|
|
||||||
");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Image::$tag_n = 0;
|
|
||||||
return new Querylet('
|
|
||||||
SELECT *
|
|
||||||
FROM (
|
|
||||||
SELECT images.*, ('.$tag_search->sql.') AS score
|
|
||||||
FROM images
|
|
||||||
LEFT JOIN image_tags ON image_tags.image_id = images.id
|
|
||||||
JOIN tags ON image_tags.tag_id = tags.id
|
|
||||||
WHERE tags.id IN (' . join(', ', $tag_id_array) . ')
|
|
||||||
GROUP BY images.id
|
|
||||||
HAVING score = :score
|
|
||||||
) AS images
|
|
||||||
WHERE 1=1
|
|
||||||
', array_merge(
|
|
||||||
$tag_search->variables,
|
|
||||||
["score"=>$positive_tag_count]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,12 @@
|
|||||||
* Move a file from PHP's temporary area into shimmie's image storage
|
* Move a file from PHP's temporary area into shimmie's image storage
|
||||||
* hierarchy, or throw an exception trying.
|
* hierarchy, or throw an exception trying.
|
||||||
*
|
*
|
||||||
|
* @param DataUploadEvent $event
|
||||||
* @throws UploadException
|
* @throws UploadException
|
||||||
*/
|
*/
|
||||||
function move_upload_to_archive(DataUploadEvent $event): void
|
function move_upload_to_archive(DataUploadEvent $event): void
|
||||||
{
|
{
|
||||||
$target = warehouse_path("images", $event->hash);
|
$target = warehouse_path(Image::IMAGE_DIR, $event->hash);
|
||||||
if (!@copy($event->tmpname, $target)) {
|
if (!@copy($event->tmpname, $target)) {
|
||||||
$errors = error_get_last();
|
$errors = error_get_last();
|
||||||
throw new UploadException(
|
throw new UploadException(
|
||||||
@ -24,7 +25,8 @@ function move_upload_to_archive(DataUploadEvent $event): void
|
|||||||
/**
|
/**
|
||||||
* Add a directory full of images
|
* Add a directory full of images
|
||||||
*
|
*
|
||||||
* #return string[]
|
* @param string $base
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
function add_dir(string $base): array
|
function add_dir(string $base): array
|
||||||
{
|
{
|
||||||
@ -48,6 +50,14 @@ function add_dir(string $base): array
|
|||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DataUploadEvent for a file.
|
||||||
|
*
|
||||||
|
* @param string $tmpname
|
||||||
|
* @param string $filename
|
||||||
|
* @param string $tags
|
||||||
|
* @throws UploadException
|
||||||
|
*/
|
||||||
function add_image(string $tmpname, string $filename, string $tags): void
|
function add_image(string $tmpname, string $filename, string $tags): void
|
||||||
{
|
{
|
||||||
assert(file_exists($tmpname));
|
assert(file_exists($tmpname));
|
||||||
@ -65,10 +75,15 @@ function add_image(string $tmpname, string $filename, string $tags): void
|
|||||||
send_event($event);
|
send_event($event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
function get_extension_from_mime(String $file_path): ?String
|
* Gets an the extension defined in MIME_TYPE_MAP for a file.
|
||||||
|
*
|
||||||
|
* @param String $file_path
|
||||||
|
* @return String The extension that was found.
|
||||||
|
* @throws UploadException if the mimetype could not be determined, or if an extension for hte mimetype could not be found.
|
||||||
|
*/
|
||||||
|
function get_extension_from_mime(String $file_path): String
|
||||||
{
|
{
|
||||||
global $config;
|
|
||||||
$mime = mime_content_type($file_path);
|
$mime = mime_content_type($file_path);
|
||||||
if (!empty($mime)) {
|
if (!empty($mime)) {
|
||||||
$ext = get_extension($mime);
|
$ext = get_extension($mime);
|
||||||
@ -80,14 +95,17 @@ function get_extension_from_mime(String $file_path): ?String
|
|||||||
throw new UploadException("Could not determine file mime type: ".$file_path);
|
throw new UploadException("Could not determine file mime type: ".$file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a full size pair of dimensions, return a pair scaled down to fit
|
* Given a full size pair of dimensions, return a pair scaled down to fit
|
||||||
* into the configured thumbnail square, with ratio intact
|
* into the configured thumbnail square, with ratio intact.
|
||||||
|
* Optionally uses the High-DPI scaling setting to adjust the final resolution.
|
||||||
*
|
*
|
||||||
* #return int[]
|
* @param int $orig_width
|
||||||
|
* @param int $orig_height
|
||||||
|
* @param bool $use_dpi_scaling Enables the High-DPI scaling.
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
function get_thumbnail_size(int $orig_width, int $orig_height): array
|
function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_scaling = false): array
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
@ -105,363 +123,97 @@ function get_thumbnail_size(int $orig_width, int $orig_height): array
|
|||||||
$orig_height = $orig_width * 5;
|
$orig_height = $orig_width * 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
$max_width = $config->get_int('thumb_width');
|
|
||||||
$max_height = $config->get_int('thumb_height');
|
|
||||||
|
|
||||||
$xscale = ($max_height / $orig_height);
|
if($use_dpi_scaling) {
|
||||||
$yscale = ($max_width / $orig_width);
|
list($max_width, $max_height) = get_thumbnail_max_size_scaled();
|
||||||
$scale = ($xscale < $yscale) ? $xscale : $yscale;
|
} else {
|
||||||
|
$max_width = $config->get_int(ImageConfig::THUMB_WIDTH);
|
||||||
|
$max_height = $config->get_int(ImageConfig::THUMB_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
if ($scale > 1 && $config->get_bool('thumb_upscale')) {
|
$output = get_scaled_by_aspect_ratio($orig_width, $orig_height, $max_width, $max_height);
|
||||||
|
|
||||||
|
if ($output[2] > 1 && $config->get_bool('thumb_upscale')) {
|
||||||
return [(int)$orig_width, (int)$orig_height];
|
return [(int)$orig_width, (int)$orig_height];
|
||||||
} else {
|
} else {
|
||||||
return [(int)($orig_width*$scale), (int)($orig_height*$scale)];
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_scaled_by_aspect_ratio(int $original_width, int $original_height, int $max_width, int $max_height) : array
|
||||||
|
{
|
||||||
|
$xscale = ($max_width/ $original_width);
|
||||||
|
$yscale = ($max_height/ $original_height);
|
||||||
|
|
||||||
|
$scale = ($yscale < $xscale) ? $yscale : $xscale ;
|
||||||
|
|
||||||
|
return [(int)($original_width*$scale), (int)($original_height*$scale), $scale];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a full size pair of dimensions, return a pair scaled down to fit
|
* Fetches the thumbnails height and width settings and applies the High-DPI scaling setting before returning the dimensions.
|
||||||
* into the configured thumbnail square, with ratio intact, using thumb_scaling
|
|
||||||
*
|
*
|
||||||
* #return int[]
|
* @return array [width, height]
|
||||||
*/
|
*/
|
||||||
function get_thumbnail_size_scaled(int $orig_width, int $orig_height): array
|
|
||||||
{
|
|
||||||
global $config;
|
|
||||||
|
|
||||||
if ($orig_width === 0) {
|
|
||||||
$orig_width = 192;
|
|
||||||
}
|
|
||||||
if ($orig_height === 0) {
|
|
||||||
$orig_height = 192;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($orig_width > $orig_height * 5) {
|
|
||||||
$orig_width = $orig_height * 5;
|
|
||||||
}
|
|
||||||
if ($orig_height > $orig_width * 5) {
|
|
||||||
$orig_height = $orig_width * 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
$max_size = get_thumbnail_max_size_scaled();
|
|
||||||
$max_width = $max_size[0];
|
|
||||||
$max_height = $max_size[1];
|
|
||||||
|
|
||||||
$xscale = ($max_height / $orig_height);
|
|
||||||
$yscale = ($max_width / $orig_width);
|
|
||||||
$scale = ($xscale < $yscale) ? $xscale : $yscale;
|
|
||||||
|
|
||||||
if ($scale > 1 && $config->get_bool('thumb_upscale')) {
|
|
||||||
return [(int)$orig_width, (int)$orig_height];
|
|
||||||
} else {
|
|
||||||
return [(int)($orig_width*$scale), (int)($orig_height*$scale)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_thumbnail_max_size_scaled(): array
|
function get_thumbnail_max_size_scaled(): array
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$scaling = $config->get_int("thumb_scaling");
|
$scaling = $config->get_int(ImageConfig::THUMB_SCALING);
|
||||||
$max_width = $config->get_int('thumb_width') * ($scaling/100);
|
$max_width = $config->get_int(ImageConfig::THUMB_WIDTH) * ($scaling/100);
|
||||||
$max_height = $config->get_int('thumb_height') * ($scaling/100);
|
$max_height = $config->get_int(ImageConfig::THUMB_HEIGHT) * ($scaling/100);
|
||||||
return [$max_width, $max_height];
|
return [$max_width, $max_height];
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_thumbnail_convert($hash): bool
|
|
||||||
{
|
function create_image_thumb(string $hash, string $type, string $engine = null) {
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$inname = warehouse_path("images", $hash);
|
$inname = warehouse_path(Image::IMAGE_DIR, $hash);
|
||||||
$outname = warehouse_path("thumbs", $hash);
|
$outname = warehouse_path(Image::THUMBNAIL_DIR, $hash);
|
||||||
|
|
||||||
$q = $config->get_int("thumb_quality");
|
|
||||||
$convert = $config->get_string("thumb_convert_path");
|
|
||||||
|
|
||||||
if ($convert==null||$convert=="") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ffff imagemagick fails sometimes, not sure why
|
|
||||||
//$format = "'%s' '%s[0]' -format '%%[fx:w] %%[fx:h]' info:";
|
|
||||||
//$cmd = sprintf($format, $convert, $inname);
|
|
||||||
//$size = shell_exec($cmd);
|
|
||||||
//$size = explode(" ", trim($size));
|
|
||||||
$tsize = get_thumbnail_max_size_scaled();
|
$tsize = get_thumbnail_max_size_scaled();
|
||||||
$w = $tsize[0];
|
|
||||||
$h = $tsize[1];
|
|
||||||
|
|
||||||
|
if(empty($engine)) {
|
||||||
// running the call with cmd.exe requires quoting for our paths
|
$engine = $config->get_string(ImageConfig::THUMB_ENGINE);
|
||||||
$type = $config->get_string('thumb_type');
|
|
||||||
|
|
||||||
$options = "";
|
|
||||||
if (!$config->get_bool('thumb_upscale')) {
|
|
||||||
$options .= "\>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$bg = "black";
|
$output_format = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||||
if ($type=="webp") {
|
if($output_format=="webp") {
|
||||||
$bg = "none";
|
$output_format = Media::WEBP_LOSSY;
|
||||||
}
|
|
||||||
$format = '"%s" -flatten -strip -thumbnail %ux%u%s -quality %u -background %s "%s[0]" %s:"%s"';
|
|
||||||
|
|
||||||
$cmd = sprintf($format, $convert, $w, $h, $options, $q, $bg, $inname, $type, $outname);
|
|
||||||
$cmd = str_replace("\"convert\"", "convert", $cmd); // quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27
|
|
||||||
exec($cmd, $output, $ret);
|
|
||||||
|
|
||||||
log_debug('handle_pixel', "Generating thumbnail with command `$cmd`, returns $ret");
|
|
||||||
|
|
||||||
if ($config->get_bool("thumb_optim", false)) {
|
|
||||||
exec("jpegoptim $outname", $output, $ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
send_event(new MediaResizeEvent(
|
||||||
|
$engine,
|
||||||
|
$inname,
|
||||||
|
$type,
|
||||||
|
$outname,
|
||||||
|
$tsize[0],
|
||||||
|
$tsize[1],
|
||||||
|
false,
|
||||||
|
$output_format,
|
||||||
|
$config->get_int(ImageConfig::THUMB_QUALITY),
|
||||||
|
true,
|
||||||
|
$config->get_bool('thumb_upscale', false)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_thumbnail_ffmpeg($hash): bool
|
|
||||||
|
const TIME_UNITS = ["s"=>60,"m"=>60,"h"=>24,"d"=>365,"y"=>PHP_INT_MAX];
|
||||||
|
function format_milliseconds(int $input): string
|
||||||
{
|
{
|
||||||
global $config;
|
$output = "";
|
||||||
|
|
||||||
$ffmpeg = $config->get_string("thumb_ffmpeg_path");
|
$remainder = floor($input / 1000);
|
||||||
if ($ffmpeg==null||$ffmpeg=="") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$inname = warehouse_path("images", $hash);
|
foreach (TIME_UNITS AS $unit=>$conversion) {
|
||||||
$outname = warehouse_path("thumbs", $hash);
|
$count = $remainder % $conversion;
|
||||||
|
$remainder = floor($remainder / $conversion);
|
||||||
$orig_size = video_size($inname);
|
if($count==0&&$remainder<1) {
|
||||||
$scaled_size = get_thumbnail_size_scaled($orig_size[0], $orig_size[1]);
|
|
||||||
|
|
||||||
$codec = "mjpeg";
|
|
||||||
$quality = $config->get_int("thumb_quality");
|
|
||||||
if ($config->get_string("thumb_type")=="webp") {
|
|
||||||
$codec = "libwebp";
|
|
||||||
} else {
|
|
||||||
// mjpeg quality ranges from 2-31, with 2 being the best quality.
|
|
||||||
$quality = floor(31 - (31 * ($quality/100)));
|
|
||||||
if ($quality<2) {
|
|
||||||
$quality = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$args = [
|
|
||||||
escapeshellarg($ffmpeg),
|
|
||||||
"-y", "-i", escapeshellarg($inname),
|
|
||||||
"-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}",
|
|
||||||
"-f", "image2",
|
|
||||||
"-vframes", "1",
|
|
||||||
"-c:v", $codec,
|
|
||||||
"-q:v", $quality,
|
|
||||||
escapeshellarg($outname),
|
|
||||||
];
|
|
||||||
|
|
||||||
$cmd = escapeshellcmd(implode(" ", $args));
|
|
||||||
|
|
||||||
exec($cmd, $output, $ret);
|
|
||||||
|
|
||||||
if ((int)$ret == (int)0) {
|
|
||||||
log_debug('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
log_error('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function video_size(string $filename): array
|
|
||||||
{
|
|
||||||
global $config;
|
|
||||||
$ffmpeg = $config->get_string("thumb_ffmpeg_path");
|
|
||||||
$cmd = escapeshellcmd(implode(" ", [
|
|
||||||
escapeshellarg($ffmpeg),
|
|
||||||
"-y", "-i", escapeshellarg($filename),
|
|
||||||
"-vstats"
|
|
||||||
]));
|
|
||||||
$output = shell_exec($cmd . " 2>&1");
|
|
||||||
// error_log("Getting size with `$cmd`");
|
|
||||||
|
|
||||||
$regex_sizes = "/Video: .* ([0-9]{1,4})x([0-9]{1,4})/";
|
|
||||||
if (preg_match($regex_sizes, $output, $regs)) {
|
|
||||||
if (preg_match("/displaymatrix: rotation of (90|270).00 degrees/", $output)) {
|
|
||||||
$size = [$regs[2], $regs[1]];
|
|
||||||
} else {
|
|
||||||
$size = [$regs[1], $regs[2]];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$size = [1, 1];
|
|
||||||
}
|
|
||||||
log_debug('imageboard/misc', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]");
|
|
||||||
return $size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check Memory usage limits
|
|
||||||
*
|
|
||||||
* Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024);
|
|
||||||
* New check: $memory_use = $width * $height * ($bits_per_channel) * channels * 2.5
|
|
||||||
*
|
|
||||||
* It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4)
|
|
||||||
* We need to consider the size that we are GOING TO instead.
|
|
||||||
*
|
|
||||||
* The factor of 2.5 is simply a rough guideline.
|
|
||||||
* http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize
|
|
||||||
*/
|
|
||||||
function calc_memory_use(array $info): int
|
|
||||||
{
|
|
||||||
if (isset($info['bits']) && isset($info['channels'])) {
|
|
||||||
$memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024;
|
|
||||||
} else {
|
|
||||||
// If we don't have bits and channel info from the image then assume default values
|
|
||||||
// of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color
|
|
||||||
$memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024;
|
|
||||||
}
|
|
||||||
return (int)$memory_use;
|
|
||||||
}
|
|
||||||
|
|
||||||
function image_resize_gd(
|
|
||||||
String $image_filename,
|
|
||||||
array $info,
|
|
||||||
int $new_width,
|
|
||||||
int $new_height,
|
|
||||||
string $output_filename=null,
|
|
||||||
string $output_type=null,
|
|
||||||
int $output_quality = 80
|
|
||||||
) {
|
|
||||||
$width = $info[0];
|
|
||||||
$height = $info[1];
|
|
||||||
|
|
||||||
if ($output_type==null) {
|
|
||||||
/* If not specified, output to the same format as the original image */
|
|
||||||
switch ($info[2]) {
|
|
||||||
case IMAGETYPE_GIF: $output_type = "gif"; break;
|
|
||||||
case IMAGETYPE_JPEG: $output_type = "jpeg"; break;
|
|
||||||
case IMAGETYPE_PNG: $output_type = "png"; break;
|
|
||||||
case IMAGETYPE_WEBP: $output_type = "webp"; break;
|
|
||||||
case IMAGETYPE_BMP: $output_type = "bmp"; break;
|
|
||||||
default: throw new ImageResizeException("Failed to save the new image - Unsupported image type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$memory_use = calc_memory_use($info);
|
|
||||||
$memory_limit = get_memory_limit();
|
|
||||||
if ($memory_use > $memory_limit) {
|
|
||||||
throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)");
|
|
||||||
}
|
|
||||||
|
|
||||||
$image = imagecreatefromstring(file_get_contents($image_filename));
|
|
||||||
$image_resized = imagecreatetruecolor($new_width, $new_height);
|
|
||||||
try {
|
|
||||||
if ($image===false) {
|
|
||||||
throw new ImageResizeException("Could not load image: ".$image_filename);
|
|
||||||
}
|
|
||||||
if ($image_resized===false) {
|
|
||||||
throw new ImageResizeException("Could not create output image with dimensions $new_width c $new_height ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle transparent images
|
|
||||||
switch ($info[2]) {
|
|
||||||
case IMAGETYPE_GIF:
|
|
||||||
$transparency = imagecolortransparent($image);
|
|
||||||
$palletsize = imagecolorstotal($image);
|
|
||||||
|
|
||||||
// If we have a specific transparent color
|
|
||||||
if ($transparency >= 0 && $transparency < $palletsize) {
|
|
||||||
// Get the original image's transparent color's RGB values
|
|
||||||
$transparent_color = imagecolorsforindex($image, $transparency);
|
|
||||||
|
|
||||||
// Allocate the same color in the new image resource
|
|
||||||
$transparency = imagecolorallocate($image_resized, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
|
|
||||||
if ($transparency===false) {
|
|
||||||
throw new ImageResizeException("Unable to allocate transparent color");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Completely fill the background of the new image with allocated color.
|
|
||||||
if (imagefill($image_resized, 0, 0, $transparency)===false) {
|
|
||||||
throw new ImageResizeException("Unable to fill new image with transparent color");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the background color for new image to transparent
|
|
||||||
imagecolortransparent($image_resized, $transparency);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IMAGETYPE_PNG:
|
|
||||||
case IMAGETYPE_WEBP:
|
|
||||||
//
|
|
||||||
// More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
|
|
||||||
//
|
|
||||||
if (imagealphablending($image_resized, false)===false) {
|
|
||||||
throw new ImageResizeException("Unable to disable image alpha blending");
|
|
||||||
}
|
|
||||||
if (imagesavealpha($image_resized, true)===false) {
|
|
||||||
throw new ImageResizeException("Unable to enable image save alpha");
|
|
||||||
}
|
|
||||||
$transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127);
|
|
||||||
if ($transparent_color===false) {
|
|
||||||
throw new ImageResizeException("Unable to allocate transparent color");
|
|
||||||
}
|
|
||||||
if (imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color)===false) {
|
|
||||||
throw new ImageResizeException("Unable to fill new image with transparent color");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
$output = "$count".$unit." ".$output;
|
||||||
// Actually resize the image.
|
|
||||||
if (imagecopyresampled(
|
|
||||||
$image_resized,
|
|
||||||
$image,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
$new_width,
|
|
||||||
$new_height,
|
|
||||||
$width,
|
|
||||||
$height
|
|
||||||
)===false) {
|
|
||||||
throw new ImageResizeException("Unable to copy resized image data to new image");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = false;
|
return trim($output);
|
||||||
switch ($output_type) {
|
|
||||||
case "bmp":
|
|
||||||
$result = imagebmp($image_resized, $output_filename, true);
|
|
||||||
break;
|
|
||||||
case "webp":
|
|
||||||
$result = imagewebp($image_resized, $output_filename, $output_quality);
|
|
||||||
break;
|
|
||||||
case "jpg":
|
|
||||||
case "jpeg":
|
|
||||||
$result = imagejpeg($image_resized, $output_filename, $output_quality);
|
|
||||||
break;
|
|
||||||
case "png":
|
|
||||||
$result = imagepng($image_resized, $output_filename, 9);
|
|
||||||
break;
|
|
||||||
case "gif":
|
|
||||||
$result = imagegif($image_resized, $output_filename);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ImageResizeException("Failed to save the new image - Unsupported image type: $output_type");
|
|
||||||
}
|
|
||||||
if ($result==false) {
|
|
||||||
throw new ImageResizeException("Failed to save the new image, function returned false when saving type: $output_type");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
imagedestroy($image);
|
|
||||||
imagedestroy($image_resized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function is_animated_gif(String $image_filename)
|
|
||||||
{
|
|
||||||
$isanigif = 0;
|
|
||||||
if (($fh = @fopen($image_filename, 'rb'))) {
|
|
||||||
//check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473)
|
|
||||||
while (!feof($fh) && $isanigif < 2) {
|
|
||||||
$chunk = fread($fh, 1024 * 100);
|
|
||||||
$isanigif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ($isanigif == 0);
|
|
||||||
}
|
}
|
@ -29,7 +29,7 @@ class Querylet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TagQuerylet
|
class TagCondition
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $tag;
|
public $tag;
|
||||||
@ -43,7 +43,7 @@ class TagQuerylet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImgQuerylet
|
class ImgCondition
|
||||||
{
|
{
|
||||||
/** @var Querylet */
|
/** @var Querylet */
|
||||||
public $qlet;
|
public $qlet;
|
||||||
|
@ -100,4 +100,12 @@ class Tag
|
|||||||
|
|
||||||
return $tag_array;
|
return $tag_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function sqlify(string $term): string
|
||||||
|
{
|
||||||
|
$term = str_replace('_', '\_', $term);
|
||||||
|
$term = str_replace('%', '\%', $term);
|
||||||
|
$term = str_replace('*', '%', $term);
|
||||||
|
return $term;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
245
core/page.php
245
core/page.php
@ -26,6 +26,13 @@
|
|||||||
* Various other common functions are available as part of the Themelet class.
|
* Various other common functions are available as part of the Themelet class.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
abstract class PageMode
|
||||||
|
{
|
||||||
|
const REDIRECT = 'redirect';
|
||||||
|
const DATA = 'data';
|
||||||
|
const PAGE = 'page';
|
||||||
|
const FILE = 'file';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Page
|
* Class Page
|
||||||
@ -40,7 +47,7 @@ class Page
|
|||||||
/** @name Overall */
|
/** @name Overall */
|
||||||
//@{
|
//@{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $mode = "page";
|
public $mode = PageMode::PAGE;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $type = "text/html; charset=utf-8";
|
public $type = "text/html; charset=utf-8";
|
||||||
|
|
||||||
@ -69,9 +76,14 @@ class Page
|
|||||||
/** @var string; public only for unit test */
|
/** @var string; public only for unit test */
|
||||||
public $data = "";
|
public $data = "";
|
||||||
|
|
||||||
|
/** @var string; */
|
||||||
|
public $file = null;
|
||||||
|
|
||||||
/** @var string; public only for unit test */
|
/** @var string; public only for unit test */
|
||||||
public $filename = null;
|
public $filename = null;
|
||||||
|
|
||||||
|
private $disposition = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the raw data to be sent.
|
* Set the raw data to be sent.
|
||||||
*/
|
*/
|
||||||
@ -80,12 +92,18 @@ class Page
|
|||||||
$this->data = $data;
|
$this->data = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function set_file(string $file): void
|
||||||
|
{
|
||||||
|
$this->file = $file;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the recommended download filename.
|
* Set the recommended download filename.
|
||||||
*/
|
*/
|
||||||
public function set_filename(string $filename): void
|
public function set_filename(string $filename, string $disposition = "attachment"): void
|
||||||
{
|
{
|
||||||
$this->filename = $filename;
|
$this->filename = $filename;
|
||||||
|
$this->disposition = $disposition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -261,7 +279,7 @@ class Page
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch ($this->mode) {
|
switch ($this->mode) {
|
||||||
case "page":
|
case PageMode::PAGE:
|
||||||
if (CACHE_HTTP) {
|
if (CACHE_HTTP) {
|
||||||
header("Vary: Cookie, Accept-Encoding");
|
header("Vary: Cookie, Accept-Encoding");
|
||||||
if ($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") {
|
if ($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") {
|
||||||
@ -281,18 +299,133 @@ class Page
|
|||||||
$this->add_cookie("flash_message", "", -1, "/");
|
$this->add_cookie("flash_message", "", -1, "/");
|
||||||
}
|
}
|
||||||
usort($this->blocks, "blockcmp");
|
usort($this->blocks, "blockcmp");
|
||||||
|
$pnbe = new PageNavBuildingEvent();
|
||||||
|
send_event($pnbe);
|
||||||
|
|
||||||
|
$nav_links = $pnbe->links;
|
||||||
|
|
||||||
|
$active_link = null;
|
||||||
|
// To save on event calls, we check if one of the top-level links has already been marked as active
|
||||||
|
foreach ($nav_links as $link) {
|
||||||
|
if($link->active===true) {
|
||||||
|
$active_link = $link;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$sub_links = null;
|
||||||
|
// If one is, we just query for sub-menu options under that one tab
|
||||||
|
if($active_link!==null) {
|
||||||
|
$psnbe = new PageSubNavBuildingEvent($active_link->name);
|
||||||
|
send_event($psnbe);
|
||||||
|
$sub_links = $psnbe->links;
|
||||||
|
} else {
|
||||||
|
// Otherwise we query for the sub-items under each of the tabs
|
||||||
|
foreach ($nav_links as $link) {
|
||||||
|
$psnbe = new PageSubNavBuildingEvent($link->name);
|
||||||
|
send_event($psnbe);
|
||||||
|
|
||||||
|
// Now we check for a current link so we can identify the sub-links to show
|
||||||
|
foreach ($psnbe->links as $sub_link) {
|
||||||
|
if($sub_link->active===true) {
|
||||||
|
$sub_links = $psnbe->links;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the active link has been detected, we break out
|
||||||
|
if($sub_links!==null) {
|
||||||
|
$link->active = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$sub_links = $sub_links??[];
|
||||||
|
usort($nav_links, "sort_nav_links");
|
||||||
|
usort($sub_links, "sort_nav_links");
|
||||||
|
|
||||||
$this->add_auto_html_headers();
|
$this->add_auto_html_headers();
|
||||||
$layout = new Layout();
|
$layout = new Layout();
|
||||||
$layout->display_page($page);
|
$layout->display_page($page, $nav_links, $sub_links);
|
||||||
break;
|
break;
|
||||||
case "data":
|
case PageMode::DATA:
|
||||||
header("Content-Length: " . strlen($this->data));
|
header("Content-Length: " . strlen($this->data));
|
||||||
if (!is_null($this->filename)) {
|
if (!is_null($this->filename)) {
|
||||||
header('Content-Disposition: attachment; filename='.$this->filename);
|
header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
|
||||||
}
|
}
|
||||||
print $this->data;
|
print $this->data;
|
||||||
break;
|
break;
|
||||||
case "redirect":
|
case PageMode::FILE:
|
||||||
|
if (!is_null($this->filename)) {
|
||||||
|
header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://gist.github.com/codler/3906826
|
||||||
|
|
||||||
|
$size = filesize($this->file); // File size
|
||||||
|
$length = $size; // Content length
|
||||||
|
$start = 0; // Start byte
|
||||||
|
$end = $size - 1; // End byte
|
||||||
|
|
||||||
|
header("Content-Length: " . strlen($size));
|
||||||
|
header('Accept-Ranges: bytes');
|
||||||
|
|
||||||
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||||
|
|
||||||
|
$c_start = $start;
|
||||||
|
$c_end = $end;
|
||||||
|
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
|
||||||
|
if (strpos($range, ',') !== false) {
|
||||||
|
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||||||
|
header("Content-Range: bytes $start-$end/$size");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($range == '-') {
|
||||||
|
$c_start = $size - substr($range, 1);
|
||||||
|
} else {
|
||||||
|
$range = explode('-', $range);
|
||||||
|
$c_start = $range[0];
|
||||||
|
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
|
||||||
|
}
|
||||||
|
$c_end = ($c_end > $end) ? $end : $c_end;
|
||||||
|
if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
|
||||||
|
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||||||
|
header("Content-Range: bytes $start-$end/$size");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$start = $c_start;
|
||||||
|
$end = $c_end;
|
||||||
|
$length = $end - $start + 1;
|
||||||
|
header('HTTP/1.1 206 Partial Content');
|
||||||
|
}
|
||||||
|
header("Content-Range: bytes $start-$end/$size");
|
||||||
|
header("Content-Length: " . $length);
|
||||||
|
|
||||||
|
|
||||||
|
$fp = fopen($this->file, 'r');
|
||||||
|
try {
|
||||||
|
fseek($fp, $start);
|
||||||
|
$buffer = 1024 * 64;
|
||||||
|
while (!feof($fp) && ($p = ftell($fp)) <= $end) {
|
||||||
|
if ($p + $buffer > $end) {
|
||||||
|
$buffer = $end - $p + 1;
|
||||||
|
}
|
||||||
|
set_time_limit(0);
|
||||||
|
echo fread($fp, $buffer);
|
||||||
|
flush();
|
||||||
|
|
||||||
|
// After flush, we can tell if the client browser has disconnected.
|
||||||
|
// This means we can start sending a large file, and if we detect they disappeared
|
||||||
|
// then we can just stop and not waste any more resources or bandwidth.
|
||||||
|
if (connection_status() != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PageMode::REDIRECT:
|
||||||
header('Location: ' . $this->redirect);
|
header('Location: ' . $this->redirect);
|
||||||
print 'You should be redirected to <a href="' . $this->redirect . '">' . $this->redirect . '</a>';
|
print 'You should be redirected to <a href="' . $this->redirect . '">' . $this->redirect . '</a>';
|
||||||
break;
|
break;
|
||||||
@ -318,7 +451,7 @@ class Page
|
|||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$data_href = get_base_href();
|
$data_href = get_base_href();
|
||||||
$theme_name = $config->get_string('theme', 'default');
|
$theme_name = $config->get_string(SetupConfig::THEME, 'default');
|
||||||
|
|
||||||
$this->add_html_header("<script type='text/javascript'>base_href = '$data_href';</script>", 40);
|
$this->add_html_header("<script type='text/javascript'>base_href = '$data_href';</script>", 40);
|
||||||
|
|
||||||
@ -384,3 +517,99 @@ class Page
|
|||||||
$this->add_html_header("<script src='$data_href/$js_cache_file' type='text/javascript'></script>", 44);
|
$this->add_html_header("<script src='$data_href/$js_cache_file' type='text/javascript'></script>", 44);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PageNavBuildingEvent extends Event
|
||||||
|
{
|
||||||
|
public $links = [];
|
||||||
|
|
||||||
|
public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
|
||||||
|
{
|
||||||
|
$this->links[] = new NavLink($name, $link, $desc, $active, $order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageSubNavBuildingEvent extends Event
|
||||||
|
{
|
||||||
|
public $parent;
|
||||||
|
|
||||||
|
public $links = [];
|
||||||
|
|
||||||
|
public function __construct(string $parent)
|
||||||
|
{
|
||||||
|
$this->parent= $parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
|
||||||
|
{
|
||||||
|
$this->links[] = new NavLink($name, $link, $desc, $active,$order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NavLink
|
||||||
|
{
|
||||||
|
public $name;
|
||||||
|
public $link;
|
||||||
|
public $description;
|
||||||
|
public $order;
|
||||||
|
public $active = false;
|
||||||
|
|
||||||
|
public function __construct(String $name, Link $link, String $description, ?bool $active = null, int $order = 50)
|
||||||
|
{
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
$this->name = $name;
|
||||||
|
$this->link = $link;
|
||||||
|
$this->description = $description;
|
||||||
|
$this->order = $order;
|
||||||
|
if($active==null) {
|
||||||
|
$query = ltrim(_get_query(), "/");
|
||||||
|
if ($query === "") {
|
||||||
|
// This indicates the front page, so we check what's set as the front page
|
||||||
|
$front_page = trim($config->get_string(SetupConfig::FRONT_PAGE), "/");
|
||||||
|
|
||||||
|
if ($front_page === $link->page) {
|
||||||
|
$this->active = true;
|
||||||
|
} else {
|
||||||
|
$this->active = self::is_active([$link->page], $front_page);
|
||||||
|
}
|
||||||
|
} elseif($query===$link->page) {
|
||||||
|
$this->active = true;
|
||||||
|
}else {
|
||||||
|
$this->active = self::is_active([$link->page]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->active = $active;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function is_active(array $pages_matched, string $url = null): bool
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.)
|
||||||
|
*/
|
||||||
|
$url = $url??ltrim(_get_query(), "/");
|
||||||
|
|
||||||
|
$re1='.*?';
|
||||||
|
$re2='((?:[a-z][a-z_]+))';
|
||||||
|
|
||||||
|
if (preg_match_all("/".$re1.$re2."/is", $url, $matches)) {
|
||||||
|
$url=$matches[1][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$count_pages_matched = count($pages_matched);
|
||||||
|
|
||||||
|
for ($i=0; $i < $count_pages_matched; $i++) {
|
||||||
|
if ($url == $pages_matched[$i]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort_nav_links(NavLink $a, NavLink $b)
|
||||||
|
{
|
||||||
|
return $a->order - $b->order;
|
||||||
|
}
|
||||||
|
70
core/permissions.php
Normal file
70
core/permissions.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class Permissions
|
||||||
|
{
|
||||||
|
public const CHANGE_SETTING = "change_setting"; # modify web-level settings, eg the config table
|
||||||
|
public const OVERRIDE_CONFIG = "override_config"; # modify sys-level settings, eg shimmie.conf.php
|
||||||
|
public const BIG_SEARCH = "big_search"; # search for more than 3 tags at once (speed mode only)
|
||||||
|
|
||||||
|
public const MANAGE_EXTENSION_LIST = "manage_extension_list";
|
||||||
|
public const MANAGE_ALIAS_LIST = "manage_alias_list";
|
||||||
|
public const MASS_TAG_EDIT = "mass_tag_edit";
|
||||||
|
|
||||||
|
public const VIEW_IP = "view_ip"; # view IP addresses associated with things
|
||||||
|
public const BAN_IP = "ban_ip";
|
||||||
|
|
||||||
|
public const EDIT_USER_NAME = "edit_user_name";
|
||||||
|
public const EDIT_USER_PASSWORD = "edit_user_password";
|
||||||
|
public const EDIT_USER_INFO = "edit_user_info"; # email address, etc
|
||||||
|
public const EDIT_USER_CLASS = "edit_user_class";
|
||||||
|
public const DELETE_USER = "delete_user";
|
||||||
|
|
||||||
|
public const CREATE_COMMENT = "create_comment";
|
||||||
|
public const DELETE_COMMENT = "delete_comment";
|
||||||
|
public const BYPASS_COMMENT_CHECKS = "bypass_comment_checks"; # spam etc
|
||||||
|
|
||||||
|
public const REPLACE_IMAGE = "replace_image";
|
||||||
|
public const CREATE_IMAGE = "create_image";
|
||||||
|
public const EDIT_IMAGE_TAG = "edit_image_tag";
|
||||||
|
public const EDIT_IMAGE_SOURCE = "edit_image_source";
|
||||||
|
public const EDIT_IMAGE_OWNER = "edit_image_owner";
|
||||||
|
public const EDIT_IMAGE_LOCK = "edit_image_lock";
|
||||||
|
public const EDIT_IMAGE_TITLE = "edit_image_title";
|
||||||
|
public const BULK_EDIT_IMAGE_TAG = "bulk_edit_image_tag";
|
||||||
|
public const BULK_EDIT_IMAGE_SOURCE = "bulk_edit_image_source";
|
||||||
|
public const DELETE_IMAGE = "delete_image";
|
||||||
|
|
||||||
|
public const BAN_IMAGE = "ban_image";
|
||||||
|
|
||||||
|
public const VIEW_EVENTLOG = "view_eventlog";
|
||||||
|
public const IGNORE_DOWNTIME = "ignore_downtime";
|
||||||
|
|
||||||
|
public const CREATE_IMAGE_REPORT = "create_image_report";
|
||||||
|
public const VIEW_IMAGE_REPORT = "view_image_report"; # deal with reported images
|
||||||
|
|
||||||
|
public const EDIT_WIKI_PAGE = "edit_wiki_page";
|
||||||
|
public const DELETE_WIKI_PAGE = "delete_wiki_page";
|
||||||
|
|
||||||
|
public const MANAGE_BLOCKS = "manage_blocks";
|
||||||
|
|
||||||
|
public const MANAGE_ADMINTOOLS = "manage_admintools";
|
||||||
|
|
||||||
|
public const VIEW_OTHER_PMS = "view_other_pms";
|
||||||
|
public const EDIT_FEATURE = "edit_feature";
|
||||||
|
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";
|
||||||
|
public const VIEW_HELLBANNED = "view_hellbanned";
|
||||||
|
|
||||||
|
public const PROTECTED = "protected"; # only admins can modify protected users (stops a moderator changing an admin's password)
|
||||||
|
|
||||||
|
public const EDIT_IMAGE_RATING = "edit_image_rating";
|
||||||
|
public const BULK_EDIT_IMAGE_RATING = "bulk_edit_image_rating";
|
||||||
|
|
||||||
|
public const VIEW_TRASH = "view_trash";
|
||||||
|
|
||||||
|
public const PERFORM_BULK_ACTIONS = "perform_bulk_actions";
|
||||||
|
|
||||||
|
}
|
@ -251,21 +251,44 @@ if (!function_exists('mb_strlen')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MIME_TYPE_MAP = [
|
const MIME_TYPE_MAP = [
|
||||||
'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
|
'jpg' => 'image/jpeg',
|
||||||
'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
|
'gif' => 'image/gif',
|
||||||
'swf' => 'application/x-shockwave-flash', 'video/x-flv' => 'flv',
|
'png' => 'image/png',
|
||||||
'svg' => 'image/svg+xml', 'pdf' => 'application/pdf',
|
'tif' => 'image/tiff',
|
||||||
'zip' => 'application/zip', 'gz' => 'application/x-gzip',
|
'tiff' => 'image/tiff',
|
||||||
'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
|
'ico' => 'image/x-icon',
|
||||||
'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
|
'swf' => 'application/x-shockwave-flash',
|
||||||
'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
|
'flv' => 'video/x-flv',
|
||||||
'css' => 'text/css', 'js' => 'text/javascript',
|
'svg' => 'image/svg+xml',
|
||||||
'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
|
'pdf' => 'application/pdf',
|
||||||
'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
|
'zip' => 'application/zip',
|
||||||
'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
|
'gz' => 'application/x-gzip',
|
||||||
'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php',
|
'tar' => 'application/x-tar',
|
||||||
'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm',
|
'bz' => 'application/x-bzip',
|
||||||
'webp' => 'image/webp', 'bmp' =>'image/x-ms-bmp', 'psd' => 'image/vnd.adobe.photoshop',
|
'bz2' => 'application/x-bzip2',
|
||||||
|
'txt' => 'text/plain',
|
||||||
|
'asc' => 'text/plain',
|
||||||
|
'htm' => 'text/html',
|
||||||
|
'html' => 'text/html',
|
||||||
|
'css' => 'text/css',
|
||||||
|
'js' => 'text/javascript',
|
||||||
|
'xml' => 'text/xml',
|
||||||
|
'xsl' => 'application/xsl+xml',
|
||||||
|
'ogg' => 'application/ogg',
|
||||||
|
'mp3' => 'audio/mpeg',
|
||||||
|
'wav' => 'audio/x-wav',
|
||||||
|
'avi' => 'video/x-msvideo',
|
||||||
|
'mpg' => 'video/mpeg',
|
||||||
|
'mpeg' => 'video/mpeg',
|
||||||
|
'mov' => 'video/quicktime',
|
||||||
|
'flv' => 'video/x-flv',
|
||||||
|
'php' => 'text/x-php',
|
||||||
|
'mp4' => 'video/mp4',
|
||||||
|
'ogv' => 'video/ogg',
|
||||||
|
'webm' => 'video/webm',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
'bmp' =>'image/x-ms-bmp',
|
||||||
|
'psd' => 'image/vnd.adobe.photoshop',
|
||||||
'mkv' => 'video/x-matroska'
|
'mkv' => 'video/x-matroska'
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -729,3 +752,53 @@ function validate_input(array $inputs): array
|
|||||||
|
|
||||||
return $outputs;
|
return $outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates all possible directory separators to the appropriate one for the current system,
|
||||||
|
* and removes any duplicate separators.
|
||||||
|
*/
|
||||||
|
function sanitize_path(string $path): string
|
||||||
|
{
|
||||||
|
return preg_replace('|[\\\\/]+|S',DIRECTORY_SEPARATOR,$path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines all path segments specified, ensuring no duplicate separators occur,
|
||||||
|
* as well as converting all possible separators to the one appropriate for the current system.
|
||||||
|
*/
|
||||||
|
function join_path(string ...$paths): string
|
||||||
|
{
|
||||||
|
$output = "";
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
if(empty($path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$path = sanitize_path($path);
|
||||||
|
if(empty($output)) {
|
||||||
|
$output = $path;
|
||||||
|
} else {
|
||||||
|
$output = rtrim($output, DIRECTORY_SEPARATOR);
|
||||||
|
$path = ltrim($path, DIRECTORY_SEPARATOR);
|
||||||
|
$output .= DIRECTORY_SEPARATOR . $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform callback on each item returned by an iterator.
|
||||||
|
*/
|
||||||
|
function iterator_map(callable $callback, iterator $iter): Generator
|
||||||
|
{
|
||||||
|
foreach($iter as $i) {
|
||||||
|
yield call_user_func($callback,$i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
return iterator_to_array(iterator_map($callback, $iter));
|
||||||
|
}
|
@ -9,9 +9,7 @@ $_shm_event_listeners = [];
|
|||||||
|
|
||||||
function _load_event_listeners(): void
|
function _load_event_listeners(): void
|
||||||
{
|
{
|
||||||
global $_shm_event_listeners, $_shm_ctx;
|
global $_shm_event_listeners;
|
||||||
|
|
||||||
$_shm_ctx->log_start("Loading extensions");
|
|
||||||
|
|
||||||
$cache_path = data_path("cache/shm_event_listeners.php");
|
$cache_path = data_path("cache/shm_event_listeners.php");
|
||||||
if (COMPILE_ELS && file_exists($cache_path)) {
|
if (COMPILE_ELS && file_exists($cache_path)) {
|
||||||
@ -23,8 +21,13 @@ function _load_event_listeners(): void
|
|||||||
_dump_event_listeners($_shm_event_listeners, $cache_path);
|
_dump_event_listeners($_shm_event_listeners, $cache_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$_shm_ctx->log_endok();
|
function _clear_cached_event_listeners(): void
|
||||||
|
{
|
||||||
|
if (file_exists(data_path("cache/shm_event_listeners.php"))) {
|
||||||
|
unlink(data_path("cache/shm_event_listeners.php"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _set_event_listeners(): void
|
function _set_event_listeners(): void
|
||||||
@ -105,35 +108,31 @@ $_shm_event_count = 0;
|
|||||||
*/
|
*/
|
||||||
function send_event(Event $event): void
|
function send_event(Event $event): void
|
||||||
{
|
{
|
||||||
global $_shm_event_listeners, $_shm_event_count, $_shm_ctx;
|
global $tracer_enabled;
|
||||||
|
|
||||||
|
global $_shm_event_listeners, $_shm_event_count, $_tracer;
|
||||||
if (!isset($_shm_event_listeners[get_class($event)])) {
|
if (!isset($_shm_event_listeners[get_class($event)])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$method_name = "on".str_replace("Event", "", get_class($event));
|
$method_name = "on".str_replace("Event", "", get_class($event));
|
||||||
|
|
||||||
// send_event() is performance sensitive, and with the number
|
// send_event() is performance sensitive, and with the number
|
||||||
// of times context gets called the time starts to add up
|
// of times tracer gets called the time starts to add up
|
||||||
$ctx_enabled = constant('CONTEXT');
|
if ($tracer_enabled) $_tracer->begin(get_class($event));
|
||||||
|
|
||||||
if ($ctx_enabled) {
|
|
||||||
$_shm_ctx->log_start(get_class($event));
|
|
||||||
}
|
|
||||||
// SHIT: http://bugs.php.net/bug.php?id=35106
|
// SHIT: http://bugs.php.net/bug.php?id=35106
|
||||||
$my_event_listeners = $_shm_event_listeners[get_class($event)];
|
$my_event_listeners = $_shm_event_listeners[get_class($event)];
|
||||||
ksort($my_event_listeners);
|
ksort($my_event_listeners);
|
||||||
|
|
||||||
foreach ($my_event_listeners as $listener) {
|
foreach ($my_event_listeners as $listener) {
|
||||||
if ($ctx_enabled) {
|
if ($tracer_enabled) $_tracer->begin(get_class($listener));
|
||||||
$_shm_ctx->log_start(get_class($listener));
|
|
||||||
}
|
|
||||||
if (method_exists($listener, $method_name)) {
|
if (method_exists($listener, $method_name)) {
|
||||||
$listener->$method_name($event);
|
$listener->$method_name($event);
|
||||||
}
|
}
|
||||||
if ($ctx_enabled) {
|
if ($tracer_enabled) $_tracer->end();
|
||||||
$_shm_ctx->log_endok();
|
if($event->stop_processing===true) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$_shm_event_count++;
|
$_shm_event_count++;
|
||||||
if ($ctx_enabled) {
|
if ($tracer_enabled) $_tracer->end();
|
||||||
$_shm_ctx->log_endok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,10 @@ function _d(string $name, $value): void
|
|||||||
}
|
}
|
||||||
_d("DATABASE_DSN", null); // string PDO database connection details
|
_d("DATABASE_DSN", null); // string PDO database connection details
|
||||||
_d("DATABASE_KA", true); // string Keep database connection alive
|
_d("DATABASE_KA", true); // string Keep database connection alive
|
||||||
|
_d("DATABASE_TIMEOUT", 10000);// int Time to wait for each statement to complete
|
||||||
_d("CACHE_DSN", null); // string cache connection details
|
_d("CACHE_DSN", null); // string cache connection details
|
||||||
_d("DEBUG", false); // boolean print various debugging details
|
_d("DEBUG", false); // boolean print various debugging details
|
||||||
_d("DEBUG_SQL", false); // boolean dump SQL queries to data/sql.log
|
|
||||||
_d("DEBUG_CACHE", false); // boolean dump cache queries to data/cache.log
|
|
||||||
_d("COVERAGE", false); // boolean activate xdebug coverage monitor
|
_d("COVERAGE", false); // boolean activate xdebug coverage monitor
|
||||||
_d("CONTEXT", null); // string file to log performance data into
|
|
||||||
_d("CACHE_HTTP", false); // boolean output explicit HTTP caching headers
|
_d("CACHE_HTTP", false); // boolean output explicit HTTP caching headers
|
||||||
_d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-shared logins, give them different prefixes
|
_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("SPEED_HAX", false); // boolean do some questionable things in the name of performance
|
||||||
@ -42,11 +40,12 @@ _d("SEARCH_ACCEL", false); // boolean use search accelerator
|
|||||||
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
|
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
|
||||||
_d("VERSION", '2.7-beta'); // string shimmie version
|
_d("VERSION", '2.7-beta'); // string shimmie version
|
||||||
_d("TIMEZONE", null); // string timezone
|
_d("TIMEZONE", null); // string timezone
|
||||||
_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable
|
_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor,media,help_pages,system"); // extensions to always enable
|
||||||
_d("EXTRA_EXTS", ""); // string optional extra extensions
|
_d("EXTRA_EXTS", ""); // string optional extra extensions
|
||||||
_d("BASE_URL", null); // string force a specific base URL (default is auto-detect)
|
_d("BASE_URL", null); // string force a specific base URL (default is auto-detect)
|
||||||
_d("MIN_PHP_VERSION", '7.1');// string minimum supported PHP version
|
_d("MIN_PHP_VERSION", '7.1');// string minimum supported PHP version
|
||||||
_d("SLOW_PAGES", null); // float log pages which take more time than this
|
_d("TRACE_FILE", null); // string file to log performance data into
|
||||||
|
_d("TRACE_THRESHOLD", 0.0); // float log pages which take more time than this many seconds
|
||||||
_d("ENABLED_MODS", "imageboard");
|
_d("ENABLED_MODS", "imageboard");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -45,4 +45,77 @@ class PolyfillsTest extends \PHPUnit\Framework\TestCase
|
|||||||
$this->assertEquals(parse_shorthand_int("43.4KB"), 44441);
|
$this->assertEquals(parse_shorthand_int("43.4KB"), 44441);
|
||||||
$this->assertEquals(parse_shorthand_int("1231231231"), 1231231231);
|
$this->assertEquals(parse_shorthand_int("1231231231"), 1231231231);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_sanitize_path()
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one",
|
||||||
|
sanitize_path("one")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two",
|
||||||
|
sanitize_path("one\\two")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two",
|
||||||
|
sanitize_path("one/two")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two",
|
||||||
|
sanitize_path("one\\\\two")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two",
|
||||||
|
sanitize_path("one//two")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two",
|
||||||
|
sanitize_path("one\\\\\\two")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two",
|
||||||
|
sanitize_path("one///two")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
DIRECTORY_SEPARATOR."one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR,
|
||||||
|
sanitize_path("\\/one/\\/\\/two\\/")
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_join_path()
|
||||||
|
{
|
||||||
|
$this->assertEquals(
|
||||||
|
"one",
|
||||||
|
join_path("one")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two",
|
||||||
|
join_path("one","two")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three",
|
||||||
|
join_path("one","two","three")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three",
|
||||||
|
join_path("one/two","three")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
DIRECTORY_SEPARATOR."one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three".DIRECTORY_SEPARATOR,
|
||||||
|
join_path("\\/////\\\\one/\///"."\\//two\/\\//\\//","//\/\\\/three/\\/\/")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
67
core/tests/util.test.php
Normal file
67
core/tests/util.test.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
require_once "core/util.php";
|
||||||
|
|
||||||
|
class UtilTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function test_warehouse_path()
|
||||||
|
{
|
||||||
|
$hash = "7ac19c10d6859415";
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1","9c",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 3)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1","9c","10",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1","9c","10","d6",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 6)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85","94",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 7)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85","94","15",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 8)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85","94","15",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 9)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
join_path(DATA_DIR,"base","7a","c1","9c","10","d6","85","94","15",$hash),
|
||||||
|
warehouse_path("base",$hash,false, 10)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,23 @@
|
|||||||
* HTML Generation *
|
* HTML Generation *
|
||||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
class Link
|
||||||
|
{
|
||||||
|
public $page;
|
||||||
|
public $query;
|
||||||
|
|
||||||
|
public function __construct(?string $page=null, ?string $query=null)
|
||||||
|
{
|
||||||
|
$this->page = $page;
|
||||||
|
$this->query = $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function make_link(): string
|
||||||
|
{
|
||||||
|
return make_link($this->page, $this->query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Figure out the correct way to link to a page, taking into account
|
* Figure out the correct way to link to a page, taking into account
|
||||||
* things like the nice URLs setting.
|
* things like the nice URLs setting.
|
||||||
@ -14,7 +31,7 @@ function make_link(?string $page=null, ?string $query=null): string
|
|||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
if (is_null($page)) {
|
if (is_null($page)) {
|
||||||
$page = $config->get_string('main_page');
|
$page = $config->get_string(SetupConfig::MAIN_PAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null(BASE_URL)) {
|
if (!is_null(BASE_URL)) {
|
||||||
|
@ -69,7 +69,7 @@ class User
|
|||||||
global $config, $database;
|
global $config, $database;
|
||||||
$row = $database->cache->get("user-session:$name-$session");
|
$row = $database->cache->get("user-session:$name-$session");
|
||||||
if (!$row) {
|
if (!$row) {
|
||||||
if ($database->get_driver_name() === "mysql") {
|
if ($database->get_driver_name() === DatabaseDriver::MYSQL) {
|
||||||
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
|
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
|
||||||
} else {
|
} else {
|
||||||
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
|
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
|
||||||
|
@ -72,129 +72,138 @@ class UserClass
|
|||||||
// action = create / view / edit / delete
|
// action = create / view / edit / delete
|
||||||
// object = image / user / tag / setting
|
// object = image / user / tag / setting
|
||||||
new UserClass("base", null, [
|
new UserClass("base", null, [
|
||||||
"change_setting" => false, # modify web-level settings, eg the config table
|
Permissions::CHANGE_SETTING => false, # modify web-level settings, eg the config table
|
||||||
"override_config" => false, # modify sys-level settings, eg shimmie.conf.php
|
Permissions::OVERRIDE_CONFIG => false, # modify sys-level settings, eg shimmie.conf.php
|
||||||
"big_search" => false, # search for more than 3 tags at once (speed mode only)
|
Permissions::BIG_SEARCH => false, # search for more than 3 tags at once (speed mode only)
|
||||||
|
|
||||||
"manage_extension_list" => false,
|
Permissions::MANAGE_EXTENSION_LIST => false,
|
||||||
"manage_alias_list" => false,
|
Permissions::MANAGE_ALIAS_LIST => false,
|
||||||
"mass_tag_edit" => false,
|
Permissions::MASS_TAG_EDIT => false,
|
||||||
|
|
||||||
"view_ip" => false, # view IP addresses associated with things
|
Permissions::VIEW_IP => false, # view IP addresses associated with things
|
||||||
"ban_ip" => false,
|
Permissions::BAN_IP => false,
|
||||||
|
|
||||||
"edit_user_name" => false,
|
Permissions::EDIT_USER_NAME => false,
|
||||||
"edit_user_password" => false,
|
Permissions::EDIT_USER_PASSWORD => false,
|
||||||
"edit_user_info" => false, # email address, etc
|
Permissions::EDIT_USER_INFO => false, # email address, etc
|
||||||
"edit_user_class" => false,
|
Permissions::EDIT_USER_CLASS => false,
|
||||||
"delete_user" => false,
|
Permissions::DELETE_USER => false,
|
||||||
|
|
||||||
"create_comment" => false,
|
Permissions::CREATE_COMMENT => false,
|
||||||
"delete_comment" => false,
|
Permissions::DELETE_COMMENT => false,
|
||||||
"bypass_comment_checks" => false, # spam etc
|
Permissions::BYPASS_COMMENT_CHECKS => false, # spam etc
|
||||||
|
|
||||||
"replace_image" => false,
|
Permissions::REPLACE_IMAGE => false,
|
||||||
"create_image" => false,
|
Permissions::CREATE_IMAGE => false,
|
||||||
"edit_image_tag" => false,
|
Permissions::EDIT_IMAGE_TAG => false,
|
||||||
"edit_image_source" => false,
|
Permissions::EDIT_IMAGE_SOURCE => false,
|
||||||
"edit_image_owner" => false,
|
Permissions::EDIT_IMAGE_OWNER => false,
|
||||||
"edit_image_lock" => false,
|
Permissions::EDIT_IMAGE_LOCK => false,
|
||||||
"bulk_edit_image_tag" => false,
|
Permissions::EDIT_IMAGE_TITLE => false,
|
||||||
"bulk_edit_image_source" => false,
|
Permissions::BULK_EDIT_IMAGE_TAG => false,
|
||||||
"delete_image" => false,
|
Permissions::BULK_EDIT_IMAGE_SOURCE => false,
|
||||||
|
Permissions::DELETE_IMAGE => false,
|
||||||
|
|
||||||
"ban_image" => false,
|
Permissions::BAN_IMAGE => false,
|
||||||
|
|
||||||
"view_eventlog" => false,
|
Permissions::VIEW_EVENTLOG => false,
|
||||||
"ignore_downtime" => false,
|
Permissions::IGNORE_DOWNTIME => false,
|
||||||
|
|
||||||
"create_image_report" => false,
|
Permissions::CREATE_IMAGE_REPORT => false,
|
||||||
"view_image_report" => false, # deal with reported images
|
Permissions::VIEW_IMAGE_REPORT => false, # deal with reported images
|
||||||
|
|
||||||
"edit_wiki_page" => false,
|
Permissions::EDIT_WIKI_PAGE => false,
|
||||||
"delete_wiki_page" => false,
|
Permissions::DELETE_WIKI_PAGE => false,
|
||||||
|
|
||||||
"manage_blocks" => false,
|
Permissions::MANAGE_BLOCKS => false,
|
||||||
|
|
||||||
"manage_admintools" => false,
|
Permissions::MANAGE_ADMINTOOLS => false,
|
||||||
|
|
||||||
"view_other_pms" => false,
|
Permissions::VIEW_OTHER_PMS => false,
|
||||||
"edit_feature" => false,
|
Permissions::EDIT_FEATURE => false,
|
||||||
"bulk_edit_vote" => false,
|
Permissions::BULK_EDIT_VOTE => false,
|
||||||
"edit_other_vote" => false,
|
Permissions::EDIT_OTHER_VOTE => false,
|
||||||
"view_sysinfo" => false,
|
Permissions::VIEW_SYSINTO => false,
|
||||||
|
|
||||||
"hellbanned" => false,
|
Permissions::HELLBANNED => false,
|
||||||
"view_hellbanned" => false,
|
Permissions::VIEW_HELLBANNED => false,
|
||||||
|
|
||||||
"protected" => false, # only admins can modify protected users (stops a moderator changing an admin's password)
|
Permissions::PROTECTED => false, # only admins can modify protected users (stops a moderator changing an admin's password)
|
||||||
|
|
||||||
"edit_image_rating" => false,
|
Permissions::EDIT_IMAGE_RATING => false,
|
||||||
"bulk_edit_image_rating" => false,
|
Permissions::BULK_EDIT_IMAGE_RATING => false,
|
||||||
|
|
||||||
|
Permissions::VIEW_TRASH => false,
|
||||||
|
|
||||||
|
Permissions::PERFORM_BULK_ACTIONS => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
new UserClass("anonymous", "base", [
|
new UserClass("anonymous", "base", [
|
||||||
]);
|
]);
|
||||||
|
|
||||||
new UserClass("user", "base", [
|
new UserClass("user", "base", [
|
||||||
"big_search" => true,
|
Permissions::BIG_SEARCH => true,
|
||||||
"create_image" => true,
|
Permissions::CREATE_IMAGE => true,
|
||||||
"create_comment" => true,
|
Permissions::CREATE_COMMENT => true,
|
||||||
"edit_image_tag" => true,
|
Permissions::EDIT_IMAGE_TAG => true,
|
||||||
"edit_image_source" => true,
|
Permissions::EDIT_IMAGE_SOURCE => true,
|
||||||
"create_image_report" => true,
|
Permissions::EDIT_IMAGE_TITLE => true,
|
||||||
"edit_image_rating" => true,
|
Permissions::CREATE_IMAGE_REPORT => true,
|
||||||
|
Permissions::EDIT_IMAGE_RATING => true,
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
new UserClass("admin", "base", [
|
new UserClass("admin", "base", [
|
||||||
"change_setting" => true,
|
Permissions::CHANGE_SETTING => true,
|
||||||
"override_config" => true,
|
Permissions::OVERRIDE_CONFIG => true,
|
||||||
"big_search" => true,
|
Permissions::BIG_SEARCH => true,
|
||||||
"edit_image_lock" => true,
|
Permissions::EDIT_IMAGE_LOCK => true,
|
||||||
"view_ip" => true,
|
Permissions::VIEW_IP => true,
|
||||||
"ban_ip" => true,
|
Permissions::BAN_IP => true,
|
||||||
"edit_user_name" => true,
|
Permissions::EDIT_USER_NAME => true,
|
||||||
"edit_user_password" => true,
|
Permissions::EDIT_USER_PASSWORD => true,
|
||||||
"edit_user_info" => true,
|
Permissions::EDIT_USER_INFO => true,
|
||||||
"edit_user_class" => true,
|
Permissions::EDIT_USER_CLASS => true,
|
||||||
"delete_user" => true,
|
Permissions::DELETE_USER => true,
|
||||||
"create_image" => true,
|
Permissions::CREATE_IMAGE => true,
|
||||||
"delete_image" => true,
|
Permissions::DELETE_IMAGE => true,
|
||||||
"ban_image" => true,
|
Permissions::BAN_IMAGE => true,
|
||||||
"create_comment" => true,
|
Permissions::CREATE_COMMENT => true,
|
||||||
"delete_comment" => true,
|
Permissions::DELETE_COMMENT => true,
|
||||||
"bypass_comment_checks" => true,
|
Permissions::BYPASS_COMMENT_CHECKS => true,
|
||||||
"replace_image" => true,
|
Permissions::REPLACE_IMAGE => true,
|
||||||
"manage_extension_list" => true,
|
Permissions::MANAGE_EXTENSION_LIST => true,
|
||||||
"manage_alias_list" => true,
|
Permissions::MANAGE_ALIAS_LIST => true,
|
||||||
"edit_image_tag" => true,
|
Permissions::EDIT_IMAGE_TAG => true,
|
||||||
"edit_image_source" => true,
|
Permissions::EDIT_IMAGE_SOURCE => true,
|
||||||
"edit_image_owner" => true,
|
Permissions::EDIT_IMAGE_OWNER => true,
|
||||||
"bulk_edit_image_tag" => true,
|
Permissions::EDIT_IMAGE_TITLE => true,
|
||||||
"bulk_edit_image_source" => true,
|
Permissions::BULK_EDIT_IMAGE_TAG => true,
|
||||||
"mass_tag_edit" => true,
|
Permissions::BULK_EDIT_IMAGE_SOURCE => true,
|
||||||
"create_image_report" => true,
|
Permissions::MASS_TAG_EDIT => true,
|
||||||
"view_image_report" => true,
|
Permissions::CREATE_IMAGE_REPORT => true,
|
||||||
"edit_wiki_page" => true,
|
Permissions::VIEW_IMAGE_REPORT => true,
|
||||||
"delete_wiki_page" => true,
|
Permissions::EDIT_WIKI_PAGE => true,
|
||||||
"view_eventlog" => true,
|
Permissions::DELETE_WIKI_PAGE => true,
|
||||||
"manage_blocks" => true,
|
Permissions::VIEW_EVENTLOG => true,
|
||||||
"manage_admintools" => true,
|
Permissions::MANAGE_BLOCKS => true,
|
||||||
"ignore_downtime" => true,
|
Permissions::MANAGE_ADMINTOOLS => true,
|
||||||
"view_other_pms" => true,
|
Permissions::IGNORE_DOWNTIME => true,
|
||||||
"edit_feature" => true,
|
Permissions::VIEW_OTHER_PMS => true,
|
||||||
"bulk_edit_vote" => true,
|
Permissions::EDIT_FEATURE => true,
|
||||||
"edit_other_vote" => true,
|
Permissions::BULK_EDIT_VOTE => true,
|
||||||
"view_sysinfo" => true,
|
Permissions::EDIT_OTHER_VOTE => true,
|
||||||
"view_hellbanned" => true,
|
Permissions::VIEW_SYSINTO => true,
|
||||||
"protected" => true,
|
Permissions::VIEW_HELLBANNED => true,
|
||||||
"edit_image_rating" => true,
|
Permissions::PROTECTED => true,
|
||||||
"bulk_edit_image_rating" => true,
|
Permissions::EDIT_IMAGE_RATING => true,
|
||||||
|
Permissions::BULK_EDIT_IMAGE_RATING => true,
|
||||||
|
Permissions::VIEW_TRASH => true,
|
||||||
|
Permissions::PERFORM_BULK_ACTIONS => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
new UserClass("hellbanned", "user", [
|
new UserClass("hellbanned", "user", [
|
||||||
"hellbanned" => true,
|
Permissions::HELLBANNED => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@include_once "data/config/user-classes.conf.php";
|
@include_once "data/config/user-classes.conf.php";
|
||||||
|
128
core/util.php
128
core/util.php
@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once "vendor/shish/libcontext-php/context.php";
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||||
* Misc *
|
* Misc *
|
||||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
const DATA_DIR = "data";
|
||||||
|
|
||||||
|
|
||||||
function mtimefile(string $file): string
|
function mtimefile(string $file): string
|
||||||
{
|
{
|
||||||
$data_href = get_base_href();
|
$data_href = get_base_href();
|
||||||
@ -15,7 +16,7 @@ function mtimefile(string $file): string
|
|||||||
function get_theme(): string
|
function get_theme(): string
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
$theme = $config->get_string("theme", "default");
|
$theme = $config->get_string(SetupConfig::THEME, "default");
|
||||||
if (!file_exists("themes/$theme")) {
|
if (!file_exists("themes/$theme")) {
|
||||||
$theme = "default";
|
$theme = "default";
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ function get_memory_limit(): int
|
|||||||
|
|
||||||
// thumbnail generation requires lots of memory
|
// thumbnail generation requires lots of memory
|
||||||
$default_limit = 8*1024*1024; // 8 MB of memory is PHP's default.
|
$default_limit = 8*1024*1024; // 8 MB of memory is PHP's default.
|
||||||
$shimmie_limit = parse_shorthand_int($config->get_int("thumb_mem_limit"));
|
$shimmie_limit = parse_shorthand_int($config->get_int(MediaConfig::MEM_LIMIT));
|
||||||
|
|
||||||
if ($shimmie_limit < 3*1024*1024) {
|
if ($shimmie_limit < 3*1024*1024) {
|
||||||
// we aren't going to fit, override
|
// we aren't going to fit, override
|
||||||
@ -159,25 +160,40 @@ function format_text(string $string): string
|
|||||||
return $tfe->formatted;
|
return $tfe->formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
function warehouse_path(string $base, string $hash, bool $create=true): string
|
/**
|
||||||
|
* Generates the path to a file under the data folder based on the file's hash.
|
||||||
|
* This process creates subfolders based on octet pairs from the file's hash.
|
||||||
|
* The calculated folder follows this pattern data/$base/octet_pairs/$hash
|
||||||
|
* @param string $base
|
||||||
|
* @param string $hash
|
||||||
|
* @param bool $create
|
||||||
|
* @param int $splits The number of octet pairs to split the hash into. Caps out at strlen($hash)/2.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function warehouse_path(string $base, string $hash, bool $create=true, int $splits = WH_SPLITS): string
|
||||||
{
|
{
|
||||||
$ab = substr($hash, 0, 2);
|
$dirs =[DATA_DIR, $base];
|
||||||
$cd = substr($hash, 2, 2);
|
$splits = min($splits, strlen($hash) / 2);
|
||||||
if (WH_SPLITS == 2) {
|
for($i = 0; $i < $splits; $i++) {
|
||||||
$pa = 'data/'.$base.'/'.$ab.'/'.$cd.'/'.$hash;
|
$dirs[] = substr($hash, $i * 2, 2);
|
||||||
} else {
|
|
||||||
$pa = 'data/'.$base.'/'.$ab.'/'.$hash;
|
|
||||||
}
|
}
|
||||||
|
$dirs[] = $hash;
|
||||||
|
|
||||||
|
$pa = join_path(...$dirs);
|
||||||
|
|
||||||
if ($create && !file_exists(dirname($pa))) {
|
if ($create && !file_exists(dirname($pa))) {
|
||||||
mkdir(dirname($pa), 0755, true);
|
mkdir(dirname($pa), 0755, true);
|
||||||
}
|
}
|
||||||
return $pa;
|
return $pa;
|
||||||
}
|
}
|
||||||
|
|
||||||
function data_path(string $filename): string
|
/**
|
||||||
|
* Determines the path to the specified file in the data folder.
|
||||||
|
*/
|
||||||
|
function data_path(string $filename, bool $create = true): string
|
||||||
{
|
{
|
||||||
$filename = "data/" . $filename;
|
$filename = join_path("data", $filename);
|
||||||
if (!file_exists(dirname($filename))) {
|
if ($create&&!file_exists(dirname($filename))) {
|
||||||
mkdir(dirname($filename), 0755, true);
|
mkdir(dirname($filename), 0755, true);
|
||||||
}
|
}
|
||||||
return $filename;
|
return $filename;
|
||||||
@ -281,21 +297,57 @@ function manual_include(string $fname): ?string
|
|||||||
function path_to_tags(string $path): string
|
function path_to_tags(string $path): string
|
||||||
{
|
{
|
||||||
$matches = [];
|
$matches = [];
|
||||||
$tags = "";
|
$tags = [];
|
||||||
if (preg_match("/\d+ - (.*)\.([a-zA-Z0-9]+)/", basename($path), $matches)) {
|
if (preg_match("/\d+ - (.+)\.([a-zA-Z0-9]+)/", basename($path), $matches)) {
|
||||||
$tags = $matches[1];
|
$tags = explode(" ", $matches[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$dir_tags = dirname($path);
|
$path = dirname($path);
|
||||||
$dir_tags = str_replace("/", " ", $dir_tags);
|
$path = str_replace(";", ":", $path);
|
||||||
$dir_tags = str_replace("__", " ", $dir_tags);
|
$path = str_replace("__", " ", $path);
|
||||||
$dir_tags = trim($dir_tags);
|
|
||||||
if ($dir_tags != "") {
|
|
||||||
$tags = trim($tags)." ".trim($dir_tags);
|
|
||||||
}
|
|
||||||
$tags = trim($tags);
|
|
||||||
|
|
||||||
return $tags;
|
|
||||||
|
$category = "";
|
||||||
|
foreach (explode("/", $path) as $dir) {
|
||||||
|
$category_to_inherit = "";
|
||||||
|
foreach (explode(" ", $dir) as $tag) {
|
||||||
|
$tag = trim($tag);
|
||||||
|
if ($tag=="") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (substr_compare($tag, ":", -1) === 0) {
|
||||||
|
// This indicates a tag that ends in a colon,
|
||||||
|
// which is for inheriting to tags on the subfolder
|
||||||
|
$category_to_inherit = $tag;
|
||||||
|
} else {
|
||||||
|
if ($category!=""&&strpos($tag, ":") === false) {
|
||||||
|
// This indicates that category inheritance is active,
|
||||||
|
// and we've encountered a tag that does not specify a category.
|
||||||
|
// So we attach the inherited category to the tag.
|
||||||
|
$tag = $category.$tag;
|
||||||
|
}
|
||||||
|
$tags[] = $tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Category inheritance only works on the immediate subfolder,
|
||||||
|
// so we hold a category until the next iteration, and then set
|
||||||
|
// it back to an empty string after that iteration
|
||||||
|
$category = $category_to_inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(" ", $tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function join_url(string $base, string ...$paths)
|
||||||
|
{
|
||||||
|
$output = $base;
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$output = rtrim($output,"/");
|
||||||
|
$path = ltrim($path, "/");
|
||||||
|
$output .= "/".$path;
|
||||||
|
}
|
||||||
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -338,19 +390,6 @@ function get_debug_info(): string
|
|||||||
return $debug;
|
return $debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
function log_slow(): void
|
|
||||||
{
|
|
||||||
global $_shm_load_start;
|
|
||||||
if (!is_null(SLOW_PAGES)) {
|
|
||||||
$_time = microtime(true) - $_shm_load_start;
|
|
||||||
if ($_time > SLOW_PAGES) {
|
|
||||||
$_query = _get_query();
|
|
||||||
$_dbg = get_debug_info();
|
|
||||||
file_put_contents("data/slow-pages.log", "$_time $_query $_dbg\n", FILE_APPEND | LOCK_EX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||||
* Request initialisation stuff *
|
* Request initialisation stuff *
|
||||||
@ -375,7 +414,7 @@ date and you should plan on moving elsewhere.
|
|||||||
|
|
||||||
function _sanitise_environment(): void
|
function _sanitise_environment(): void
|
||||||
{
|
{
|
||||||
global $_shm_ctx;
|
global $_tracer;
|
||||||
|
|
||||||
if (TIMEZONE) {
|
if (TIMEZONE) {
|
||||||
date_default_timezone_set(TIMEZONE);
|
date_default_timezone_set(TIMEZONE);
|
||||||
@ -387,10 +426,7 @@ function _sanitise_environment(): void
|
|||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
$_shm_ctx = new Context();
|
$_tracer = new EventTracer();
|
||||||
if (CONTEXT) {
|
|
||||||
$_shm_ctx->set_log(CONTEXT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (COVERAGE) {
|
if (COVERAGE) {
|
||||||
_start_coverage();
|
_start_coverage();
|
||||||
@ -552,8 +588,8 @@ function show_ip(string $ip, string $ban_reason): string
|
|||||||
global $user;
|
global $user;
|
||||||
$u_reason = url_escape($ban_reason);
|
$u_reason = url_escape($ban_reason);
|
||||||
$u_end = url_escape("+1 week");
|
$u_end = url_escape("+1 week");
|
||||||
$ban = $user->can("ban_ip") ? ", <a href='".make_link("ip_ban/list", "ip=$ip&reason=$u_reason&end=$u_end#add")."'>Ban</a>" : "";
|
$ban = $user->can(Permissions::BAN_IP) ? ", <a href='".make_link("ip_ban/list", "ip=$ip&reason=$u_reason&end=$u_end#add")."'>Ban</a>" : "";
|
||||||
$ip = $user->can("view_ip") ? $ip.$ban : "";
|
$ip = $user->can(Permissions::VIEW_IP) ? $ip.$ban : "";
|
||||||
return $ip;
|
return $ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class AdminPage extends Extension
|
|||||||
global $page, $user;
|
global $page, $user;
|
||||||
|
|
||||||
if ($event->page_matches("admin")) {
|
if ($event->page_matches("admin")) {
|
||||||
if (!$user->can("manage_admintools")) {
|
if (!$user->can(Permissions::MANAGE_ADMINTOOLS)) {
|
||||||
$this->theme->display_permission_denied();
|
$this->theme->display_permission_denied();
|
||||||
} else {
|
} else {
|
||||||
if ($event->count_args() == 0) {
|
if ($event->count_args() == 0) {
|
||||||
@ -70,7 +70,7 @@ class AdminPage extends Extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($aae->redirect) {
|
if ($aae->redirect) {
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("admin"));
|
$page->set_redirect(make_link("admin"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,10 +108,20 @@ class AdminPage extends Extension
|
|||||||
$this->theme->display_form();
|
$this->theme->display_form();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="system") {
|
||||||
|
if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
|
||||||
|
$event->add_nav_link("admin", new Link('admin'), "Board Admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("manage_admintools")) {
|
if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
|
||||||
$event->add_link("Board Admin", make_link("admin"));
|
$event->add_link("Board Admin", make_link("admin"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,6 +147,7 @@ class AdminPage extends Extension
|
|||||||
global $page;
|
global $page;
|
||||||
$query = $_POST['query'];
|
$query = $_POST['query'];
|
||||||
$reason = @$_POST['reason'];
|
$reason = @$_POST['reason'];
|
||||||
|
|
||||||
assert(strlen($query) > 1);
|
assert(strlen($query) > 1);
|
||||||
|
|
||||||
$images = Image::find_images(0, 1000000, Tag::explode($query));
|
$images = Image::find_images(0, 1000000, Tag::explode($query));
|
||||||
@ -146,10 +157,10 @@ class AdminPage extends Extension
|
|||||||
if ($reason && class_exists("ImageBan")) {
|
if ($reason && class_exists("ImageBan")) {
|
||||||
send_event(new AddImageHashBanEvent($image->hash, $reason));
|
send_event(new AddImageHashBanEvent($image->hash, $reason));
|
||||||
}
|
}
|
||||||
send_event(new ImageDeletionEvent($image));
|
send_event(new ImageDeletionEvent($image, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/list"));
|
$page->set_redirect(make_link("post/list"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -201,14 +212,14 @@ class AdminPage extends Extension
|
|||||||
$database = $matches['dbname'];
|
$database = $matches['dbname'];
|
||||||
|
|
||||||
switch ($software) {
|
switch ($software) {
|
||||||
case 'mysql':
|
case DatabaseDriver::MYSQL:
|
||||||
$cmd = "mysqldump -h$hostname -u$username -p$password $database";
|
$cmd = "mysqldump -h$hostname -u$username -p$password $database";
|
||||||
break;
|
break;
|
||||||
case 'pgsql':
|
case DatabaseDriver::PGSQL:
|
||||||
putenv("PGPASSWORD=$password");
|
putenv("PGPASSWORD=$password");
|
||||||
$cmd = "pg_dump -h $hostname -U $username $database";
|
$cmd = "pg_dump -h $hostname -U $username $database";
|
||||||
break;
|
break;
|
||||||
case 'sqlite':
|
case DatabaseDriver::SQLITE:
|
||||||
$cmd = "sqlite3 $database .dump";
|
$cmd = "sqlite3 $database .dump";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -218,7 +229,7 @@ class AdminPage extends Extension
|
|||||||
//FIXME: .SQL dump is empty if cmd doesn't exist
|
//FIXME: .SQL dump is empty if cmd doesn't exist
|
||||||
|
|
||||||
if ($cmd) {
|
if ($cmd) {
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type("application/x-unknown");
|
$page->set_type("application/x-unknown");
|
||||||
$page->set_filename('shimmie-'.date('Ymd').'.sql');
|
$page->set_filename('shimmie-'.date('Ymd').'.sql');
|
||||||
$page->set_data(shell_exec($cmd));
|
$page->set_data(shell_exec($cmd));
|
||||||
@ -237,13 +248,13 @@ class AdminPage extends Extension
|
|||||||
$zip = new ZipArchive;
|
$zip = new ZipArchive;
|
||||||
if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
|
if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
|
||||||
foreach ($images as $img) {
|
foreach ($images as $img) {
|
||||||
$img_loc = warehouse_path("images", $img["hash"], false);
|
$img_loc = warehouse_path(Image::IMAGE_DIR, $img["hash"], false);
|
||||||
$zip->addFile($img_loc, $img["hash"].".".$img["ext"]);
|
$zip->addFile($img_loc, $img["hash"].".".$img["ext"]);
|
||||||
}
|
}
|
||||||
$zip->close();
|
$zip->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link($filename)); //TODO: Delete file after downloaded?
|
$page->set_redirect(make_link($filename)); //TODO: Delete file after downloaded?
|
||||||
|
|
||||||
return false; // we do want a redirect, but a manual one
|
return false; // we do want a redirect, but a manual one
|
||||||
@ -257,7 +268,7 @@ class AdminPage extends Extension
|
|||||||
//TODO: Update score_log (Having an optional ID column for score_log would be nice..)
|
//TODO: Update score_log (Having an optional ID column for score_log would be nice..)
|
||||||
preg_match("#^(?P<proto>\w+)\:(?:user=(?P<user>\w+)(?:;|$)|password=(?P<password>\w*)(?:;|$)|host=(?P<host>[\w\.\-]+)(?:;|$)|dbname=(?P<dbname>[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches);
|
preg_match("#^(?P<proto>\w+)\:(?:user=(?P<user>\w+)(?:;|$)|password=(?P<password>\w*)(?:;|$)|host=(?P<host>[\w\.\-]+)(?:;|$)|dbname=(?P<dbname>[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches);
|
||||||
|
|
||||||
if ($matches['proto'] == "mysql") {
|
if ($matches['proto'] == DatabaseDriver::MYSQL) {
|
||||||
$tables = $database->get_col("SELECT TABLE_NAME
|
$tables = $database->get_col("SELECT TABLE_NAME
|
||||||
FROM information_schema.KEY_COLUMN_USAGE
|
FROM information_schema.KEY_COLUMN_USAGE
|
||||||
WHERE TABLE_SCHEMA = :db
|
WHERE TABLE_SCHEMA = :db
|
||||||
@ -280,9 +291,9 @@ class AdminPage extends Extension
|
|||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
$database->execute("ALTER TABLE images AUTO_INCREMENT=".(count($ids) + 1));
|
$database->execute("ALTER TABLE images AUTO_INCREMENT=".(count($ids) + 1));
|
||||||
} elseif ($matches['proto'] == "pgsql") {
|
} elseif ($matches['proto'] == DatabaseDriver::PGSQL) {
|
||||||
//TODO: Make this work with PostgreSQL
|
//TODO: Make this work with PostgreSQL
|
||||||
} elseif ($matches['proto'] == "sqlite") {
|
} elseif ($matches['proto'] == DatabaseDriver::SQLITE) {
|
||||||
//TODO: Make this work with SQLite
|
//TODO: Make this work with SQLite
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -45,7 +45,7 @@ class AdminPageTheme extends Themelet
|
|||||||
$html .= $this->button("Download all images", "download_all_images", false);
|
$html .= $this->button("Download all images", "download_all_images", false);
|
||||||
}
|
}
|
||||||
$html .= $this->button("Download database contents", "database_dump", false);
|
$html .= $this->button("Download database contents", "database_dump", false);
|
||||||
if ($database->get_driver_name() == "mysql") {
|
if ($database->get_driver_name() == DatabaseDriver::MYSQL) {
|
||||||
$html .= $this->button("Reset image IDs", "reset_image_ids", true);
|
$html .= $this->button("Reset image IDs", "reset_image_ids", true);
|
||||||
}
|
}
|
||||||
$page->add_block(new Block("Misc Admin Tools", $html));
|
$page->add_block(new Block("Misc Admin Tools", $html));
|
||||||
@ -55,16 +55,18 @@ class AdminPageTheme extends Themelet
|
|||||||
$html .= "<input type='submit' value='Set Tag Case'>";
|
$html .= "<input type='submit' value='Set Tag Case'>";
|
||||||
$html .= "</form>\n";
|
$html .= "</form>\n";
|
||||||
$page->add_block(new Block("Set Tag Case", $html));
|
$page->add_block(new Block("Set Tag Case", $html));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dbq_html($terms)
|
public function dbq_html($terms)
|
||||||
{
|
{
|
||||||
$h_terms = html_escape($terms);
|
if(ext_is_live("Trash")) {
|
||||||
$h_reason = "";
|
$warning = "This delete method will bypass the trash<br/>";
|
||||||
|
}
|
||||||
if (class_exists("ImageBan")) {
|
if (class_exists("ImageBan")) {
|
||||||
$h_reason = "<input type='text' name='reason' placeholder='Ban reason (leave blank to not ban)'>";
|
$h_reason = "<input type='text' name='reason' placeholder='Ban reason (leave blank to not ban)'>";
|
||||||
}
|
}
|
||||||
$html = make_form(make_link("admin/delete_by_query"), "POST") . "
|
$html = $warning.make_form(make_link("admin/delete_by_query"), "POST") . "
|
||||||
<input type='button' class='shm-unlocker' data-unlock-sel='#dbqsubmit' value='Unlock'>
|
<input type='button' class='shm-unlocker' data-unlock-sel='#dbqsubmit' value='Unlock'>
|
||||||
<input type='hidden' name='query' value='$h_terms'>
|
<input type='hidden' name='query' value='$h_terms'>
|
||||||
$h_reason
|
$h_reason
|
||||||
@ -73,4 +75,6 @@ class AdminPageTheme extends Themelet
|
|||||||
";
|
";
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,12 @@ class AliasEditor extends Extension
|
|||||||
|
|
||||||
if ($event->page_matches("alias")) {
|
if ($event->page_matches("alias")) {
|
||||||
if ($event->get_arg(0) == "add") {
|
if ($event->get_arg(0) == "add") {
|
||||||
if ($user->can("manage_alias_list")) {
|
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
|
||||||
if (isset($_POST['oldtag']) && isset($_POST['newtag'])) {
|
if (isset($_POST['oldtag']) && isset($_POST['newtag'])) {
|
||||||
try {
|
try {
|
||||||
$aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']);
|
$aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']);
|
||||||
send_event($aae);
|
send_event($aae);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("alias/list"));
|
$page->set_redirect(make_link("alias/list"));
|
||||||
} catch (AddAliasException $ex) {
|
} catch (AddAliasException $ex) {
|
||||||
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
|
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
|
||||||
@ -49,12 +49,12 @@ class AliasEditor extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($event->get_arg(0) == "remove") {
|
} elseif ($event->get_arg(0) == "remove") {
|
||||||
if ($user->can("manage_alias_list")) {
|
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
|
||||||
if (isset($_POST['oldtag'])) {
|
if (isset($_POST['oldtag'])) {
|
||||||
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", ["oldtag" => $_POST['oldtag']]);
|
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", ["oldtag" => $_POST['oldtag']]);
|
||||||
log_info("alias_editor", "Deleted alias for ".$_POST['oldtag'], "Deleted alias");
|
log_info("alias_editor", "Deleted alias for ".$_POST['oldtag'], "Deleted alias");
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("alias/list"));
|
$page->set_redirect(make_link("alias/list"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,18 +80,18 @@ class AliasEditor extends Extension
|
|||||||
|
|
||||||
$this->theme->display_aliases($alias, $page_number + 1, $total_pages);
|
$this->theme->display_aliases($alias, $page_number + 1, $total_pages);
|
||||||
} elseif ($event->get_arg(0) == "export") {
|
} elseif ($event->get_arg(0) == "export") {
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type("text/csv");
|
$page->set_type("text/csv");
|
||||||
$page->set_filename("aliases.csv");
|
$page->set_filename("aliases.csv");
|
||||||
$page->set_data($this->get_alias_csv($database));
|
$page->set_data($this->get_alias_csv($database));
|
||||||
} elseif ($event->get_arg(0) == "import") {
|
} elseif ($event->get_arg(0) == "import") {
|
||||||
if ($user->can("manage_alias_list")) {
|
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
|
||||||
if (count($_FILES) > 0) {
|
if (count($_FILES) > 0) {
|
||||||
$tmp = $_FILES['alias_file']['tmp_name'];
|
$tmp = $_FILES['alias_file']['tmp_name'];
|
||||||
$contents = file_get_contents($tmp);
|
$contents = file_get_contents($tmp);
|
||||||
$this->add_alias_csv($database, $contents);
|
$this->add_alias_csv($database, $contents);
|
||||||
log_info("alias_editor", "Imported aliases from file", "Imported aliases"); # FIXME: how many?
|
log_info("alias_editor", "Imported aliases from file", "Imported aliases"); # FIXME: how many?
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("alias/list"));
|
$page->set_redirect(make_link("alias/list"));
|
||||||
} else {
|
} else {
|
||||||
$this->theme->display_error(400, "No File Specified", "You have to upload a file");
|
$this->theme->display_error(400, "No File Specified", "You have to upload a file");
|
||||||
@ -117,10 +117,17 @@ class AliasEditor extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->parent=="tags") {
|
||||||
|
$event->add_nav_link("aliases", new Link('alias/list'), "Aliases", NavLink::is_active(["alias"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("manage_alias_list")) {
|
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
|
||||||
$event->add_link("Alias Editor", make_link("alias/list"));
|
$event->add_link("Alias Editor", make_link("alias/list"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class AliasEditorTheme extends Themelet
|
|||||||
{
|
{
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
|
|
||||||
$can_manage = $user->can("manage_alias_list");
|
$can_manage = $user->can(Permissions::MANAGE_ALIAS_LIST);
|
||||||
if ($can_manage) {
|
if ($can_manage) {
|
||||||
$h_action = "<th width='10%'>Action</th>";
|
$h_action = "<th width='10%'>Action</th>";
|
||||||
$h_add = "
|
$h_add = "
|
||||||
|
@ -47,12 +47,23 @@ class Artists extends Extension
|
|||||||
public function onSearchTermParse(SearchTermParseEvent $event)
|
public function onSearchTermParse(SearchTermParseEvent $event)
|
||||||
{
|
{
|
||||||
$matches = [];
|
$matches = [];
|
||||||
if (preg_match("/^author[=|:](.*)$/i", $event->term, $matches)) {
|
if (preg_match("/^(author|artist)[=|:](.*)$/i", $event->term, $matches)) {
|
||||||
$char = $matches[1];
|
$char = $matches[1];
|
||||||
$event->add_querylet(new Querylet("Author = :author_char", ["author_char"=>$char]));
|
$event->add_querylet(new Querylet("Author = :author_char", ["author_char"=>$char]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->key===HelpPages::SEARCH) {
|
||||||
|
$block = new Block();
|
||||||
|
$block->header = "Artist";
|
||||||
|
$block->body = $this->theme->get_help_html();
|
||||||
|
$event->add_block($block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function onInitExt(InitExtEvent $event)
|
public function onInitExt(InitExtEvent $event)
|
||||||
{
|
{
|
||||||
global $config, $database;
|
global $config, $database;
|
||||||
@ -172,7 +183,7 @@ class Artists extends Extension
|
|||||||
}
|
}
|
||||||
case "new_artist":
|
case "new_artist":
|
||||||
{
|
{
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/new"));
|
$page->set_redirect(make_link("artist/new"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -183,7 +194,7 @@ class Artists extends Extension
|
|||||||
if ($newArtistID == -1) {
|
if ($newArtistID == -1) {
|
||||||
$this->theme->display_error(400, "Error", "Error when entering artist data.");
|
$this->theme->display_error(400, "Error", "Error when entering artist data.");
|
||||||
} else {
|
} else {
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$newArtistID));
|
$page->set_redirect(make_link("artist/view/".$newArtistID));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -238,7 +249,7 @@ class Artists extends Extension
|
|||||||
case "edit_artist":
|
case "edit_artist":
|
||||||
{
|
{
|
||||||
$artistID = $_POST['artist_id'];
|
$artistID = $_POST['artist_id'];
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/edit/".$artistID));
|
$page->set_redirect(make_link("artist/edit/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -246,14 +257,14 @@ class Artists extends Extension
|
|||||||
{
|
{
|
||||||
$artistID = int_escape($_POST['id']);
|
$artistID = int_escape($_POST['id']);
|
||||||
$this->update_artist();
|
$this->update_artist();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "nuke_artist":
|
case "nuke_artist":
|
||||||
{
|
{
|
||||||
$artistID = $_POST['artist_id'];
|
$artistID = $_POST['artist_id'];
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/nuke/".$artistID));
|
$page->set_redirect(make_link("artist/nuke/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -261,7 +272,7 @@ class Artists extends Extension
|
|||||||
{
|
{
|
||||||
$artistID = $event->get_arg(1);
|
$artistID = $event->get_arg(1);
|
||||||
$this->delete_artist($artistID); // this will delete the artist, its alias, its urls and its members
|
$this->delete_artist($artistID); // this will delete the artist, its alias, its urls and its members
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/list"));
|
$page->set_redirect(make_link("artist/list"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -291,7 +302,7 @@ class Artists extends Extension
|
|||||||
{
|
{
|
||||||
$artistID = $_POST['artistID'];
|
$artistID = $_POST['artistID'];
|
||||||
$this->add_alias();
|
$this->add_alias();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -300,7 +311,7 @@ class Artists extends Extension
|
|||||||
$aliasID = $event->get_arg(2);
|
$aliasID = $event->get_arg(2);
|
||||||
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
||||||
$this->delete_alias($aliasID);
|
$this->delete_alias($aliasID);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -316,7 +327,7 @@ class Artists extends Extension
|
|||||||
$this->update_alias();
|
$this->update_alias();
|
||||||
$aliasID = int_escape($_POST['aliasID']);
|
$aliasID = int_escape($_POST['aliasID']);
|
||||||
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -332,7 +343,7 @@ class Artists extends Extension
|
|||||||
{
|
{
|
||||||
$artistID = $_POST['artistID'];
|
$artistID = $_POST['artistID'];
|
||||||
$this->add_urls();
|
$this->add_urls();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -341,7 +352,7 @@ class Artists extends Extension
|
|||||||
$urlID = $event->get_arg(2);
|
$urlID = $event->get_arg(2);
|
||||||
$artistID = $this->get_artistID_by_urlID($urlID);
|
$artistID = $this->get_artistID_by_urlID($urlID);
|
||||||
$this->delete_url($urlID);
|
$this->delete_url($urlID);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -357,7 +368,7 @@ class Artists extends Extension
|
|||||||
$this->update_url();
|
$this->update_url();
|
||||||
$urlID = int_escape($_POST['urlID']);
|
$urlID = int_escape($_POST['urlID']);
|
||||||
$artistID = $this->get_artistID_by_urlID($urlID);
|
$artistID = $this->get_artistID_by_urlID($urlID);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -372,7 +383,7 @@ class Artists extends Extension
|
|||||||
{
|
{
|
||||||
$artistID = $_POST['artistID'];
|
$artistID = $_POST['artistID'];
|
||||||
$this->add_members();
|
$this->add_members();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -381,7 +392,7 @@ class Artists extends Extension
|
|||||||
$memberID = int_escape($event->get_arg(2));
|
$memberID = int_escape($event->get_arg(2));
|
||||||
$artistID = $this->get_artistID_by_memberID($memberID);
|
$artistID = $this->get_artistID_by_memberID($memberID);
|
||||||
$this->delete_member($memberID);
|
$this->delete_member($memberID);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -397,7 +408,7 @@ class Artists extends Extension
|
|||||||
$this->update_member();
|
$this->update_member();
|
||||||
$memberID = int_escape($_POST['memberID']);
|
$memberID = int_escape($_POST['memberID']);
|
||||||
$artistID = $this->get_artistID_by_memberID($memberID);
|
$artistID = $this->get_artistID_by_memberID($memberID);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -545,4 +545,14 @@ class ArtistsTheme extends Themelet
|
|||||||
}
|
}
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_help_html()
|
||||||
|
{
|
||||||
|
return '<p>Search for images with a particular artist.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>artist=leonardo</pre>
|
||||||
|
<p>Returns images with the artist "leonardo".</p>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class AutoComplete extends Extension
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type("application/json");
|
$page->set_type("application/json");
|
||||||
|
|
||||||
$s = strtolower($_GET["s"]);
|
$s = strtolower($_GET["s"]);
|
||||||
@ -38,7 +38,9 @@ class AutoComplete extends Extension
|
|||||||
//$limit = 0;
|
//$limit = 0;
|
||||||
$cache_key = "autocomplete-$s";
|
$cache_key = "autocomplete-$s";
|
||||||
$limitSQL = "";
|
$limitSQL = "";
|
||||||
$SQLarr = ["search"=>"$s%"];
|
$s = str_replace('_','\_', $s);
|
||||||
|
$s = str_replace('%','\%', $s);
|
||||||
|
$SQLarr = ["search"=>"$s%"]; #, "cat_search"=>"%:$s%"];
|
||||||
if (isset($_GET["limit"]) && $_GET["limit"] !== 0) {
|
if (isset($_GET["limit"]) && $_GET["limit"] !== 0) {
|
||||||
$limitSQL = "LIMIT :limit";
|
$limitSQL = "LIMIT :limit";
|
||||||
$SQLarr['limit'] = $_GET["limit"];
|
$SQLarr['limit'] = $_GET["limit"];
|
||||||
@ -52,6 +54,7 @@ class AutoComplete extends Extension
|
|||||||
SELECT tag, count
|
SELECT tag, count
|
||||||
FROM tags
|
FROM tags
|
||||||
WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:search)
|
WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:search)
|
||||||
|
-- OR SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:cat_search)
|
||||||
AND count > 0
|
AND count > 0
|
||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
$limitSQL"),
|
$limitSQL"),
|
||||||
|
@ -58,7 +58,7 @@ xanax
|
|||||||
public function onCommentPosting(CommentPostingEvent $event)
|
public function onCommentPosting(CommentPostingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if (!$user->can("bypass_comment_checks")) {
|
if (!$user->can(Permissions::BYPASS_COMMENT_CHECKS)) {
|
||||||
$this->test_text($event->comment, new CommentPostingException("Comment contains banned terms"));
|
$this->test_text($event->comment, new CommentPostingException("Comment contains banned terms"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,20 @@ class Blocks extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="system") {
|
||||||
|
if ($user->can(Permissions::MANAGE_BLOCKS)) {
|
||||||
|
$event->add_nav_link("blocks", new Link('blocks/list'), "Blocks Editor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("manage_blocks")) {
|
if ($user->can(Permissions::MANAGE_BLOCKS)) {
|
||||||
$event->add_link("Blocks Editor", make_link("blocks/list"));
|
$event->add_link("Blocks Editor", make_link("blocks/list"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +62,7 @@ class Blocks extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($event->page_matches("blocks") && $user->can("manage_blocks")) {
|
if ($event->page_matches("blocks") && $user->can(Permissions::MANAGE_BLOCKS)) {
|
||||||
if ($event->get_arg(0) == "add") {
|
if ($event->get_arg(0) == "add") {
|
||||||
if ($user->check_auth_token()) {
|
if ($user->check_auth_token()) {
|
||||||
$database->execute("
|
$database->execute("
|
||||||
@ -61,7 +71,7 @@ class Blocks extends Extension
|
|||||||
", [$_POST['pages'], $_POST['title'], $_POST['area'], (int)$_POST['priority'], $_POST['content']]);
|
", [$_POST['pages'], $_POST['title'], $_POST['area'], (int)$_POST['priority'], $_POST['content']]);
|
||||||
log_info("blocks", "Added Block #".($database->get_last_insert_id('blocks_id_seq'))." (".$_POST['title'].")");
|
log_info("blocks", "Added Block #".($database->get_last_insert_id('blocks_id_seq'))." (".$_POST['title'].")");
|
||||||
$database->cache->delete("blocks");
|
$database->cache->delete("blocks");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("blocks/list"));
|
$page->set_redirect(make_link("blocks/list"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +91,7 @@ class Blocks extends Extension
|
|||||||
log_info("blocks", "Updated Block #".$_POST['id']." (".$_POST['title'].")");
|
log_info("blocks", "Updated Block #".$_POST['id']." (".$_POST['title'].")");
|
||||||
}
|
}
|
||||||
$database->cache->delete("blocks");
|
$database->cache->delete("blocks");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("blocks/list"));
|
$page->set_redirect(make_link("blocks/list"));
|
||||||
}
|
}
|
||||||
} elseif ($event->get_arg(0) == "list") {
|
} elseif ($event->get_arg(0) == "list") {
|
||||||
|
@ -56,6 +56,17 @@ class Blotter extends Extension
|
|||||||
$event->panel->add_block($sb);
|
$event->panel->add_block($sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="system") {
|
||||||
|
if ($user->is_admin()) {
|
||||||
|
$event->add_nav_link("blotter", new Link('blotter/editor'), "Blotter Editor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
@ -102,7 +113,7 @@ class Blotter extends Extension
|
|||||||
[$entry_text, $important]
|
[$entry_text, $important]
|
||||||
);
|
);
|
||||||
log_info("blotter", "Added Message: $entry_text");
|
log_info("blotter", "Added Message: $entry_text");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("blotter/editor"));
|
$page->set_redirect(make_link("blotter/editor"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -119,7 +130,7 @@ class Blotter extends Extension
|
|||||||
}
|
}
|
||||||
$database->Execute("DELETE FROM blotter WHERE id=:id", ["id"=>$id]);
|
$database->Execute("DELETE FROM blotter WHERE id=:id", ["id"=>$id]);
|
||||||
log_info("blotter", "Removed Entry #$id");
|
log_info("blotter", "Removed Entry #$id");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("blotter/editor"));
|
$page->set_redirect(make_link("blotter/editor"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -27,14 +27,14 @@ class BrowserSearch extends Extension
|
|||||||
|
|
||||||
// Add in header code to let the browser know that the search plugin exists
|
// Add in header code to let the browser know that the search plugin exists
|
||||||
// We need to build the data for the header
|
// We need to build the data for the header
|
||||||
$search_title = $config->get_string('title');
|
$search_title = $config->get_string(SetupConfig::TITLE);
|
||||||
$search_file_url = make_link('browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml');
|
$search_file_url = make_link('browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml');
|
||||||
$page->add_html_header("<link rel='search' type='application/opensearchdescription+xml' title='$search_title' href='$search_file_url'>");
|
$page->add_html_header("<link rel='search' type='application/opensearchdescription+xml' title='$search_title' href='$search_file_url'>");
|
||||||
|
|
||||||
// The search.xml file that is generated on the fly
|
// The search.xml file that is generated on the fly
|
||||||
if ($event->page_matches("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml")) {
|
if ($event->page_matches("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml")) {
|
||||||
// First, we need to build all the variables we'll need
|
// First, we need to build all the variables we'll need
|
||||||
$search_title = $config->get_string('title');
|
$search_title = $config->get_string(SetupConfig::TITLE);
|
||||||
$search_form_url = make_link('post/list/{searchTerms}');
|
$search_form_url = make_link('post/list/{searchTerms}');
|
||||||
$suggenton_url = make_link('browser_search/')."{searchTerms}";
|
$suggenton_url = make_link('browser_search/')."{searchTerms}";
|
||||||
$icon_b64 = base64_encode(file_get_contents("ext/handle_static/static/favicon.ico"));
|
$icon_b64 = base64_encode(file_get_contents("ext/handle_static/static/favicon.ico"));
|
||||||
@ -54,7 +54,7 @@ class BrowserSearch extends Extension
|
|||||||
";
|
";
|
||||||
|
|
||||||
// And now to send it to the browser
|
// And now to send it to the browser
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type("text/xml");
|
$page->set_type("text/xml");
|
||||||
$page->set_data($xml);
|
$page->set_data($xml);
|
||||||
} elseif (
|
} elseif (
|
||||||
@ -85,7 +85,7 @@ class BrowserSearch extends Extension
|
|||||||
|
|
||||||
// And now for the final output
|
// And now for the final output
|
||||||
$json_string = "[\"$tag_search\",[\"$json_tag_list\"],[],[]]";
|
$json_string = "[\"$tag_search\",[\"$json_tag_list\"],[],[]]";
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_data($json_string);
|
$page->set_data($json_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,22 +14,31 @@ class BulkActionBlockBuildingEvent extends Event
|
|||||||
/** @var array */
|
/** @var array */
|
||||||
public $actions = [];
|
public $actions = [];
|
||||||
|
|
||||||
public function add_action(String $action, string $button_text, String $confirmation_message = "", String $block = "", int $position = 40)
|
public $search_terms = [];
|
||||||
|
|
||||||
|
public function add_action(String $action, string $button_text, string $access_key = null, String $confirmation_message = "", String $block = "", int $position = 40)
|
||||||
{
|
{
|
||||||
if ($block == null) {
|
if ($block == null) {
|
||||||
$block = "";
|
$block = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
array_push(
|
if(!empty($access_key)) {
|
||||||
$this->actions,
|
assert(strlen($access_key)==1);
|
||||||
[
|
foreach ($this->actions as $existing) {
|
||||||
|
if($existing["access_key"]==$access_key) {
|
||||||
|
throw new SCoreException("Access key $access_key is already in use");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->actions[] =[
|
||||||
"block" => $block,
|
"block" => $block,
|
||||||
|
"access_key" => $access_key,
|
||||||
"confirmation_message" => $confirmation_message,
|
"confirmation_message" => $confirmation_message,
|
||||||
"action" => $action,
|
"action" => $action,
|
||||||
"button_text" => $button_text,
|
"button_text" => $button_text,
|
||||||
"position" => $position
|
"position" => $position
|
||||||
]
|
];
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +51,7 @@ class BulkActionEvent extends Event
|
|||||||
/** @var PageRequestEvent */
|
/** @var PageRequestEvent */
|
||||||
public $page_request;
|
public $page_request;
|
||||||
|
|
||||||
public function __construct(String $action, PageRequestEvent $pageRequestEvent, array $items)
|
public function __construct(String $action, PageRequestEvent $pageRequestEvent, Generator $items)
|
||||||
{
|
{
|
||||||
$this->action = $action;
|
$this->action = $action;
|
||||||
$this->page_request = $pageRequestEvent;
|
$this->page_request = $pageRequestEvent;
|
||||||
@ -58,6 +67,8 @@ class BulkActions extends Extension
|
|||||||
|
|
||||||
if ($user->is_logged_in()) {
|
if ($user->is_logged_in()) {
|
||||||
$babbe = new BulkActionBlockBuildingEvent();
|
$babbe = new BulkActionBlockBuildingEvent();
|
||||||
|
$babbe->search_terms = $event->search_terms;
|
||||||
|
|
||||||
send_event($babbe);
|
send_event($babbe);
|
||||||
|
|
||||||
if (sizeof($babbe->actions) == 0) {
|
if (sizeof($babbe->actions) == 0) {
|
||||||
@ -74,16 +85,23 @@ class BulkActions extends Extension
|
|||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
|
|
||||||
if ($user->can("delete_image")) {
|
if ($user->can(Permissions::DELETE_IMAGE)) {
|
||||||
$event->add_action("bulk_delete", "Delete", "Delete selected images?", "", 10);
|
$event->add_action("bulk_delete", "(D)elete", "d", "Delete selected images?", $this->theme->render_ban_reason_input(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user->can("bulk_edit_image_tag")) {
|
if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) {
|
||||||
$event->add_action("bulk_tag", "Tag", "", $this->theme->render_tag_input(), 10);
|
|
||||||
|
$event->add_action(
|
||||||
|
"bulk_tag",
|
||||||
|
"Tag",
|
||||||
|
"t",
|
||||||
|
"",
|
||||||
|
$this->theme->render_tag_input(),
|
||||||
|
10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user->can("bulk_edit_image_source")) {
|
if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) {
|
||||||
$event->add_action("bulk_source", "Set Source", "", $this->theme->render_source_input(), 10);
|
$event->add_action("bulk_source", "Set (S)ource", "s","", $this->theme->render_source_input(), 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +111,7 @@ class BulkActions extends Extension
|
|||||||
|
|
||||||
switch ($event->action) {
|
switch ($event->action) {
|
||||||
case "bulk_delete":
|
case "bulk_delete":
|
||||||
if ($user->can("delete_image")) {
|
if ($user->can(Permissions::DELETE_IMAGE)) {
|
||||||
$i = $this->delete_items($event->items);
|
$i = $this->delete_items($event->items);
|
||||||
flash_message("Deleted $i items");
|
flash_message("Deleted $i items");
|
||||||
}
|
}
|
||||||
@ -102,7 +120,7 @@ class BulkActions extends Extension
|
|||||||
if (!isset($_POST['bulk_tags'])) {
|
if (!isset($_POST['bulk_tags'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($user->can("bulk_edit_image_tag")) {
|
if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) {
|
||||||
$tags = $_POST['bulk_tags'];
|
$tags = $_POST['bulk_tags'];
|
||||||
$replace = false;
|
$replace = false;
|
||||||
if (isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace'] == "true") {
|
if (isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace'] == "true") {
|
||||||
@ -117,7 +135,7 @@ class BulkActions extends Extension
|
|||||||
if (!isset($_POST['bulk_source'])) {
|
if (!isset($_POST['bulk_source'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($user->can("bulk_edit_image_source")) {
|
if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) {
|
||||||
$source = $_POST['bulk_source'];
|
$source = $_POST['bulk_source'];
|
||||||
$i = $this->set_source($event->items, $source);
|
$i = $this->set_source($event->items, $source);
|
||||||
flash_message("Set source for $i items");
|
flash_message("Set source for $i items");
|
||||||
@ -129,49 +147,33 @@ class BulkActions extends Extension
|
|||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
if ($event->page_matches("bulk_action") && $user->is_admin()) {
|
if ($event->page_matches("bulk_action") && $user->can(Permissions::PERFORM_BULK_ACTIONS)) {
|
||||||
if (!isset($_POST['bulk_action'])) {
|
if (!isset($_POST['bulk_action'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$action = $_POST['bulk_action'];
|
$action = $_POST['bulk_action'];
|
||||||
|
|
||||||
$items = [];
|
$items = null;
|
||||||
if (isset($_POST['bulk_selected_ids']) && $_POST['bulk_selected_ids'] != "") {
|
if (isset($_POST['bulk_selected_ids']) && $_POST['bulk_selected_ids'] != "") {
|
||||||
$data = json_decode($_POST['bulk_selected_ids']);
|
$data = json_decode($_POST['bulk_selected_ids']);
|
||||||
if (is_array($data)) {
|
if (is_array($data)&&!empty($data)) {
|
||||||
foreach ($data as $id) {
|
$items = $this->yield_items($data);
|
||||||
if (is_numeric($id)) {
|
|
||||||
array_push($items, int_escape($id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||||
$query = $_POST['bulk_query'];
|
$query = $_POST['bulk_query'];
|
||||||
if ($query != null && $query != "") {
|
if ($query != null && $query != "") {
|
||||||
$n = 0;
|
$items = $this->yield_search_results($query);
|
||||||
$tags = Tag::explode($query);
|
|
||||||
while (true) {
|
|
||||||
$results = Image::find_image_ids($n, 100, $tags);
|
|
||||||
if (count($results) == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset($results); // rewind to first element in array.
|
|
||||||
$items = array_merge($items, $results);
|
|
||||||
$n += count($results);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sizeof($items) > 0) {
|
if (is_iterable($items)) {
|
||||||
reset($items); // rewind to first element in array.
|
|
||||||
$newEvent = new BulkActionEvent($action, $event, $items);
|
$newEvent = new BulkActionEvent($action, $event, $items);
|
||||||
send_event($newEvent);
|
send_event($newEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||||
$_SERVER['HTTP_REFERER'] = make_link();
|
$_SERVER['HTTP_REFERER'] = make_link();
|
||||||
}
|
}
|
||||||
@ -179,31 +181,50 @@ class BulkActions extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function yield_items(array $data): Generator
|
||||||
|
{
|
||||||
|
foreach ($data as $id) {
|
||||||
|
if (is_numeric($id)) {
|
||||||
|
$image = Image::by_id($id);
|
||||||
|
if($image!=null) {
|
||||||
|
yield $image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function yield_search_results(string $query): Generator
|
||||||
|
{
|
||||||
|
$tags = Tag::explode($query);
|
||||||
|
return Image::find_images_iterable(0, null, $tags);
|
||||||
|
}
|
||||||
|
|
||||||
private function sort_blocks($a, $b)
|
private function sort_blocks($a, $b)
|
||||||
{
|
{
|
||||||
return $a["position"] - $b["position"];
|
return $a["position"] - $b["position"];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function delete_items(array $items): int
|
private function delete_items(iterable $items): int
|
||||||
{
|
{
|
||||||
$total = 0;
|
$total = 0;
|
||||||
foreach ($items as $id) {
|
foreach ($items as $image) {
|
||||||
try {
|
try {
|
||||||
$image = Image::by_id($id);
|
if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) {
|
||||||
if ($image==null) {
|
$reason = $_POST['bulk_ban_reason'];
|
||||||
continue;
|
if ($reason) {
|
||||||
|
send_event(new AddImageHashBanEvent($image->hash, $reason));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send_event(new ImageDeletionEvent($image));
|
send_event(new ImageDeletionEvent($image));
|
||||||
$total++;
|
$total++;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
flash_message("Error while removing $id: " . $e->getMessage(), "error");
|
flash_message("Error while removing {$image->id}: " . $e->getMessage(), "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $total;
|
return $total;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function tag_items(array $items, string $tags, bool $replace): int
|
private function tag_items(iterable $items, string $tags, bool $replace): int
|
||||||
{
|
{
|
||||||
$tags = Tag::explode($tags);
|
$tags = Tag::explode($tags);
|
||||||
|
|
||||||
@ -219,28 +240,21 @@ class BulkActions extends Extension
|
|||||||
|
|
||||||
$total = 0;
|
$total = 0;
|
||||||
if ($replace) {
|
if ($replace) {
|
||||||
foreach ($items as $id) {
|
foreach ($items as $image) {
|
||||||
$image = Image::by_id($id);
|
|
||||||
if ($image==null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
send_event(new TagSetEvent($image, $tags));
|
send_event(new TagSetEvent($image, $tags));
|
||||||
$total++;
|
$total++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($items as $id) {
|
foreach ($items as $image) {
|
||||||
$image = Image::by_id($id);
|
$img_tags = array_map("strtolower",$image->get_tag_array());
|
||||||
if ($image==null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$img_tags = [];
|
|
||||||
if (!empty($neg_tag_array)) {
|
if (!empty($neg_tag_array)) {
|
||||||
$img_tags = array_merge($pos_tag_array, $image->get_tag_array());
|
$neg_tag_array = array_map("strtolower",$neg_tag_array);
|
||||||
|
|
||||||
|
$img_tags = array_merge($pos_tag_array, $img_tags);
|
||||||
$img_tags = array_diff($img_tags, $neg_tag_array);
|
$img_tags = array_diff($img_tags, $neg_tag_array);
|
||||||
} else {
|
} else {
|
||||||
$img_tags = array_merge($tags, $image->get_tag_array());
|
$img_tags = array_merge($tags, $img_tags);
|
||||||
}
|
}
|
||||||
send_event(new TagSetEvent($image, $img_tags));
|
send_event(new TagSetEvent($image, $img_tags));
|
||||||
$total++;
|
$total++;
|
||||||
@ -250,23 +264,17 @@ class BulkActions extends Extension
|
|||||||
return $total;
|
return $total;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function set_source(array $items, String $source): int
|
private function set_source(iterable $items, String $source): int
|
||||||
{
|
{
|
||||||
$total = 0;
|
$total = 0;
|
||||||
foreach ($items as $id) {
|
foreach ($items as $image) {
|
||||||
try {
|
try {
|
||||||
$image = Image::by_id($id);
|
|
||||||
if ($image==null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
send_event(new SourceSetEvent($image, $source));
|
send_event(new SourceSetEvent($image, $source));
|
||||||
$total++;
|
$total++;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
flash_message("Error while setting source for $id: " . $e->getMessage(), "error");
|
flash_message("Error while setting source for {$image->id}: " . $e->getMessage(), "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $total;
|
return $total;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ function validate_selections(form, confirmationMessage) {
|
|||||||
function activate_bulk_selector () {
|
function activate_bulk_selector () {
|
||||||
set_selected_items([]);
|
set_selected_items([]);
|
||||||
if(!bulk_selector_initialized) {
|
if(!bulk_selector_initialized) {
|
||||||
$("a.shm-thumb").each(
|
$(".shm-thumb").each(
|
||||||
function (index, block) {
|
function (index, block) {
|
||||||
add_selector_button($(block));
|
add_selector_button($(block));
|
||||||
}
|
}
|
||||||
@ -67,11 +67,11 @@ function get_selected_items() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function set_selected_items(items) {
|
function set_selected_items(items) {
|
||||||
$("a.shm-thumb").removeClass('selected');
|
$(".shm-thumb").removeClass('selected');
|
||||||
|
|
||||||
$(items).each(
|
$(items).each(
|
||||||
function(index,item) {
|
function(index,item) {
|
||||||
$('a.shm-thumb[data-post-id="' + item + '"]').addClass('selected');
|
$('.shm-thumb[data-post-id="' + item + '"]').addClass('selected');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ function toggle_selection( id ) {
|
|||||||
|
|
||||||
function select_all() {
|
function select_all() {
|
||||||
var items = [];
|
var items = [];
|
||||||
$("a.shm-thumb").each(
|
$(".shm-thumb").each(
|
||||||
function ( index, block ) {
|
function ( index, block ) {
|
||||||
block = $(block);
|
block = $(block);
|
||||||
var id = block.data("post-id");
|
var id = block.data("post-id");
|
||||||
@ -122,7 +122,7 @@ function select_all() {
|
|||||||
function select_invert() {
|
function select_invert() {
|
||||||
var currentItems = get_selected_items();
|
var currentItems = get_selected_items();
|
||||||
var items = [];
|
var items = [];
|
||||||
$("a.shm-thumb").each(
|
$(".shm-thumb").each(
|
||||||
function ( index, block ) {
|
function ( index, block ) {
|
||||||
block = $(block);
|
block = $(block);
|
||||||
var id = block.data("post-id");
|
var id = block.data("post-id");
|
||||||
@ -141,7 +141,7 @@ function select_none() {
|
|||||||
function select_range(start, end) {
|
function select_range(start, end) {
|
||||||
var data = get_selected_items();
|
var data = get_selected_items();
|
||||||
var selecting = false;
|
var selecting = false;
|
||||||
$("a.shm-thumb").each(
|
$(".shm-thumb").each(
|
||||||
function ( index, block ) {
|
function ( index, block ) {
|
||||||
block = $(block);
|
block = $(block);
|
||||||
var id = block.data("post-id");
|
var id = block.data("post-id");
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
class BulkActionsTheme extends Themelet
|
class BulkActionsTheme extends Themelet
|
||||||
{
|
{
|
||||||
public function display_selector(Page $page, $actions, $query)
|
public function display_selector(Page $page, array $actions, string $query)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
|
|
||||||
$body = "<input type='hidden' name='bulk_selected_ids' id='bulk_selected_ids' />
|
$body = "<input type='hidden' name='bulk_selected_ids' id='bulk_selected_ids' />
|
||||||
<input id='bulk_selector_activate' type='button' onclick='activate_bulk_selector();' value='Activate Selector'/>
|
<input id='bulk_selector_activate' type='button' onclick='activate_bulk_selector();' value='Activate (M)anual Select' accesskey='m'/>
|
||||||
<div id='bulk_selector_controls' style='display: none;'>
|
<div id='bulk_selector_controls' style='display: none;'>
|
||||||
<input id='bulk_selector_deactivate' type='button' onclick='deactivate_bulk_selector();' value='Deactivate Selector'/>
|
<input id='bulk_selector_deactivate' type='button' onclick='deactivate_bulk_selector();' value='Deactivate (M)anual Select' accesskey='m'/>
|
||||||
Click on images to mark them.
|
Click on images to mark them.
|
||||||
<br />
|
<br />
|
||||||
<table><tr><td>
|
<table><tr><td>
|
||||||
@ -36,7 +36,7 @@ class BulkActionsTheme extends Themelet
|
|||||||
"<input type='hidden' name='bulk_selected_ids' />" .
|
"<input type='hidden' name='bulk_selected_ids' />" .
|
||||||
"<input type='hidden' name='bulk_action' value='" . $action["action"] . "' />" .
|
"<input type='hidden' name='bulk_action' value='" . $action["action"] . "' />" .
|
||||||
$action["block"] .
|
$action["block"] .
|
||||||
"<input type='submit' name='submit_button' value='" . $action["button_text"] . "'/>" .
|
"<input type='submit' name='submit_button' accesskey='{$action["access_key"]}' value='" . $action["button_text"] . "'/>" .
|
||||||
"</form></div>";
|
"</form></div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +47,15 @@ class BulkActionsTheme extends Themelet
|
|||||||
$page->add_block($block);
|
$page->add_block($block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function render_ban_reason_input()
|
||||||
|
{
|
||||||
|
if (class_exists("ImageBan")) {
|
||||||
|
return "<input type='text' name='bulk_ban_reason' placeholder='Ban reason (leave blank to not ban)' />";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function render_tag_input()
|
public function render_tag_input()
|
||||||
{
|
{
|
||||||
return "<label><input type='checkbox' style='width:13px;' name='bulk_tags_replace' value='true'/>Replace tags</label>" .
|
return "<label><input type='checkbox' style='width:13px;' name='bulk_tags_replace' value='true'/>Replace tags</label>" .
|
||||||
|
@ -81,7 +81,7 @@ class BulkAddCSV extends Extension
|
|||||||
send_event($ratingevent);
|
send_event($ratingevent);
|
||||||
}
|
}
|
||||||
if (file_exists($thumbfile)) {
|
if (file_exists($thumbfile)) {
|
||||||
copy($thumbfile, warehouse_path("thumbs", $event->hash));
|
copy($thumbfile, warehouse_path(Image::THUMBNAIL_DIR, $event->hash));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,6 +157,21 @@ class CommentList extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function onPageNavBuilding(PageNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
$event->add_nav_link("comment", new Link('comment/list'), "Comments");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->parent=="comment") {
|
||||||
|
$event->add_nav_link("comment_list", new Link('comment/list'), "All");
|
||||||
|
$event->add_nav_link("comment_help", new Link('ext_doc/comment'), "Help");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
if ($event->page_matches("comment")) {
|
if ($event->page_matches("comment")) {
|
||||||
@ -178,7 +193,7 @@ class CommentList extends Extension
|
|||||||
$i_iid = int_escape($_POST['image_id']);
|
$i_iid = int_escape($_POST['image_id']);
|
||||||
$cpe = new CommentPostingEvent($_POST['image_id'], $user, $_POST['comment']);
|
$cpe = new CommentPostingEvent($_POST['image_id'], $user, $_POST['comment']);
|
||||||
send_event($cpe);
|
send_event($cpe);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
|
$page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
|
||||||
} catch (CommentPostingException $ex) {
|
} catch (CommentPostingException $ex) {
|
||||||
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
|
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
|
||||||
@ -189,12 +204,12 @@ class CommentList extends Extension
|
|||||||
private function onPageRequest_delete(PageRequestEvent $event)
|
private function onPageRequest_delete(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $page;
|
global $user, $page;
|
||||||
if ($user->can("delete_comment")) {
|
if ($user->can(Permissions::DELETE_COMMENT)) {
|
||||||
// FIXME: post, not args
|
// FIXME: post, not args
|
||||||
if ($event->count_args() === 3) {
|
if ($event->count_args() === 3) {
|
||||||
send_event(new CommentDeletionEvent($event->get_arg(1)));
|
send_event(new CommentDeletionEvent($event->get_arg(1)));
|
||||||
flash_message("Deleted comment");
|
flash_message("Deleted comment");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
if (!empty($_SERVER['HTTP_REFERER'])) {
|
if (!empty($_SERVER['HTTP_REFERER'])) {
|
||||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||||
} else {
|
} else {
|
||||||
@ -209,7 +224,7 @@ class CommentList extends Extension
|
|||||||
private function onPageRequest_bulk_delete()
|
private function onPageRequest_bulk_delete()
|
||||||
{
|
{
|
||||||
global $user, $database, $page;
|
global $user, $database, $page;
|
||||||
if ($user->can("delete_comment") && !empty($_POST["ip"])) {
|
if ($user->can(Permissions::DELETE_COMMENT) && !empty($_POST["ip"])) {
|
||||||
$ip = $_POST['ip'];
|
$ip = $_POST['ip'];
|
||||||
|
|
||||||
$comment_ids = $database->get_col("
|
$comment_ids = $database->get_col("
|
||||||
@ -224,7 +239,7 @@ class CommentList extends Extension
|
|||||||
}
|
}
|
||||||
flash_message("Deleted $num comments");
|
flash_message("Deleted $num comments");
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("admin"));
|
$page->set_redirect(make_link("admin"));
|
||||||
} else {
|
} else {
|
||||||
$this->theme->display_permission_denied();
|
$this->theme->display_permission_denied();
|
||||||
@ -288,7 +303,7 @@ class CommentList extends Extension
|
|||||||
$this->theme->display_image_comments(
|
$this->theme->display_image_comments(
|
||||||
$event->image,
|
$event->image,
|
||||||
$this->get_comments($event->image->id),
|
$this->get_comments($event->image->id),
|
||||||
$user->can("create_comment")
|
$user->can(Permissions::CREATE_COMMENT)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,6 +366,16 @@ class CommentList extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->key===HelpPages::SEARCH) {
|
||||||
|
$block = new Block();
|
||||||
|
$block->header = "Comments";
|
||||||
|
$block->body = $this->theme->get_help_html();
|
||||||
|
$event->add_block($block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// page building {{{
|
// page building {{{
|
||||||
private function build_page(int $current_page)
|
private function build_page(int $current_page)
|
||||||
{
|
{
|
||||||
@ -399,7 +424,7 @@ class CommentList extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->theme->display_comment_list($images, $current_page, $total_pages, $user->can("create_comment"));
|
$this->theme->display_comment_list($images, $current_page, $total_pages, $user->can(Permissions::CREATE_COMMENT));
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
@ -480,14 +505,14 @@ class CommentList extends Extension
|
|||||||
global $config, $database;
|
global $config, $database;
|
||||||
|
|
||||||
// sqlite fails at intervals
|
// sqlite fails at intervals
|
||||||
if ($database->get_driver_name() === "sqlite") {
|
if ($database->get_driver_name() === DatabaseDriver::SQLITE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$window = int_escape($config->get_int('comment_window'));
|
$window = int_escape($config->get_int('comment_window'));
|
||||||
$max = int_escape($config->get_int('comment_limit'));
|
$max = int_escape($config->get_int('comment_limit'));
|
||||||
|
|
||||||
if ($database->get_driver_name() == "mysql") {
|
if ($database->get_driver_name() == DatabaseDriver::MYSQL) {
|
||||||
$window_sql = "interval $window minute";
|
$window_sql = "interval $window minute";
|
||||||
} else {
|
} else {
|
||||||
$window_sql = "interval '$window minute'";
|
$window_sql = "interval '$window minute'";
|
||||||
@ -574,7 +599,7 @@ class CommentList extends Extension
|
|||||||
{
|
{
|
||||||
global $database, $page;
|
global $database, $page;
|
||||||
|
|
||||||
if (!$user->can("bypass_comment_checks")) {
|
if (!$user->can(Permissions::BYPASS_COMMENT_CHECKS)) {
|
||||||
// will raise an exception if anything is wrong
|
// will raise an exception if anything is wrong
|
||||||
$this->comment_checks($image_id, $user, $comment);
|
$this->comment_checks($image_id, $user, $comment);
|
||||||
}
|
}
|
||||||
@ -600,7 +625,7 @@ class CommentList extends Extension
|
|||||||
global $config, $page;
|
global $config, $page;
|
||||||
|
|
||||||
// basic sanity checks
|
// basic sanity checks
|
||||||
if (!$user->can("create_comment")) {
|
if (!$user->can(Permissions::CREATE_COMMENT)) {
|
||||||
throw new CommentPostingException("Anonymous posting has been disabled");
|
throw new CommentPostingException("Anonymous posting has been disabled");
|
||||||
} elseif (is_null(Image::by_id($image_id))) {
|
} elseif (is_null(Image::by_id($image_id))) {
|
||||||
throw new CommentPostingException("The image does not exist");
|
throw new CommentPostingException("The image does not exist");
|
||||||
|
@ -218,9 +218,9 @@ class CommentListTheme extends Themelet
|
|||||||
if (!array_key_exists($comment->poster_ip, $this->anon_map)) {
|
if (!array_key_exists($comment->poster_ip, $this->anon_map)) {
|
||||||
$this->anon_map[$comment->poster_ip] = $this->anon_id;
|
$this->anon_map[$comment->poster_ip] = $this->anon_id;
|
||||||
}
|
}
|
||||||
#if($user->can("view_ip")) {
|
#if($user->can(UserAbilities::VIEW_IP)) {
|
||||||
#$style = " style='color: ".$this->get_anon_colour($comment->poster_ip).";'";
|
#$style = " style='color: ".$this->get_anon_colour($comment->poster_ip).";'";
|
||||||
if ($user->can("view_ip") || $config->get_bool("comment_samefags_public", false)) {
|
if ($user->can(Permissions::VIEW_IP) || $config->get_bool("comment_samefags_public", false)) {
|
||||||
if ($this->anon_map[$comment->poster_ip] != $this->anon_id) {
|
if ($this->anon_map[$comment->poster_ip] != $this->anon_id) {
|
||||||
$anoncode2 = '<sup>('.$this->anon_map[$comment->poster_ip].')</sup>';
|
$anoncode2 = '<sup>('.$this->anon_map[$comment->poster_ip].')</sup>';
|
||||||
}
|
}
|
||||||
@ -248,9 +248,9 @@ class CommentListTheme extends Themelet
|
|||||||
$h_avatar = "<img src=\"//www.gravatar.com/avatar/$hash.jpg?cacheBreak=$cb\"><br>";
|
$h_avatar = "<img src=\"//www.gravatar.com/avatar/$hash.jpg?cacheBreak=$cb\"><br>";
|
||||||
}
|
}
|
||||||
$h_reply = " - <a href='javascript: replyTo($i_image_id, $i_comment_id, \"$h_name\")'>Reply</a>";
|
$h_reply = " - <a href='javascript: replyTo($i_image_id, $i_comment_id, \"$h_name\")'>Reply</a>";
|
||||||
$h_ip = $user->can("view_ip") ? "<br>".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : "";
|
$h_ip = $user->can(Permissions::VIEW_IP) ? "<br>".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : "";
|
||||||
$h_del = "";
|
$h_del = "";
|
||||||
if ($user->can("delete_comment")) {
|
if ($user->can(Permissions::DELETE_COMMENT)) {
|
||||||
$comment_preview = substr(html_unescape($tfe->stripped), 0, 50);
|
$comment_preview = substr(html_unescape($tfe->stripped), 0, 50);
|
||||||
$j_delete_confirm_message = json_encode("Delete comment by {$comment->owner_name}:\n$comment_preview");
|
$j_delete_confirm_message = json_encode("Delete comment by {$comment->owner_name}:\n$comment_preview");
|
||||||
$h_delete_script = html_escape("return confirm($j_delete_confirm_message);");
|
$h_delete_script = html_escape("return confirm($j_delete_confirm_message);");
|
||||||
@ -290,4 +290,28 @@ class CommentListTheme extends Themelet
|
|||||||
</div>
|
</div>
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_help_html()
|
||||||
|
{
|
||||||
|
return '<p>Search for images containing a certain number of comments, or comments by a particular individual.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>comments=1</pre>
|
||||||
|
<p>Returns images with exactly 1 comment.</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>comments>0</pre>
|
||||||
|
<p>Returns images with 1 or more comments. </p>
|
||||||
|
</div>
|
||||||
|
<p>Can use <, <=, >, >=, or =.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>commented_by:username</pre>
|
||||||
|
<p>Returns images that have been commented on by "username". </p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>commented_by_userno:123</pre>
|
||||||
|
<p>Returns images that have been commented on by user 123. </p>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Name: Cron Uploader
|
* Name: Cron Uploader
|
||||||
* Author: YaoiFox <admin@yaoifox.com>
|
* Authors: YaoiFox <admin@yaoifox.com>, Matthew Barbour <matthew@darkholme.net>
|
||||||
* Link: http://www.yaoifox.com/
|
* Link: http://www.yaoifox.com/
|
||||||
* License: GPLv2
|
* License: GPLv2
|
||||||
* Description: Uploads images automatically using Cron Jobs
|
* Description: Uploads images automatically using Cron Jobs
|
||||||
* Documentation: Installation guide: activate this extension and navigate to www.yoursite.com/cron_upload
|
* Documentation: Installation guide: activate this extension and navigate to www.yoursite.com/cron_upload
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class CronUploader extends Extension
|
class CronUploader extends Extension
|
||||||
{
|
{
|
||||||
// TODO: Checkbox option to only allow localhost + a list of additional IP adresses that can be set in /cron_upload
|
// TODO: Checkbox option to only allow localhost + a list of additional IP adresses that can be set in /cron_upload
|
||||||
// TODO: Change logging to MySQL + display log at /cron_upload
|
// TODO: Change logging to MySQL + display log at /cron_upload
|
||||||
// TODO: Move stuff to theme.php
|
// TODO: Move stuff to theme.php
|
||||||
|
|
||||||
|
const QUEUE_DIR = "queue";
|
||||||
|
const UPLOADED_DIR = "uploaded";
|
||||||
|
const FAILED_DIR = "failed_to_upload";
|
||||||
|
|
||||||
|
const CONFIG_KEY = "cron_uploader_key";
|
||||||
|
const CONFIG_COUNT = "cron_uploader_count";
|
||||||
|
const CONFIG_DIR = "cron_uploader_dir";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all log events this session
|
* Lists all log events this session
|
||||||
* @var string
|
* @var string
|
||||||
@ -46,12 +56,23 @@ class CronUploader extends Extension
|
|||||||
global $config, $user;
|
global $config, $user;
|
||||||
|
|
||||||
if ($event->page_matches("cron_upload")) {
|
if ($event->page_matches("cron_upload")) {
|
||||||
$this->upload_key = $config->get_string("cron_uploader_key", "");
|
$this->upload_key = $config->get_string(self::CONFIG_KEY, "");
|
||||||
|
|
||||||
// If the key is in the url, upload
|
// If the key is in the url, upload
|
||||||
if ($this->upload_key != "" && $event->get_arg(0) == $this->upload_key) {
|
if ($this->upload_key != "" && $event->get_arg(0) == $this->upload_key) {
|
||||||
// log in as admin
|
// log in as admin
|
||||||
|
$this->set_dir();
|
||||||
|
|
||||||
|
$lockfile = fopen($this->root_dir . "/.lock", "w");
|
||||||
|
if (!flock($lockfile, LOCK_EX | LOCK_NB)) {
|
||||||
|
throw new Exception("Cron upload process is already running");
|
||||||
|
}
|
||||||
|
try {
|
||||||
$this->process_upload(); // Start upload
|
$this->process_upload(); // Start upload
|
||||||
|
} finally {
|
||||||
|
flock($lockfile, LOCK_UN);
|
||||||
|
fclose($lockfile);
|
||||||
|
}
|
||||||
} elseif ($user->is_admin()) {
|
} elseif ($user->is_admin()) {
|
||||||
$this->set_dir();
|
$this->set_dir();
|
||||||
$this->display_documentation();
|
$this->display_documentation();
|
||||||
@ -65,9 +86,9 @@ class CronUploader extends Extension
|
|||||||
$this->set_dir(); // Determines path to cron_uploader_dir
|
$this->set_dir(); // Determines path to cron_uploader_dir
|
||||||
|
|
||||||
|
|
||||||
$queue_dir = $this->root_dir . "/queue";
|
$queue_dir = $this->root_dir . "/" . self::QUEUE_DIR;
|
||||||
$uploaded_dir = $this->root_dir . "/uploaded";
|
$uploaded_dir = $this->root_dir . "/" . self::UPLOADED_DIR;
|
||||||
$failed_dir = $this->root_dir . "/failed_to_upload";
|
$failed_dir = $this->root_dir . "/" . self::FAILED_DIR;
|
||||||
|
|
||||||
$queue_dirinfo = $this->scan_dir($queue_dir);
|
$queue_dirinfo = $this->scan_dir($queue_dir);
|
||||||
$uploaded_dirinfo = $this->scan_dir($uploaded_dir);
|
$uploaded_dirinfo = $this->scan_dir($uploaded_dir);
|
||||||
@ -131,6 +152,8 @@ class CronUploader extends Extension
|
|||||||
<br />This link can be found under 'Cron Command' in the board config, just remove the 'wget ' part and only the url remains.
|
<br />This link can be found under 'Cron Command' in the board config, just remove the 'wget ' part and only the url remains.
|
||||||
<br />(<b>$cron_url</b>)";
|
<br />(<b>$cron_url</b>)";
|
||||||
|
|
||||||
|
$page->set_title("Cron Uploader");
|
||||||
|
$page->set_heading("Cron Uploader");
|
||||||
|
|
||||||
$block = new Block("Cron Uploader", $info_html, "main", 10);
|
$block = new Block("Cron Uploader", $info_html, "main", 10);
|
||||||
$block_install = new Block("Installation Guide", $install_html, "main", 20);
|
$block_install = new Block("Installation Guide", $install_html, "main", 20);
|
||||||
@ -142,13 +165,14 @@ class CronUploader extends Extension
|
|||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
// Set default values
|
// Set default values
|
||||||
$this->upload_key = $config->get_string("cron_uploader_key", "");
|
$config->set_default_int(self::CONFIG_COUNT, 1);
|
||||||
if (strlen($this->upload_key)<=0) {
|
$this->set_dir();
|
||||||
|
|
||||||
|
$this->upload_key = $config->get_string(self::CONFIG_KEY, "");
|
||||||
|
if (empty($this->upload_key)) {
|
||||||
$this->upload_key = $this->generate_key();
|
$this->upload_key = $this->generate_key();
|
||||||
|
|
||||||
$config->set_default_int('cron_uploader_count', 1);
|
$config->set_string(self::CONFIG_KEY, $this->upload_key);
|
||||||
$config->set_default_string('cron_uploader_key', $this->upload_key);
|
|
||||||
$this->set_dir();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,10 +186,10 @@ class CronUploader extends Extension
|
|||||||
|
|
||||||
$sb = new SetupBlock("Cron Uploader");
|
$sb = new SetupBlock("Cron Uploader");
|
||||||
$sb->add_label("<b>Settings</b><br>");
|
$sb->add_label("<b>Settings</b><br>");
|
||||||
$sb->add_int_option("cron_uploader_count", "How many to upload each time");
|
$sb->add_int_option(self::CONFIG_COUNT, "How many to upload each time");
|
||||||
$sb->add_text_option("cron_uploader_dir", "<br>Set Cron Uploader root directory<br>");
|
$sb->add_text_option(self::CONFIG_DIR, "<br>Set Cron Uploader root directory<br>");
|
||||||
|
|
||||||
$sb->add_label("<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br>
|
$sb->add_label("<br>Cron Command: <input type='text' size='60' readonly='readonly' value='" . html_escape($cron_cmd) . "'><br>
|
||||||
Create a cron job with the command above.<br/>
|
Create a cron job with the command above.<br/>
|
||||||
<a href='$documentation_link'>Read the documentation</a> if you're not sure what to do.");
|
<a href='$documentation_link'>Read the documentation</a> if you're not sure what to do.");
|
||||||
|
|
||||||
@ -196,23 +220,23 @@ class CronUploader extends Extension
|
|||||||
global $config;
|
global $config;
|
||||||
// Determine directory (none = default)
|
// Determine directory (none = default)
|
||||||
|
|
||||||
$dir = $config->get_string("cron_uploader_dir", "");
|
$dir = $config->get_string(self::CONFIG_DIR, "");
|
||||||
|
|
||||||
// Sets new default dir if not in config yet/anymore
|
// Sets new default dir if not in config yet/anymore
|
||||||
if ($dir == "") {
|
if ($dir == "") {
|
||||||
$dir = data_path("cron_uploader");
|
$dir = data_path("cron_uploader");
|
||||||
$config->set_string('cron_uploader_dir', $dir);
|
$config->set_string(self::CONFIG_DIR, $dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the directory if it doesn't exist yet
|
// Make the directory if it doesn't exist yet
|
||||||
if (!is_dir($dir . "/queue/")) {
|
if (!is_dir($dir . "/" . self::QUEUE_DIR . "/")) {
|
||||||
mkdir($dir . "/queue/", 0775, true);
|
mkdir($dir . "/" . self::QUEUE_DIR . "/", 0775, true);
|
||||||
}
|
}
|
||||||
if (!is_dir($dir . "/uploaded/")) {
|
if (!is_dir($dir . "/" . self::UPLOADED_DIR . "/")) {
|
||||||
mkdir($dir . "/uploaded/", 0775, true);
|
mkdir($dir . "/" . self::UPLOADED_DIR . "/", 0775, true);
|
||||||
}
|
}
|
||||||
if (!is_dir($dir . "/failed_to_upload/")) {
|
if (!is_dir($dir . "/" . self::FAILED_DIR . "/")) {
|
||||||
mkdir($dir . "/failed_to_upload/", 0775, true);
|
mkdir($dir . "/" . self::FAILED_DIR . "/", 0775, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->root_dir = $dir;
|
$this->root_dir = $dir;
|
||||||
@ -246,15 +270,15 @@ class CronUploader extends Extension
|
|||||||
{
|
{
|
||||||
global $config, $database;
|
global $config, $database;
|
||||||
|
|
||||||
set_time_limit(0);
|
//set_time_limit(0);
|
||||||
|
|
||||||
|
|
||||||
$output_subdir = date('Ymd-His', time()) . "/";
|
$output_subdir = date('Ymd-His', time()) . "/";
|
||||||
$this->set_dir();
|
|
||||||
$this->generate_image_queue();
|
$this->generate_image_queue();
|
||||||
|
|
||||||
// Gets amount of imgs to upload
|
// Gets amount of imgs to upload
|
||||||
if ($upload_count == 0) {
|
if ($upload_count == 0) {
|
||||||
$upload_count = $config->get_int("cron_uploader_count", 1);
|
$upload_count = $config->get_int(self::CONFIG_COUNT, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw exception if there's nothing in the queue
|
// Throw exception if there's nothing in the queue
|
||||||
@ -271,18 +295,17 @@ class CronUploader extends Extension
|
|||||||
$added = 0;
|
$added = 0;
|
||||||
$failed = 0;
|
$failed = 0;
|
||||||
|
|
||||||
$failedItems = [];
|
|
||||||
|
|
||||||
// Upload the file(s)
|
// Upload the file(s)
|
||||||
for ($i = 0; $i < $upload_count && sizeof($this->image_queue) > 0; $i++) {
|
for ($i = 0; $i < $upload_count && sizeof($this->image_queue) > 0; $i++) {
|
||||||
$img = array_pop($this->image_queue);
|
$img = array_pop($this->image_queue);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$database->beginTransaction();
|
$database->beginTransaction();
|
||||||
|
$this->add_upload_info("Adding file: {$img[1]} - tags: {$img[2]}");
|
||||||
$result = $this->add_image($img[0], $img[1], $img[2]);
|
$result = $this->add_image($img[0], $img[1], $img[2]);
|
||||||
$database->commit();
|
$database->commit();
|
||||||
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
|
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
|
||||||
if ($result==null) {
|
if ($result->merged) {
|
||||||
$merged++;
|
$merged++;
|
||||||
} else {
|
} else {
|
||||||
$added++;
|
$added++;
|
||||||
@ -292,11 +315,7 @@ class CronUploader extends Extension
|
|||||||
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
|
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
|
||||||
$msgNumber = $this->add_upload_info("(" . gettype($e) . ") " . $e->getMessage());
|
$msgNumber = $this->add_upload_info("(" . gettype($e) . ") " . $e->getMessage());
|
||||||
$msgNumber = $this->add_upload_info($e->getTraceAsString());
|
$msgNumber = $this->add_upload_info($e->getTraceAsString());
|
||||||
if (strpos($e->getMessage(), 'SQLSTATE') !== false) {
|
|
||||||
// Postgres invalidates the transaction if there is an SQL error,
|
|
||||||
// so all subsequence transactions will fail.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
$database->rollback();
|
$database->rollback();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@ -310,7 +329,6 @@ class CronUploader extends Extension
|
|||||||
$msgNumber = $this->add_upload_info("Items failed: $failed");
|
$msgNumber = $this->add_upload_info("Items failed: $failed");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Display & save upload log
|
// Display & save upload log
|
||||||
$this->handle_log();
|
$this->handle_log();
|
||||||
|
|
||||||
@ -329,10 +347,10 @@ class CronUploader extends Extension
|
|||||||
// Determine which dir to move to
|
// Determine which dir to move to
|
||||||
if ($corrupt) {
|
if ($corrupt) {
|
||||||
// Move to corrupt dir
|
// Move to corrupt dir
|
||||||
$newDir .= "/failed_to_upload/".$output_subdir.$relativeDir;
|
$newDir .= "/" . self::FAILED_DIR . "/" . $output_subdir . $relativeDir;
|
||||||
$info = "ERROR: Image was not uploaded.";
|
$info = "ERROR: Image was not uploaded.";
|
||||||
} else {
|
} else {
|
||||||
$newDir .= "/uploaded/".$output_subdir.$relativeDir;
|
$newDir .= "/" . self::UPLOADED_DIR . "/" . $output_subdir . $relativeDir;
|
||||||
$info = "Image successfully uploaded. ";
|
$info = "Image successfully uploaded. ";
|
||||||
}
|
}
|
||||||
$newDir = str_replace("//", "/", $newDir . "/");
|
$newDir = str_replace("//", "/", $newDir . "/");
|
||||||
@ -350,17 +368,22 @@ class CronUploader extends Extension
|
|||||||
/**
|
/**
|
||||||
* Generate the necessary DataUploadEvent for a given image and tags.
|
* Generate the necessary DataUploadEvent for a given image and tags.
|
||||||
*/
|
*/
|
||||||
private function add_image(string $tmpname, string $filename, string $tags)
|
private function add_image(string $tmpname, string $filename, string $tags): DataUploadEvent
|
||||||
{
|
{
|
||||||
assert(file_exists($tmpname));
|
assert(file_exists($tmpname));
|
||||||
|
|
||||||
|
$tagArray = Tag::explode($tags);
|
||||||
|
if (count($tagArray)==0) {
|
||||||
|
$tagArray[] = "tagme";
|
||||||
|
}
|
||||||
|
|
||||||
$pathinfo = pathinfo($filename);
|
$pathinfo = pathinfo($filename);
|
||||||
$metadata = [];
|
$metadata = [];
|
||||||
$metadata ['filename'] = $pathinfo ['basename'];
|
$metadata ['filename'] = $pathinfo ['basename'];
|
||||||
if (array_key_exists('extension', $pathinfo)) {
|
if (array_key_exists('extension', $pathinfo)) {
|
||||||
$metadata ['extension'] = $pathinfo ['extension'];
|
$metadata ['extension'] = $pathinfo ['extension'];
|
||||||
}
|
}
|
||||||
$metadata ['tags'] = Tag::explode($tags);
|
$metadata ['tags'] = $tagArray; // doesn't work when not logged in here, handled below
|
||||||
$metadata ['source'] = null;
|
$metadata ['source'] = null;
|
||||||
$event = new DataUploadEvent($tmpname, $metadata);
|
$event = new DataUploadEvent($tmpname, $metadata);
|
||||||
send_event($event);
|
send_event($event);
|
||||||
@ -369,18 +392,23 @@ class CronUploader extends Extension
|
|||||||
$infomsg = ""; // Will contain info message
|
$infomsg = ""; // Will contain info message
|
||||||
if ($event->image_id == -1) {
|
if ($event->image_id == -1) {
|
||||||
throw new Exception("File type not recognised. Filename: {$filename}");
|
throw new Exception("File type not recognised. Filename: {$filename}");
|
||||||
} elseif ($event->image_id == null) {
|
} elseif ($event->merged === true) {
|
||||||
$infomsg = "Image merged. Filename: {$filename}";
|
$infomsg = "Image merged. ID: {$event->image_id} Filename: {$filename}";
|
||||||
} else {
|
} else {
|
||||||
$infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename}";
|
$infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename}";
|
||||||
}
|
}
|
||||||
$msgNumber = $this->add_upload_info($infomsg);
|
$msgNumber = $this->add_upload_info($infomsg);
|
||||||
return $event->image_id;
|
|
||||||
|
// Set tags
|
||||||
|
$img = Image::by_id($event->image_id);
|
||||||
|
$img->set_tags(array_merge($tagArray, $img->get_tag_array()));
|
||||||
|
|
||||||
|
return $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_image_queue(): void
|
private function generate_image_queue(): void
|
||||||
{
|
{
|
||||||
$base = $this->root_dir . "/queue";
|
$base = $this->root_dir . "/" . self::QUEUE_DIR;
|
||||||
|
|
||||||
if (!is_dir($base)) {
|
if (!is_dir($base)) {
|
||||||
$this->add_upload_info("Image Queue Directory could not be found at \"$base\".");
|
$this->add_upload_info("Image Queue Directory could not be found at \"$base\".");
|
||||||
@ -438,7 +466,7 @@ class CronUploader extends Extension
|
|||||||
global $page;
|
global $page;
|
||||||
|
|
||||||
// Display message
|
// Display message
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type("text/plain");
|
$page->set_type("text/plain");
|
||||||
$page->set_data($this->upload_info);
|
$page->set_data($this->upload_info);
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ class custom_html_headers extends Extension
|
|||||||
global $config, $page;
|
global $config, $page;
|
||||||
|
|
||||||
// get config values
|
// get config values
|
||||||
$site_title = $config->get_string("title");
|
$site_title = $config->get_string(SetupConfig::TITLE);
|
||||||
$sitename_in_title = $config->get_int("sitename_in_title");
|
$sitename_in_title = $config->get_int("sitename_in_title");
|
||||||
|
|
||||||
// if feature is enabled & sitename isn't already in title
|
// if feature is enabled & sitename isn't already in title
|
||||||
|
@ -60,7 +60,7 @@ class DanbooruApi extends Extension
|
|||||||
private function api_danbooru(PageRequestEvent $event)
|
private function api_danbooru(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $page;
|
global $page;
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
|
|
||||||
if (($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) {
|
if (($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) {
|
||||||
// No XML data is returned from this function
|
// No XML data is returned from this function
|
||||||
@ -80,7 +80,7 @@ class DanbooruApi extends Extension
|
|||||||
// This redirects that to http://shimmie/post/view/123
|
// This redirects that to http://shimmie/post/view/123
|
||||||
elseif (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) {
|
elseif (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) {
|
||||||
$fixedlocation = make_link("post/view/" . $event->get_arg(3));
|
$fixedlocation = make_link("post/view/" . $event->get_arg(3));
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect($fixedlocation);
|
$page->set_redirect($fixedlocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,7 +297,7 @@ class DanbooruApi extends Extension
|
|||||||
// Now we check if a file was uploaded or a url was provided to transload
|
// Now we check if a file was uploaded or a url was provided to transload
|
||||||
// Much of this code is borrowed from /ext/upload
|
// Much of this code is borrowed from /ext/upload
|
||||||
|
|
||||||
if (!$user->can("create_image")) {
|
if (!$user->can(Permissions::CREATE_IMAGE)) {
|
||||||
$page->set_code(409);
|
$page->set_code(409);
|
||||||
$page->add_http_header("X-Danbooru-Errors: authentication error");
|
$page->add_http_header("X-Danbooru-Errors: authentication error");
|
||||||
return;
|
return;
|
||||||
|
@ -32,7 +32,7 @@ class Downtime extends Extension
|
|||||||
global $config, $page, $user;
|
global $config, $page, $user;
|
||||||
|
|
||||||
if ($config->get_bool("downtime")) {
|
if ($config->get_bool("downtime")) {
|
||||||
if (!$user->can("ignore_downtime") && !$this->is_safe_page($event)) {
|
if (!$user->can(Permissions::IGNORE_DOWNTIME) && !$this->is_safe_page($event)) {
|
||||||
$msg = $config->get_string("downtime_message");
|
$msg = $config->get_string("downtime_message");
|
||||||
$this->theme->display_message($msg);
|
$this->theme->display_message($msg);
|
||||||
if (!defined("UNITTEST")) { // hax D:
|
if (!defined("UNITTEST")) { // hax D:
|
||||||
|
@ -21,12 +21,12 @@ class DowntimeTheme extends Themelet
|
|||||||
public function display_message(string $message)
|
public function display_message(string $message)
|
||||||
{
|
{
|
||||||
global $config, $user, $page;
|
global $config, $user, $page;
|
||||||
$theme_name = $config->get_string('theme');
|
$theme_name = $config->get_string(SetupConfig::THEME);
|
||||||
$data_href = get_base_href();
|
$data_href = get_base_href();
|
||||||
$login_link = make_link("user_admin/login");
|
$login_link = make_link("user_admin/login");
|
||||||
$auth = $user->get_auth_html();
|
$auth = $user->get_auth_html();
|
||||||
|
|
||||||
$page->set_mode('data');
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_code(503);
|
$page->set_code(503);
|
||||||
$page->set_data(
|
$page->set_data(
|
||||||
<<<EOD
|
<<<EOD
|
||||||
|
@ -18,7 +18,7 @@ class EmoticonListTheme extends Themelet
|
|||||||
}
|
}
|
||||||
$html .= "</tr></table>";
|
$html .= "</tr></table>";
|
||||||
$html .= "</body></html>";
|
$html .= "</body></html>";
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_data($html);
|
$page->set_data($html);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,28 @@ class ET extends Extension
|
|||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($event->page_matches("system_info")) {
|
if ($event->page_matches("system_info")) {
|
||||||
if ($user->can("view_sysinfo")) {
|
if ($user->can(Permissions::VIEW_SYSINTO)) {
|
||||||
$this->theme->display_info_page($this->get_info());
|
$this->theme->display_info_page($this->get_info());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="system") {
|
||||||
|
if ($user->can(Permissions::VIEW_SYSINTO)) {
|
||||||
|
$event->add_nav_link("system_info", new Link('system_info'), "System Info", null, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("view_sysinfo")) {
|
if ($user->can(Permissions::VIEW_SYSINTO)) {
|
||||||
$event->add_link("System Info", make_link("system_info"));
|
$event->add_link("System Info", make_link("system_info"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,8 +52,8 @@ class ET extends Extension
|
|||||||
global $config, $database;
|
global $config, $database;
|
||||||
|
|
||||||
$info = [];
|
$info = [];
|
||||||
$info['site_title'] = $config->get_string("title");
|
$info['site_title'] = $config->get_string(SetupConfig::TITLE);
|
||||||
$info['site_theme'] = $config->get_string("theme");
|
$info['site_theme'] = $config->get_string(SetupConfig::THEME);
|
||||||
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
|
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
|
||||||
|
|
||||||
$info['sys_shimmie'] = VERSION;
|
$info['sys_shimmie'] = VERSION;
|
||||||
@ -53,13 +65,16 @@ class ET extends Extension
|
|||||||
to_shorthand_int(disk_total_space("./"));
|
to_shorthand_int(disk_total_space("./"));
|
||||||
$info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown';
|
$info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown';
|
||||||
|
|
||||||
$info['thumb_engine'] = $config->get_string("thumb_engine");
|
$info[MediaConfig::FFMPEG_PATH] = $config->get_string(MediaConfig::FFMPEG_PATH);
|
||||||
$info['thumb_quality'] = $config->get_int('thumb_quality');
|
$info[MediaConfig::CONVERT_PATH] = $config->get_string(MediaConfig::CONVERT_PATH);
|
||||||
$info['thumb_width'] = $config->get_int('thumb_width');
|
$info[MediaConfig::MEM_LIMIT] = $config->get_int(MediaConfig::MEM_LIMIT);
|
||||||
$info['thumb_height'] = $config->get_int('thumb_height');
|
|
||||||
$info['thumb_scaling'] = $config->get_int('thumb_scaling');
|
$info[ImageConfig::THUMB_ENGINE] = $config->get_string(ImageConfig::THUMB_ENGINE);
|
||||||
$info['thumb_type'] = $config->get_string('thumb_type');
|
$info[ImageConfig::THUMB_QUALITY] = $config->get_int(ImageConfig::THUMB_QUALITY);
|
||||||
$info['thumb_mem'] = $config->get_int("thumb_mem_limit");
|
$info[ImageConfig::THUMB_WIDTH] = $config->get_int(ImageConfig::THUMB_WIDTH);
|
||||||
|
$info[ImageConfig::THUMB_HEIGHT] = $config->get_int(ImageConfig::THUMB_HEIGHT);
|
||||||
|
$info[ImageConfig::THUMB_SCALING] = $config->get_int(ImageConfig::THUMB_SCALING);
|
||||||
|
$info[ImageConfig::THUMB_TYPE] = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||||
|
|
||||||
$info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images");
|
$info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images");
|
||||||
$info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments");
|
$info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments");
|
||||||
|
@ -35,14 +35,16 @@ Database: {$info['sys_db']}
|
|||||||
Server: {$info['sys_server']}
|
Server: {$info['sys_server']}
|
||||||
Disk use: {$info['sys_disk']}
|
Disk use: {$info['sys_disk']}
|
||||||
|
|
||||||
|
Media System:
|
||||||
|
Memory Limit: {$info[MediaConfig::MEM_LIMIT]}
|
||||||
|
|
||||||
Thumbnail Generation:
|
Thumbnail Generation:
|
||||||
Engine: {$info['thumb_engine']}
|
Engine: {$info[ImageConfig::THUMB_ENGINE]}
|
||||||
Type: {$info['thumb_type']}
|
Type: {$info[ImageConfig::THUMB_TYPE]}
|
||||||
Memory: {$info['thumb_mem']}
|
Quality: {$info[ImageConfig::THUMB_QUALITY]}
|
||||||
Quality: {$info['thumb_quality']}
|
Width: {$info[ImageConfig::THUMB_WIDTH]}
|
||||||
Width: {$info['thumb_width']}
|
Height: {$info[ImageConfig::THUMB_HEIGHT]}
|
||||||
Height: {$info['thumb_height']}
|
Scaling: {$info[ImageConfig::THUMB_SCALING]}
|
||||||
Scaling: {$info['thumb_scaling']}
|
|
||||||
|
|
||||||
Shimmie stats:
|
Shimmie stats:
|
||||||
Images: {$info['stat_images']}
|
Images: {$info['stat_images']}
|
||||||
|
@ -22,8 +22,7 @@ class ExtensionInfo
|
|||||||
public $ext_name;
|
public $ext_name;
|
||||||
public $name;
|
public $name;
|
||||||
public $link;
|
public $link;
|
||||||
public $author;
|
public $authors;
|
||||||
public $email;
|
|
||||||
public $description;
|
public $description;
|
||||||
public $documentation;
|
public $documentation;
|
||||||
public $version;
|
public $version;
|
||||||
@ -39,6 +38,7 @@ class ExtensionInfo
|
|||||||
$this->ext_name = $matches[1];
|
$this->ext_name = $matches[1];
|
||||||
$this->name = $this->ext_name;
|
$this->name = $this->ext_name;
|
||||||
$this->enabled = $this->is_enabled($this->ext_name);
|
$this->enabled = $this->is_enabled($this->ext_name);
|
||||||
|
$this->authors = [];
|
||||||
|
|
||||||
for ($i = 0; $i < $number_of_lines; $i++) {
|
for ($i = 0; $i < $number_of_lines; $i++) {
|
||||||
$line = $lines[$i];
|
$line = $lines[$i];
|
||||||
@ -53,11 +53,15 @@ class ExtensionInfo
|
|||||||
}
|
}
|
||||||
} elseif (preg_match("/Version: (.*)/", $line, $matches)) {
|
} elseif (preg_match("/Version: (.*)/", $line, $matches)) {
|
||||||
$this->version = $matches[1];
|
$this->version = $matches[1];
|
||||||
} elseif (preg_match("/Author: (.*) [<\(](.*@.*)[>\)]/", $line, $matches)) {
|
} elseif (preg_match("/Authors?: (.*)/", $line, $matches)) {
|
||||||
$this->author = $matches[1];
|
$author_list = explode(',', $matches[1]);
|
||||||
$this->email = $matches[2];
|
foreach ($author_list as $author) {
|
||||||
} elseif (preg_match("/Author: (.*)/", $line, $matches)) {
|
if (preg_match("/(.*) [<\(](.*@.*)[>\)]/", $author, $matches)) {
|
||||||
$this->author = $matches[1];
|
$this->authors[] = new ExtensionAuthor($matches[1], $matches[2]);
|
||||||
|
} else {
|
||||||
|
$this->authors[] = new ExtensionAuthor($author, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
} elseif (preg_match("/(.*)Description: ?(.*)/", $line, $matches)) {
|
} elseif (preg_match("/(.*)Description: ?(.*)/", $line, $matches)) {
|
||||||
$this->description = $matches[2];
|
$this->description = $matches[2];
|
||||||
$start = $matches[1] . " ";
|
$start = $matches[1] . " ";
|
||||||
@ -96,18 +100,30 @@ class ExtensionInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ExtensionAuthor
|
||||||
|
{
|
||||||
|
public $name;
|
||||||
|
public $email;
|
||||||
|
|
||||||
|
public function __construct(string $name, ?string $email)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->email = $email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ExtManager extends Extension
|
class ExtManager extends Extension
|
||||||
{
|
{
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
if ($event->page_matches("ext_manager")) {
|
if ($event->page_matches("ext_manager")) {
|
||||||
if ($user->can("manage_extension_list")) {
|
if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
|
||||||
if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
|
if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
|
||||||
if (is_writable("data/config")) {
|
if (is_writable("data/config")) {
|
||||||
$this->set_things($_POST);
|
$this->set_things($_POST);
|
||||||
log_warning("ext_manager", "Active extensions changed", "Active extensions changed");
|
log_warning("ext_manager", "Active extensions changed", "Active extensions changed");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("ext_manager"));
|
$page->set_redirect(make_link("ext_manager"));
|
||||||
} else {
|
} else {
|
||||||
$this->theme->display_error(
|
$this->theme->display_error(
|
||||||
@ -146,11 +162,22 @@ class ExtManager extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="system") {
|
||||||
|
if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
|
||||||
|
$event->add_nav_link("ext_manager", new Link('ext_manager'), "Extension Manager");
|
||||||
|
} else {
|
||||||
|
$event->add_nav_link("ext_doc", new Link('ext_doc'), "Board Help");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("manage_extension_list")) {
|
if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
|
||||||
$event->add_link("Extension Manager", make_link("ext_manager"));
|
$event->add_link("Extension Manager", make_link("ext_manager"));
|
||||||
} else {
|
} else {
|
||||||
$event->add_link("Help", make_link("ext_doc"));
|
$event->add_link("Help", make_link("ext_doc"));
|
||||||
@ -208,8 +235,6 @@ class ExtManager extends Extension
|
|||||||
// when the list of active extensions changes, we can be
|
// when the list of active extensions changes, we can be
|
||||||
// pretty sure that the list of who reacts to what will
|
// pretty sure that the list of who reacts to what will
|
||||||
// change too
|
// change too
|
||||||
if (file_exists("data/cache/event_listeners.php")) {
|
_clear_cached_event_listeners();
|
||||||
unlink("data/cache/event_listeners.php");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,13 +116,21 @@ class ExtManagerTheme extends Themelet
|
|||||||
public function display_doc(Page $page, ExtensionInfo $info)
|
public function display_doc(Page $page, ExtensionInfo $info)
|
||||||
{
|
{
|
||||||
$author = "";
|
$author = "";
|
||||||
if ($info->author) {
|
if (count($info->authors) > 0) {
|
||||||
if ($info->email) {
|
$author = "<br /><b>Author";
|
||||||
$author = "<br><b>Author:</b> <a href=\"mailto:".html_escape($info->email)."\">".html_escape($info->author)."</a>";
|
if (count($info->authors) > 1) {
|
||||||
|
$author .= "s";
|
||||||
|
}
|
||||||
|
$author .= ":</b>";
|
||||||
|
foreach ($info->authors as $auth) {
|
||||||
|
if (!empty($auth->email)) {
|
||||||
|
$author .= "<a href=\"mailto:" . html_escape($auth->email) . "\">" . html_escape($auth->name) . "</a>";
|
||||||
} else {
|
} else {
|
||||||
$author = "<br><b>Author:</b> ".html_escape($info->author);
|
$author .= html_escape($auth->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$version = ($info->version) ? "<br><b>Version:</b> " . html_escape($info->version) : "";
|
$version = ($info->version) ? "<br><b>Version:</b> " . html_escape($info->version) : "";
|
||||||
$link = ($info->link) ? "<br><b>Home Page:</b> <a href=\"" . html_escape($info->link) . "\">Link</a>" : "";
|
$link = ($info->link) ? "<br><b>Home Page:</b> <a href=\"" . html_escape($info->link) . "\">Link</a>" : "";
|
||||||
$doc = $info->documentation;
|
$doc = $info->documentation;
|
||||||
|
@ -81,7 +81,7 @@ class Favorites extends Extension
|
|||||||
log_debug("favourite", "Favourite removed for $image_id", "Favourite removed");
|
log_debug("favourite", "Favourite removed for $image_id", "Favourite removed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/$image_id"));
|
$page->set_redirect(make_link("post/view/$image_id"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,6 +155,30 @@ class Favorites extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->key===HelpPages::SEARCH) {
|
||||||
|
$block = new Block();
|
||||||
|
$block->header = "Favorites";
|
||||||
|
$block->body = $this->theme->get_help_html();
|
||||||
|
$event->add_block($block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent=="posts") {
|
||||||
|
$event->add_nav_link("posts_favorites", new Link("post/list/favorited_by={$user->name}/1"), "My Favorites");
|
||||||
|
}
|
||||||
|
|
||||||
|
if($event->parent==="user") {
|
||||||
|
if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
|
||||||
|
$username = url_escape($user->name);
|
||||||
|
$event->add_nav_link("favorites", new Link("post/list/favorited_by=$username/1"), "My Favorites");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function install()
|
private function install()
|
||||||
{
|
{
|
||||||
|
@ -34,4 +34,28 @@ class FavoritesTheme extends Themelet
|
|||||||
|
|
||||||
$page->add_block(new Block("Favorited By", $html, "left", 25));
|
$page->add_block(new Block("Favorited By", $html, "left", 25));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_help_html()
|
||||||
|
{
|
||||||
|
return '<p>Search for images that have been favorited a certain number of times, or favorited by a particular individual.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>favorites=1</pre>
|
||||||
|
<p>Returns images that have been favorited once.</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>favorites>0</pre>
|
||||||
|
<p>Returns images that have been favorited 1 or more times</p>
|
||||||
|
</div>
|
||||||
|
<p>Can use <, <=, >, >=, or =.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>favorited_by:username</pre>
|
||||||
|
<p>Returns images that have been favorited by "username". </p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>favorited_by_userno:123</pre>
|
||||||
|
<p>Returns images that have been favorited by user 123. </p>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,12 +32,12 @@ class Featured extends Extension
|
|||||||
global $config, $page, $user;
|
global $config, $page, $user;
|
||||||
if ($event->page_matches("featured_image")) {
|
if ($event->page_matches("featured_image")) {
|
||||||
if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
|
if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
|
||||||
if ($user->can("edit_feature") && isset($_POST['image_id'])) {
|
if ($user->can(Permissions::EDIT_FEATURE) && isset($_POST['image_id'])) {
|
||||||
$id = int_escape($_POST['image_id']);
|
$id = int_escape($_POST['image_id']);
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
$config->set_int("featured_id", $id);
|
$config->set_int("featured_id", $id);
|
||||||
log_info("featured", "Featured image set to $id", "Featured image set");
|
log_info("featured", "Featured image set to $id", "Featured image set");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/$id"));
|
$page->set_redirect(make_link("post/view/$id"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ class Featured extends Extension
|
|||||||
if ($event->get_arg(0) == "download") {
|
if ($event->get_arg(0) == "download") {
|
||||||
$image = Image::by_id($config->get_int("featured_id"));
|
$image = Image::by_id($config->get_int("featured_id"));
|
||||||
if (!is_null($image)) {
|
if (!is_null($image)) {
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_type($image->get_mime_type());
|
$page->set_type($image->get_mime_type());
|
||||||
$page->set_data(file_get_contents($image->get_image_filename()));
|
$page->set_data(file_get_contents($image->get_image_filename()));
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ class Featured extends Extension
|
|||||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("edit_feature")) {
|
if ($user->can(Permissions::EDIT_FEATURE)) {
|
||||||
$event->add_part($this->theme->get_buttons_html($event->image->id));
|
$event->add_part($this->theme->get_buttons_html($event->image->id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ class Forum extends Extension
|
|||||||
$redirectTo = "forum/view/".$newThreadID."/1";
|
$redirectTo = "forum/view/".$newThreadID."/1";
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link($redirectTo));
|
$page->set_redirect(make_link($redirectTo));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -151,7 +151,7 @@ class Forum extends Extension
|
|||||||
$this->delete_post($postID);
|
$this->delete_post($postID);
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("forum/view/".$threadID));
|
$page->set_redirect(make_link("forum/view/".$threadID));
|
||||||
break;
|
break;
|
||||||
case "nuke":
|
case "nuke":
|
||||||
@ -161,7 +161,7 @@ class Forum extends Extension
|
|||||||
$this->delete_thread($threadID);
|
$this->delete_thread($threadID);
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("forum/index"));
|
$page->set_redirect(make_link("forum/index"));
|
||||||
break;
|
break;
|
||||||
case "answer":
|
case "answer":
|
||||||
@ -176,11 +176,11 @@ class Forum extends Extension
|
|||||||
}
|
}
|
||||||
$this->save_new_post($threadID, $user);
|
$this->save_new_post($threadID, $user);
|
||||||
}
|
}
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("forum/view/".$threadID."/".$total_pages));
|
$page->set_redirect(make_link("forum/view/".$threadID."/".$total_pages));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("forum/index"));
|
$page->set_redirect(make_link("forum/index"));
|
||||||
//$this->theme->display_error(400, "Invalid action", "You should check forum/index.");
|
//$this->theme->display_error(400, "Invalid action", "You should check forum/index.");
|
||||||
break;
|
break;
|
||||||
|
@ -14,7 +14,7 @@ class Handle404 extends Extension
|
|||||||
{
|
{
|
||||||
global $config, $page;
|
global $config, $page;
|
||||||
// hax.
|
// hax.
|
||||||
if ($page->mode == "page" && (!isset($page->blocks) || $this->count_main($page->blocks) == 0)) {
|
if ($page->mode == PageMode::PAGE && (!isset($page->blocks) || $this->count_main($page->blocks) == 0)) {
|
||||||
$h_pagename = html_escape(implode('/', $event->args));
|
$h_pagename = html_escape(implode('/', $event->args));
|
||||||
log_debug("handle_404", "Hit 404: $h_pagename");
|
log_debug("handle_404", "Hit 404: $h_pagename");
|
||||||
$page->set_code(404);
|
$page->set_code(404);
|
||||||
|
@ -8,12 +8,32 @@
|
|||||||
|
|
||||||
class FlashFileHandler extends DataHandlerExtension
|
class FlashFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
protected function create_thumb(string $hash): bool
|
|
||||||
|
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
||||||
|
{
|
||||||
|
switch ($event->ext) {
|
||||||
|
case "swf":
|
||||||
|
$event->lossless = true;
|
||||||
|
$event->video = true;
|
||||||
|
|
||||||
|
$info = getimagesize($event->file_name);
|
||||||
|
if (!$info) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->width = $info[0];
|
||||||
|
$event->height = $info[1];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function create_thumb(string $hash, string $type): bool
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
if (!create_thumbnail_ffmpeg($hash)) {
|
if (!Media::create_thumbnail_ffmpeg($hash)) {
|
||||||
copy("ext/handle_flash/thumb.jpg", warehouse_path("thumbs", $hash));
|
copy("ext/handle_flash/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -35,13 +55,7 @@ class FlashFileHandler extends DataHandlerExtension
|
|||||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||||
$image->source = $metadata['source'];
|
$image->source = $metadata['source'];
|
||||||
|
|
||||||
$info = getimagesize($filename);
|
|
||||||
if (!$info) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$image->width = $info[0];
|
|
||||||
$image->height = $info[1];
|
|
||||||
|
|
||||||
return $image;
|
return $image;
|
||||||
}
|
}
|
||||||
|
@ -5,53 +5,42 @@
|
|||||||
* Description: Handle windows icons
|
* Description: Handle windows icons
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class IcoFileHandler extends Extension
|
class IcoFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
public function onDataUpload(DataUploadEvent $event)
|
const SUPPORTED_EXTENSIONS = ["ico", "ani", "cur"];
|
||||||
|
|
||||||
|
|
||||||
|
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
||||||
{
|
{
|
||||||
if ($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
|
if(in_array($event->ext, self::SUPPORTED_EXTENSIONS)) {
|
||||||
$hash = $event->hash;
|
$event->lossless = true;
|
||||||
$ha = substr($hash, 0, 2);
|
$event->video = false;
|
||||||
move_upload_to_archive($event);
|
$event->audio = false;
|
||||||
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
|
|
||||||
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
|
|
||||||
if (is_null($image)) {
|
|
||||||
throw new UploadException("Icon handler failed to create image object from data");
|
|
||||||
}
|
|
||||||
$iae = new ImageAdditionEvent($image);
|
|
||||||
send_event($iae);
|
|
||||||
$event->image_id = $iae->image->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
|
||||||
{
|
|
||||||
global $page;
|
|
||||||
if ($this->supported_ext($event->image->ext)) {
|
|
||||||
$this->theme->display_image($page, $event->image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function supported_ext(string $ext): bool
|
|
||||||
{
|
|
||||||
$exts = ["ico", "ani", "cur"];
|
|
||||||
return in_array(strtolower($ext), $exts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function create_image_from_data(string $filename, array $metadata)
|
|
||||||
{
|
|
||||||
$image = new Image();
|
|
||||||
|
|
||||||
$fp = fopen($filename, "r");
|
|
||||||
$header = unpack("Snull/Stype/Scount", fread($fp, 6));
|
|
||||||
|
|
||||||
|
$fp = fopen($event->file_name, "r");
|
||||||
|
try {
|
||||||
|
unpack("Snull/Stype/Scount", fread($fp, 6));
|
||||||
$subheader = unpack("Cwidth/Cheight/Ccolours/Cnull/Splanes/Sbpp/Lsize/loffset", fread($fp, 16));
|
$subheader = unpack("Cwidth/Cheight/Ccolours/Cnull/Splanes/Sbpp/Lsize/loffset", fread($fp, 16));
|
||||||
|
} finally {
|
||||||
fclose($fp);
|
fclose($fp);
|
||||||
|
}
|
||||||
|
|
||||||
$width = $subheader['width'];
|
$width = $subheader['width'];
|
||||||
$height = $subheader['height'];
|
$height = $subheader['height'];
|
||||||
$image->width = $width == 0 ? 256 : $width;
|
$event->width = $width == 0 ? 256 : $width;
|
||||||
$image->height = $height == 0 ? 256 : $height;
|
$event->height = $height == 0 ? 256 : $height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function supported_ext(string $ext): bool
|
||||||
|
{
|
||||||
|
return in_array(strtolower($ext), self::SUPPORTED_EXTENSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function create_image_from_data(string $filename, array $metadata)
|
||||||
|
{
|
||||||
|
$image = new Image();
|
||||||
|
|
||||||
$image->filesize = $metadata['size'];
|
$image->filesize = $metadata['size'];
|
||||||
$image->hash = $metadata['hash'];
|
$image->hash = $metadata['hash'];
|
||||||
@ -63,7 +52,7 @@ class IcoFileHandler extends Extension
|
|||||||
return $image;
|
return $image;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function check_contents(string $file): bool
|
protected function check_contents(string $file): bool
|
||||||
{
|
{
|
||||||
if (!file_exists($file)) {
|
if (!file_exists($file)) {
|
||||||
return false;
|
return false;
|
||||||
@ -74,27 +63,14 @@ class IcoFileHandler extends Extension
|
|||||||
return ($header['null'] == 0 && ($header['type'] == 0 || $header['type'] == 1));
|
return ($header['null'] == 0 && ($header['type'] == 0 || $header['type'] == 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function create_thumb(string $hash): bool
|
protected function create_thumb(string $hash, string $type): bool
|
||||||
{
|
{
|
||||||
global $config;
|
try {
|
||||||
|
create_image_thumb($hash, $type, MediaEngine::IMAGICK);
|
||||||
$inname = warehouse_path("images", $hash);
|
|
||||||
$outname = warehouse_path("thumbs", $hash);
|
|
||||||
|
|
||||||
$tsize = get_thumbnail_size_scaled($width, $height);
|
|
||||||
$w = $tsize[0];
|
|
||||||
$h = $tsise[1];
|
|
||||||
|
|
||||||
$q = $config->get_int("thumb_quality");
|
|
||||||
$mem = $config->get_int("thumb_mem_limit") / 1024 / 1024; // IM takes memory in MB
|
|
||||||
|
|
||||||
if ($config->get_bool("ico_convert")) {
|
|
||||||
// "-limit memory $mem" broken?
|
|
||||||
exec("convert {$inname}[0] -geometry {$w}x{$h} -quality {$q} jpg:$outname");
|
|
||||||
} else {
|
|
||||||
copy($inname, $outname);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
} catch (MediaException $e) {
|
||||||
|
log_warning("handle_ico", "Could not generate thumbnail. " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,22 @@
|
|||||||
|
|
||||||
class MP3FileHandler extends DataHandlerExtension
|
class MP3FileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
protected function create_thumb(string $hash): bool
|
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
||||||
{
|
{
|
||||||
copy("ext/handle_mp3/thumb.jpg", warehouse_path("thumbs", $hash));
|
switch ($event->ext) {
|
||||||
|
case "mp3":
|
||||||
|
$event->audio = true;
|
||||||
|
$event->video = false;
|
||||||
|
$event->lossless = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO: Buff out audio format support, length scanning
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function create_thumb(string $hash, string $type): bool
|
||||||
|
{
|
||||||
|
copy("ext/handle_mp3/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +50,7 @@ class MP3FileHandler extends DataHandlerExtension
|
|||||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||||
$image->source = $metadata['source'];
|
$image->source = $metadata['source'];
|
||||||
|
|
||||||
|
|
||||||
return $image;
|
return $image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,25 +8,56 @@
|
|||||||
|
|
||||||
class PixelFileHandler extends DataHandlerExtension
|
class PixelFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
|
const SUPPORTED_EXTENSIONS = ["jpg", "jpeg", "gif", "png", "webp"];
|
||||||
|
|
||||||
|
|
||||||
|
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
||||||
|
{
|
||||||
|
if(in_array($event->ext, Media::LOSSLESS_FORMATS)) {
|
||||||
|
$event->lossless = true;
|
||||||
|
} elseif($event->ext=="webp") {
|
||||||
|
$event->lossless = Media::is_lossless_webp($event->file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(in_array($event->ext,self::SUPPORTED_EXTENSIONS)) {
|
||||||
|
if($event->lossless==null) {
|
||||||
|
$event->lossless = false;
|
||||||
|
}
|
||||||
|
$event->audio = false;
|
||||||
|
switch ($event->ext) {
|
||||||
|
case "gif":
|
||||||
|
$event->video = Media::is_animated_gif($event->file_name);
|
||||||
|
break;
|
||||||
|
case "webp":
|
||||||
|
$event->video = Media::is_animated_webp($event->file_name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$event->video = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = getimagesize($event->file_name);
|
||||||
|
if (!$info) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->width = $info[0];
|
||||||
|
$event->height = $info[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected function supported_ext(string $ext): bool
|
protected function supported_ext(string $ext): bool
|
||||||
{
|
{
|
||||||
$exts = ["jpg", "jpeg", "gif", "png", "webp"];
|
|
||||||
$ext = (($pos = strpos($ext, '?')) !== false) ? substr($ext, 0, $pos) : $ext;
|
$ext = (($pos = strpos($ext, '?')) !== false) ? substr($ext, 0, $pos) : $ext;
|
||||||
return in_array(strtolower($ext), $exts);
|
return in_array(strtolower($ext), self::SUPPORTED_EXTENSIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function create_image_from_data(string $filename, array $metadata)
|
protected function create_image_from_data(string $filename, array $metadata)
|
||||||
{
|
{
|
||||||
$image = new Image();
|
$image = new Image();
|
||||||
|
|
||||||
$info = getimagesize($filename);
|
|
||||||
if (!$info) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$image->width = $info[0];
|
|
||||||
$image->height = $info[1];
|
|
||||||
|
|
||||||
$image->filesize = $metadata['size'];
|
$image->filesize = $metadata['size'];
|
||||||
$image->hash = $metadata['hash'];
|
$image->hash = $metadata['hash'];
|
||||||
$image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename'];
|
$image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename'];
|
||||||
@ -53,26 +84,24 @@ class PixelFileHandler extends DataHandlerExtension
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function create_thumb(string $hash): bool
|
protected function create_thumb(string $hash, string $type): bool
|
||||||
{
|
{
|
||||||
global $config;
|
try {
|
||||||
|
create_image_thumb($hash, $type);
|
||||||
$inname = warehouse_path("images", $hash);
|
return true;
|
||||||
$outname = warehouse_path("thumbs", $hash);
|
} catch (InsufficientMemoryException $e) {
|
||||||
|
$tsize = get_thumbnail_max_size_scaled();
|
||||||
$ok = false;
|
$thumb = imagecreatetruecolor($tsize[0], min($tsize[1], 64));
|
||||||
|
$white = imagecolorallocate($thumb, 255, 255, 255);
|
||||||
switch ($config->get_string("thumb_engine")) {
|
$black = imagecolorallocate($thumb, 0, 0, 0);
|
||||||
default:
|
imagefill($thumb, 0, 0, $white);
|
||||||
case 'gd':
|
log_warning("handle_pixel", "Insufficient memory while creating thumbnail: ".$e->getMessage());
|
||||||
$ok = $this->make_thumb_gd($inname, $outname);
|
imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black);
|
||||||
break;
|
return true;
|
||||||
case 'convert':
|
} catch (Exception $e) {
|
||||||
$ok = create_thumbnail_convert($hash);
|
log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage());
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||||
@ -89,38 +118,4 @@ class PixelFileHandler extends DataHandlerExtension
|
|||||||
", 20);
|
", 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GD thumber {{{
|
|
||||||
private function make_thumb_gd(string $inname, string $outname): bool
|
|
||||||
{
|
|
||||||
global $config;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$info = getimagesize($inname);
|
|
||||||
$tsize = get_thumbnail_size_scaled($info[0], $info[1]);
|
|
||||||
$image = image_resize_gd(
|
|
||||||
$inname,
|
|
||||||
$info,
|
|
||||||
$tsize[0],
|
|
||||||
$tsize[1],
|
|
||||||
$outname,
|
|
||||||
$config->get_string('thumb_type'),
|
|
||||||
$config->get_int('thumb_quality')
|
|
||||||
);
|
|
||||||
} catch (InsufficientMemoryException $e) {
|
|
||||||
$tsize = get_thumbnail_max_size_scaled();
|
|
||||||
$thumb = imagecreatetruecolor($tsize[0], min($tsize[1], 64));
|
|
||||||
$white = imagecolorallocate($thumb, 255, 255, 255);
|
|
||||||
$black = imagecolorallocate($thumb, 0, 0, 0);
|
|
||||||
imagefill($thumb, 0, 0, $white);
|
|
||||||
log_warning("handle_pixel", "Insufficient memory while creating thumbnail: ".$e->getMessage());
|
|
||||||
imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black);
|
|
||||||
return true;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// }}}
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ class PixelFileHandlerTheme extends Themelet
|
|||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$u_ilink = $image->get_image_link();
|
$u_ilink = $image->get_image_link();
|
||||||
if ($config->get_bool("image_show_meta") && function_exists("exif_read_data")) {
|
if ($config->get_bool(ImageConfig::SHOW_META) && function_exists(ImageIO::EXIF_READ_FUNCTION)) {
|
||||||
# FIXME: only read from jpegs?
|
# FIXME: only read from jpegs?
|
||||||
$exif = @exif_read_data($image->get_image_filename(), 0, true);
|
$exif = @exif_read_data($image->get_image_filename(), 0, true);
|
||||||
if ($exif) {
|
if ($exif) {
|
||||||
|
@ -14,10 +14,10 @@ class HandleStatic extends Extension
|
|||||||
{
|
{
|
||||||
global $config, $page;
|
global $config, $page;
|
||||||
// hax.
|
// hax.
|
||||||
if ($page->mode == "page" && (!isset($page->blocks) || $this->count_main($page->blocks) == 0)) {
|
if ($page->mode == PageMode::PAGE && (!isset($page->blocks) || $this->count_main($page->blocks) == 0)) {
|
||||||
$h_pagename = html_escape(implode('/', $event->args));
|
$h_pagename = html_escape(implode('/', $event->args));
|
||||||
$f_pagename = preg_replace("/[^a-z_\-\.]+/", "_", $h_pagename);
|
$f_pagename = preg_replace("/[^a-z_\-\.]+/", "_", $h_pagename);
|
||||||
$theme_name = $config->get_string("theme", "default");
|
$theme_name = $config->get_string(SetupConfig::THEME, "default");
|
||||||
|
|
||||||
$theme_file = "themes/$theme_name/static/$f_pagename";
|
$theme_file = "themes/$theme_name/static/$f_pagename";
|
||||||
$static_file = "ext/handle_static/static/$f_pagename";
|
$static_file = "ext/handle_static/static/$f_pagename";
|
||||||
@ -27,7 +27,7 @@ class HandleStatic extends Extension
|
|||||||
|
|
||||||
$page->add_http_header("Cache-control: public, max-age=600");
|
$page->add_http_header("Cache-control: public, max-age=600");
|
||||||
$page->add_http_header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT');
|
$page->add_http_header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT');
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_data(file_get_contents($filename));
|
$page->set_data(file_get_contents($filename));
|
||||||
if (endsWith($filename, ".ico")) {
|
if (endsWith($filename, ".ico")) {
|
||||||
$page->set_type("image/x-icon");
|
$page->set_type("image/x-icon");
|
||||||
|
@ -7,12 +7,13 @@ TD>INPUT[type="text"],
|
|||||||
TD>INPUT[type="password"],
|
TD>INPUT[type="password"],
|
||||||
TD>INPUT[type="email"],
|
TD>INPUT[type="email"],
|
||||||
TD>SELECT,
|
TD>SELECT,
|
||||||
TD>TEXTAREA {width: 100%;}
|
TD>TEXTAREA,
|
||||||
|
TD>BUTTON {width: 100%;}
|
||||||
|
|
||||||
TABLE.form {width: 300px;}
|
TABLE.form {width: 300px;}
|
||||||
TABLE.form TD, TABLE.form TH {vertical-align: middle;}
|
TABLE.form TD, TABLE.form TH {vertical-align: middle;}
|
||||||
TABLE.form TBODY TD {text-align: left;}
|
TABLE.form TBODY TD {text-align: left;}
|
||||||
TABLE.form TBODY TH {text-align: right; padding-right: 4px; width: 1%;}
|
TABLE.form TBODY TH {text-align: right; padding-right: 4px; width: 1%; white-space: nowrap;}
|
||||||
TABLE.form TD + TH {padding-left: 8px;}
|
TABLE.form TD + TH {padding-left: 8px;}
|
||||||
|
|
||||||
*[onclick],
|
*[onclick],
|
||||||
|
@ -10,6 +10,24 @@ use enshrined\svgSanitize\Sanitizer;
|
|||||||
|
|
||||||
class SVGFileHandler extends DataHandlerExtension
|
class SVGFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
||||||
|
{
|
||||||
|
switch ($event->ext) {
|
||||||
|
case "svg":
|
||||||
|
$event->lossless = true;
|
||||||
|
$event->video = false;
|
||||||
|
$event->audio = false;
|
||||||
|
|
||||||
|
$msp = new MiniSVGParser($event->file_name);
|
||||||
|
$event->width = $msp->width;
|
||||||
|
$event->height = $msp->height;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function onDataUpload(DataUploadEvent $event)
|
public function onDataUpload(DataUploadEvent $event)
|
||||||
{
|
{
|
||||||
if ($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
|
if ($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
|
||||||
@ -19,25 +37,30 @@ class SVGFileHandler extends DataHandlerExtension
|
|||||||
$sanitizer->removeRemoteReferences(true);
|
$sanitizer->removeRemoteReferences(true);
|
||||||
$dirtySVG = file_get_contents($event->tmpname);
|
$dirtySVG = file_get_contents($event->tmpname);
|
||||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||||
file_put_contents(warehouse_path("images", $hash), $cleanSVG);
|
file_put_contents(warehouse_path(Image::IMAGE_DIR, $hash), $cleanSVG);
|
||||||
|
|
||||||
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
|
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
|
||||||
$image = $this->create_image_from_data(warehouse_path("images", $hash), $event->metadata);
|
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $hash), $event->metadata);
|
||||||
if (is_null($image)) {
|
if (is_null($image)) {
|
||||||
throw new UploadException("SVG handler failed to create image object from data");
|
throw new UploadException("SVG handler failed to create image object from data");
|
||||||
}
|
}
|
||||||
$iae = new ImageAdditionEvent($image);
|
$iae = new ImageAdditionEvent($image);
|
||||||
send_event($iae);
|
send_event($iae);
|
||||||
$event->image_id = $iae->image->id;
|
$event->image_id = $iae->image->id;
|
||||||
|
$event->merged = $iae->merged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function create_thumb(string $hash): bool
|
protected function create_thumb(string $hash, string $type): bool
|
||||||
{
|
{
|
||||||
if (!create_thumbnail_convert($hash)) {
|
try {
|
||||||
copy("ext/handle_svg/thumb.jpg", warehouse_path("thumbs", $hash));
|
create_image_thumb($hash, $type, MediaEngine::IMAGICK);
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
|
} catch (MediaException $e) {
|
||||||
|
log_warning("handle_svg", "Could not generate thumbnail. " . $e->getMessage());
|
||||||
|
copy("ext/handle_svg/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||||
@ -57,11 +80,11 @@ class SVGFileHandler extends DataHandlerExtension
|
|||||||
$hash = $image->hash;
|
$hash = $image->hash;
|
||||||
|
|
||||||
$page->set_type("image/svg+xml");
|
$page->set_type("image/svg+xml");
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
|
|
||||||
$sanitizer = new Sanitizer();
|
$sanitizer = new Sanitizer();
|
||||||
$sanitizer->removeRemoteReferences(true);
|
$sanitizer->removeRemoteReferences(true);
|
||||||
$dirtySVG = file_get_contents(warehouse_path("images", $hash));
|
$dirtySVG = file_get_contents(warehouse_path(Image::IMAGE_DIR, $hash));
|
||||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||||
$page->set_data($cleanSVG);
|
$page->set_data($cleanSVG);
|
||||||
}
|
}
|
||||||
@ -77,10 +100,6 @@ class SVGFileHandler extends DataHandlerExtension
|
|||||||
{
|
{
|
||||||
$image = new Image();
|
$image = new Image();
|
||||||
|
|
||||||
$msp = new MiniSVGParser($filename);
|
|
||||||
$image->width = $msp->width;
|
|
||||||
$image->height = $msp->height;
|
|
||||||
|
|
||||||
$image->filesize = $metadata['size'];
|
$image->filesize = $metadata['size'];
|
||||||
$image->hash = $metadata['hash'];
|
$image->hash = $metadata['hash'];
|
||||||
$image->filename = $metadata['filename'];
|
$image->filename = $metadata['filename'];
|
||||||
|
@ -16,20 +16,21 @@
|
|||||||
|
|
||||||
class VideoFileHandler extends DataHandlerExtension
|
class VideoFileHandler extends DataHandlerExtension
|
||||||
{
|
{
|
||||||
|
const SUPPORTED_MIME = [
|
||||||
|
'video/webm',
|
||||||
|
'video/mp4',
|
||||||
|
'video/ogg',
|
||||||
|
'video/flv',
|
||||||
|
'video/x-flv'
|
||||||
|
];
|
||||||
|
const SUPPORTED_EXT = ["flv", "mp4", "m4v", "ogv", "webm"];
|
||||||
|
|
||||||
public function onInitExt(InitExtEvent $event)
|
public function onInitExt(InitExtEvent $event)
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
if ($config->get_int("ext_handle_video_version") < 1) {
|
if ($config->get_int("ext_handle_video_version") < 1) {
|
||||||
if ($ffmpeg = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffmpeg')) {
|
// This used to set the ffmpeg path. It does not do this anymore, that is now in the base graphic extension.
|
||||||
//ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static
|
|
||||||
if (is_executable(strtok($ffmpeg, PHP_EOL))) {
|
|
||||||
$config->set_default_string('thumb_ffmpeg_path', 'ffmpeg');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$config->set_default_string('thumb_ffmpeg_path', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
$config->set_int("ext_handle_video_version", 1);
|
$config->set_int("ext_handle_video_version", 1);
|
||||||
log_info("handle_video", "extension installed");
|
log_info("handle_video", "extension installed");
|
||||||
}
|
}
|
||||||
@ -41,37 +42,83 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$sb = new SetupBlock("Video Options");
|
$sb = new SetupBlock("Video Options");
|
||||||
$sb->add_label("<br>Path to ffmpeg: ");
|
|
||||||
$sb->add_text_option("thumb_ffmpeg_path");
|
|
||||||
$sb->add_label("<br>");
|
|
||||||
$sb->add_bool_option("video_playback_autoplay", "Autoplay: ");
|
$sb->add_bool_option("video_playback_autoplay", "Autoplay: ");
|
||||||
$sb->add_label("<br>");
|
$sb->add_label("<br>");
|
||||||
$sb->add_bool_option("video_playback_loop", "Loop: ");
|
$sb->add_bool_option("video_playback_loop", "Loop: ");
|
||||||
$event->panel->add_block($sb);
|
$event->panel->add_block($sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
|
||||||
|
{
|
||||||
|
if(in_array($event->ext, self::SUPPORTED_EXT)) {
|
||||||
|
$event->video = true;
|
||||||
|
try {
|
||||||
|
$data = Media::get_ffprobe_data($event->file_name);
|
||||||
|
|
||||||
|
if(is_array($data)) {
|
||||||
|
if(array_key_exists("streams", $data)) {
|
||||||
|
$video = false;
|
||||||
|
$audio = true;
|
||||||
|
$streams = $data["streams"];
|
||||||
|
if (is_array($streams)) {
|
||||||
|
foreach ($streams as $stream) {
|
||||||
|
if(is_array($stream)) {
|
||||||
|
if (array_key_exists("codec_type", $stream)) {
|
||||||
|
$type = $stream["codec_type"];
|
||||||
|
switch ($type) {
|
||||||
|
case "audio":
|
||||||
|
$audio = true;
|
||||||
|
break;
|
||||||
|
case "video":
|
||||||
|
$video = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (array_key_exists("width", $stream) && !empty($stream["width"])
|
||||||
|
&& is_numeric($stream["width"]) && intval($stream["width"]) > ($event->width) ?? 0) {
|
||||||
|
$event->width = intval($stream["width"]);
|
||||||
|
}
|
||||||
|
if (array_key_exists("height", $stream) && !empty($stream["height"])
|
||||||
|
&& is_numeric($stream["height"]) && intval($stream["height"]) > ($event->height) ?? 0) {
|
||||||
|
$event->height = intval($stream["height"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$event->video = $video;
|
||||||
|
$event->audio = $audio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(array_key_exists("format", $data)&& is_array($data["format"])) {
|
||||||
|
$format = $data["format"];
|
||||||
|
if(array_key_exists("duration", $format) && is_numeric($format["duration"])) {
|
||||||
|
$event->length = floor(floatval($format["duration"]) * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(MediaException $e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the Thumbnail image for particular file.
|
* Generate the Thumbnail image for particular file.
|
||||||
*/
|
*/
|
||||||
protected function create_thumb(string $hash): bool
|
protected function create_thumb(string $hash, string $type): bool
|
||||||
{
|
{
|
||||||
return create_thumbnail_ffmpeg($hash);
|
return Media::create_thumbnail_ffmpeg($hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function supported_ext(string $ext): bool
|
protected function supported_ext(string $ext): bool
|
||||||
{
|
{
|
||||||
$exts = ["flv", "mp4", "m4v", "ogv", "webm"];
|
return in_array(strtolower($ext), self::SUPPORTED_EXT);
|
||||||
return in_array(strtolower($ext), $exts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function create_image_from_data(string $filename, array $metadata): Image
|
protected function create_image_from_data(string $filename, array $metadata): Image
|
||||||
{
|
{
|
||||||
$image = new Image();
|
$image = new Image();
|
||||||
|
|
||||||
$size = video_size($filename);
|
|
||||||
$image->width = $size[0];
|
|
||||||
$image->height = $size[1];
|
|
||||||
|
|
||||||
switch (getMimeType($filename)) {
|
switch (getMimeType($filename)) {
|
||||||
case "video/webm":
|
case "video/webm":
|
||||||
$image->ext = "webm";
|
$image->ext = "webm";
|
||||||
@ -103,13 +150,7 @@ class VideoFileHandler extends DataHandlerExtension
|
|||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
file_exists($tmpname) &&
|
file_exists($tmpname) &&
|
||||||
in_array(getMimeType($tmpname), [
|
in_array(getMimeType($tmpname), self::SUPPORTED_MIME)
|
||||||
'video/webm',
|
|
||||||
'video/mp4',
|
|
||||||
'video/ogg',
|
|
||||||
'video/flv',
|
|
||||||
'video/x-flv'
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,15 @@ class VideoFileHandlerTheme extends Themelet
|
|||||||
$loop = $config->get_bool("video_playback_loop");
|
$loop = $config->get_bool("video_playback_loop");
|
||||||
$player = make_link('vendor/bower-asset/mediaelement/build/flashmediaelement.swf');
|
$player = make_link('vendor/bower-asset/mediaelement/build/flashmediaelement.swf');
|
||||||
|
|
||||||
|
$width="auto";
|
||||||
|
if($image->width>1) {
|
||||||
|
$width = $image->width."px";
|
||||||
|
}
|
||||||
|
$height="auto";
|
||||||
|
if($image->height>1) {
|
||||||
|
$height = $image->height."px";
|
||||||
|
}
|
||||||
|
|
||||||
$html = "Video not playing? <a href='$ilink'>Click here</a> to download the file.<br/>";
|
$html = "Video not playing? <a href='$ilink'>Click here</a> to download the file.<br/>";
|
||||||
|
|
||||||
//Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
//Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
||||||
@ -48,7 +57,8 @@ class VideoFileHandlerTheme extends Themelet
|
|||||||
$loop = ($loop ? ' loop' : '');
|
$loop = ($loop ? ' loop' : '');
|
||||||
|
|
||||||
$html .= "
|
$html .= "
|
||||||
<video controls class='shm-main-image' id='main_image' alt='main image' {$autoplay} {$loop} style='max-width: 100%'>
|
<video controls class='shm-main-image' id='main_image' alt='main image' poster='$thumb_url' {$autoplay} {$loop}
|
||||||
|
style='height: $height; width: $width; max-width: 100%'>
|
||||||
<source src='{$ilink}' type='{$supportedExts[$ext]}'>
|
<source src='{$ilink}' type='{$supportedExts[$ext]}'>
|
||||||
|
|
||||||
<!-- If browser doesn't support filetype, fallback to flash -->
|
<!-- If browser doesn't support filetype, fallback to flash -->
|
||||||
|
@ -9,9 +9,9 @@ class HellBan extends Extension
|
|||||||
{
|
{
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
|
|
||||||
if ($user->can("hellbanned")) {
|
if ($user->can(Permissions::HELLBANNED)) {
|
||||||
$s = "";
|
$s = "";
|
||||||
} elseif ($user->can("view_hellbanned")) {
|
} elseif ($user->can(Permissions::VIEW_HELLBANNED)) {
|
||||||
$s = "DIV.hb, TR.hb TD {border: 1px solid red !important;}";
|
$s = "DIV.hb, TR.hb TD {border: 1px solid red !important;}";
|
||||||
} else {
|
} else {
|
||||||
$s = ".hb {display: none !important;}";
|
$s = ".hb {display: none !important;}";
|
||||||
|
BIN
ext/help_pages/baseline_help_outline_black_18dp.png
Normal file
BIN
ext/help_pages/baseline_help_outline_black_18dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 290 B |
94
ext/help_pages/main.php
Normal file
94
ext/help_pages/main.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Name: Help Pages
|
||||||
|
* Author: Matthew Barbour <matthew@darkholme.net>
|
||||||
|
* License: MIT
|
||||||
|
* Description: Provides documentation screens
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HelpPageListBuildingEvent extends Event
|
||||||
|
{
|
||||||
|
public $pages = [];
|
||||||
|
|
||||||
|
public function add_page(string $key, string $name)
|
||||||
|
{
|
||||||
|
$this->pages[$key] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class HelpPageBuildingEvent extends Event
|
||||||
|
{
|
||||||
|
public $key;
|
||||||
|
public $blocks = [];
|
||||||
|
|
||||||
|
public function __construct(string $key)
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_block(Block $block, int $position = 50)
|
||||||
|
{
|
||||||
|
if(!array_key_exists("$position",$this->blocks))
|
||||||
|
{
|
||||||
|
$this->blocks["$position"] = [];
|
||||||
|
}
|
||||||
|
$this->blocks["$position"][] = $block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HelpPages extends Extension
|
||||||
|
{
|
||||||
|
public const SEARCH = "search";
|
||||||
|
|
||||||
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
|
{
|
||||||
|
global $page, $user;
|
||||||
|
|
||||||
|
if ($event->page_matches("help")) {
|
||||||
|
$e = new HelpPageListBuildingEvent();
|
||||||
|
send_event($e);
|
||||||
|
$page->set_mode(PageMode::PAGE);
|
||||||
|
|
||||||
|
if ($event->count_args() == 0) {
|
||||||
|
$this->theme->display_list_page($e->pages);
|
||||||
|
} else {
|
||||||
|
$name = $event->get_arg(0);
|
||||||
|
$title = $name;
|
||||||
|
if(array_key_exists($name, $e->pages)) {
|
||||||
|
$title = $e->pages[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->theme->display_help_page($title);
|
||||||
|
|
||||||
|
$hpbe = new HelpPageBuildingEvent($name);
|
||||||
|
send_event($hpbe);
|
||||||
|
asort($hpbe->blocks);
|
||||||
|
|
||||||
|
foreach ($hpbe->blocks as $key=>$value) {
|
||||||
|
foreach($value as $block) {
|
||||||
|
$page->add_block($block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onHelpPageListBuilding(HelpPageListBuildingEvent $event)
|
||||||
|
{
|
||||||
|
$event->add_page("search", "Searching");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageNavBuilding(PageNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
$event->add_nav_link("help", new Link('help'), "Help");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
$event->add_link("Help", make_link("help"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
13
ext/help_pages/style.css
Normal file
13
ext/help_pages/style.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.command_example {
|
||||||
|
margin: 12pt;
|
||||||
|
padding-left: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command_example pre {
|
||||||
|
padding:4pt;
|
||||||
|
border: dashed 2px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command_example p {
|
||||||
|
padding-left: 16pt;
|
||||||
|
}
|
31
ext/help_pages/theme.php
Normal file
31
ext/help_pages/theme.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class HelpPagesTheme extends Themelet
|
||||||
|
{
|
||||||
|
|
||||||
|
public function display_list_page(array $pages)
|
||||||
|
{
|
||||||
|
global $page;
|
||||||
|
|
||||||
|
$page->set_title("Help Pages");
|
||||||
|
$page->set_heading("Help Pages");
|
||||||
|
|
||||||
|
$nav_block = new Block("Help", "", "left", 0);
|
||||||
|
foreach ($pages as $link=>$desc) {
|
||||||
|
$link = make_link("help/{$link}");
|
||||||
|
$nav_block->body .= "<a href='{$link}'>".html_escape($desc)."</a><br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$page->add_block($nav_block);
|
||||||
|
$page->add_block(new Block("Help Pages", "See list of pages to left"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function display_help_page(String $title)
|
||||||
|
{
|
||||||
|
global $page;
|
||||||
|
|
||||||
|
$page->set_title("Help - $title");
|
||||||
|
$page->set_heading("Help - $title");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,8 +22,8 @@ class Home extends Extension
|
|||||||
global $config, $page;
|
global $config, $page;
|
||||||
if ($event->page_matches("home")) {
|
if ($event->page_matches("home")) {
|
||||||
$base_href = get_base_href();
|
$base_href = get_base_href();
|
||||||
$sitename = $config->get_string('title');
|
$sitename = $config->get_string(SetupConfig::TITLE);
|
||||||
$theme_name = $config->get_string('theme');
|
$theme_name = $config->get_string(SetupConfig::THEME);
|
||||||
|
|
||||||
$body = $this->get_body();
|
$body = $this->get_body();
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ class Home extends Extension
|
|||||||
// returns just the contents of the body
|
// returns just the contents of the body
|
||||||
global $config;
|
global $config;
|
||||||
$base_href = get_base_href();
|
$base_href = get_base_href();
|
||||||
$sitename = $config->get_string('title');
|
$sitename = $config->get_string(SetupConfig::TITLE);
|
||||||
$contact_link = contact_link();
|
$contact_link = contact_link();
|
||||||
if (is_null($contact_link)) {
|
if (is_null($contact_link)) {
|
||||||
$contact_link = "";
|
$contact_link = "";
|
||||||
|
@ -4,7 +4,7 @@ class HomeTheme extends Themelet
|
|||||||
{
|
{
|
||||||
public function display_page(Page $page, $sitename, $base_href, $theme_name, $body)
|
public function display_page(Page $page, $sitename, $base_href, $theme_name, $body)
|
||||||
{
|
{
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->add_auto_html_headers();
|
$page->add_auto_html_headers();
|
||||||
$hh = $page->get_all_html_headers();
|
$hh = $page->get_all_html_headers();
|
||||||
$page->set_data(
|
$page->set_data(
|
||||||
|
@ -9,41 +9,75 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
abstract class ImageConfig {
|
||||||
|
const THUMB_ENGINE = 'thumb_engine';
|
||||||
|
const THUMB_WIDTH = 'thumb_width';
|
||||||
|
const THUMB_HEIGHT = 'thumb_height';
|
||||||
|
const THUMB_SCALING = 'thumb_scaling';
|
||||||
|
const THUMB_QUALITY = 'thumb_quality';
|
||||||
|
const THUMB_TYPE = 'thumb_type';
|
||||||
|
|
||||||
|
const SHOW_META = 'image_show_meta';
|
||||||
|
const ILINK = 'image_ilink';
|
||||||
|
const TLINK = 'image_tlink';
|
||||||
|
const TIP = 'image_tip';
|
||||||
|
const EXPIRES = 'image_expires';
|
||||||
|
const UPLOAD_COLLISION_HANDLER = 'upload_collision_handler';
|
||||||
|
|
||||||
|
const COLLISION_MERGE = 'merge';
|
||||||
|
const COLLISION_ERROR = 'error';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to handle adding / getting / removing image files from the disk.
|
* A class to handle adding / getting / removing image files from the disk.
|
||||||
*/
|
*/
|
||||||
class ImageIO extends Extension
|
class ImageIO extends Extension
|
||||||
{
|
{
|
||||||
|
|
||||||
|
const COLLISION_OPTIONS = ['Error'=>ImageConfig::COLLISION_ERROR, 'Merge'=>ImageConfig::COLLISION_MERGE];
|
||||||
|
|
||||||
|
const EXIF_READ_FUNCTION = "exif_read_data";
|
||||||
|
|
||||||
|
|
||||||
|
const THUMBNAIL_ENGINES = [
|
||||||
|
'Built-in GD' => MediaEngine::GD,
|
||||||
|
'ImageMagick' => MediaEngine::IMAGICK
|
||||||
|
];
|
||||||
|
|
||||||
|
const THUMBNAIL_TYPES = [
|
||||||
|
'JPEG' => "jpg",
|
||||||
|
'WEBP (Not IE/Safari compatible)' => "webp"
|
||||||
|
];
|
||||||
|
|
||||||
public function onInitExt(InitExtEvent $event)
|
public function onInitExt(InitExtEvent $event)
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
$config->set_default_int('thumb_width', 192);
|
$config->set_default_int(ImageConfig::THUMB_WIDTH, 192);
|
||||||
$config->set_default_int('thumb_height', 192);
|
$config->set_default_int(ImageConfig::THUMB_HEIGHT, 192);
|
||||||
$config->set_default_int('thumb_scaling', 100);
|
$config->set_default_int(ImageConfig::THUMB_SCALING, 100);
|
||||||
$config->set_default_int('thumb_quality', 75);
|
$config->set_default_int(ImageConfig::THUMB_QUALITY, 75);
|
||||||
$config->set_default_string('thumb_type', 'jpg');
|
$config->set_default_string(ImageConfig::THUMB_TYPE, 'jpg');
|
||||||
$config->set_default_int('thumb_mem_limit', parse_shorthand_int('8MB'));
|
|
||||||
$config->set_default_string('thumb_convert_path', 'convert');
|
|
||||||
|
|
||||||
if (function_exists("exif_read_data")) {
|
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||||
$config->set_default_bool('image_show_meta', false);
|
$config->set_default_bool(ImageConfig::SHOW_META, false);
|
||||||
}
|
}
|
||||||
$config->set_default_string('image_ilink', '');
|
$config->set_default_string(ImageConfig::ILINK, '');
|
||||||
$config->set_default_string('image_tlink', '');
|
$config->set_default_string(ImageConfig::TLINK, '');
|
||||||
$config->set_default_string('image_tip', '$tags // $size // $filesize');
|
$config->set_default_string(ImageConfig::TIP, '$tags // $size // $filesize');
|
||||||
$config->set_default_string('upload_collision_handler', 'error');
|
$config->set_default_string(ImageConfig::UPLOAD_COLLISION_HANDLER, ImageConfig::COLLISION_ERROR);
|
||||||
$config->set_default_int('image_expires', (60*60*24*31)); // defaults to one month
|
$config->set_default_int(ImageConfig::EXPIRES, (60*60*24*31)); // defaults to one month
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
if ($event->page_matches("image/delete")) {
|
if ($event->page_matches("image/delete")) {
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
if ($user->can("delete_image") && isset($_POST['image_id']) && $user->check_auth_token()) {
|
if ($user->can(Permissions::DELETE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
|
||||||
$image = Image::by_id($_POST['image_id']);
|
$image = Image::by_id($_POST['image_id']);
|
||||||
if ($image) {
|
if ($image) {
|
||||||
send_event(new ImageDeletionEvent($image));
|
send_event(new ImageDeletionEvent($image));
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
if (isset($_SERVER['HTTP_REFERER']) && !strstr($_SERVER['HTTP_REFERER'], 'post/view')) {
|
if (isset($_SERVER['HTTP_REFERER']) && !strstr($_SERVER['HTTP_REFERER'], 'post/view')) {
|
||||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||||
} else {
|
} else {
|
||||||
@ -53,10 +87,10 @@ class ImageIO extends Extension
|
|||||||
}
|
}
|
||||||
} elseif ($event->page_matches("image/replace")) {
|
} elseif ($event->page_matches("image/replace")) {
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
if ($user->can("replace_image") && isset($_POST['image_id']) && $user->check_auth_token()) {
|
if ($user->can(Permissions::REPLACE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
|
||||||
$image = Image::by_id($_POST['image_id']);
|
$image = Image::by_id($_POST['image_id']);
|
||||||
if ($image) {
|
if ($image) {
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link('upload/replace/'.$image->id));
|
$page->set_redirect(make_link('upload/replace/'.$image->id));
|
||||||
} else {
|
} else {
|
||||||
/* Invalid image ID */
|
/* Invalid image ID */
|
||||||
@ -76,11 +110,11 @@ class ImageIO extends Extension
|
|||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
|
|
||||||
if ($user->can("delete_image")) {
|
if ($user->can(Permissions::DELETE_IMAGE)) {
|
||||||
$event->add_part($this->theme->get_deleter_html($event->image->id));
|
$event->add_part($this->theme->get_deleter_html($event->image->id));
|
||||||
}
|
}
|
||||||
/* In the future, could perhaps allow users to replace images that they own as well... */
|
/* In the future, could perhaps allow users to replace images that they own as well... */
|
||||||
if ($user->can("replace_image")) {
|
if ($user->can(Permissions::REPLACE_IMAGE)) {
|
||||||
$event->add_part($this->theme->get_replace_html($event->image->id));
|
$event->add_part($this->theme->get_replace_html($event->image->id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +122,7 @@ class ImageIO extends Extension
|
|||||||
public function onImageAddition(ImageAdditionEvent $event)
|
public function onImageAddition(ImageAdditionEvent $event)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->add_image($event->image);
|
$this->add_image($event);
|
||||||
} catch (ImageAdditionException $e) {
|
} catch (ImageAdditionException $e) {
|
||||||
throw new UploadException($e->error);
|
throw new UploadException($e->error);
|
||||||
}
|
}
|
||||||
@ -125,60 +159,48 @@ class ImageIO extends Extension
|
|||||||
$sb = new SetupBlock("Image Options");
|
$sb = new SetupBlock("Image Options");
|
||||||
$sb->position = 30;
|
$sb->position = 30;
|
||||||
// advanced only
|
// advanced only
|
||||||
//$sb->add_text_option("image_ilink", "Image link: ");
|
//$sb->add_text_option(ImageConfig::ILINK, "Image link: ");
|
||||||
//$sb->add_text_option("image_tlink", "<br>Thumbnail link: ");
|
//$sb->add_text_option(ImageConfig::TLINK, "<br>Thumbnail link: ");
|
||||||
$sb->add_text_option("image_tip", "Image tooltip: ");
|
$sb->add_text_option(ImageConfig::TIP, "Image tooltip: ");
|
||||||
$sb->add_choice_option("upload_collision_handler", ['Error'=>'error', 'Merge'=>'merge'], "<br>Upload collision handler: ");
|
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "<br>Upload collision handler: ");
|
||||||
if (function_exists("exif_read_data")) {
|
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||||
$sb->add_bool_option("image_show_meta", "<br>Show metadata: ");
|
$sb->add_bool_option(ImageConfig::SHOW_META, "<br>Show metadata: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
$event->panel->add_block($sb);
|
$event->panel->add_block($sb);
|
||||||
|
|
||||||
$thumbers = [];
|
|
||||||
$thumbers['Built-in GD'] = "gd";
|
|
||||||
$thumbers['ImageMagick'] = "convert";
|
|
||||||
|
|
||||||
$thumb_types = [];
|
|
||||||
$thumb_types['JPEG'] = "jpg";
|
|
||||||
$thumb_types['WEBP'] = "webp";
|
|
||||||
|
|
||||||
|
|
||||||
$sb = new SetupBlock("Thumbnailing");
|
$sb = new SetupBlock("Thumbnailing");
|
||||||
$sb->add_choice_option("thumb_engine", $thumbers, "Engine: ");
|
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine: ");
|
||||||
$sb->add_label("<br>");
|
$sb->add_label("<br>");
|
||||||
$sb->add_choice_option("thumb_type", $thumb_types, "Filetype: ");
|
$sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype: ");
|
||||||
|
|
||||||
$sb->add_label("<br>Size ");
|
$sb->add_label("<br>Size ");
|
||||||
$sb->add_int_option("thumb_width");
|
$sb->add_int_option(ImageConfig::THUMB_WIDTH);
|
||||||
$sb->add_label(" x ");
|
$sb->add_label(" x ");
|
||||||
$sb->add_int_option("thumb_height");
|
$sb->add_int_option(ImageConfig::THUMB_HEIGHT);
|
||||||
$sb->add_label(" px at ");
|
$sb->add_label(" px at ");
|
||||||
$sb->add_int_option("thumb_quality");
|
$sb->add_int_option(ImageConfig::THUMB_QUALITY);
|
||||||
$sb->add_label(" % quality ");
|
$sb->add_label(" % quality ");
|
||||||
|
|
||||||
$sb->add_label("<br>High-DPI scaling ");
|
$sb->add_label("<br>High-DPI scaling ");
|
||||||
$sb->add_int_option("thumb_scaling");
|
$sb->add_int_option(ImageConfig::THUMB_SCALING);
|
||||||
$sb->add_label("%");
|
$sb->add_label("%");
|
||||||
|
|
||||||
if ($config->get_string("thumb_engine") == "convert") {
|
|
||||||
$sb->add_label("<br>ImageMagick Binary: ");
|
|
||||||
$sb->add_text_option("thumb_convert_path");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($config->get_string("thumb_engine") == "gd") {
|
|
||||||
$sb->add_shorthand_int_option("thumb_mem_limit", "<br>Max memory use: ");
|
|
||||||
}
|
|
||||||
|
|
||||||
$event->panel->add_block($sb);
|
$event->panel->add_block($sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// add image {{{
|
// add image {{{
|
||||||
private function add_image(Image $image)
|
private function add_image(ImageAdditionEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $database, $config;
|
global $user, $database, $config;
|
||||||
|
|
||||||
|
$image = $event->image;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Validate things
|
* Validate things
|
||||||
*/
|
*/
|
||||||
@ -191,8 +213,8 @@ class ImageIO extends Extension
|
|||||||
*/
|
*/
|
||||||
$existing = Image::by_hash($image->hash);
|
$existing = Image::by_hash($image->hash);
|
||||||
if (!is_null($existing)) {
|
if (!is_null($existing)) {
|
||||||
$handler = $config->get_string("upload_collision_handler");
|
$handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER);
|
||||||
if ($handler == "merge" || isset($_GET['update'])) {
|
if ($handler == ImageConfig::COLLISION_MERGE || isset($_GET['update'])) {
|
||||||
$merged = array_merge($image->get_tag_array(), $existing->get_tag_array());
|
$merged = array_merge($image->get_tag_array(), $existing->get_tag_array());
|
||||||
send_event(new TagSetEvent($existing, $merged));
|
send_event(new TagSetEvent($existing, $merged));
|
||||||
if (isset($_GET['rating']) && isset($_GET['update']) && ext_is_live("Ratings")) {
|
if (isset($_GET['rating']) && isset($_GET['update']) && ext_is_live("Ratings")) {
|
||||||
@ -201,7 +223,9 @@ class ImageIO extends Extension
|
|||||||
if (isset($_GET['source']) && isset($_GET['update'])) {
|
if (isset($_GET['source']) && isset($_GET['update'])) {
|
||||||
send_event(new SourceSetEvent($existing, $_GET['source']));
|
send_event(new SourceSetEvent($existing, $_GET['source']));
|
||||||
}
|
}
|
||||||
return null;
|
$event->merged = true;
|
||||||
|
$event->image = Image::by_id($existing->id);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
$error = "Image <a href='".make_link("post/view/{$existing->id}")."'>{$existing->id}</a> ".
|
$error = "Image <a href='".make_link("post/view/{$existing->id}")."'>{$existing->id}</a> ".
|
||||||
"already has hash {$image->hash}:<p>".$this->theme->build_thumb_html($existing);
|
"already has hash {$image->hash}:<p>".$this->theme->build_thumb_html($existing);
|
||||||
@ -217,11 +241,11 @@ class ImageIO extends Extension
|
|||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
:owner_id, :owner_ip, :filename, :filesize,
|
:owner_id, :owner_ip, :filename, :filesize,
|
||||||
:hash, :ext, :width, :height, now(), :source
|
:hash, :ext, 0, 0, now(), :source
|
||||||
)",
|
)",
|
||||||
[
|
[
|
||||||
"owner_id"=>$user->id, "owner_ip"=>$_SERVER['REMOTE_ADDR'], "filename"=>substr($image->filename, 0, 60), "filesize"=>$image->filesize,
|
"owner_id" => $user->id, "owner_ip" => $_SERVER['REMOTE_ADDR'], "filename" => substr($image->filename, 0, 255), "filesize" => $image->filesize,
|
||||||
"hash"=>$image->hash, "ext"=>strtolower($image->ext), "width"=>$image->width, "height"=>$image->height, "source"=>$image->source
|
"hash" => $image->hash, "ext" => strtolower($image->ext), "source" => $image->source
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$image->id = $database->get_last_insert_id('images_id_seq');
|
$image->id = $database->get_last_insert_id('images_id_seq');
|
||||||
@ -240,6 +264,13 @@ class ImageIO extends Extension
|
|||||||
if ($image->source !== null) {
|
if ($image->source !== null) {
|
||||||
log_info("core-image", "Source for Image #{$image->id} set to: {$image->source}");
|
log_info("core-image", "Source for Image #{$image->id} set to: {$image->source}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Media::update_image_media_properties($image->hash, strtolower($image->ext));
|
||||||
|
} catch(MediaException $e) {
|
||||||
|
log_warning("add_image","Error while running update_image_media_properties: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// }}} end add
|
// }}} end add
|
||||||
|
|
||||||
@ -251,9 +282,8 @@ class ImageIO extends Extension
|
|||||||
|
|
||||||
global $page;
|
global $page;
|
||||||
if (!is_null($image)) {
|
if (!is_null($image)) {
|
||||||
$page->set_mode("data");
|
|
||||||
if ($type == "thumb") {
|
if ($type == "thumb") {
|
||||||
$ext = $config->get_string("thumb_type");
|
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||||
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
||||||
$page->set_type(MIME_TYPE_MAP[$ext]);
|
$page->set_type(MIME_TYPE_MAP[$ext]);
|
||||||
} else {
|
} else {
|
||||||
@ -274,17 +304,20 @@ class ImageIO extends Extension
|
|||||||
$gmdate_mod = gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT';
|
$gmdate_mod = gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT';
|
||||||
|
|
||||||
if ($if_modified_since == $gmdate_mod) {
|
if ($if_modified_since == $gmdate_mod) {
|
||||||
|
$page->set_mode(PageMode::DATA);
|
||||||
$page->set_code(304);
|
$page->set_code(304);
|
||||||
$page->set_data("");
|
$page->set_data("");
|
||||||
} else {
|
} else {
|
||||||
|
$page->set_mode(PageMode::FILE);
|
||||||
$page->add_http_header("Last-Modified: $gmdate_mod");
|
$page->add_http_header("Last-Modified: $gmdate_mod");
|
||||||
if ($type != "thumb") {
|
if ($type != "thumb") {
|
||||||
$page->add_http_header("Content-Disposition: inline; filename=".$image->get_nice_image_name());
|
$page->set_filename($image->get_nice_image_name(), 'inline');
|
||||||
}
|
}
|
||||||
$page->set_data(file_get_contents($file));
|
|
||||||
|
|
||||||
if ($config->get_int("image_expires")) {
|
$page->set_file($file);
|
||||||
$expires = date(DATE_RFC1123, time() + $config->get_int("image_expires"));
|
|
||||||
|
if ($config->get_int(ImageConfig::EXPIRES)) {
|
||||||
|
$expires = date(DATE_RFC1123, time() + $config->get_int(ImageConfig::EXPIRES));
|
||||||
} else {
|
} else {
|
||||||
$expires = 'Fri, 2 Sep 2101 12:42:42 GMT'; // War was beginning
|
$expires = 'Fri, 2 Sep 2101 12:42:42 GMT'; // War was beginning
|
||||||
}
|
}
|
||||||
@ -314,10 +347,36 @@ class ImageIO extends Extension
|
|||||||
throw new ImageReplaceException("Image to replace does not exist!");
|
throw new ImageReplaceException("Image to replace does not exist!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$duplicate = Image::by_hash($image->hash);
|
||||||
|
|
||||||
|
if(!is_null($duplicate) && $duplicate->id!=$id) {
|
||||||
|
$error = "Image <a href='" . make_link("post/view/{$duplicate->id}") . "'>{$duplicate->id}</a> " .
|
||||||
|
"already has hash {$image->hash}:<p>" . $this->theme->build_thumb_html($duplicate);
|
||||||
|
throw new ImageReplaceException($error);
|
||||||
|
}
|
||||||
|
|
||||||
if (strlen(trim($image->source)) == 0) {
|
if (strlen(trim($image->source)) == 0) {
|
||||||
$image->source = $existing->get_source();
|
$image->source = $existing->get_source();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the data in the database.
|
||||||
|
$database->Execute(
|
||||||
|
"UPDATE images SET
|
||||||
|
filename = :filename, filesize = :filesize, hash = :hash,
|
||||||
|
ext = :ext, width = 0, height = 0, source = :source
|
||||||
|
WHERE
|
||||||
|
id = :id
|
||||||
|
",
|
||||||
|
[
|
||||||
|
"filename" => substr($image->filename, 0, 255),
|
||||||
|
"filesize" => $image->filesize,
|
||||||
|
"hash" => $image->hash,
|
||||||
|
"ext" => strtolower($image->ext),
|
||||||
|
"source" => $image->source,
|
||||||
|
"id" => $id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This step could be optional, ie: perhaps move the image somewhere
|
This step could be optional, ie: perhaps move the image somewhere
|
||||||
and have it stored in a 'replaced images' list that could be
|
and have it stored in a 'replaced images' list that could be
|
||||||
@ -327,20 +386,11 @@ class ImageIO extends Extension
|
|||||||
log_debug("image", "Removing image with hash " . $existing->hash);
|
log_debug("image", "Removing image with hash " . $existing->hash);
|
||||||
$existing->remove_image_only(); // Actually delete the old image file from disk
|
$existing->remove_image_only(); // Actually delete the old image file from disk
|
||||||
|
|
||||||
// Update the data in the database.
|
try {
|
||||||
$database->Execute(
|
Media::update_image_media_properties($image->hash, $image->ext);
|
||||||
"UPDATE images SET
|
} catch(MediaException $e) {
|
||||||
filename = :filename, filesize = :filesize, hash = :hash,
|
log_warning("image_replace","Error while running update_image_media_properties: ".$e->getMessage());
|
||||||
ext = :ext, width = :width, height = :height, source = :source
|
}
|
||||||
WHERE
|
|
||||||
id = :id
|
|
||||||
",
|
|
||||||
[
|
|
||||||
"filename"=>$image->filename, "filesize"=>$image->filesize, "hash"=>$image->hash,
|
|
||||||
"ext"=>strtolower($image->ext), "width"=>$image->width, "height"=>$image->height, "source"=>$image->source,
|
|
||||||
"id"=>$id
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Generate new thumbnail */
|
/* Generate new thumbnail */
|
||||||
send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->ext)));
|
send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->ext)));
|
||||||
|
@ -64,7 +64,7 @@ class ImageBan extends Extension
|
|||||||
global $database, $page, $user;
|
global $database, $page, $user;
|
||||||
|
|
||||||
if ($event->page_matches("image_hash_ban")) {
|
if ($event->page_matches("image_hash_ban")) {
|
||||||
if ($user->can("ban_image")) {
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
if ($event->get_arg(0) == "add") {
|
if ($event->get_arg(0) == "add") {
|
||||||
$image = isset($_POST['image_id']) ? Image::by_id(int_escape($_POST['image_id'])) : null;
|
$image = isset($_POST['image_id']) ? Image::by_id(int_escape($_POST['image_id'])) : null;
|
||||||
$hash = isset($_POST["hash"]) ? $_POST["hash"] : $image->hash;
|
$hash = isset($_POST["hash"]) ? $_POST["hash"] : $image->hash;
|
||||||
@ -79,7 +79,7 @@ class ImageBan extends Extension
|
|||||||
flash_message("Image deleted");
|
flash_message("Image deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||||
}
|
}
|
||||||
} elseif ($event->get_arg(0) == "remove") {
|
} elseif ($event->get_arg(0) == "remove") {
|
||||||
@ -87,7 +87,7 @@ class ImageBan extends Extension
|
|||||||
send_event(new RemoveImageHashBanEvent($_POST['hash']));
|
send_event(new RemoveImageHashBanEvent($_POST['hash']));
|
||||||
|
|
||||||
flash_message("Image ban removed");
|
flash_message("Image ban removed");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||||
}
|
}
|
||||||
} elseif ($event->get_arg(0) == "list") {
|
} elseif ($event->get_arg(0) == "list") {
|
||||||
@ -103,10 +103,21 @@ class ImageBan extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="system") {
|
||||||
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
|
$event->add_nav_link("image_bans", new Link('image_hash_ban/list/1'), "Image Bans", NavLink::is_active(["image_hash_ban"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("ban_image")) {
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
$event->add_link("Image Bans", make_link("image_hash_ban/list/1"));
|
$event->add_link("Image Bans", make_link("image_hash_ban/list/1"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,7 +141,7 @@ class ImageBan extends Extension
|
|||||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("ban_image")) {
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
$event->add_part($this->theme->get_buttons_html($event->image));
|
$event->add_part($this->theme->get_buttons_html($event->image));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,16 +233,16 @@ class Index extends Extension
|
|||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $database, $page;
|
global $database, $page, $user;
|
||||||
if ($event->page_matches("post/list")) {
|
if ($event->page_matches("post/list")) {
|
||||||
if (isset($_GET['search'])) {
|
if (isset($_GET['search'])) {
|
||||||
// implode(explode()) to resolve aliases and sanitise
|
// implode(explode()) to resolve aliases and sanitise
|
||||||
$search = url_escape(Tag::implode(Tag::explode($_GET['search'], false)));
|
$search = url_escape(Tag::implode(Tag::explode($_GET['search'], false)));
|
||||||
if (empty($search)) {
|
if (empty($search)) {
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/list/1"));
|
$page->set_redirect(make_link("post/list/1"));
|
||||||
} else {
|
} else {
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link('post/list/'.$search.'/1'));
|
$page->set_redirect(make_link('post/list/'.$search.'/1'));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -257,13 +257,32 @@ class Index extends Extension
|
|||||||
try {
|
try {
|
||||||
#log_debug("index", "Search for ".Tag::implode($search_terms), false, array("terms"=>$search_terms));
|
#log_debug("index", "Search for ".Tag::implode($search_terms), false, array("terms"=>$search_terms));
|
||||||
$total_pages = Image::count_pages($search_terms);
|
$total_pages = Image::count_pages($search_terms);
|
||||||
if (SPEED_HAX && $count_search_terms === 0 && ($page_number < 10)) { // extra caching for the first few post/list pages
|
$images = [];
|
||||||
|
|
||||||
|
if (SPEED_HAX) {
|
||||||
|
if (!$user->can("big_search")) {
|
||||||
|
$fast_page_limit = 500;
|
||||||
|
if ($total_pages > $fast_page_limit) $total_pages = $fast_page_limit;
|
||||||
|
if ($page_number > $fast_page_limit) {
|
||||||
|
$this->theme->display_error(
|
||||||
|
404, "Search limit hit",
|
||||||
|
"Only $fast_page_limit pages of results are searchable - " .
|
||||||
|
"if you want to find older results, use more specific search terms"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($count_search_terms === 0 && ($page_number < 10)) {
|
||||||
|
// extra caching for the first few post/list pages
|
||||||
$images = $database->cache->get("post-list:$page_number");
|
$images = $database->cache->get("post-list:$page_number");
|
||||||
if (!$images) {
|
if (!$images) {
|
||||||
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
|
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
|
||||||
$database->cache->set("post-list:$page_number", $images, 60);
|
$database->cache->set("post-list:$page_number", $images, 60);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$images) {
|
||||||
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
|
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
|
||||||
}
|
}
|
||||||
} catch (SearchTermParseException $stpe) {
|
} catch (SearchTermParseException $stpe) {
|
||||||
@ -278,7 +297,7 @@ class Index extends Extension
|
|||||||
$this->theme->display_intro($page);
|
$this->theme->display_intro($page);
|
||||||
send_event(new PostListBuildingEvent($search_terms));
|
send_event(new PostListBuildingEvent($search_terms));
|
||||||
} elseif ($count_search_terms > 0 && $count_images === 1 && $page_number === 1) {
|
} elseif ($count_search_terms > 0 && $count_images === 1 && $page_number === 1) {
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link('post/view/'.$images[0]->id));
|
$page->set_redirect(make_link('post/view/'.$images[0]->id));
|
||||||
} else {
|
} else {
|
||||||
$plbe = new PostListBuildingEvent($search_terms);
|
$plbe = new PostListBuildingEvent($search_terms);
|
||||||
@ -313,6 +332,29 @@ class Index extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageNavBuilding(PageNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
$event->add_nav_link("posts", new Link('post/list'), "Posts", NavLink::is_active(["post","view"]),20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->parent=="posts") {
|
||||||
|
$event->add_nav_link("posts_all", new Link('post/list'), "All");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->key===HelpPages::SEARCH) {
|
||||||
|
$block = new Block();
|
||||||
|
$block->header = "General";
|
||||||
|
$block->body = $this->theme->get_help_html();
|
||||||
|
$event->add_block($block, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function onSearchTermParse(SearchTermParseEvent $event)
|
public function onSearchTermParse(SearchTermParseEvent $event)
|
||||||
{
|
{
|
||||||
$matches = [];
|
$matches = [];
|
||||||
@ -361,6 +403,7 @@ class Index extends Extension
|
|||||||
$event->add_querylet(new Querylet('images.source LIKE :src', ["src"=>"%$source%"]));
|
$event->add_querylet(new Querylet('images.source LIKE :src', ["src"=>"%$source%"]));
|
||||||
}
|
}
|
||||||
} elseif (preg_match("/^posted([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])([0-9-]*)$/i", $event->term, $matches)) {
|
} elseif (preg_match("/^posted([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])([0-9-]*)$/i", $event->term, $matches)) {
|
||||||
|
// TODO Make this able to search = without needing a time component.
|
||||||
$cmp = ltrim($matches[1], ":") ?: "=";
|
$cmp = ltrim($matches[1], ":") ?: "=";
|
||||||
$val = $matches[2];
|
$val = $matches[2];
|
||||||
$event->add_querylet(new Querylet("images.posted $cmp :posted{$this->stpen}", ["posted{$this->stpen}"=>$val]));
|
$event->add_querylet(new Querylet("images.posted $cmp :posted{$this->stpen}", ["posted{$this->stpen}"=>$val]));
|
||||||
|
@ -157,7 +157,7 @@ class IndexTest extends ShimmiePHPUnitTestCase
|
|||||||
|
|
||||||
global $database;
|
global $database;
|
||||||
$db = $database->get_driver_name();
|
$db = $database->get_driver_name();
|
||||||
if ($db == "pgsql" || $db == "sqlite") {
|
if ($db == DatabaseDriver::PGSQL || $db == DatabaseDriver::SQLITE) {
|
||||||
$this->markTestIncomplete();
|
$this->markTestIncomplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ and of course start organising your images :-)
|
|||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
if (count($this->search_terms) == 0) {
|
if (count($this->search_terms) == 0) {
|
||||||
$page_title = $config->get_string('title');
|
$page_title = $config->get_string(SetupConfig::TITLE);
|
||||||
} else {
|
} else {
|
||||||
$search_string = implode(' ', $this->search_terms);
|
$search_string = implode(' ', $this->search_terms);
|
||||||
$page_title = html_escape($search_string);
|
$page_title = html_escape($search_string);
|
||||||
@ -144,4 +144,201 @@ and of course start organising your images :-)
|
|||||||
$this->display_paginator($page, "post/list", null, $this->page_number, $this->total_pages, true);
|
$this->display_paginator($page, "post/list", null, $this->page_number, $this->total_pages, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_help_html()
|
||||||
|
{
|
||||||
|
return '<p>Searching is largely based on tags, with a number of special keywords available that allow searching based on properties of the images.</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>tagname</pre>
|
||||||
|
<p>Returns images that are tagged with "tagname".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>tagname othertagname</pre>
|
||||||
|
<p>Returns images that are tagged with "tagname" and "othertagname".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Most tags and keywords can be prefaced with a negative sign (-) to indicate that you want to search for images that do not match something.</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>-tagname</pre>
|
||||||
|
<p>Returns images that are not tagged with "tagname".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>-tagname -othertagname</pre>
|
||||||
|
<p>Returns images that are not tagged with "tagname" and "othertagname". This is different than without the negative sign, as images with "tagname" or "othertagname" can still be returned as long as the other one is not present.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>tagname -othertagname</pre>
|
||||||
|
<p>Returns images that are tagged with "tagname", but are not tagged with "othertagname".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Wildcard searches are possible as well using * for "any one, more, or none" and ? for "any one".</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>tagn*</pre>
|
||||||
|
<p>Returns images that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>tagn?me</pre>
|
||||||
|
<p>Returns images that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>tags=1</pre>
|
||||||
|
<p>Returns images with exactly 1 tag.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>tags>0</pre>
|
||||||
|
<p>Returns images with 1 or more tags. </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Can use <, <=, >, >=, or =.</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Search for images by aspect ratio</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>ratio=4:3</pre>
|
||||||
|
<p>Returns images with an aspect ratio of 4:3.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>ratio>16:9</pre>
|
||||||
|
<p>Returns images with an aspect ratio greater than 16:9. </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Can use <, <=, >, >=, or =. The relation is calculated by dividing width by height.</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Search for images by file size</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>filesize=1</pre>
|
||||||
|
<p>Returns images exactly 1 byte in size.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>filesize>100mb</pre>
|
||||||
|
<p>Returns images greater than 100 megabytes in size. </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Can use <, <=, >, >=, or =. Supported suffixes are kb, mb, and gb. Uses multiples of 1024.</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Search for images by MD5 hash</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>hash=0D3512CAA964B2BA5D7851AF5951F33B</pre>
|
||||||
|
<p>Returns image with an MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Search for images by file type</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>filetype=jpg</pre>
|
||||||
|
<p>Returns images that are of type "jpg".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Search for images by file name</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>filename=picasso.jpg</pre>
|
||||||
|
<p>Returns images that are named "picasso.jpg".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Search for images by source</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>source=http://google.com/</pre>
|
||||||
|
<p>Returns images with a source of "http://google.com/".</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>source=any</pre>
|
||||||
|
<p>Returns images with a source set.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>source=none</pre>
|
||||||
|
<p>Returns images without a source set.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Search for images by date posted.</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>posted>=07-19-2019</pre>
|
||||||
|
<p>Returns images posted on or after 07-19-2019.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Can use <, <=, >, >=, or =. Date format is mm-dd-yyyy. Date posted includes time component, so = will not work unless the time is exact.</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Search for images by image dimensions</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>size=640x480</pre>
|
||||||
|
<p>Returns images exactly 640 pixels wide by 480 pixels high.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>size>1920x1080</pre>
|
||||||
|
<p>Returns images with a width larger than 1920 and a height larger than 1080.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>width=1000</pre>
|
||||||
|
<p>Returns images exactly 1000 pixels wide.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>height=1000</pre>
|
||||||
|
<p>Returns images exactly 1000 pixels high.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Can use <, <=, >, >=, or =.</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Sorting search results can be done using the pattern order:field_direction. _direction can be either _asc or _desc, indicating ascending (123) or descending (321) order.</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>order:id_asc</pre>
|
||||||
|
<p>Returns images sorted by ID, smallest first.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>order:width_desc</pre>
|
||||||
|
<p>Returns images sorted by width, largest first.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>These fields are supported:
|
||||||
|
<ul>
|
||||||
|
<li>id</li>
|
||||||
|
<li>width</li>
|
||||||
|
<li>height</li>
|
||||||
|
<li>filesize</li>
|
||||||
|
<li>filename</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
';
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ class IPBan extends Extension
|
|||||||
{
|
{
|
||||||
if ($event->page_matches("ip_ban")) {
|
if ($event->page_matches("ip_ban")) {
|
||||||
global $page, $user;
|
global $page, $user;
|
||||||
if ($user->can("ban_ip")) {
|
if ($user->can(Permissions::BAN_IP)) {
|
||||||
if ($event->get_arg(0) == "add" && $user->check_auth_token()) {
|
if ($event->get_arg(0) == "add" && $user->check_auth_token()) {
|
||||||
if (isset($_POST['ip']) && isset($_POST['reason']) && isset($_POST['end'])) {
|
if (isset($_POST['ip']) && isset($_POST['reason']) && isset($_POST['end'])) {
|
||||||
if (empty($_POST['end'])) {
|
if (empty($_POST['end'])) {
|
||||||
@ -77,7 +77,7 @@ class IPBan extends Extension
|
|||||||
send_event(new AddIPBanEvent($_POST['ip'], $_POST['reason'], $end));
|
send_event(new AddIPBanEvent($_POST['ip'], $_POST['reason'], $end));
|
||||||
|
|
||||||
flash_message("Ban for {$_POST['ip']} added");
|
flash_message("Ban for {$_POST['ip']} added");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("ip_ban/list"));
|
$page->set_redirect(make_link("ip_ban/list"));
|
||||||
}
|
}
|
||||||
} elseif ($event->get_arg(0) == "remove" && $user->check_auth_token()) {
|
} elseif ($event->get_arg(0) == "remove" && $user->check_auth_token()) {
|
||||||
@ -85,7 +85,7 @@ class IPBan extends Extension
|
|||||||
send_event(new RemoveIPBanEvent($_POST['id']));
|
send_event(new RemoveIPBanEvent($_POST['id']));
|
||||||
|
|
||||||
flash_message("Ban removed");
|
flash_message("Ban removed");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("ip_ban/list"));
|
$page->set_redirect(make_link("ip_ban/list"));
|
||||||
}
|
}
|
||||||
} elseif ($event->get_arg(0) == "list") {
|
} elseif ($event->get_arg(0) == "list") {
|
||||||
@ -105,10 +105,20 @@ class IPBan extends Extension
|
|||||||
$event->panel->add_block($sb);
|
$event->panel->add_block($sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="system") {
|
||||||
|
if ($user->can(Permissions::BAN_IP)) {
|
||||||
|
$event->add_nav_link("ip_bans", new Link('ip_ban/list'), "IP Bans", NavLink::is_active(["ip_ban"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("ban_ip")) {
|
if ($user->can(Permissions::BAN_IP)) {
|
||||||
$event->add_link("IP Bans", make_link("ip_ban/list"));
|
$event->add_link("IP Bans", make_link("ip_ban/list"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,7 +245,7 @@ class IPBan extends Extension
|
|||||||
{
|
{
|
||||||
global $config, $database;
|
global $config, $database;
|
||||||
|
|
||||||
$prefix = ($database->get_driver_name() == "sqlite" ? "bans." : "");
|
$prefix = ($database->get_driver_name() == DatabaseDriver::SQLITE ? "bans." : "");
|
||||||
|
|
||||||
$bans = $this->get_active_bans();
|
$bans = $this->get_active_bans();
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class IPBanTheme extends Themelet
|
|||||||
{
|
{
|
||||||
global $database, $user;
|
global $database, $user;
|
||||||
$h_bans = "";
|
$h_bans = "";
|
||||||
$prefix = ($database->get_driver_name() == "sqlite" ? "bans." : "");
|
$prefix = ($database->get_driver_name() == DatabaseDriver::SQLITE ? "bans." : "");
|
||||||
foreach ($bans as $ban) {
|
foreach ($bans as $ban) {
|
||||||
$end_human = date('Y-m-d', $ban[$prefix.'end_timestamp']);
|
$end_human = date('Y-m-d', $ban[$prefix.'end_timestamp']);
|
||||||
$h_bans .= "
|
$h_bans .= "
|
||||||
|
@ -48,7 +48,7 @@ class LogDatabase extends Extension
|
|||||||
{
|
{
|
||||||
global $database, $user;
|
global $database, $user;
|
||||||
if ($event->page_matches("log/view")) {
|
if ($event->page_matches("log/view")) {
|
||||||
if ($user->can("view_eventlog")) {
|
if ($user->can(Permissions::VIEW_EVENTLOG)) {
|
||||||
$wheres = [];
|
$wheres = [];
|
||||||
$args = [];
|
$args = [];
|
||||||
$page_num = int_escape($event->get_arg(0));
|
$page_num = int_escape($event->get_arg(0));
|
||||||
@ -68,7 +68,7 @@ class LogDatabase extends Extension
|
|||||||
$args["module"] = $_GET["module"];
|
$args["module"] = $_GET["module"];
|
||||||
}
|
}
|
||||||
if (!empty($_GET["user"])) {
|
if (!empty($_GET["user"])) {
|
||||||
if ($database->get_driver_name() == "pgsql") {
|
if ($database->get_driver_name() == DatabaseDriver::PGSQL) {
|
||||||
if (preg_match("#\d+\.\d+\.\d+\.\d+(/\d+)?#", $_GET["user"])) {
|
if (preg_match("#\d+\.\d+\.\d+\.\d+(/\d+)?#", $_GET["user"])) {
|
||||||
$wheres[] = "(username = :user1 OR text(address) = :user2)";
|
$wheres[] = "(username = :user1 OR text(address) = :user2)";
|
||||||
$args["user1"] = $_GET["user"];
|
$args["user1"] = $_GET["user"];
|
||||||
@ -120,10 +120,20 @@ class LogDatabase extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="system") {
|
||||||
|
if ($user->can(Permissions::VIEW_EVENTLOG)) {
|
||||||
|
$event->add_nav_link("event_log", new Link('log/view'), "Event Log");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("view_eventlog")) {
|
if ($user->can(Permissions::VIEW_EVENTLOG)) {
|
||||||
$event->add_link("Event Log", make_link("log/view"));
|
$event->add_link("Event Log", make_link("log/view"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ class MailTest extends Extension
|
|||||||
{
|
{
|
||||||
if ($event->page_matches("mail/test")) {
|
if ($event->page_matches("mail/test")) {
|
||||||
global $page;
|
global $page;
|
||||||
$page->set_mode("data");
|
$page->set_mode(PageMode::DATA);
|
||||||
echo "Alert: uncomment this page's code on /ext/mail/main.php starting on line 33, and change the email address. Make sure you're using a server with a domain, not localhost.";
|
echo "Alert: uncomment this page's code on /ext/mail/main.php starting on line 33, and change the email address. Make sure you're using a server with a domain, not localhost.";
|
||||||
/*
|
/*
|
||||||
echo "Preparing to send message:<br>";
|
echo "Preparing to send message:<br>";
|
||||||
|
@ -64,7 +64,7 @@ class MassTagger extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||||
$_SERVER['HTTP_REFERER'] = make_link();
|
$_SERVER['HTTP_REFERER'] = make_link();
|
||||||
}
|
}
|
||||||
|
1143
ext/media/main.php
Normal file
1143
ext/media/main.php
Normal file
File diff suppressed because it is too large
Load Diff
47
ext/media/theme.php
Normal file
47
ext/media/theme.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class MediaTheme extends Themelet
|
||||||
|
{
|
||||||
|
public function display_form(array $types)
|
||||||
|
{
|
||||||
|
global $page, $database;
|
||||||
|
|
||||||
|
$html = "Use this to force scanning for media properties.";
|
||||||
|
$html .= make_form(make_link("admin/media_rescan"));
|
||||||
|
$html .= "<table class='form'>";
|
||||||
|
$html .= "<tr><th>Image Type</th><td><select name='media_rescan_type'><option value=''>All</option>";
|
||||||
|
foreach ($types as $type) {
|
||||||
|
$html .= "<option value='".$type["ext"]."'>".$type["ext"]." (".$type["count"].")</option>";
|
||||||
|
}
|
||||||
|
$html .= "</select></td></tr>";
|
||||||
|
$html .= "<tr><td colspan='2'><input type='submit' value='Scan Media Information'></td></tr>";
|
||||||
|
$html .= "</table></form>\n";
|
||||||
|
$page->add_block(new Block("Media Tools", $html));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_buttons_html(int $image_id): string
|
||||||
|
{
|
||||||
|
return "
|
||||||
|
".make_form(make_link("media_rescan/"))."
|
||||||
|
<input type='hidden' name='image_id' value='$image_id'>
|
||||||
|
<input type='submit' value='Scan Media Properties'>
|
||||||
|
</form>
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_help_html()
|
||||||
|
{
|
||||||
|
return '<p>Search for items based on the type of media.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>content:audio</pre>
|
||||||
|
<p>Returns items that contain audio, including videos and audio files.</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>content:video</pre>
|
||||||
|
<p>Returns items that contain video, including animated GIFs.</p>
|
||||||
|
</div>
|
||||||
|
<p>These search terms depend on the items being scanned for media content. Automatic scanning was implemented in mid-2019, so items uploaded before, or items uploaded on a system without ffmpeg, will require additional scanning before this will work.</p>
|
||||||
|
';
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -58,10 +58,20 @@ class NotATag extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="tags") {
|
||||||
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
|
$event->add_nav_link("untags", new Link('untag/list/1'), "UnTags");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("ban_image")) {
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
$event->add_link("UnTags", make_link("untag/list/1"));
|
$event->add_link("UnTags", make_link("untag/list/1"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,7 +81,7 @@ class NotATag extends Extension
|
|||||||
global $database, $page, $user;
|
global $database, $page, $user;
|
||||||
|
|
||||||
if ($event->page_matches("untag")) {
|
if ($event->page_matches("untag")) {
|
||||||
if ($user->can("ban_image")) {
|
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||||
if ($event->get_arg(0) == "add") {
|
if ($event->get_arg(0) == "add") {
|
||||||
$tag = $_POST["tag"];
|
$tag = $_POST["tag"];
|
||||||
$redirect = isset($_POST['redirect']) ? $_POST['redirect'] : "DNP";
|
$redirect = isset($_POST['redirect']) ? $_POST['redirect'] : "DNP";
|
||||||
@ -81,14 +91,14 @@ class NotATag extends Extension
|
|||||||
[$tag, $redirect]
|
[$tag, $redirect]
|
||||||
);
|
);
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||||
} elseif ($event->get_arg(0) == "remove") {
|
} elseif ($event->get_arg(0) == "remove") {
|
||||||
if (isset($_POST['tag'])) {
|
if (isset($_POST['tag'])) {
|
||||||
$database->Execute("DELETE FROM untags WHERE tag = ?", [$_POST['tag']]);
|
$database->Execute("DELETE FROM untags WHERE tag = ?", [$_POST['tag']]);
|
||||||
|
|
||||||
flash_message("Image ban removed");
|
flash_message("Image ban removed");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||||
}
|
}
|
||||||
} elseif ($event->get_arg(0) == "list") {
|
} elseif ($event->get_arg(0) == "list") {
|
||||||
|
@ -100,7 +100,7 @@ class Notes extends Extension
|
|||||||
$this->revert_history($noteID, $reviewID);
|
$this->revert_history($noteID, $reviewID);
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("note/updated"));
|
$page->set_redirect(make_link("note/updated"));
|
||||||
break;
|
break;
|
||||||
case "add_note":
|
case "add_note":
|
||||||
@ -108,7 +108,7 @@ class Notes extends Extension
|
|||||||
$this->add_new_note();
|
$this->add_new_note();
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||||
break;
|
break;
|
||||||
case "add_request":
|
case "add_request":
|
||||||
@ -116,7 +116,7 @@ class Notes extends Extension
|
|||||||
$this->add_note_request();
|
$this->add_note_request();
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||||
break;
|
break;
|
||||||
case "nuke_notes":
|
case "nuke_notes":
|
||||||
@ -124,7 +124,7 @@ class Notes extends Extension
|
|||||||
$this->nuke_notes();
|
$this->nuke_notes();
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||||
break;
|
break;
|
||||||
case "nuke_requests":
|
case "nuke_requests":
|
||||||
@ -132,25 +132,25 @@ class Notes extends Extension
|
|||||||
$this->nuke_requests();
|
$this->nuke_requests();
|
||||||
}
|
}
|
||||||
|
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||||
break;
|
break;
|
||||||
case "edit_note":
|
case "edit_note":
|
||||||
if (!$user->is_anonymous()) {
|
if (!$user->is_anonymous()) {
|
||||||
$this->update_note();
|
$this->update_note();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/" . $_POST["image_id"]));
|
$page->set_redirect(make_link("post/view/" . $_POST["image_id"]));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "delete_note":
|
case "delete_note":
|
||||||
if ($user->is_admin()) {
|
if ($user->is_admin()) {
|
||||||
$this->delete_note();
|
$this->delete_note();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("note/list"));
|
$page->set_redirect(make_link("note/list"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -210,12 +210,22 @@ class Notes extends Extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)"));
|
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)"));
|
||||||
} elseif (preg_match("/^notes_by_userno[=|:](\d+)$/i", $event->term, $matches)) {
|
} elseif (preg_match("/^(notes_by_userno|notes_by_user_id)[=|:](\d+)$/i", $event->term, $matches)) {
|
||||||
$user_id = int_escape($matches[1]);
|
$user_id = int_escape($matches[2]);
|
||||||
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)"));
|
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->key===HelpPages::SEARCH) {
|
||||||
|
$block = new Block();
|
||||||
|
$block->header = "Notes";
|
||||||
|
$block->body = $this->theme->get_help_html();
|
||||||
|
$event->add_block($block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HERE WE GET ALL NOTES FOR DISPLAYED IMAGE.
|
* HERE WE GET ALL NOTES FOR DISPLAYED IMAGE.
|
||||||
|
@ -247,4 +247,29 @@ class NotesTheme extends Themelet
|
|||||||
|
|
||||||
$this->display_paginator($page, "note/updated", null, $pageNumber, $totalPages);
|
$this->display_paginator($page, "note/updated", null, $pageNumber, $totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_help_html()
|
||||||
|
{
|
||||||
|
return '<p>Search for images with notes.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>note=noted</pre>
|
||||||
|
<p>Returns images with a note matching "noted".</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>notes>0</pre>
|
||||||
|
<p>Returns images with 1 or more notes.</p>
|
||||||
|
</div>
|
||||||
|
<p>Can use <, <=, >, >=, or =.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>notes_by=username</pre>
|
||||||
|
<p>Returns images with note(s) by "username".</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>notes_by_user_id=123</pre>
|
||||||
|
<p>Returns images with note(s) by user 123.</p>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ class NumericScore extends Extension
|
|||||||
public function onUserPageBuilding(UserPageBuildingEvent $event)
|
public function onUserPageBuilding(UserPageBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("edit_other_vote")) {
|
if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
|
||||||
$this->theme->get_nuller($event->display_user);
|
$this->theme->get_nuller($event->display_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,11 +94,11 @@ class NumericScore extends Extension
|
|||||||
if (!is_null($score) && $image_id>0) {
|
if (!is_null($score) && $image_id>0) {
|
||||||
send_event(new NumericScoreSetEvent($image_id, $user, $score));
|
send_event(new NumericScoreSetEvent($image_id, $user, $score));
|
||||||
}
|
}
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/$image_id"));
|
$page->set_redirect(make_link("post/view/$image_id"));
|
||||||
}
|
}
|
||||||
} elseif ($event->page_matches("numeric_score/remove_votes_on") && $user->check_auth_token()) {
|
} elseif ($event->page_matches("numeric_score/remove_votes_on") && $user->check_auth_token()) {
|
||||||
if ($user->can("edit_other_vote")) {
|
if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
|
||||||
$image_id = int_escape($_POST['image_id']);
|
$image_id = int_escape($_POST['image_id']);
|
||||||
$database->execute(
|
$database->execute(
|
||||||
"DELETE FROM numeric_score_votes WHERE image_id=?",
|
"DELETE FROM numeric_score_votes WHERE image_id=?",
|
||||||
@ -108,13 +108,13 @@ class NumericScore extends Extension
|
|||||||
"UPDATE images SET numeric_score=0 WHERE id=?",
|
"UPDATE images SET numeric_score=0 WHERE id=?",
|
||||||
[$image_id]
|
[$image_id]
|
||||||
);
|
);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/$image_id"));
|
$page->set_redirect(make_link("post/view/$image_id"));
|
||||||
}
|
}
|
||||||
} elseif ($event->page_matches("numeric_score/remove_votes_by") && $user->check_auth_token()) {
|
} elseif ($event->page_matches("numeric_score/remove_votes_by") && $user->check_auth_token()) {
|
||||||
if ($user->can("edit_other_vote")) {
|
if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
|
||||||
$this->delete_votes_by(int_escape($_POST['user_id']));
|
$this->delete_votes_by(int_escape($_POST['user_id']));
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link());
|
$page->set_redirect(make_link());
|
||||||
}
|
}
|
||||||
} elseif ($event->page_matches("popular_by_day") || $event->page_matches("popular_by_month") || $event->page_matches("popular_by_year")) {
|
} elseif ($event->page_matches("popular_by_day") || $event->page_matches("popular_by_month") || $event->page_matches("popular_by_year")) {
|
||||||
@ -228,6 +228,16 @@ class NumericScore extends Extension
|
|||||||
$event->replace('$score', $event->image->numeric_score);
|
$event->replace('$score', $event->image->numeric_score);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->key===HelpPages::SEARCH) {
|
||||||
|
$block = new Block();
|
||||||
|
$block->header = "Numeric Score";
|
||||||
|
$block->body = $this->theme->get_help_html();
|
||||||
|
$event->add_block($block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onSearchTermParse(SearchTermParseEvent $event)
|
public function onSearchTermParse(SearchTermParseEvent $event)
|
||||||
{
|
{
|
||||||
$matches = [];
|
$matches = [];
|
||||||
@ -294,6 +304,16 @@ class NumericScore extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->parent=="posts") {
|
||||||
|
$event->add_nav_link("numeric_score_day", new Link('popular_by_day'), "Popular by Day");
|
||||||
|
$event->add_nav_link("numeric_score_month", new Link('popular_by_month'), "Popular by Month");
|
||||||
|
$event->add_nav_link("numeric_score_year", new Link('popular_by_year'), "Popular by Year");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function install()
|
private function install()
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
|
@ -32,7 +32,7 @@ class NumericScoreTheme extends Themelet
|
|||||||
<input type='submit' value='Vote Down'>
|
<input type='submit' value='Vote Down'>
|
||||||
</form>
|
</form>
|
||||||
";
|
";
|
||||||
if ($user->can("edit_other_vote")) {
|
if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
|
||||||
$html .= "
|
$html .= "
|
||||||
<form action='".make_link("numeric_score/remove_votes_on")."' method='POST'>
|
<form action='".make_link("numeric_score/remove_votes_on")."' method='POST'>
|
||||||
".$user->get_auth_html()."
|
".$user->get_auth_html()."
|
||||||
@ -87,8 +87,51 @@ class NumericScoreTheme extends Themelet
|
|||||||
|
|
||||||
$nav_html = "<a href=".make_link().">Index</a>";
|
$nav_html = "<a href=".make_link().">Index</a>";
|
||||||
|
|
||||||
$page->set_heading($config->get_string('title'));
|
$page->set_heading($config->get_string(SetupConfig::TITLE));
|
||||||
$page->add_block(new Block("Navigation", $nav_html, "left", 10));
|
$page->add_block(new Block("Navigation", $nav_html, "left", 10));
|
||||||
$page->add_block(new Block(null, $html, "main", 30));
|
$page->add_block(new Block(null, $html, "main", 30));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function get_help_html()
|
||||||
|
{
|
||||||
|
return '<p>Search for images that have received numeric scores by the score or by the scorer.</p>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>score=1</pre>
|
||||||
|
<p>Returns images with a score of 1.</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>score>0</pre>
|
||||||
|
<p>Returns images with a score of 1 or more.</p>
|
||||||
|
</div>
|
||||||
|
<p>Can use <, <=, >, >=, or =.</p>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>upvoted_by=username</pre>
|
||||||
|
<p>Returns images upvoted by "username".</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>upvoted_by_id=123</pre>
|
||||||
|
<p>Returns images upvoted by user 123.</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>downvoted_by=username</pre>
|
||||||
|
<p>Returns images downvoted by "username".</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>downvoted_by_id=123</pre>
|
||||||
|
<p>Returns images downvoted by user 123.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>order:score_desc</pre>
|
||||||
|
<p>Sorts the search results by score, descending.</p>
|
||||||
|
</div>
|
||||||
|
<div class="command_example">
|
||||||
|
<pre>order:score_asc</pre>
|
||||||
|
<p>Sorts the search results by score, ascending.</p>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class Oekaki extends Extension
|
|||||||
global $user, $page;
|
global $user, $page;
|
||||||
|
|
||||||
if ($event->page_matches("oekaki")) {
|
if ($event->page_matches("oekaki")) {
|
||||||
if ($user->can("create_image")) {
|
if ($user->can(Permissions::CREATE_IMAGE)) {
|
||||||
if ($event->get_arg(0) == "create") {
|
if ($event->get_arg(0) == "create") {
|
||||||
$this->theme->display_page();
|
$this->theme->display_page();
|
||||||
$this->theme->display_block();
|
$this->theme->display_block();
|
||||||
@ -41,7 +41,7 @@ class Oekaki extends Extension
|
|||||||
throw new UploadException("File type not recognised");
|
throw new UploadException("File type not recognised");
|
||||||
} else {
|
} else {
|
||||||
unlink($tmpname);
|
unlink($tmpname);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("post/view/".$duev->image_id));
|
$page->set_redirect(make_link("post/view/".$duev->image_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ class Oekaki extends Extension
|
|||||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
if ($user->can("create_image")) {
|
if ($user->can(Permissions::CREATE_IMAGE)) {
|
||||||
$this->theme->display_block();
|
$this->theme->display_block();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,8 +231,8 @@ class _SafeOuroborosImage
|
|||||||
$this->has_notes = false;
|
$this->has_notes = false;
|
||||||
|
|
||||||
// thumb
|
// thumb
|
||||||
$this->preview_height = $config->get_int('thumb_height');
|
$this->preview_height = $config->get_int(ImageConfig::THUMB_HEIGHT);
|
||||||
$this->preview_width = $config->get_int('thumb_width');
|
$this->preview_width = $config->get_int(ImageConfig::THUMB_WIDTH);
|
||||||
$this->preview_url = make_http($img->get_thumb_link());
|
$this->preview_url = make_http($img->get_thumb_link());
|
||||||
|
|
||||||
// sample (use the full image here)
|
// sample (use the full image here)
|
||||||
@ -404,13 +404,13 @@ class OuroborosAPI extends Extension
|
|||||||
} elseif ($this->type == 'xml') {
|
} elseif ($this->type == 'xml') {
|
||||||
$page->set_type('text/xml; charset=utf-8');
|
$page->set_type('text/xml; charset=utf-8');
|
||||||
}
|
}
|
||||||
$page->set_mode('data');
|
$page->set_mode(PageMode::DATA);
|
||||||
$this->tryAuth();
|
$this->tryAuth();
|
||||||
|
|
||||||
if ($event->page_matches('post')) {
|
if ($event->page_matches('post')) {
|
||||||
if ($this->match('create')) {
|
if ($this->match('create')) {
|
||||||
// Create
|
// Create
|
||||||
if ($user->can("create_image")) {
|
if ($user->can(Permissions::CREATE_IMAGE)) {
|
||||||
$md5 = !empty($_REQUEST['md5']) ? filter_var($_REQUEST['md5'], FILTER_SANITIZE_STRING) : null;
|
$md5 = !empty($_REQUEST['md5']) ? filter_var($_REQUEST['md5'], FILTER_SANITIZE_STRING) : null;
|
||||||
$this->postCreate(new OuroborosPost($_REQUEST['post']), $md5);
|
$this->postCreate(new OuroborosPost($_REQUEST['post']), $md5);
|
||||||
} else {
|
} else {
|
||||||
@ -464,7 +464,7 @@ class OuroborosAPI extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($event->page_matches('post/show')) {
|
} elseif ($event->page_matches('post/show')) {
|
||||||
$page->set_mode('redirect');
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link(str_replace('post/show', 'post/view', implode('/', $event->args))));
|
$page->set_redirect(make_link(str_replace('post/show', 'post/view', implode('/', $event->args))));
|
||||||
$page->display();
|
$page->display();
|
||||||
die();
|
die();
|
||||||
@ -481,8 +481,8 @@ class OuroborosAPI extends Extension
|
|||||||
protected function postCreate(OuroborosPost $post, string $md5 = '')
|
protected function postCreate(OuroborosPost $post, string $md5 = '')
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
$handler = $config->get_string("upload_collision_handler");
|
$handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER);
|
||||||
if (!empty($md5) && !($handler == 'merge')) {
|
if (!empty($md5) && !($handler == ImageConfig::COLLISION_MERGE)) {
|
||||||
$img = Image::by_hash($md5);
|
$img = Image::by_hash($md5);
|
||||||
if (!is_null($img)) {
|
if (!is_null($img)) {
|
||||||
$this->sendResponse(420, self::ERROR_POST_CREATE_DUPE);
|
$this->sendResponse(420, self::ERROR_POST_CREATE_DUPE);
|
||||||
@ -524,8 +524,8 @@ class OuroborosAPI extends Extension
|
|||||||
if (!empty($meta['hash'])) {
|
if (!empty($meta['hash'])) {
|
||||||
$img = Image::by_hash($meta['hash']);
|
$img = Image::by_hash($meta['hash']);
|
||||||
if (!is_null($img)) {
|
if (!is_null($img)) {
|
||||||
$handler = $config->get_string("upload_collision_handler");
|
$handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER);
|
||||||
if ($handler == "merge") {
|
if ($handler == ImageConfig::COLLISION_MERGE) {
|
||||||
$postTags = is_array($post->tags) ? $post->tags : Tag::explode($post->tags);
|
$postTags = is_array($post->tags) ? $post->tags : Tag::explode($post->tags);
|
||||||
$merged = array_merge($postTags, $img->get_tag_array());
|
$merged = array_merge($postTags, $img->get_tag_array());
|
||||||
send_event(new TagSetEvent($img, $merged));
|
send_event(new TagSetEvent($img, $merged));
|
||||||
|
@ -93,6 +93,19 @@ class PrivMsg extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
if($event->parent==="user") {
|
||||||
|
if (!$user->is_anonymous()) {
|
||||||
|
$count = $this->count_pms($user);
|
||||||
|
$h_count = $count > 0 ? " <span class='unread'>($count)</span>" : "";
|
||||||
|
$event->add_nav_link("pm", new Link('user#private-messages'), "Private Messages$h_count");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $user;
|
global $user;
|
||||||
@ -108,7 +121,7 @@ class PrivMsg extends Extension
|
|||||||
global $page, $user;
|
global $page, $user;
|
||||||
$duser = $event->display_user;
|
$duser = $event->display_user;
|
||||||
if (!$user->is_anonymous() && !$duser->is_anonymous()) {
|
if (!$user->is_anonymous() && !$duser->is_anonymous()) {
|
||||||
if (($user->id == $duser->id) || $user->can("view_other_pms")) {
|
if (($user->id == $duser->id) || $user->can(Permissions::VIEW_OTHER_PMS)) {
|
||||||
$this->theme->display_pms($page, $this->get_pms($duser));
|
$this->theme->display_pms($page, $this->get_pms($duser));
|
||||||
}
|
}
|
||||||
if ($user->id != $duser->id) {
|
if ($user->id != $duser->id) {
|
||||||
@ -128,7 +141,7 @@ class PrivMsg extends Extension
|
|||||||
$pm = $database->get_row("SELECT * FROM private_message WHERE id = :id", ["id" => $pm_id]);
|
$pm = $database->get_row("SELECT * FROM private_message WHERE id = :id", ["id" => $pm_id]);
|
||||||
if (is_null($pm)) {
|
if (is_null($pm)) {
|
||||||
$this->theme->display_error(404, "No such PM", "There is no PM #$pm_id");
|
$this->theme->display_error(404, "No such PM", "There is no PM #$pm_id");
|
||||||
} elseif (($pm["to_id"] == $user->id) || $user->can("view_other_pms")) {
|
} elseif (($pm["to_id"] == $user->id) || $user->can(Permissions::VIEW_OTHER_PMS)) {
|
||||||
$from_user = User::by_id(int_escape($pm["from_id"]));
|
$from_user = User::by_id(int_escape($pm["from_id"]));
|
||||||
if ($pm["to_id"] == $user->id) {
|
if ($pm["to_id"] == $user->id) {
|
||||||
$database->execute("UPDATE private_message SET is_read='Y' WHERE id = :id", ["id" => $pm_id]);
|
$database->execute("UPDATE private_message SET is_read='Y' WHERE id = :id", ["id" => $pm_id]);
|
||||||
@ -145,11 +158,11 @@ class PrivMsg extends Extension
|
|||||||
$pm = $database->get_row("SELECT * FROM private_message WHERE id = :id", ["id" => $pm_id]);
|
$pm = $database->get_row("SELECT * FROM private_message WHERE id = :id", ["id" => $pm_id]);
|
||||||
if (is_null($pm)) {
|
if (is_null($pm)) {
|
||||||
$this->theme->display_error(404, "No such PM", "There is no PM #$pm_id");
|
$this->theme->display_error(404, "No such PM", "There is no PM #$pm_id");
|
||||||
} elseif (($pm["to_id"] == $user->id) || $user->can("view_other_pms")) {
|
} elseif (($pm["to_id"] == $user->id) || $user->can(Permissions::VIEW_OTHER_PMS)) {
|
||||||
$database->execute("DELETE FROM private_message WHERE id = :id", ["id" => $pm_id]);
|
$database->execute("DELETE FROM private_message WHERE id = :id", ["id" => $pm_id]);
|
||||||
$database->cache->delete("pm-count-{$user->id}");
|
$database->cache->delete("pm-count-{$user->id}");
|
||||||
log_info("pm", "Deleted PM #$pm_id", "PM deleted");
|
log_info("pm", "Deleted PM #$pm_id", "PM deleted");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect($_SERVER["HTTP_REFERER"]);
|
$page->set_redirect($_SERVER["HTTP_REFERER"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,7 +175,7 @@ class PrivMsg extends Extension
|
|||||||
$message = $_POST["message"];
|
$message = $_POST["message"];
|
||||||
send_event(new SendPMEvent(new PM($from_id, $_SERVER["REMOTE_ADDR"], $to_id, $subject, $message)));
|
send_event(new SendPMEvent(new PM($from_id, $_SERVER["REMOTE_ADDR"], $to_id, $subject, $message)));
|
||||||
flash_message("PM sent");
|
flash_message("PM sent");
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect($_SERVER["HTTP_REFERER"]);
|
$page->set_redirect($_SERVER["HTTP_REFERER"]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -27,7 +27,7 @@ class PrivMsgTheme extends Themelet
|
|||||||
$h_subject = "<b>$h_subject</b>";
|
$h_subject = "<b>$h_subject</b>";
|
||||||
$readYN = "N";
|
$readYN = "N";
|
||||||
}
|
}
|
||||||
$hb = $from->can("hellbanned") ? "hb" : "";
|
$hb = $from->can(Permissions::HELLBANNED) ? "hb" : "";
|
||||||
$html .= "<tr class='$hb'>
|
$html .= "<tr class='$hb'>
|
||||||
<td>$readYN</td>
|
<td>$readYN</td>
|
||||||
<td><a href='$pm_url'>$h_subject</a></td>
|
<td><a href='$pm_url'>$h_subject</a></td>
|
||||||
|
@ -9,6 +9,18 @@
|
|||||||
* Useful for related images like in a comic, etc.
|
* Useful for related images like in a comic, etc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
abstract class PoolsConfig
|
||||||
|
{
|
||||||
|
const MAX_IMPORT_RESULTS = "poolsMaxImportResults";
|
||||||
|
const IMAGES_PER_PAGE = "poolsImagesPerPage";
|
||||||
|
const LISTS_PER_PAGE = "poolsListsPerPage";
|
||||||
|
const UPDATED_PER_PAGE = "poolsUpdatedPerPage";
|
||||||
|
const INFO_ON_VIEW_IMAGE = "poolsInfoOnViewImage";
|
||||||
|
const ADDER_ON_VIEW_IMAGE = "poolsAdderOnViewImage";
|
||||||
|
const SHOW_NAV_LINKS = "poolsShowNavLinks";
|
||||||
|
const AUTO_INCREMENT_ORDER = "poolsAutoIncrementOrder";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is just a wrapper around SCoreException.
|
* This class is just a wrapper around SCoreException.
|
||||||
*/
|
*/
|
||||||
@ -23,6 +35,41 @@ class PoolCreationException extends SCoreException
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PoolAddPostsEvent extends Event
|
||||||
|
{
|
||||||
|
public $pool_id;
|
||||||
|
|
||||||
|
public $posts = [];
|
||||||
|
|
||||||
|
public function __construct(int $pool_id, array $posts)
|
||||||
|
{
|
||||||
|
$this->pool_id = $pool_id;
|
||||||
|
$this->posts = $posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class PoolCreationEvent extends Event
|
||||||
|
{
|
||||||
|
public $title;
|
||||||
|
public $user;
|
||||||
|
public $public;
|
||||||
|
public $description;
|
||||||
|
|
||||||
|
public $new_id = -1;
|
||||||
|
|
||||||
|
public function __construct(string $title, User $pool_user = null, bool $public = false, string $description = "")
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
|
||||||
|
$this->title = $title;
|
||||||
|
$this->user = $pool_user ?? $user;
|
||||||
|
$this->public = $public;
|
||||||
|
$this->description = $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class Pools extends Extension
|
class Pools extends Extension
|
||||||
{
|
{
|
||||||
public function onInitExt(InitExtEvent $event)
|
public function onInitExt(InitExtEvent $event)
|
||||||
@ -30,14 +77,14 @@ class Pools extends Extension
|
|||||||
global $config, $database;
|
global $config, $database;
|
||||||
|
|
||||||
// Set the defaults for the pools extension
|
// Set the defaults for the pools extension
|
||||||
$config->set_default_int("poolsMaxImportResults", 1000);
|
$config->set_default_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000);
|
||||||
$config->set_default_int("poolsImagesPerPage", 20);
|
$config->set_default_int(PoolsConfig::IMAGES_PER_PAGE, 20);
|
||||||
$config->set_default_int("poolsListsPerPage", 20);
|
$config->set_default_int(PoolsConfig::LISTS_PER_PAGE, 20);
|
||||||
$config->set_default_int("poolsUpdatedPerPage", 20);
|
$config->set_default_int(PoolsConfig::UPDATED_PER_PAGE, 20);
|
||||||
$config->set_default_bool("poolsInfoOnViewImage", false);
|
$config->set_default_bool(PoolsConfig::INFO_ON_VIEW_IMAGE, false);
|
||||||
$config->set_default_bool("poolsAdderOnViewImage", false);
|
$config->set_default_bool(PoolsConfig::ADDER_ON_VIEW_IMAGE, false);
|
||||||
$config->set_default_bool("poolsShowNavLinks", false);
|
$config->set_default_bool(PoolsConfig::SHOW_NAV_LINKS, false);
|
||||||
$config->set_default_bool("poolsAutoIncrementOrder", false);
|
$config->set_default_bool(PoolsConfig::AUTO_INCREMENT_ORDER, false);
|
||||||
|
|
||||||
// Create the database tables
|
// Create the database tables
|
||||||
if ($config->get_int("ext_pools_version") < 1) {
|
if ($config->get_int("ext_pools_version") < 1) {
|
||||||
@ -86,21 +133,38 @@ class Pools extends Extension
|
|||||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$sb = new SetupBlock("Pools");
|
$sb = new SetupBlock("Pools");
|
||||||
$sb->add_int_option("poolsMaxImportResults", "Max results on import: ");
|
$sb->add_int_option(PoolsConfig::MAX_IMPORT_RESULTS, "Max results on import: ");
|
||||||
$sb->add_int_option("poolsImagesPerPage", "<br>Images per page: ");
|
$sb->add_int_option(PoolsConfig::IMAGES_PER_PAGE, "<br>Images per page: ");
|
||||||
$sb->add_int_option("poolsListsPerPage", "<br>Index list items per page: ");
|
$sb->add_int_option(PoolsConfig::LISTS_PER_PAGE, "<br>Index list items per page: ");
|
||||||
$sb->add_int_option("poolsUpdatedPerPage", "<br>Updated list items per page: ");
|
$sb->add_int_option(PoolsConfig::UPDATED_PER_PAGE, "<br>Updated list items per page: ");
|
||||||
$sb->add_bool_option("poolsInfoOnViewImage", "<br>Show pool info on image: ");
|
$sb->add_bool_option(PoolsConfig::INFO_ON_VIEW_IMAGE, "<br>Show pool info on image: ");
|
||||||
$sb->add_bool_option("poolsShowNavLinks", "<br>Show 'Prev' & 'Next' links when viewing pool images: ");
|
$sb->add_bool_option(PoolsConfig::SHOW_NAV_LINKS, "<br>Show 'Prev' & 'Next' links when viewing pool images: ");
|
||||||
$sb->add_bool_option("poolsAutoIncrementOrder", "<br>Autoincrement order when post is added to pool:");
|
$sb->add_bool_option(PoolsConfig::AUTO_INCREMENT_ORDER, "<br>Autoincrement order when post is added to pool:");
|
||||||
//$sb->add_bool_option("poolsAdderOnViewImage", "<br>Show pool adder on image: ");
|
//$sb->add_bool_option(PoolsConfig::ADDER_ON_VIEW_IMAGE, "<br>Show pool adder on image: ");
|
||||||
|
|
||||||
$event->panel->add_block($sb);
|
$event->panel->add_block($sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onPageNavBuilding(PageNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
$event->add_nav_link("pool", new Link('pool/list'), "Pools");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->parent=="pool") {
|
||||||
|
$event->add_nav_link("pool_list", new Link('pool/list'), "List");
|
||||||
|
$event->add_nav_link("pool_new", new Link('pool/new'), "Create");
|
||||||
|
$event->add_nav_link("pool_updated", new Link('pool/updated'), "Changes");
|
||||||
|
$event->add_nav_link("pool_help", new Link('ext_doc/pools'), "Help");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $page, $user;
|
global $page, $user, $database;
|
||||||
|
|
||||||
if ($event->page_matches("pool")) {
|
if ($event->page_matches("pool")) {
|
||||||
$pool_id = 0;
|
$pool_id = 0;
|
||||||
@ -129,9 +193,16 @@ class Pools extends Extension
|
|||||||
|
|
||||||
case "create": // ADD _POST
|
case "create": // ADD _POST
|
||||||
try {
|
try {
|
||||||
$newPoolID = $this->add_pool();
|
$title = $_POST["title"];
|
||||||
$page->set_mode("redirect");
|
$event = new PoolCreationEvent(
|
||||||
$page->set_redirect(make_link("pool/view/".$newPoolID));
|
$title,
|
||||||
|
$user,
|
||||||
|
$_POST["public"] === "Y",
|
||||||
|
$_POST["description"]);
|
||||||
|
|
||||||
|
send_event($event);
|
||||||
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
|
$page->set_redirect(make_link("pool/view/" . $event->new_id));
|
||||||
} catch (PoolCreationException $e) {
|
} catch (PoolCreationException $e) {
|
||||||
$this->theme->display_error(400, "Error", $e->error);
|
$this->theme->display_error(400, "Error", $e->error);
|
||||||
}
|
}
|
||||||
@ -150,7 +221,7 @@ class Pools extends Extension
|
|||||||
if (!$user->is_anonymous()) {
|
if (!$user->is_anonymous()) {
|
||||||
$historyID = int_escape($event->get_arg(1));
|
$historyID = int_escape($event->get_arg(1));
|
||||||
$this->revert_history($historyID);
|
$this->revert_history($historyID);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/updated"));
|
$page->set_redirect(make_link("pool/updated"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -159,7 +230,7 @@ class Pools extends Extension
|
|||||||
if ($this->have_permission($user, $pool)) {
|
if ($this->have_permission($user, $pool)) {
|
||||||
$this->theme->edit_pool($page, $this->get_pool($pool_id), $this->edit_posts($pool_id));
|
$this->theme->edit_pool($page, $this->get_pool($pool_id), $this->edit_posts($pool_id));
|
||||||
} else {
|
} else {
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -169,13 +240,13 @@ class Pools extends Extension
|
|||||||
if ($this->have_permission($user, $pool)) {
|
if ($this->have_permission($user, $pool)) {
|
||||||
$this->theme->edit_order($page, $this->get_pool($pool_id), $this->edit_order($pool_id));
|
$this->theme->edit_order($page, $this->get_pool($pool_id), $this->edit_order($pool_id));
|
||||||
} else {
|
} else {
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($this->have_permission($user, $pool)) {
|
if ($this->have_permission($user, $pool)) {
|
||||||
$this->order_posts();
|
$this->order_posts();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||||
} else {
|
} else {
|
||||||
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
||||||
@ -193,8 +264,12 @@ class Pools extends Extension
|
|||||||
|
|
||||||
case "add_posts":
|
case "add_posts":
|
||||||
if ($this->have_permission($user, $pool)) {
|
if ($this->have_permission($user, $pool)) {
|
||||||
$this->add_posts();
|
$images = [];
|
||||||
$page->set_mode("redirect");
|
foreach ($_POST['check'] as $imageID) {
|
||||||
|
$images[] = $imageID;
|
||||||
|
}
|
||||||
|
send_event(new PoolAddPostsEvent($pool_id, $images));
|
||||||
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||||
} else {
|
} else {
|
||||||
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
||||||
@ -204,7 +279,7 @@ class Pools extends Extension
|
|||||||
case "remove_posts":
|
case "remove_posts":
|
||||||
if ($this->have_permission($user, $pool)) {
|
if ($this->have_permission($user, $pool)) {
|
||||||
$this->remove_posts();
|
$this->remove_posts();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||||
} else {
|
} else {
|
||||||
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
||||||
@ -215,7 +290,7 @@ class Pools extends Extension
|
|||||||
case "edit_description":
|
case "edit_description":
|
||||||
if ($this->have_permission($user, $pool)) {
|
if ($this->have_permission($user, $pool)) {
|
||||||
$this->edit_description();
|
$this->edit_description();
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||||
} else {
|
} else {
|
||||||
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
||||||
@ -228,7 +303,7 @@ class Pools extends Extension
|
|||||||
// -> Only admins and owners may do this
|
// -> Only admins and owners may do this
|
||||||
if ($user->is_admin() || $user->id == $pool['user_id']) {
|
if ($user->is_admin() || $user->id == $pool['user_id']) {
|
||||||
$this->nuke_pool($pool_id);
|
$this->nuke_pool($pool_id);
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/list"));
|
$page->set_redirect(make_link("pool/list"));
|
||||||
} else {
|
} else {
|
||||||
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
||||||
@ -236,7 +311,7 @@ class Pools extends Extension
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$page->set_mode("redirect");
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
$page->set_redirect(make_link("pool/list"));
|
$page->set_redirect(make_link("pool/list"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -260,11 +335,11 @@ class Pools extends Extension
|
|||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
if ($config->get_bool("poolsInfoOnViewImage")) {
|
if ($config->get_bool(PoolsConfig::INFO_ON_VIEW_IMAGE)) {
|
||||||
$imageID = $event->image->id;
|
$imageID = $event->image->id;
|
||||||
$poolsIDs = $this->get_pool_ids($imageID);
|
$poolsIDs = $this->get_pool_ids($imageID);
|
||||||
|
|
||||||
$show_nav = $config->get_bool("poolsShowNavLinks", false);
|
$show_nav = $config->get_bool(PoolsConfig::SHOW_NAV_LINKS, false);
|
||||||
|
|
||||||
$navInfo = [];
|
$navInfo = [];
|
||||||
foreach ($poolsIDs as $poolID) {
|
foreach ($poolsIDs as $poolID) {
|
||||||
@ -285,7 +360,7 @@ class Pools extends Extension
|
|||||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||||
{
|
{
|
||||||
global $config, $database, $user;
|
global $config, $database, $user;
|
||||||
if ($config->get_bool("poolsAdderOnViewImage") && !$user->is_anonymous()) {
|
if ($config->get_bool(PoolsConfig::ADDER_ON_VIEW_IMAGE) && !$user->is_anonymous()) {
|
||||||
if ($user->is_admin()) {
|
if ($user->is_admin()) {
|
||||||
$pools = $database->get_all("SELECT * FROM pools");
|
$pools = $database->get_all("SELECT * FROM pools");
|
||||||
} else {
|
} else {
|
||||||
@ -297,6 +372,17 @@ class Pools extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
|
||||||
|
{
|
||||||
|
if($event->key===HelpPages::SEARCH) {
|
||||||
|
$block = new Block();
|
||||||
|
$block->header = "Pools";
|
||||||
|
$block->body = $this->theme->get_help_html();
|
||||||
|
$event->add_block($block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function onSearchTermParse(SearchTermParseEvent $event)
|
public function onSearchTermParse(SearchTermParseEvent $event)
|
||||||
{
|
{
|
||||||
$matches = [];
|
$matches = [];
|
||||||
@ -350,6 +436,46 @@ class Pools extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||||
|
{
|
||||||
|
global $user, $database;
|
||||||
|
|
||||||
|
$pools = $database->get_all("SELECT * FROM pools ORDER BY title ");
|
||||||
|
|
||||||
|
|
||||||
|
$event->add_action("bulk_pool_add_existing", "Add To (P)ool", "p","", $this->theme->get_bulk_pool_selector($pools));
|
||||||
|
$event->add_action("bulk_pool_add_new", "Create Pool", "","", $this->theme->get_bulk_pool_input($event->search_terms));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onBulkAction(BulkActionEvent $event)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
|
||||||
|
switch ($event->action) {
|
||||||
|
case "bulk_pool_add_existing":
|
||||||
|
if (!isset($_POST['bulk_pool_select'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$pool_id = intval($_POST['bulk_pool_select']);
|
||||||
|
$pool = $this->get_pool($pool_id);
|
||||||
|
|
||||||
|
if ($this->have_permission($user, $pool)) {
|
||||||
|
send_event(
|
||||||
|
new PoolAddPostsEvent($pool_id,iterator_map_to_array("image_to_id", $event->items)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "bulk_pool_add_new":
|
||||||
|
if (!isset($_POST['bulk_pool_new'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$new_pool_title = $_POST['bulk_pool_new'];
|
||||||
|
$pce = new PoolCreationEvent($new_pool_title);
|
||||||
|
send_event($pce);
|
||||||
|
send_event(new PoolAddPostsEvent($pce->new_id, iterator_map_to_array("image_to_id", $event->items)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------- */
|
/* ------------------------------------------------- */
|
||||||
/* -------------- Private Functions -------------- */
|
/* -------------- Private Functions -------------- */
|
||||||
/* ------------------------------------------------- */
|
/* ------------------------------------------------- */
|
||||||
@ -378,7 +504,7 @@ class Pools extends Extension
|
|||||||
|
|
||||||
$pageNumber = clamp($pageNumber, 1, null) - 1;
|
$pageNumber = clamp($pageNumber, 1, null) - 1;
|
||||||
|
|
||||||
$poolsPerPage = $config->get_int("poolsListsPerPage");
|
$poolsPerPage = $config->get_int(PoolsConfig::LISTS_PER_PAGE);
|
||||||
|
|
||||||
$order_by = "";
|
$order_by = "";
|
||||||
$order = $page->get_cookie("ui-order-pool");
|
$order = $page->get_cookie("ui-order-pool");
|
||||||
@ -411,31 +537,32 @@ class Pools extends Extension
|
|||||||
/**
|
/**
|
||||||
* HERE WE CREATE A NEW POOL
|
* HERE WE CREATE A NEW POOL
|
||||||
*/
|
*/
|
||||||
private function add_pool(): int
|
public function onPoolCreation(PoolCreationEvent $event)
|
||||||
{
|
{
|
||||||
global $user, $database;
|
global $user, $database;
|
||||||
|
|
||||||
if ($user->is_anonymous()) {
|
if ($user->is_anonymous()) {
|
||||||
throw new PoolCreationException("You must be registered and logged in to add a image.");
|
throw new PoolCreationException("You must be registered and logged in to add a image.");
|
||||||
}
|
}
|
||||||
if (empty($_POST["title"])) {
|
if (empty($event->title)) {
|
||||||
throw new PoolCreationException("Pool title is empty.");
|
throw new PoolCreationException("Pool title is empty.");
|
||||||
}
|
}
|
||||||
if ($this->get_single_pool_from_title($_POST["title"])) {
|
if ($this->get_single_pool_from_title($event->title)) {
|
||||||
throw new PoolCreationException("A pool using this title already exists.");
|
throw new PoolCreationException("A pool using this title already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$public = $_POST["public"] === "Y" ? "Y" : "N";
|
|
||||||
$database->execute(
|
$database->execute(
|
||||||
"
|
"
|
||||||
INSERT INTO pools (user_id, public, title, description, date)
|
INSERT INTO pools (user_id, public, title, description, date)
|
||||||
VALUES (:uid, :public, :title, :desc, now())",
|
VALUES (:uid, :public, :title, :desc, now())",
|
||||||
["uid"=>$user->id, "public"=>$public, "title"=>$_POST["title"], "desc"=>$_POST["description"]]
|
["uid" => $event->user->id, "public" => $event->public ? "Y" : "N", "title" => $event->title, "desc" => $event->description]
|
||||||
);
|
);
|
||||||
|
|
||||||
$poolID = $database->get_last_insert_id('pools_id_seq');
|
$poolID = $database->get_last_insert_id('pools_id_seq');
|
||||||
log_info("pools", "Pool {$poolID} created by {$user->name}");
|
log_info("pools", "Pool {$poolID} created by {$user->name}");
|
||||||
return $poolID;
|
|
||||||
|
$event->new_id = $poolID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -461,7 +588,7 @@ class Pools extends Extension
|
|||||||
/**
|
/**
|
||||||
* Retrieve information about a pool given a pool title.
|
* Retrieve information about a pool given a pool title.
|
||||||
*/
|
*/
|
||||||
private function get_single_pool_from_title(string $poolTitle): array
|
private function get_single_pool_from_title(string $poolTitle): ?array
|
||||||
{
|
{
|
||||||
global $database;
|
global $database;
|
||||||
return $database->get_row("SELECT * FROM pools WHERE title=:title", ["title" => $poolTitle]);
|
return $database->get_row("SELECT * FROM pools WHERE title=:title", ["title" => $poolTitle]);
|
||||||
@ -493,7 +620,7 @@ class Pools extends Extension
|
|||||||
{
|
{
|
||||||
global $page, $config;
|
global $page, $config;
|
||||||
|
|
||||||
$poolsMaxResults = $config->get_int("poolsMaxImportResults", 1000);
|
$poolsMaxResults = $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000);
|
||||||
|
|
||||||
$images = $images = Image::find_images(0, $poolsMaxResults, Tag::explode($_POST["pool_tag"]));
|
$images = $images = Image::find_images(0, $poolsMaxResults, Tag::explode($_POST["pool_tag"]));
|
||||||
$this->theme->pool_result($page, $images, $this->get_pool($pool_id));
|
$this->theme->pool_result($page, $images, $this->get_pool($pool_id));
|
||||||
@ -503,41 +630,27 @@ class Pools extends Extension
|
|||||||
/**
|
/**
|
||||||
* HERE WE ADD CHECKED IMAGES FROM POOL AND UPDATE THE HISTORY
|
* HERE WE ADD CHECKED IMAGES FROM POOL AND UPDATE THE HISTORY
|
||||||
*
|
*
|
||||||
* TODO: Fix this so that the pool ID and images are passed as Arguments to the function.
|
|
||||||
*/
|
*/
|
||||||
private function add_posts(): int
|
public function onPoolAddPosts(PoolAddPostsEvent $event)
|
||||||
{
|
{
|
||||||
global $database;
|
global $database, $user;
|
||||||
|
|
||||||
|
$pool = $this->get_single_pool($event->pool_id);
|
||||||
|
if (!$this->have_permission($user, $pool)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$poolID = int_escape($_POST['pool_id']);
|
|
||||||
$images = " ";
|
$images = " ";
|
||||||
|
foreach ($event->posts as $post_id) {
|
||||||
foreach ($_POST['check'] as $imageID) {
|
if ($this->add_post($event->pool_id, $post_id, false)) {
|
||||||
if (!$this->check_post($poolID, $imageID)) {
|
$images .= " " . $post_id;
|
||||||
$database->execute(
|
|
||||||
"
|
|
||||||
INSERT INTO pool_images (pool_id, image_id)
|
|
||||||
VALUES (:pid, :iid)",
|
|
||||||
["pid"=>$poolID, "iid"=>$imageID]
|
|
||||||
);
|
|
||||||
|
|
||||||
$images .= " ".$imageID;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strlen($images) == 0) {
|
if (!strlen($images) == 0) {
|
||||||
$count = int_escape($database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid"=>$poolID]));
|
$count = int_escape($database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid" => $event->pool_id]));
|
||||||
$this->add_history($poolID, 1, $images, $count);
|
$this->add_history($event->pool_id, 1, $images, $count);
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->Execute(
|
|
||||||
"
|
|
||||||
UPDATE pools
|
|
||||||
SET posts=(SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid)
|
|
||||||
WHERE id=:pid",
|
|
||||||
["pid"=>$poolID]
|
|
||||||
);
|
|
||||||
return $poolID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -682,20 +795,28 @@ class Pools extends Extension
|
|||||||
$poolID = int_escape($poolID);
|
$poolID = int_escape($poolID);
|
||||||
$pool = $this->get_pool($poolID);
|
$pool = $this->get_pool($poolID);
|
||||||
|
|
||||||
$imagesPerPage = $config->get_int("poolsImagesPerPage");
|
$imagesPerPage = $config->get_int(PoolsConfig::IMAGES_PER_PAGE);
|
||||||
|
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
INNER JOIN images AS i ON i.id = p.image_id
|
||||||
|
WHERE p.pool_id = :pid
|
||||||
|
";
|
||||||
|
|
||||||
|
|
||||||
// WE CHECK IF THE EXTENSION RATING IS INSTALLED, WHICH VERSION AND IF IT
|
// WE CHECK IF THE EXTENSION RATING IS INSTALLED, WHICH VERSION AND IF IT
|
||||||
// WORKS TO SHOW/HIDE SAFE, QUESTIONABLE, EXPLICIT AND UNRATED IMAGES FROM USER
|
// WORKS TO SHOW/HIDE SAFE, QUESTIONABLE, EXPLICIT AND UNRATED IMAGES FROM USER
|
||||||
if (ext_is_live("Ratings")) {
|
if (ext_is_live("Ratings")) {
|
||||||
$rating = Ratings::privs_to_sql(Ratings::get_user_privs($user));
|
$query .= "AND i.rating IN (".Ratings::privs_to_sql(Ratings::get_user_privs($user)).")";
|
||||||
}
|
}
|
||||||
if (isset($rating) && !empty($rating)) {
|
if(ext_is_live("trash")) {
|
||||||
|
$query .= $database->scoreql_to_sql(" AND trash = SCORE_BOOL_N ");
|
||||||
|
}
|
||||||
|
|
||||||
$result = $database->get_all(
|
$result = $database->get_all(
|
||||||
"
|
"
|
||||||
SELECT p.image_id
|
SELECT p.image_id FROM pool_images p
|
||||||
FROM pool_images AS p
|
$query
|
||||||
INNER JOIN images AS i ON i.id = p.image_id
|
|
||||||
WHERE p.pool_id = :pid AND i.rating IN ($rating)
|
|
||||||
ORDER BY p.image_order ASC
|
ORDER BY p.image_order ASC
|
||||||
LIMIT :l OFFSET :o",
|
LIMIT :l OFFSET :o",
|
||||||
["pid" => $poolID, "l" => $imagesPerPage, "o" => $pageNumber * $imagesPerPage]
|
["pid" => $poolID, "l" => $imagesPerPage, "o" => $pageNumber * $imagesPerPage]
|
||||||
@ -703,25 +824,12 @@ class Pools extends Extension
|
|||||||
|
|
||||||
$totalPages = ceil($database->get_one(
|
$totalPages = ceil($database->get_one(
|
||||||
"
|
"
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*) FROM pool_images p
|
||||||
FROM pool_images AS p
|
$query",
|
||||||
INNER JOIN images AS i ON i.id = p.image_id
|
|
||||||
WHERE pool_id=:pid AND i.rating IN ($rating)",
|
|
||||||
["pid" => $poolID]
|
["pid" => $poolID]
|
||||||
) / $imagesPerPage);
|
) / $imagesPerPage);
|
||||||
} else {
|
|
||||||
$result = $database->get_all(
|
|
||||||
"
|
|
||||||
SELECT image_id
|
|
||||||
FROM pool_images
|
|
||||||
WHERE pool_id=:pid
|
|
||||||
ORDER BY image_order ASC
|
|
||||||
LIMIT :l OFFSET :o",
|
|
||||||
["pid"=>$poolID, "l"=>$imagesPerPage, "o"=>$pageNumber * $imagesPerPage]
|
|
||||||
);
|
|
||||||
|
|
||||||
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid"=>$poolID]) / $imagesPerPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
$images = [];
|
$images = [];
|
||||||
foreach ($result as $singleResult) {
|
foreach ($result as $singleResult) {
|
||||||
@ -831,7 +939,7 @@ class Pools extends Extension
|
|||||||
$pageNumber--;
|
$pageNumber--;
|
||||||
}
|
}
|
||||||
|
|
||||||
$historiesPerPage = $config->get_int("poolsUpdatedPerPage");
|
$historiesPerPage = $config->get_int(PoolsConfig::UPDATED_PER_PAGE);
|
||||||
|
|
||||||
$history = $database->get_all("
|
$history = $database->get_all("
|
||||||
SELECT h.id, h.pool_id, h.user_id, h.action, h.images,
|
SELECT h.id, h.pool_id, h.user_id, h.action, h.images,
|
||||||
@ -898,17 +1006,17 @@ class Pools extends Extension
|
|||||||
* HERE WE ADD A SIMPLE POST FROM POOL.
|
* HERE WE ADD A SIMPLE POST FROM POOL.
|
||||||
* USED WITH FOREACH IN revert_history() & onTagTermParse().
|
* USED WITH FOREACH IN revert_history() & onTagTermParse().
|
||||||
*/
|
*/
|
||||||
private function add_post(int $poolID, int $imageID, bool $history=false, int $imageOrder=0)
|
private function add_post(int $poolID, int $imageID, bool $history = false, int $imageOrder = 0): bool
|
||||||
{
|
{
|
||||||
global $database, $config;
|
global $database, $config;
|
||||||
|
|
||||||
if (!$this->check_post($poolID, $imageID)) {
|
if (!$this->check_post($poolID, $imageID)) {
|
||||||
if ($config->get_bool("poolsAutoIncrementOrder") && $imageOrder === 0) {
|
if ($config->get_bool(PoolsConfig::AUTO_INCREMENT_ORDER) && $imageOrder === 0) {
|
||||||
$imageOrder = $database->get_one(
|
$imageOrder = $database->get_one(
|
||||||
"
|
"
|
||||||
SELECT CASE WHEN image_order IS NOT NULL THEN MAX(image_order) + 1 ELSE 0 END
|
SELECT COALESCE(MAX(image_order),0) + 1
|
||||||
FROM pool_images
|
FROM pool_images
|
||||||
WHERE pool_id = :pid",
|
WHERE pool_id = :pid AND image_order IS NOT NULL",
|
||||||
["pid" => $poolID]
|
["pid" => $poolID]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -919,14 +1027,25 @@ class Pools extends Extension
|
|||||||
VALUES (:pid, :iid, :ord)",
|
VALUES (:pid, :iid, :ord)",
|
||||||
["pid" => $poolID, "iid" => $imageID, "ord" => $imageOrder]
|
["pid" => $poolID, "iid" => $imageID, "ord" => $imageOrder]
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// If the post is already added, there is nothing else to do
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->execute("UPDATE pools SET posts=(SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid) WHERE id=:pid", ["pid"=>$poolID]);
|
$this->update_count($poolID);
|
||||||
|
|
||||||
if ($history) {
|
if ($history) {
|
||||||
$count = $database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid" => $poolID]);
|
$count = $database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid" => $poolID]);
|
||||||
$this->add_history($poolID, 1, $imageID, $count);
|
$this->add_history($poolID, 1, $imageID, $count);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function update_count($pool_id)
|
||||||
|
{
|
||||||
|
global $database;
|
||||||
|
$database->execute("UPDATE pools SET posts=(SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid) WHERE id=:pid", ["pid" => $pool_id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -938,7 +1057,8 @@ class Pools extends Extension
|
|||||||
global $database;
|
global $database;
|
||||||
|
|
||||||
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid", ["pid" => $poolID, "iid" => $imageID]);
|
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid", ["pid" => $poolID, "iid" => $imageID]);
|
||||||
$database->execute("UPDATE pools SET posts=(SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid) WHERE id=:pid", ["pid"=>$poolID]);
|
|
||||||
|
$this->update_count($poolID);
|
||||||
|
|
||||||
if ($history) {
|
if ($history) {
|
||||||
$count = $database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid" => $poolID]);
|
$count = $database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid" => $poolID]);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user