Merge remote-tracking branch 'upstream/develop' into custom_ratings
This commit is contained in:
commit
8e3b8a7a1b
@ -3,6 +3,7 @@
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->exclude('ext/amazon_s3/lib')
|
||||
->exclude('vendor')
|
||||
->exclude('data')
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
|
@ -99,24 +99,24 @@ For example, one can override the default anonymous "allow nothing"
|
||||
permissions like so:
|
||||
|
||||
```php
|
||||
new UserClass("anonymous", "base", array(
|
||||
"create_comment" => True,
|
||||
"edit_image_tag" => True,
|
||||
"edit_image_source" => True,
|
||||
"create_image_report" => True,
|
||||
));
|
||||
new UserClass("anonymous", "base", [
|
||||
Permissions::CREATE_COMMENT => True,
|
||||
Permissions::EDIT_IMAGE_TAG => True,
|
||||
Permissions::EDIT_IMAGE_SOURCE => True,
|
||||
Permissions::CREATE_IMAGE_REPORT => True,
|
||||
]);
|
||||
```
|
||||
|
||||
For a moderator class, being a regular user who can delete images and comments:
|
||||
|
||||
```php
|
||||
new UserClass("moderator", "user", array(
|
||||
"delete_image" => True,
|
||||
"delete_comment" => True,
|
||||
));
|
||||
new UserClass("moderator", "user", [
|
||||
Permissions::DELETE_IMAGE => 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
|
||||
|
@ -31,7 +31,7 @@
|
||||
"ifixit/php-akismet" : "1.*",
|
||||
"google/recaptcha" : "~1.1",
|
||||
"dapphp/securimage" : "3.6.*",
|
||||
"shish/libcontext-php" : "dev-master",
|
||||
"shish/eventtracer-php" : "dev-master",
|
||||
"enshrined/svg-sanitize" : "0.8.2",
|
||||
|
||||
"bower-asset/jquery" : "1.12.3",
|
||||
@ -42,7 +42,7 @@
|
||||
},
|
||||
|
||||
"require-dev" : {
|
||||
"phpunit/phpunit" : "6.*"
|
||||
"phpunit/phpunit" : "7.*"
|
||||
},
|
||||
|
||||
"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
|
||||
*/
|
||||
|
||||
global $config, $database, $user, $page, $_shm_ctx;
|
||||
global $config, $database, $user, $page, $_tracer;
|
||||
|
||||
require_once "core/sys_config.php";
|
||||
require_once "core/polyfills.php";
|
||||
require_once "core/util.php";
|
||||
require_once "vendor/shish/libcontext-php/context.php";
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
// set up and purify the environment
|
||||
_version_check();
|
||||
_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
|
||||
$_shm_ctx->log_start("Opening files");
|
||||
$_tracer->begin("Bootstrap");
|
||||
$_tracer->begin("Opening files");
|
||||
$_shm_files = array_merge(
|
||||
zglob("core/*.php"),
|
||||
zglob("core/{".ENABLED_MODS."}/*.php"),
|
||||
@ -30,23 +35,27 @@ foreach ($_shm_files as $_shm_filename) {
|
||||
}
|
||||
unset($_shm_files);
|
||||
unset($_shm_filename);
|
||||
$_shm_ctx->log_endok();
|
||||
$_tracer->end();
|
||||
|
||||
// connect to the database
|
||||
$_shm_ctx->log_start("Connecting to DB");
|
||||
$_tracer->begin("Connecting to DB");
|
||||
$database = new Database();
|
||||
$config = new DatabaseConfig($database);
|
||||
$_shm_ctx->log_endok();
|
||||
$_tracer->end();
|
||||
|
||||
// load the theme parts
|
||||
$_shm_ctx->log_start("Loading themelets");
|
||||
$_tracer->begin("Loading themelets");
|
||||
foreach (_get_themelet_files(get_theme()) as $themelet) {
|
||||
require_once $themelet;
|
||||
}
|
||||
unset($themelet);
|
||||
$page = class_exists("CustomPage") ? new CustomPage() : new Page();
|
||||
$_shm_ctx->log_endok();
|
||||
$_tracer->end();
|
||||
|
||||
// hook up event handlers
|
||||
$_tracer->begin("Loading extensions");
|
||||
_load_event_listeners();
|
||||
$_tracer->end();
|
||||
|
||||
send_event(new InitExtEvent());
|
||||
$_tracer->end();
|
||||
|
@ -20,6 +20,8 @@
|
||||
ob_start();
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
define("DATABASE_TIMEOUT", 10000);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@ -58,6 +60,9 @@ date_default_timezone_set('UTC');
|
||||
<?php }
|
||||
|
||||
// Pull in necessary files
|
||||
require_once "vendor/autoload.php";
|
||||
$_tracer = new EventTracer();
|
||||
|
||||
require_once "core/exceptions.php";
|
||||
require_once "core/cacheengine.php";
|
||||
require_once "core/dbengine.php";
|
||||
@ -110,7 +115,7 @@ function do_install()
|
||||
{ // {{{
|
||||
if (file_exists("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));
|
||||
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'])) {
|
||||
@ -121,7 +126,6 @@ function do_install()
|
||||
}
|
||||
|
||||
define("CACHE_DSN", null);
|
||||
define("DEBUG_SQL", false);
|
||||
define("DATABASE_KA", true);
|
||||
install_process();
|
||||
} // }}}
|
||||
@ -153,9 +157,9 @@ function ask_questions()
|
||||
|
||||
$drivers = PDO::getAvailableDrivers();
|
||||
if (
|
||||
!in_array("mysql", $drivers) &&
|
||||
!in_array("pgsql", $drivers) &&
|
||||
!in_array("sqlite", $drivers)
|
||||
!in_array(DatabaseDriver::MYSQL, $drivers) &&
|
||||
!in_array(DatabaseDriver::PGSQL, $drivers) &&
|
||||
!in_array(DatabaseDriver::SQLITE, $drivers)
|
||||
) {
|
||||
$errors[] = "
|
||||
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_p = in_array("pgsql", $drivers) ? '<option value="pgsql">PostgreSQL</option>' : "";
|
||||
$db_s = in_array("sqlite", $drivers) ? '<option value="sqlite">SQLite</option>' : "";
|
||||
$db_m = in_array(DatabaseDriver::MYSQL, $drivers) ? '<option value="'. DatabaseDriver::MYSQL .'">MySQL</option>' : "";
|
||||
$db_p = in_array(DatabaseDriver::PGSQL, $drivers) ? '<option value="'. DatabaseDriver::PGSQL .'">PostgreSQL</option>' : "";
|
||||
$db_s = in_array(DatabaseDriver::SQLITE, $drivers) ? '<option value="'. DatabaseDriver::SQLITE .'">SQLite</option>' : "";
|
||||
|
||||
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
|
||||
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
|
||||
|
@ -58,7 +58,7 @@ class BaseThemelet
|
||||
$tsize = get_thumbnail_size($image->width, $image->height);
|
||||
} else {
|
||||
//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 = "";
|
||||
|
@ -170,7 +170,7 @@ class Cache
|
||||
{
|
||||
$matches = [];
|
||||
$c = null;
|
||||
if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches)) {
|
||||
if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches) && !isset($_GET['DISABLE_CACHE'])) {
|
||||
if ($matches[1] == "memcache") {
|
||||
$c = new MemcacheCache($matches[2]);
|
||||
} elseif ($matches[1] == "memcached") {
|
||||
@ -188,34 +188,34 @@ class Cache
|
||||
|
||||
public function get(string $key)
|
||||
{
|
||||
global $_tracer;
|
||||
$_tracer->begin("Cache Query", ["key"=>$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) {
|
||||
$res = "hit";
|
||||
$this->hits++;
|
||||
return $val;
|
||||
} else {
|
||||
$res = "miss";
|
||||
$this->misses++;
|
||||
return false;
|
||||
}
|
||||
$_tracer->end(null, ["result"=>$res]);
|
||||
return $val;
|
||||
}
|
||||
|
||||
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);
|
||||
if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
|
||||
file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND);
|
||||
}
|
||||
$_tracer->end();
|
||||
}
|
||||
|
||||
public function delete(string $key)
|
||||
{
|
||||
global $_tracer;
|
||||
$_tracer->begin("Cache Delete", ["key"=>$key]);
|
||||
$this->engine->delete($key);
|
||||
if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
|
||||
file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND);
|
||||
}
|
||||
$_tracer->end();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if (is_null($this->get($name))) {
|
||||
@ -170,6 +177,11 @@ abstract class BaseConfig implements Config
|
||||
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
|
||||
{
|
||||
return $this->get($name, $default);
|
||||
|
@ -1,9 +1,17 @@
|
||||
<?php
|
||||
abstract class DatabaseDriver
|
||||
{
|
||||
public const MYSQL = "mysql";
|
||||
public const PGSQL = "pgsql";
|
||||
public const SQLITE = "sqlite";
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for controlled database access
|
||||
*/
|
||||
class Database
|
||||
{
|
||||
|
||||
/**
|
||||
* The PDO database connection object, for anyone who wants direct access.
|
||||
* @var null|PDO
|
||||
@ -72,7 +80,7 @@ class Database
|
||||
|
||||
// https://bugs.php.net/bug.php?id=70221
|
||||
$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;
|
||||
}
|
||||
|
||||
@ -96,11 +104,11 @@ class Database
|
||||
throw new SCoreException("Can't figure out database engine");
|
||||
}
|
||||
|
||||
if ($db_proto === "mysql") {
|
||||
if ($db_proto === DatabaseDriver::MYSQL) {
|
||||
$this->engine = new MySQL();
|
||||
} elseif ($db_proto === "pgsql") {
|
||||
} elseif ($db_proto === DatabaseDriver::PGSQL) {
|
||||
$this->engine = new PostgreSQL();
|
||||
} elseif ($db_proto === "sqlite") {
|
||||
} elseif ($db_proto === DatabaseDriver::SQLITE) {
|
||||
$this->engine = new SQLite();
|
||||
} else {
|
||||
die('Unknown PDO driver: '.$db_proto);
|
||||
@ -159,6 +167,19 @@ class Database
|
||||
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
|
||||
{
|
||||
if (is_null($this->engine)) {
|
||||
@ -167,35 +188,16 @@ class Database
|
||||
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'])) {
|
||||
$sql = trim(preg_replace('/\s+/msi', ' ', $sql));
|
||||
if (isset($inputarray) && is_array($inputarray) && !empty($inputarray)) {
|
||||
$text = $sql." -- ".join(", ", $inputarray)."\n";
|
||||
} else {
|
||||
$text = $sql."\n";
|
||||
}
|
||||
file_put_contents("data/sql.log", $text, FILE_APPEND);
|
||||
global $_tracer, $tracer_enabled;
|
||||
$dur = microtime(true) - $start;
|
||||
if($tracer_enabled) {
|
||||
$query = trim(preg_replace('/^[\t ]+/m', '', $query)); // trim leading whitespace
|
||||
$_tracer->complete($start * 1000000, $dur * 1000000, "DB Query", ["query"=>$query, "args"=>$args, "method"=>$method]);
|
||||
}
|
||||
if (!is_array($inputarray)) {
|
||||
$this->query_count++;
|
||||
}
|
||||
# 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;
|
||||
$this->query_count++;
|
||||
$this->dbtime += $dur;
|
||||
}
|
||||
|
||||
public function execute(string $query, array $args=[]): PDOStatement
|
||||
@ -204,7 +206,6 @@ class Database
|
||||
if (is_null($this->db)) {
|
||||
$this->connect_db();
|
||||
}
|
||||
$this->count_execs($query, $args);
|
||||
$stmt = $this->db->prepare(
|
||||
"-- " . str_replace("%2F", "/", urlencode(@$_GET['q'])). "\n" .
|
||||
$query
|
||||
@ -225,7 +226,7 @@ class Database
|
||||
return $stmt;
|
||||
} catch (PDOException $pdoe) {
|
||||
throw new SCoreException($pdoe->getMessage()."<p><b>Query:</b> ".$query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -235,7 +236,18 @@ class Database
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$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;
|
||||
}
|
||||
|
||||
@ -246,7 +258,7 @@ class Database
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$row = $this->execute($query, $args)->fetch();
|
||||
$this->count_time("get_row", $_start);
|
||||
$this->count_time("get_row", $_start, $query, $args);
|
||||
return $row ? $row : null;
|
||||
}
|
||||
|
||||
@ -256,27 +268,32 @@ class Database
|
||||
public function get_col(string $query, array $args=[]): array
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$stmt = $this->execute($query, $args);
|
||||
$res = [];
|
||||
foreach ($stmt as $row) {
|
||||
$res[] = $row[0];
|
||||
}
|
||||
$this->count_time("get_col", $_start);
|
||||
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_COLUMN);
|
||||
$this->count_time("get_col", $_start, $query, $args);
|
||||
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
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$stmt = $this->execute($query, $args);
|
||||
$res = [];
|
||||
foreach ($stmt as $row) {
|
||||
$res[$row[0]] = $row[1];
|
||||
}
|
||||
$this->count_time("get_pairs", $_start);
|
||||
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$this->count_time("get_pairs", $_start, $query, $args);
|
||||
return $res;
|
||||
}
|
||||
|
||||
@ -287,7 +304,7 @@ class Database
|
||||
{
|
||||
$_start = microtime(true);
|
||||
$row = $this->execute($query, $args)->fetch();
|
||||
$this->count_time("get_one", $_start);
|
||||
$this->count_time("get_one", $_start, $query, $args);
|
||||
return $row[0];
|
||||
}
|
||||
|
||||
@ -296,7 +313,7 @@ class Database
|
||||
*/
|
||||
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);
|
||||
} else {
|
||||
return $this->db->lastInsertId();
|
||||
@ -326,15 +343,15 @@ class Database
|
||||
$this->connect_db();
|
||||
}
|
||||
|
||||
if ($this->engine->name === "mysql") {
|
||||
if ($this->engine->name === DatabaseDriver::MYSQL) {
|
||||
return count(
|
||||
$this->get_all("SHOW TABLES")
|
||||
);
|
||||
} elseif ($this->engine->name === "pgsql") {
|
||||
} elseif ($this->engine->name === DatabaseDriver::PGSQL) {
|
||||
return count(
|
||||
$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(
|
||||
$this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'")
|
||||
);
|
||||
|
@ -1,9 +1,24 @@
|
||||
<?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
|
||||
{
|
||||
/** @var null|string */
|
||||
public $name = null;
|
||||
|
||||
public $BOOL_Y = null;
|
||||
public $BOOL_N = null;
|
||||
|
||||
public function init(PDO $db)
|
||||
{
|
||||
}
|
||||
@ -22,7 +37,10 @@ class DBEngine
|
||||
class MySQL extends DBEngine
|
||||
{
|
||||
/** @var string */
|
||||
public $name = "mysql";
|
||||
public $name = DatabaseDriver::MYSQL;
|
||||
|
||||
public $BOOL_Y = 'Y';
|
||||
public $BOOL_N = 'N';
|
||||
|
||||
public function init(PDO $db)
|
||||
{
|
||||
@ -31,15 +49,15 @@ class MySQL extends DBEngine
|
||||
|
||||
public function scoreql_to_sql(string $data): string
|
||||
{
|
||||
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY auto_increment", $data);
|
||||
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
|
||||
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
|
||||
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
|
||||
$data = str_replace("SCORE_BOOL", "ENUM('Y', 'N')", $data);
|
||||
$data = str_replace("SCORE_DATETIME", "DATETIME", $data);
|
||||
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
|
||||
$data = str_replace("SCORE_STRNORM", "", $data);
|
||||
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
|
||||
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY auto_increment", $data);
|
||||
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
|
||||
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
|
||||
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
|
||||
$data = str_replace(SCORE::BOOL, "ENUM('Y', 'N')", $data);
|
||||
$data = str_replace(SCORE::DATETIME, "DATETIME", $data);
|
||||
$data = str_replace(SCORE::NOW, "\"1970-01-01\"", $data);
|
||||
$data = str_replace(SCORE::STRNORM, "", $data);
|
||||
$data = str_replace(SCORE::ILIKE, "LIKE", $data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
@ -53,8 +71,13 @@ class MySQL extends DBEngine
|
||||
|
||||
class PostgreSQL extends DBEngine
|
||||
{
|
||||
|
||||
|
||||
/** @var string */
|
||||
public $name = "pgsql";
|
||||
public $name = DatabaseDriver::PGSQL;
|
||||
|
||||
public $BOOL_Y = 't';
|
||||
public $BOOL_N = 'f';
|
||||
|
||||
public function init(PDO $db)
|
||||
{
|
||||
@ -63,20 +86,20 @@ class PostgreSQL extends DBEngine
|
||||
} else {
|
||||
$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
|
||||
{
|
||||
$data = str_replace("SCORE_AIPK", "SERIAL PRIMARY KEY", $data);
|
||||
$data = str_replace("SCORE_INET", "INET", $data);
|
||||
$data = str_replace("SCORE_BOOL_Y", "'t'", $data);
|
||||
$data = str_replace("SCORE_BOOL_N", "'f'", $data);
|
||||
$data = str_replace("SCORE_BOOL", "BOOL", $data);
|
||||
$data = str_replace("SCORE_DATETIME", "TIMESTAMP", $data);
|
||||
$data = str_replace("SCORE_NOW", "current_timestamp", $data);
|
||||
$data = str_replace("SCORE_STRNORM", "lower", $data);
|
||||
$data = str_replace("SCORE_ILIKE", "ILIKE", $data);
|
||||
$data = str_replace(SCORE::AIPK, "SERIAL PRIMARY KEY", $data);
|
||||
$data = str_replace(SCORE::INET, "INET", $data);
|
||||
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
|
||||
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
|
||||
$data = str_replace(SCORE::BOOL, "BOOL", $data);
|
||||
$data = str_replace(SCORE::DATETIME, "TIMESTAMP", $data);
|
||||
$data = str_replace(SCORE::NOW, "current_timestamp", $data);
|
||||
$data = str_replace(SCORE::STRNORM, "lower", $data);
|
||||
$data = str_replace(SCORE::ILIKE, "ILIKE", $data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
@ -136,7 +159,11 @@ function _ln($n)
|
||||
class SQLite extends DBEngine
|
||||
{
|
||||
/** @var string */
|
||||
public $name = "sqlite";
|
||||
public $name = DatabaseDriver::SQLITE;
|
||||
|
||||
public $BOOL_Y = 'Y';
|
||||
public $BOOL_N = 'N';
|
||||
|
||||
|
||||
public function init(PDO $db)
|
||||
{
|
||||
@ -156,14 +183,14 @@ class SQLite extends DBEngine
|
||||
|
||||
public function scoreql_to_sql(string $data): string
|
||||
{
|
||||
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY", $data);
|
||||
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
|
||||
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
|
||||
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
|
||||
$data = str_replace("SCORE_BOOL", "CHAR(1)", $data);
|
||||
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
|
||||
$data = str_replace("SCORE_STRNORM", "lower", $data);
|
||||
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
|
||||
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY", $data);
|
||||
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
|
||||
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
|
||||
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
|
||||
$data = str_replace(SCORE::BOOL, "CHAR(1)", $data);
|
||||
$data = str_replace(SCORE::NOW, "\"1970-01-01\"", $data);
|
||||
$data = str_replace(SCORE::STRNORM, "lower", $data);
|
||||
$data = str_replace(SCORE::ILIKE, "LIKE", $data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
*/
|
||||
abstract class Event
|
||||
{
|
||||
public $stop_processing = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@ -58,7 +60,7 @@ class PageRequestEvent extends Event
|
||||
|
||||
// if path is not specified, use the default front page
|
||||
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
|
||||
|
@ -182,7 +182,7 @@ abstract class DataHandlerExtension extends Extension
|
||||
|
||||
// even more hax..
|
||||
$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)) {
|
||||
throw new UploadException("Data handler failed to create image object from data");
|
||||
@ -192,13 +192,14 @@ abstract class DataHandlerExtension extends Extension
|
||||
send_event($ire);
|
||||
$event->image_id = $image_id;
|
||||
} 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)) {
|
||||
throw new UploadException("Data handler failed to create image object from data");
|
||||
}
|
||||
$iae = new ImageAdditionEvent($image);
|
||||
send_event($iae);
|
||||
$event->image_id = $iae->image->id;
|
||||
$event->merged = $iae->merged;
|
||||
|
||||
// Rating Stuff.
|
||||
if (!empty($event->metadata['rating'])) {
|
||||
@ -222,13 +223,13 @@ abstract class DataHandlerExtension extends Extension
|
||||
$result = false;
|
||||
if ($this->supported_ext($event->type)) {
|
||||
if ($event->force) {
|
||||
$result = $this->create_thumb($event->hash);
|
||||
$result = $this->create_thumb($event->hash, $event->type);
|
||||
} else {
|
||||
$outname = warehouse_path("thumbs", $event->hash);
|
||||
$outname = warehouse_path(Image::THUMBNAIL_DIR, $event->hash);
|
||||
if (file_exists($outname)) {
|
||||
return;
|
||||
}
|
||||
$result = $this->create_thumb($event->hash);
|
||||
$result = $this->create_thumb($event->hash, $event->type);
|
||||
}
|
||||
}
|
||||
if ($result) {
|
||||
@ -256,5 +257,5 @@ abstract class DataHandlerExtension extends Extension
|
||||
abstract protected function supported_ext(string $ext): bool;
|
||||
abstract protected function check_contents(string $tmpname): bool;
|
||||
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 */
|
||||
public $image;
|
||||
|
||||
public $merged = false;
|
||||
|
||||
/**
|
||||
* Inserts a new image into the database with its associated
|
||||
* information. Also calls TagSetEvent to set the tags for
|
||||
@ -40,15 +42,19 @@ class ImageDeletionEvent extends Event
|
||||
/** @var Image */
|
||||
public $image;
|
||||
|
||||
/** @var bool */
|
||||
public $force = false;
|
||||
|
||||
/**
|
||||
* Deletes an image.
|
||||
*
|
||||
* Used by things like tags and comments handlers to
|
||||
* clean out related rows in their tables.
|
||||
*/
|
||||
public function __construct(Image $image)
|
||||
public function __construct(Image $image, bool $force = false)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->force = $force;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,9 @@
|
||||
*/
|
||||
class Image
|
||||
{
|
||||
public const IMAGE_DIR = "images";
|
||||
public const THUMBNAIL_DIR = "thumbs";
|
||||
|
||||
private static $tag_n = 0; // temp hack
|
||||
public static $order_sql = null; // this feels ugly
|
||||
|
||||
@ -51,6 +54,19 @@ class Image
|
||||
/** @var boolean */
|
||||
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
|
||||
* 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
|
||||
*
|
||||
@ -108,92 +161,30 @@ class Image
|
||||
*/
|
||||
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 = [];
|
||||
|
||||
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()) {
|
||||
foreach ($result as $row) {
|
||||
$images[] = new Image($row);
|
||||
}
|
||||
Image::$order_sql = null;
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for an array of image IDs
|
||||
*
|
||||
* #param string[] $tags
|
||||
* #return int[]
|
||||
* Search for an array of images, returning a iterable object of Image
|
||||
*/
|
||||
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;
|
||||
|
||||
$images = [];
|
||||
|
||||
if ($start < 0) {
|
||||
$start = 0;
|
||||
$result = self::find_images_internal($start, $limit, $tags);
|
||||
foreach ($result as $row) {
|
||||
yield new Image($row);
|
||||
}
|
||||
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
|
||||
*/
|
||||
public static function get_acceleratable(array $tags): ?array
|
||||
public static function get_acceleratable(array $tag_conditions): ?array
|
||||
{
|
||||
$ret = [
|
||||
"yays" => [],
|
||||
@ -201,16 +192,17 @@ class Image
|
||||
];
|
||||
$yays = 0;
|
||||
$nays = 0;
|
||||
foreach ($tags as $tag) {
|
||||
if (!preg_match("/^-?[a-zA-Z0-9_'-]+$/", $tag)) {
|
||||
foreach ($tag_conditions as $tq) {
|
||||
if (strpos($tq->tag, "*") !== false) {
|
||||
// can't deal with wildcards
|
||||
return null;
|
||||
}
|
||||
if ($tag[0] == "-") {
|
||||
$nays++;
|
||||
$ret["nays"][] = substr($tag, 1);
|
||||
} else {
|
||||
if ($tq->positive) {
|
||||
$yays++;
|
||||
$ret["yays"][] = $tag;
|
||||
$ret["yays"][] = $tq->tag;
|
||||
} else {
|
||||
$nays++;
|
||||
$ret["nays"][] = $tq->tag;
|
||||
}
|
||||
}
|
||||
if ($yays > 1 || $nays > 0) {
|
||||
@ -219,11 +211,15 @@ class Image
|
||||
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;
|
||||
|
||||
$req = Image::get_acceleratable($tags);
|
||||
$req = Image::get_acceleratable($tag_conditions);
|
||||
if (!$req) {
|
||||
return null;
|
||||
}
|
||||
@ -231,8 +227,8 @@ class Image
|
||||
$req["limit"] = $limit;
|
||||
|
||||
$response = Image::query_accelerator($req);
|
||||
$list = implode(",", $response);
|
||||
if ($list) {
|
||||
if ($response) {
|
||||
$list = implode(",", $response);
|
||||
$result = $database->execute("SELECT * FROM images WHERE id IN ($list) ORDER BY images.id DESC");
|
||||
} else {
|
||||
$result = $database->execute("SELECT * FROM images WHERE 1=0 ORDER BY images.id DESC");
|
||||
@ -240,9 +236,13 @@ class Image
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@ -253,17 +253,21 @@ class Image
|
||||
|
||||
public static function query_accelerator($req)
|
||||
{
|
||||
global $_tracer;
|
||||
$fp = @fsockopen("127.0.0.1", 21212);
|
||||
if (!$fp) {
|
||||
return null;
|
||||
}
|
||||
fwrite($fp, json_encode($req));
|
||||
$req_str = json_encode($req);
|
||||
$_tracer->begin("Accelerator Query", ["req"=>$req_str]);
|
||||
fwrite($fp, $req_str);
|
||||
$data = "";
|
||||
while (($buffer = fgets($fp, 4096)) !== false) {
|
||||
$data .= $buffer;
|
||||
}
|
||||
$_tracer->end();
|
||||
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);
|
||||
return json_decode($data);
|
||||
@ -295,9 +299,10 @@ class Image
|
||||
["tag"=>$tags[0]]
|
||||
);
|
||||
} 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)) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@ -318,6 +323,49 @@ class Image
|
||||
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
|
||||
*/
|
||||
@ -352,7 +400,8 @@ class Image
|
||||
');
|
||||
} else {
|
||||
$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');
|
||||
$row = $database->get_row($querylet->sql, $querylet->variables);
|
||||
}
|
||||
@ -427,7 +476,7 @@ class Image
|
||||
*/
|
||||
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
|
||||
{
|
||||
global $config;
|
||||
$ext = $config->get_string("thumb_type");
|
||||
return $this->get_link('image_tlink', '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext);
|
||||
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||
return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -476,7 +525,7 @@ class Image
|
||||
public function get_tooltip(): string
|
||||
{
|
||||
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
|
||||
if ($this->ext === 'mp3') {
|
||||
@ -502,7 +551,7 @@ class Image
|
||||
*/
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
$database->execute(
|
||||
"
|
||||
@ -764,7 +813,7 @@ class Image
|
||||
$tmpl = str_replace('$size', "{$this->width}x{$this->height}", $tmpl);
|
||||
$tmpl = str_replace('$filesize', to_shorthand_int($this->filesize), $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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
static $flexihash = null;
|
||||
static $fh_last_opts = null;
|
||||
static $flexihashes = [];
|
||||
$matches = [];
|
||||
if (preg_match("/(.*){(.*)}(.*)/", $tmpl, $matches)) {
|
||||
$pre = $matches[1];
|
||||
$opts = $matches[2];
|
||||
$post = $matches[3];
|
||||
|
||||
if ($opts != $fh_last_opts) {
|
||||
$fh_last_opts = $opts;
|
||||
if(isset($flexihashes[$opts])) {
|
||||
$flexihash = $flexihashes[$opts];
|
||||
}
|
||||
else {
|
||||
$flexihash = new Flexihash\Flexihash();
|
||||
foreach (explode(",", $opts) as $opt) {
|
||||
$parts = explode("=", $opt);
|
||||
@ -799,6 +849,7 @@ class Image
|
||||
}
|
||||
$flexihash->addTarget($opt_val, $opt_weight);
|
||||
}
|
||||
$flexihashes[$opts] = $flexihash;
|
||||
}
|
||||
|
||||
// $choice = $flexihash->lookup($pre.$post);
|
||||
@ -813,57 +864,17 @@ class Image
|
||||
/**
|
||||
* #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;
|
||||
|
||||
$tag_querylets = [];
|
||||
$img_querylets = [];
|
||||
$positive_tag_count = 0;
|
||||
$negative_tag_count = 0;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
foreach ($tag_conditions as $tq) {
|
||||
if ($tq->positive) {
|
||||
$positive_tag_count++;
|
||||
} 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++;
|
||||
} else {
|
||||
$negative_tag_count++;
|
||||
}
|
||||
}
|
||||
$negative_tag_count++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
else {
|
||||
if ($database->get_driver_name() === "mysql") {
|
||||
$query = Image::build_ugly_search_querylet($tag_querylets);
|
||||
} else {
|
||||
$query = Image::build_accurate_search_querylet($tag_querylets);
|
||||
}
|
||||
$query = Image::build_accurate_search_querylet($tag_conditions);
|
||||
}
|
||||
|
||||
/*
|
||||
* Merge all the image metadata searches into one generic querylet
|
||||
* and append to the base querylet with "AND blah"
|
||||
*/
|
||||
if (!empty($img_querylets)) {
|
||||
if (!empty($img_conditions)) {
|
||||
$n = 0;
|
||||
$img_sql = "";
|
||||
$img_vars = [];
|
||||
foreach ($img_querylets as $iq) {
|
||||
foreach ($img_conditions as $iq) {
|
||||
if ($n++ > 0) {
|
||||
$img_sql .= " AND";
|
||||
}
|
||||
@ -940,47 +930,30 @@ class Image
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this description is no longer accurate, though it does get across
|
||||
* 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
|
||||
* #param TagQuerylet[] $tag_conditions
|
||||
*/
|
||||
private static function build_accurate_search_querylet(array $tag_querylets): Querylet
|
||||
private static function build_accurate_search_querylet(array $tag_conditions): Querylet
|
||||
{
|
||||
global $database;
|
||||
|
||||
$positive_tag_id_array = [];
|
||||
$positive_wildcard_id_array = [];
|
||||
$negative_tag_id_array = [];
|
||||
|
||||
foreach ($tag_querylets as $tq) {
|
||||
foreach ($tag_conditions 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" => Tag::sqlify($tq->tag)]
|
||||
);
|
||||
|
||||
$tag_count = count($tag_ids);
|
||||
|
||||
if ($tq->positive) {
|
||||
$positive_tag_id_array = array_merge($positive_tag_id_array, $tag_ids);
|
||||
if (count($tag_ids) == 0) {
|
||||
if ($tag_count== 0) {
|
||||
# one of the positive tags had zero results, therefor there
|
||||
# can be no results; "where 1=0" should shortcut things
|
||||
return new Querylet("
|
||||
@ -988,115 +961,71 @@ class Image
|
||||
FROM images
|
||||
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 {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
assert($positive_tag_id_array || $negative_tag_id_array, @$_GET['q']);
|
||||
$wheres = [];
|
||||
if (!empty($positive_tag_id_array)) {
|
||||
$positive_tag_id_list = join(', ', $positive_tag_id_array);
|
||||
$wheres[] = "tag_id IN ($positive_tag_id_list)";
|
||||
}
|
||||
if (!empty($negative_tag_id_array)) {
|
||||
$sql = "";
|
||||
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)) {
|
||||
foreach($positive_tag_id_array as $tag) {
|
||||
$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)) {
|
||||
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
||||
$sub_query .= " LEFT JOIN image_tags negative ON negative.image_id = it.image_id AND negative.tag_id IN ($negative_tag_id_list) ";
|
||||
}
|
||||
$sub_query .= "WHERE it.tag_id $first ";
|
||||
if(!empty($negative_tag_id_array)) {
|
||||
$sub_query .= " AND negative.image_id IS NULL";
|
||||
}
|
||||
$sub_query .= " GROUP BY it.image_id ";
|
||||
|
||||
$sql = "
|
||||
SELECT images.*
|
||||
FROM images INNER JOIN (
|
||||
$sub_query
|
||||
) a on a.image_id = images.id
|
||||
";
|
||||
} elseif(!empty($negative_tag_id_array)) {
|
||||
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
||||
$wheres[] = "tag_id NOT IN ($negative_tag_id_list)";
|
||||
}
|
||||
$wheres_str = join(" AND ", $wheres);
|
||||
return new Querylet("
|
||||
SELECT images.*
|
||||
FROM images
|
||||
WHERE images.id IN (
|
||||
SELECT image_id
|
||||
FROM image_tags
|
||||
WHERE $wheres_str
|
||||
GROUP BY image_id
|
||||
HAVING COUNT(image_id) >= :search_score
|
||||
)
|
||||
", ["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++;
|
||||
}
|
||||
$sql = "
|
||||
SELECT images.*
|
||||
FROM images LEFT JOIN image_tags negative ON negative.image_id = images.id AND negative.tag_id in ($negative_tag_id_list)
|
||||
WHERE negative.image_id IS NULL
|
||||
";
|
||||
} else {
|
||||
throw new SCoreException("No criteria specified");
|
||||
}
|
||||
|
||||
// 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.*
|
||||
FROM images
|
||||
WHERE 1=0
|
||||
");
|
||||
}
|
||||
|
||||
// merge all the tag querylets into one generic one
|
||||
$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]
|
||||
));
|
||||
return new Querylet($sql);
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,12 @@
|
||||
* Move a file from PHP's temporary area into shimmie's image storage
|
||||
* hierarchy, or throw an exception trying.
|
||||
*
|
||||
* @param DataUploadEvent $event
|
||||
* @throws UploadException
|
||||
*/
|
||||
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)) {
|
||||
$errors = error_get_last();
|
||||
throw new UploadException(
|
||||
@ -24,7 +25,8 @@ function move_upload_to_archive(DataUploadEvent $event): void
|
||||
/**
|
||||
* Add a directory full of images
|
||||
*
|
||||
* #return string[]
|
||||
* @param string $base
|
||||
* @return array
|
||||
*/
|
||||
function add_dir(string $base): array
|
||||
{
|
||||
@ -48,6 +50,14 @@ function add_dir(string $base): array
|
||||
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
|
||||
{
|
||||
assert(file_exists($tmpname));
|
||||
@ -65,10 +75,15 @@ function add_image(string $tmpname, string $filename, string $tags): void
|
||||
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);
|
||||
if (!empty($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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@ -105,363 +123,97 @@ function get_thumbnail_size(int $orig_width, int $orig_height): array
|
||||
$orig_height = $orig_width * 5;
|
||||
}
|
||||
|
||||
$max_width = $config->get_int('thumb_width');
|
||||
$max_height = $config->get_int('thumb_height');
|
||||
|
||||
$xscale = ($max_height / $orig_height);
|
||||
$yscale = ($max_width / $orig_width);
|
||||
$scale = ($xscale < $yscale) ? $xscale : $yscale;
|
||||
if($use_dpi_scaling) {
|
||||
list($max_width, $max_height) = get_thumbnail_max_size_scaled();
|
||||
} 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];
|
||||
} 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
|
||||
* into the configured thumbnail square, with ratio intact, using thumb_scaling
|
||||
* Fetches the thumbnails height and width settings and applies the High-DPI scaling setting before returning the dimensions.
|
||||
*
|
||||
* #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
|
||||
{
|
||||
global $config;
|
||||
|
||||
$scaling = $config->get_int("thumb_scaling");
|
||||
$max_width = $config->get_int('thumb_width') * ($scaling/100);
|
||||
$max_height = $config->get_int('thumb_height') * ($scaling/100);
|
||||
$scaling = $config->get_int(ImageConfig::THUMB_SCALING);
|
||||
$max_width = $config->get_int(ImageConfig::THUMB_WIDTH) * ($scaling/100);
|
||||
$max_height = $config->get_int(ImageConfig::THUMB_HEIGHT) * ($scaling/100);
|
||||
return [$max_width, $max_height];
|
||||
}
|
||||
|
||||
function create_thumbnail_convert($hash): bool
|
||||
{
|
||||
|
||||
function create_image_thumb(string $hash, string $type, string $engine = null) {
|
||||
global $config;
|
||||
|
||||
$inname = warehouse_path("images", $hash);
|
||||
$outname = warehouse_path("thumbs", $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));
|
||||
$inname = warehouse_path(Image::IMAGE_DIR, $hash);
|
||||
$outname = warehouse_path(Image::THUMBNAIL_DIR, $hash);
|
||||
$tsize = get_thumbnail_max_size_scaled();
|
||||
$w = $tsize[0];
|
||||
$h = $tsize[1];
|
||||
|
||||
|
||||
// running the call with cmd.exe requires quoting for our paths
|
||||
$type = $config->get_string('thumb_type');
|
||||
|
||||
$options = "";
|
||||
if (!$config->get_bool('thumb_upscale')) {
|
||||
$options .= "\>";
|
||||
if(empty($engine)) {
|
||||
$engine = $config->get_string(ImageConfig::THUMB_ENGINE);
|
||||
}
|
||||
|
||||
$bg = "black";
|
||||
if ($type=="webp") {
|
||||
$bg = "none";
|
||||
}
|
||||
$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);
|
||||
$output_format = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||
if($output_format=="webp") {
|
||||
$output_format = Media::WEBP_LOSSY;
|
||||
}
|
||||
|
||||
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");
|
||||
if ($ffmpeg==null||$ffmpeg=="") {
|
||||
return false;
|
||||
$remainder = floor($input / 1000);
|
||||
|
||||
foreach (TIME_UNITS AS $unit=>$conversion) {
|
||||
$count = $remainder % $conversion;
|
||||
$remainder = floor($remainder / $conversion);
|
||||
if($count==0&&$remainder<1) {
|
||||
break;
|
||||
}
|
||||
$output = "$count".$unit." ".$output;
|
||||
}
|
||||
|
||||
$inname = warehouse_path("images", $hash);
|
||||
$outname = warehouse_path("thumbs", $hash);
|
||||
|
||||
$orig_size = video_size($inname);
|
||||
$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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
return trim($output);
|
||||
}
|
@ -29,7 +29,7 @@ class Querylet
|
||||
}
|
||||
}
|
||||
|
||||
class TagQuerylet
|
||||
class TagCondition
|
||||
{
|
||||
/** @var string */
|
||||
public $tag;
|
||||
@ -43,7 +43,7 @@ class TagQuerylet
|
||||
}
|
||||
}
|
||||
|
||||
class ImgQuerylet
|
||||
class ImgCondition
|
||||
{
|
||||
/** @var Querylet */
|
||||
public $qlet;
|
||||
|
@ -100,4 +100,12 @@ class Tag
|
||||
|
||||
return $tag_array;
|
||||
}
|
||||
|
||||
public static function sqlify(string $term): string
|
||||
{
|
||||
$term = str_replace('_', '\_', $term);
|
||||
$term = str_replace('%', '\%', $term);
|
||||
$term = str_replace('*', '%', $term);
|
||||
return $term;
|
||||
}
|
||||
}
|
||||
|
269
core/page.php
269
core/page.php
@ -26,6 +26,13 @@
|
||||
* 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
|
||||
@ -40,7 +47,7 @@ class Page
|
||||
/** @name Overall */
|
||||
//@{
|
||||
/** @var string */
|
||||
public $mode = "page";
|
||||
public $mode = PageMode::PAGE;
|
||||
/** @var string */
|
||||
public $type = "text/html; charset=utf-8";
|
||||
|
||||
@ -69,9 +76,14 @@ class Page
|
||||
/** @var string; public only for unit test */
|
||||
public $data = "";
|
||||
|
||||
/** @var string; */
|
||||
public $file = null;
|
||||
|
||||
/** @var string; public only for unit test */
|
||||
public $filename = null;
|
||||
|
||||
private $disposition = null;
|
||||
|
||||
/**
|
||||
* Set the raw data to be sent.
|
||||
*/
|
||||
@ -80,12 +92,18 @@ class Page
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function set_file(string $file): void
|
||||
{
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->disposition = $disposition;
|
||||
}
|
||||
|
||||
|
||||
@ -165,7 +183,7 @@ class Page
|
||||
/**
|
||||
* Add a line to the HTML head section.
|
||||
*/
|
||||
public function add_html_header(string $line, int $position=50): void
|
||||
public function add_html_header(string $line, int $position = 50): void
|
||||
{
|
||||
while (isset($this->html_headers[$position])) {
|
||||
$position++;
|
||||
@ -176,7 +194,7 @@ class Page
|
||||
/**
|
||||
* Add a http header to be sent to the client.
|
||||
*/
|
||||
public function add_http_header(string $line, int $position=50): void
|
||||
public function add_http_header(string $line, int $position = 50): void
|
||||
{
|
||||
while (isset($this->http_headers[$position])) {
|
||||
$position++;
|
||||
@ -191,13 +209,13 @@ class Page
|
||||
*/
|
||||
public function add_cookie(string $name, string $value, int $time, string $path): void
|
||||
{
|
||||
$full_name = COOKIE_PREFIX."_".$name;
|
||||
$full_name = COOKIE_PREFIX . "_" . $name;
|
||||
$this->cookies[] = [$full_name, $value, $time, $path];
|
||||
}
|
||||
|
||||
public function get_cookie(string $name): ?string
|
||||
{
|
||||
$full_name = COOKIE_PREFIX."_".$name;
|
||||
$full_name = COOKIE_PREFIX . "_" . $name;
|
||||
if (isset($_COOKIE[$full_name])) {
|
||||
return $_COOKIE[$full_name];
|
||||
} else {
|
||||
@ -246,8 +264,8 @@ class Page
|
||||
global $page, $user;
|
||||
|
||||
header("HTTP/1.0 {$this->code} Shimmie");
|
||||
header("Content-type: ".$this->type);
|
||||
header("X-Powered-By: SCore-".SCORE_VERSION);
|
||||
header("Content-type: " . $this->type);
|
||||
header("X-Powered-By: SCore-" . SCORE_VERSION);
|
||||
|
||||
if (!headers_sent()) {
|
||||
foreach ($this->http_headers as $head) {
|
||||
@ -261,7 +279,7 @@ class Page
|
||||
}
|
||||
|
||||
switch ($this->mode) {
|
||||
case "page":
|
||||
case PageMode::PAGE:
|
||||
if (CACHE_HTTP) {
|
||||
header("Vary: Cookie, Accept-Encoding");
|
||||
if ($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") {
|
||||
@ -281,20 +299,135 @@ class Page
|
||||
$this->add_cookie("flash_message", "", -1, "/");
|
||||
}
|
||||
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();
|
||||
$layout = new Layout();
|
||||
$layout->display_page($page);
|
||||
$layout->display_page($page, $nav_links, $sub_links);
|
||||
break;
|
||||
case "data":
|
||||
header("Content-Length: ".strlen($this->data));
|
||||
case PageMode::DATA:
|
||||
header("Content-Length: " . strlen($this->data));
|
||||
if (!is_null($this->filename)) {
|
||||
header('Content-Disposition: attachment; filename='.$this->filename);
|
||||
header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
|
||||
}
|
||||
print $this->data;
|
||||
break;
|
||||
case "redirect":
|
||||
header('Location: '.$this->redirect);
|
||||
print 'You should be redirected to <a href="'.$this->redirect.'">'.$this->redirect.'</a>';
|
||||
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);
|
||||
print 'You should be redirected to <a href="' . $this->redirect . '">' . $this->redirect . '</a>';
|
||||
break;
|
||||
default:
|
||||
print "Invalid page mode";
|
||||
@ -318,7 +451,7 @@ class Page
|
||||
global $config;
|
||||
|
||||
$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);
|
||||
|
||||
@ -335,7 +468,7 @@ class Page
|
||||
/*** Generate CSS cache files ***/
|
||||
$css_latest = $config_latest;
|
||||
$css_files = array_merge(
|
||||
zglob("ext/{".ENABLED_EXTS."}/style.css"),
|
||||
zglob("ext/{" . ENABLED_EXTS . "}/style.css"),
|
||||
zglob("themes/$theme_name/style.css")
|
||||
);
|
||||
foreach ($css_files as $css) {
|
||||
@ -348,7 +481,7 @@ class Page
|
||||
foreach ($css_files as $file) {
|
||||
$file_data = file_get_contents($file);
|
||||
$pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/';
|
||||
$replace = 'url("../../../'.dirname($file).'/$1")';
|
||||
$replace = 'url("../../../' . dirname($file) . '/$1")';
|
||||
$file_data = preg_replace($pattern, $replace, $file_data);
|
||||
$css_data .= $file_data . "\n";
|
||||
}
|
||||
@ -366,7 +499,7 @@ class Page
|
||||
"vendor/bower-asset/js-cookie/src/js.cookie.js",
|
||||
"ext/handle_static/modernizr-3.3.1.custom.js",
|
||||
],
|
||||
zglob("ext/{".ENABLED_EXTS."}/script.js"),
|
||||
zglob("ext/{" . ENABLED_EXTS . "}/script.js"),
|
||||
zglob("themes/$theme_name/script.js")
|
||||
);
|
||||
foreach ($js_files as $js) {
|
||||
@ -384,3 +517,99 @@ class Page
|
||||
$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 = [
|
||||
'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
|
||||
'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
|
||||
'swf' => 'application/x-shockwave-flash', 'video/x-flv' => 'flv',
|
||||
'svg' => 'image/svg+xml', 'pdf' => 'application/pdf',
|
||||
'zip' => 'application/zip', 'gz' => 'application/x-gzip',
|
||||
'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
|
||||
'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',
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'ico' => 'image/x-icon',
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'flv' => 'video/x-flv',
|
||||
'svg' => 'image/svg+xml',
|
||||
'pdf' => 'application/pdf',
|
||||
'zip' => 'application/zip',
|
||||
'gz' => 'application/x-gzip',
|
||||
'tar' => 'application/x-tar',
|
||||
'bz' => 'application/x-bzip',
|
||||
'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'
|
||||
];
|
||||
|
||||
@ -729,3 +752,53 @@ function validate_input(array $inputs): array
|
||||
|
||||
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
|
||||
{
|
||||
global $_shm_event_listeners, $_shm_ctx;
|
||||
|
||||
$_shm_ctx->log_start("Loading extensions");
|
||||
global $_shm_event_listeners;
|
||||
|
||||
$cache_path = data_path("cache/shm_event_listeners.php");
|
||||
if (COMPILE_ELS && file_exists($cache_path)) {
|
||||
@ -23,8 +21,13 @@ function _load_event_listeners(): void
|
||||
_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
|
||||
@ -105,35 +108,31 @@ $_shm_event_count = 0;
|
||||
*/
|
||||
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)])) {
|
||||
return;
|
||||
}
|
||||
$method_name = "on".str_replace("Event", "", get_class($event));
|
||||
|
||||
// send_event() is performance sensitive, and with the number
|
||||
// of times context gets called the time starts to add up
|
||||
$ctx_enabled = constant('CONTEXT');
|
||||
|
||||
if ($ctx_enabled) {
|
||||
$_shm_ctx->log_start(get_class($event));
|
||||
}
|
||||
// of times tracer gets called the time starts to add up
|
||||
if ($tracer_enabled) $_tracer->begin(get_class($event));
|
||||
// SHIT: http://bugs.php.net/bug.php?id=35106
|
||||
$my_event_listeners = $_shm_event_listeners[get_class($event)];
|
||||
ksort($my_event_listeners);
|
||||
|
||||
foreach ($my_event_listeners as $listener) {
|
||||
if ($ctx_enabled) {
|
||||
$_shm_ctx->log_start(get_class($listener));
|
||||
}
|
||||
if ($tracer_enabled) $_tracer->begin(get_class($listener));
|
||||
if (method_exists($listener, $method_name)) {
|
||||
$listener->$method_name($event);
|
||||
}
|
||||
if ($ctx_enabled) {
|
||||
$_shm_ctx->log_endok();
|
||||
if ($tracer_enabled) $_tracer->end();
|
||||
if($event->stop_processing===true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$_shm_event_count++;
|
||||
if ($ctx_enabled) {
|
||||
$_shm_ctx->log_endok();
|
||||
}
|
||||
if ($tracer_enabled) $_tracer->end();
|
||||
}
|
||||
|
@ -27,12 +27,10 @@ function _d(string $name, $value): void
|
||||
}
|
||||
_d("DATABASE_DSN", null); // string PDO database connection details
|
||||
_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("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("CONTEXT", null); // string file to log performance data into
|
||||
_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("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("VERSION", '2.7-beta'); // string shimmie version
|
||||
_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("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("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");
|
||||
|
||||
/*
|
||||
|
@ -45,4 +45,77 @@ class PolyfillsTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals(parse_shorthand_int("43.4KB"), 44441);
|
||||
$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 *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
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
|
||||
* things like the nice URLs setting.
|
||||
@ -14,7 +31,7 @@ function make_link(?string $page=null, ?string $query=null): string
|
||||
global $config;
|
||||
|
||||
if (is_null($page)) {
|
||||
$page = $config->get_string('main_page');
|
||||
$page = $config->get_string(SetupConfig::MAIN_PAGE);
|
||||
}
|
||||
|
||||
if (!is_null(BASE_URL)) {
|
||||
|
@ -69,7 +69,7 @@ class User
|
||||
global $config, $database;
|
||||
$row = $database->cache->get("user-session:$name-$session");
|
||||
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";
|
||||
} else {
|
||||
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
|
||||
|
@ -72,129 +72,138 @@ class UserClass
|
||||
// action = create / view / edit / delete
|
||||
// object = image / user / tag / setting
|
||||
new UserClass("base", null, [
|
||||
"change_setting" => false, # modify web-level settings, eg the config table
|
||||
"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::CHANGE_SETTING => false, # modify web-level settings, eg the config table
|
||||
Permissions::OVERRIDE_CONFIG => false, # modify sys-level settings, eg shimmie.conf.php
|
||||
Permissions::BIG_SEARCH => false, # search for more than 3 tags at once (speed mode only)
|
||||
|
||||
"manage_extension_list" => false,
|
||||
"manage_alias_list" => false,
|
||||
"mass_tag_edit" => false,
|
||||
Permissions::MANAGE_EXTENSION_LIST => false,
|
||||
Permissions::MANAGE_ALIAS_LIST => false,
|
||||
Permissions::MASS_TAG_EDIT => false,
|
||||
|
||||
"view_ip" => false, # view IP addresses associated with things
|
||||
"ban_ip" => false,
|
||||
Permissions::VIEW_IP => false, # view IP addresses associated with things
|
||||
Permissions::BAN_IP => false,
|
||||
|
||||
"edit_user_name" => false,
|
||||
"edit_user_password" => false,
|
||||
"edit_user_info" => false, # email address, etc
|
||||
"edit_user_class" => false,
|
||||
"delete_user" => false,
|
||||
Permissions::EDIT_USER_NAME => false,
|
||||
Permissions::EDIT_USER_PASSWORD => false,
|
||||
Permissions::EDIT_USER_INFO => false, # email address, etc
|
||||
Permissions::EDIT_USER_CLASS => false,
|
||||
Permissions::DELETE_USER => false,
|
||||
|
||||
"create_comment" => false,
|
||||
"delete_comment" => false,
|
||||
"bypass_comment_checks" => false, # spam etc
|
||||
Permissions::CREATE_COMMENT => false,
|
||||
Permissions::DELETE_COMMENT => false,
|
||||
Permissions::BYPASS_COMMENT_CHECKS => false, # spam etc
|
||||
|
||||
"replace_image" => false,
|
||||
"create_image" => false,
|
||||
"edit_image_tag" => false,
|
||||
"edit_image_source" => false,
|
||||
"edit_image_owner" => false,
|
||||
"edit_image_lock" => false,
|
||||
"bulk_edit_image_tag" => false,
|
||||
"bulk_edit_image_source" => false,
|
||||
"delete_image" => false,
|
||||
Permissions::REPLACE_IMAGE => false,
|
||||
Permissions::CREATE_IMAGE => false,
|
||||
Permissions::EDIT_IMAGE_TAG => false,
|
||||
Permissions::EDIT_IMAGE_SOURCE => false,
|
||||
Permissions::EDIT_IMAGE_OWNER => false,
|
||||
Permissions::EDIT_IMAGE_LOCK => false,
|
||||
Permissions::EDIT_IMAGE_TITLE => false,
|
||||
Permissions::BULK_EDIT_IMAGE_TAG => false,
|
||||
Permissions::BULK_EDIT_IMAGE_SOURCE => false,
|
||||
Permissions::DELETE_IMAGE => false,
|
||||
|
||||
"ban_image" => false,
|
||||
Permissions::BAN_IMAGE => false,
|
||||
|
||||
"view_eventlog" => false,
|
||||
"ignore_downtime" => false,
|
||||
Permissions::VIEW_EVENTLOG => false,
|
||||
Permissions::IGNORE_DOWNTIME => false,
|
||||
|
||||
"create_image_report" => false,
|
||||
"view_image_report" => false, # deal with reported images
|
||||
Permissions::CREATE_IMAGE_REPORT => false,
|
||||
Permissions::VIEW_IMAGE_REPORT => false, # deal with reported images
|
||||
|
||||
"edit_wiki_page" => false,
|
||||
"delete_wiki_page" => false,
|
||||
Permissions::EDIT_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,
|
||||
"edit_feature" => false,
|
||||
"bulk_edit_vote" => false,
|
||||
"edit_other_vote" => false,
|
||||
"view_sysinfo" => false,
|
||||
Permissions::VIEW_OTHER_PMS => false,
|
||||
Permissions::EDIT_FEATURE => false,
|
||||
Permissions::BULK_EDIT_VOTE => false,
|
||||
Permissions::EDIT_OTHER_VOTE => false,
|
||||
Permissions::VIEW_SYSINTO => false,
|
||||
|
||||
"hellbanned" => false,
|
||||
"view_hellbanned" => false,
|
||||
Permissions::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,
|
||||
"bulk_edit_image_rating" => false,
|
||||
Permissions::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("user", "base", [
|
||||
"big_search" => true,
|
||||
"create_image" => true,
|
||||
"create_comment" => true,
|
||||
"edit_image_tag" => true,
|
||||
"edit_image_source" => true,
|
||||
"create_image_report" => true,
|
||||
"edit_image_rating" => true,
|
||||
Permissions::BIG_SEARCH => true,
|
||||
Permissions::CREATE_IMAGE => true,
|
||||
Permissions::CREATE_COMMENT => true,
|
||||
Permissions::EDIT_IMAGE_TAG => true,
|
||||
Permissions::EDIT_IMAGE_SOURCE => true,
|
||||
Permissions::EDIT_IMAGE_TITLE => true,
|
||||
Permissions::CREATE_IMAGE_REPORT => true,
|
||||
Permissions::EDIT_IMAGE_RATING => true,
|
||||
|
||||
]);
|
||||
|
||||
new UserClass("admin", "base", [
|
||||
"change_setting" => true,
|
||||
"override_config" => true,
|
||||
"big_search" => true,
|
||||
"edit_image_lock" => true,
|
||||
"view_ip" => true,
|
||||
"ban_ip" => true,
|
||||
"edit_user_name" => true,
|
||||
"edit_user_password" => true,
|
||||
"edit_user_info" => true,
|
||||
"edit_user_class" => true,
|
||||
"delete_user" => true,
|
||||
"create_image" => true,
|
||||
"delete_image" => true,
|
||||
"ban_image" => true,
|
||||
"create_comment" => true,
|
||||
"delete_comment" => true,
|
||||
"bypass_comment_checks" => true,
|
||||
"replace_image" => true,
|
||||
"manage_extension_list" => true,
|
||||
"manage_alias_list" => true,
|
||||
"edit_image_tag" => true,
|
||||
"edit_image_source" => true,
|
||||
"edit_image_owner" => true,
|
||||
"bulk_edit_image_tag" => true,
|
||||
"bulk_edit_image_source" => true,
|
||||
"mass_tag_edit" => true,
|
||||
"create_image_report" => true,
|
||||
"view_image_report" => true,
|
||||
"edit_wiki_page" => true,
|
||||
"delete_wiki_page" => true,
|
||||
"view_eventlog" => true,
|
||||
"manage_blocks" => true,
|
||||
"manage_admintools" => true,
|
||||
"ignore_downtime" => true,
|
||||
"view_other_pms" => true,
|
||||
"edit_feature" => true,
|
||||
"bulk_edit_vote" => true,
|
||||
"edit_other_vote" => true,
|
||||
"view_sysinfo" => true,
|
||||
"view_hellbanned" => true,
|
||||
"protected" => true,
|
||||
"edit_image_rating" => true,
|
||||
"bulk_edit_image_rating" => true,
|
||||
Permissions::CHANGE_SETTING => true,
|
||||
Permissions::OVERRIDE_CONFIG => true,
|
||||
Permissions::BIG_SEARCH => true,
|
||||
Permissions::EDIT_IMAGE_LOCK => true,
|
||||
Permissions::VIEW_IP => true,
|
||||
Permissions::BAN_IP => true,
|
||||
Permissions::EDIT_USER_NAME => true,
|
||||
Permissions::EDIT_USER_PASSWORD => true,
|
||||
Permissions::EDIT_USER_INFO => true,
|
||||
Permissions::EDIT_USER_CLASS => true,
|
||||
Permissions::DELETE_USER => true,
|
||||
Permissions::CREATE_IMAGE => true,
|
||||
Permissions::DELETE_IMAGE => true,
|
||||
Permissions::BAN_IMAGE => true,
|
||||
Permissions::CREATE_COMMENT => true,
|
||||
Permissions::DELETE_COMMENT => true,
|
||||
Permissions::BYPASS_COMMENT_CHECKS => true,
|
||||
Permissions::REPLACE_IMAGE => true,
|
||||
Permissions::MANAGE_EXTENSION_LIST => true,
|
||||
Permissions::MANAGE_ALIAS_LIST => true,
|
||||
Permissions::EDIT_IMAGE_TAG => true,
|
||||
Permissions::EDIT_IMAGE_SOURCE => true,
|
||||
Permissions::EDIT_IMAGE_OWNER => true,
|
||||
Permissions::EDIT_IMAGE_TITLE => true,
|
||||
Permissions::BULK_EDIT_IMAGE_TAG => true,
|
||||
Permissions::BULK_EDIT_IMAGE_SOURCE => true,
|
||||
Permissions::MASS_TAG_EDIT => true,
|
||||
Permissions::CREATE_IMAGE_REPORT => true,
|
||||
Permissions::VIEW_IMAGE_REPORT => true,
|
||||
Permissions::EDIT_WIKI_PAGE => true,
|
||||
Permissions::DELETE_WIKI_PAGE => true,
|
||||
Permissions::VIEW_EVENTLOG => true,
|
||||
Permissions::MANAGE_BLOCKS => true,
|
||||
Permissions::MANAGE_ADMINTOOLS => true,
|
||||
Permissions::IGNORE_DOWNTIME => true,
|
||||
Permissions::VIEW_OTHER_PMS => true,
|
||||
Permissions::EDIT_FEATURE => true,
|
||||
Permissions::BULK_EDIT_VOTE => true,
|
||||
Permissions::EDIT_OTHER_VOTE => true,
|
||||
Permissions::VIEW_SYSINTO => true,
|
||||
Permissions::VIEW_HELLBANNED => true,
|
||||
Permissions::PROTECTED => 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", [
|
||||
"hellbanned" => true,
|
||||
Permissions::HELLBANNED => true,
|
||||
]);
|
||||
|
||||
@include_once "data/config/user-classes.conf.php";
|
||||
|
128
core/util.php
128
core/util.php
@ -1,10 +1,11 @@
|
||||
<?php
|
||||
require_once "vendor/shish/libcontext-php/context.php";
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* Misc *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
const DATA_DIR = "data";
|
||||
|
||||
|
||||
function mtimefile(string $file): string
|
||||
{
|
||||
$data_href = get_base_href();
|
||||
@ -15,7 +16,7 @@ function mtimefile(string $file): string
|
||||
function get_theme(): string
|
||||
{
|
||||
global $config;
|
||||
$theme = $config->get_string("theme", "default");
|
||||
$theme = $config->get_string(SetupConfig::THEME, "default");
|
||||
if (!file_exists("themes/$theme")) {
|
||||
$theme = "default";
|
||||
}
|
||||
@ -78,7 +79,7 @@ function get_memory_limit(): int
|
||||
|
||||
// thumbnail generation requires lots of memory
|
||||
$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) {
|
||||
// we aren't going to fit, override
|
||||
@ -159,25 +160,40 @@ function format_text(string $string): string
|
||||
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);
|
||||
$cd = substr($hash, 2, 2);
|
||||
if (WH_SPLITS == 2) {
|
||||
$pa = 'data/'.$base.'/'.$ab.'/'.$cd.'/'.$hash;
|
||||
} else {
|
||||
$pa = 'data/'.$base.'/'.$ab.'/'.$hash;
|
||||
$dirs =[DATA_DIR, $base];
|
||||
$splits = min($splits, strlen($hash) / 2);
|
||||
for($i = 0; $i < $splits; $i++) {
|
||||
$dirs[] = substr($hash, $i * 2, 2);
|
||||
}
|
||||
$dirs[] = $hash;
|
||||
|
||||
$pa = join_path(...$dirs);
|
||||
|
||||
if ($create && !file_exists(dirname($pa))) {
|
||||
mkdir(dirname($pa), 0755, true);
|
||||
}
|
||||
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;
|
||||
if (!file_exists(dirname($filename))) {
|
||||
$filename = join_path("data", $filename);
|
||||
if ($create&&!file_exists(dirname($filename))) {
|
||||
mkdir(dirname($filename), 0755, true);
|
||||
}
|
||||
return $filename;
|
||||
@ -281,21 +297,57 @@ function manual_include(string $fname): ?string
|
||||
function path_to_tags(string $path): string
|
||||
{
|
||||
$matches = [];
|
||||
$tags = "";
|
||||
if (preg_match("/\d+ - (.*)\.([a-zA-Z0-9]+)/", basename($path), $matches)) {
|
||||
$tags = $matches[1];
|
||||
$tags = [];
|
||||
if (preg_match("/\d+ - (.+)\.([a-zA-Z0-9]+)/", basename($path), $matches)) {
|
||||
$tags = explode(" ", $matches[1]);
|
||||
}
|
||||
|
||||
$path = dirname($path);
|
||||
$path = str_replace(";", ":", $path);
|
||||
$path = str_replace("__", " ", $path);
|
||||
|
||||
$dir_tags = dirname($path);
|
||||
$dir_tags = str_replace("/", " ", $dir_tags);
|
||||
$dir_tags = str_replace("__", " ", $dir_tags);
|
||||
$dir_tags = trim($dir_tags);
|
||||
if ($dir_tags != "") {
|
||||
$tags = trim($tags)." ".trim($dir_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;
|
||||
}
|
||||
$tags = trim($tags);
|
||||
|
||||
return $tags;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 *
|
||||
@ -375,7 +414,7 @@ date and you should plan on moving elsewhere.
|
||||
|
||||
function _sanitise_environment(): void
|
||||
{
|
||||
global $_shm_ctx;
|
||||
global $_tracer;
|
||||
|
||||
if (TIMEZONE) {
|
||||
date_default_timezone_set(TIMEZONE);
|
||||
@ -387,10 +426,7 @@ function _sanitise_environment(): void
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
$_shm_ctx = new Context();
|
||||
if (CONTEXT) {
|
||||
$_shm_ctx->set_log(CONTEXT);
|
||||
}
|
||||
$_tracer = new EventTracer();
|
||||
|
||||
if (COVERAGE) {
|
||||
_start_coverage();
|
||||
@ -552,8 +588,8 @@ function show_ip(string $ip, string $ban_reason): string
|
||||
global $user;
|
||||
$u_reason = url_escape($ban_reason);
|
||||
$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>" : "";
|
||||
$ip = $user->can("view_ip") ? $ip.$ban : "";
|
||||
$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(Permissions::VIEW_IP) ? $ip.$ban : "";
|
||||
return $ip;
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ class AdminPage extends Extension
|
||||
global $page, $user;
|
||||
|
||||
if ($event->page_matches("admin")) {
|
||||
if (!$user->can("manage_admintools")) {
|
||||
if (!$user->can(Permissions::MANAGE_ADMINTOOLS)) {
|
||||
$this->theme->display_permission_denied();
|
||||
} else {
|
||||
if ($event->count_args() == 0) {
|
||||
@ -70,7 +70,7 @@ class AdminPage extends Extension
|
||||
}
|
||||
|
||||
if ($aae->redirect) {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("admin"));
|
||||
}
|
||||
}
|
||||
@ -108,10 +108,20 @@ class AdminPage extends Extension
|
||||
$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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("manage_admintools")) {
|
||||
if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
|
||||
$event->add_link("Board Admin", make_link("admin"));
|
||||
}
|
||||
}
|
||||
@ -137,6 +147,7 @@ class AdminPage extends Extension
|
||||
global $page;
|
||||
$query = $_POST['query'];
|
||||
$reason = @$_POST['reason'];
|
||||
|
||||
assert(strlen($query) > 1);
|
||||
|
||||
$images = Image::find_images(0, 1000000, Tag::explode($query));
|
||||
@ -146,10 +157,10 @@ class AdminPage extends Extension
|
||||
if ($reason && class_exists("ImageBan")) {
|
||||
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"));
|
||||
return false;
|
||||
}
|
||||
@ -201,14 +212,14 @@ class AdminPage extends Extension
|
||||
$database = $matches['dbname'];
|
||||
|
||||
switch ($software) {
|
||||
case 'mysql':
|
||||
case DatabaseDriver::MYSQL:
|
||||
$cmd = "mysqldump -h$hostname -u$username -p$password $database";
|
||||
break;
|
||||
case 'pgsql':
|
||||
case DatabaseDriver::PGSQL:
|
||||
putenv("PGPASSWORD=$password");
|
||||
$cmd = "pg_dump -h $hostname -U $username $database";
|
||||
break;
|
||||
case 'sqlite':
|
||||
case DatabaseDriver::SQLITE:
|
||||
$cmd = "sqlite3 $database .dump";
|
||||
break;
|
||||
default:
|
||||
@ -218,7 +229,7 @@ class AdminPage extends Extension
|
||||
//FIXME: .SQL dump is empty if cmd doesn't exist
|
||||
|
||||
if ($cmd) {
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("application/x-unknown");
|
||||
$page->set_filename('shimmie-'.date('Ymd').'.sql');
|
||||
$page->set_data(shell_exec($cmd));
|
||||
@ -237,13 +248,13 @@ class AdminPage extends Extension
|
||||
$zip = new ZipArchive;
|
||||
if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
|
||||
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->close();
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link($filename)); //TODO: Delete file after downloaded?
|
||||
|
||||
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..)
|
||||
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
|
||||
FROM information_schema.KEY_COLUMN_USAGE
|
||||
WHERE TABLE_SCHEMA = :db
|
||||
@ -280,9 +291,9 @@ class AdminPage extends Extension
|
||||
$i++;
|
||||
}
|
||||
$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
|
||||
} elseif ($matches['proto'] == "sqlite") {
|
||||
} elseif ($matches['proto'] == DatabaseDriver::SQLITE) {
|
||||
//TODO: Make this work with SQLite
|
||||
}
|
||||
return true;
|
||||
|
@ -45,7 +45,7 @@ class AdminPageTheme extends Themelet
|
||||
$html .= $this->button("Download all images", "download_all_images", 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);
|
||||
}
|
||||
$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 .= "</form>\n";
|
||||
$page->add_block(new Block("Set Tag Case", $html));
|
||||
|
||||
}
|
||||
|
||||
public function dbq_html($terms)
|
||||
{
|
||||
$h_terms = html_escape($terms);
|
||||
$h_reason = "";
|
||||
if(ext_is_live("Trash")) {
|
||||
$warning = "This delete method will bypass the trash<br/>";
|
||||
}
|
||||
if (class_exists("ImageBan")) {
|
||||
$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='hidden' name='query' value='$h_terms'>
|
||||
$h_reason
|
||||
@ -73,4 +75,6 @@ class AdminPageTheme extends Themelet
|
||||
";
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ class AliasEditor extends Extension
|
||||
|
||||
if ($event->page_matches("alias")) {
|
||||
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'])) {
|
||||
try {
|
||||
$aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']);
|
||||
send_event($aae);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("alias/list"));
|
||||
} catch (AddAliasException $ex) {
|
||||
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
|
||||
@ -49,12 +49,12 @@ class AliasEditor extends Extension
|
||||
}
|
||||
}
|
||||
} elseif ($event->get_arg(0) == "remove") {
|
||||
if ($user->can("manage_alias_list")) {
|
||||
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
|
||||
if (isset($_POST['oldtag'])) {
|
||||
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", ["oldtag" => $_POST['oldtag']]);
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@ -80,18 +80,18 @@ class AliasEditor extends Extension
|
||||
|
||||
$this->theme->display_aliases($alias, $page_number + 1, $total_pages);
|
||||
} elseif ($event->get_arg(0) == "export") {
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/csv");
|
||||
$page->set_filename("aliases.csv");
|
||||
$page->set_data($this->get_alias_csv($database));
|
||||
} elseif ($event->get_arg(0) == "import") {
|
||||
if ($user->can("manage_alias_list")) {
|
||||
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
|
||||
if (count($_FILES) > 0) {
|
||||
$tmp = $_FILES['alias_file']['tmp_name'];
|
||||
$contents = file_get_contents($tmp);
|
||||
$this->add_alias_csv($database, $contents);
|
||||
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"));
|
||||
} else {
|
||||
$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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("manage_alias_list")) {
|
||||
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
|
||||
$event->add_link("Alias Editor", make_link("alias/list"));
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class AliasEditorTheme extends Themelet
|
||||
{
|
||||
global $page, $user;
|
||||
|
||||
$can_manage = $user->can("manage_alias_list");
|
||||
$can_manage = $user->can(Permissions::MANAGE_ALIAS_LIST);
|
||||
if ($can_manage) {
|
||||
$h_action = "<th width='10%'>Action</th>";
|
||||
$h_add = "
|
||||
|
@ -47,12 +47,23 @@ class Artists extends Extension
|
||||
public function onSearchTermParse(SearchTermParseEvent $event)
|
||||
{
|
||||
$matches = [];
|
||||
if (preg_match("/^author[=|:](.*)$/i", $event->term, $matches)) {
|
||||
if (preg_match("/^(author|artist)[=|:](.*)$/i", $event->term, $matches)) {
|
||||
$char = $matches[1];
|
||||
$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)
|
||||
{
|
||||
global $config, $database;
|
||||
@ -172,7 +183,7 @@ class Artists extends Extension
|
||||
}
|
||||
case "new_artist":
|
||||
{
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/new"));
|
||||
break;
|
||||
}
|
||||
@ -183,7 +194,7 @@ class Artists extends Extension
|
||||
if ($newArtistID == -1) {
|
||||
$this->theme->display_error(400, "Error", "Error when entering artist data.");
|
||||
} else {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$newArtistID));
|
||||
}
|
||||
} else {
|
||||
@ -238,7 +249,7 @@ class Artists extends Extension
|
||||
case "edit_artist":
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/edit/".$artistID));
|
||||
break;
|
||||
}
|
||||
@ -246,14 +257,14 @@ class Artists extends Extension
|
||||
{
|
||||
$artistID = int_escape($_POST['id']);
|
||||
$this->update_artist();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
case "nuke_artist":
|
||||
{
|
||||
$artistID = $_POST['artist_id'];
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/nuke/".$artistID));
|
||||
break;
|
||||
}
|
||||
@ -261,7 +272,7 @@ class Artists extends Extension
|
||||
{
|
||||
$artistID = $event->get_arg(1);
|
||||
$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"));
|
||||
break;
|
||||
}
|
||||
@ -291,7 +302,7 @@ class Artists extends Extension
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_alias();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
@ -300,7 +311,7 @@ class Artists extends Extension
|
||||
$aliasID = $event->get_arg(2);
|
||||
$artistID = $this->get_artistID_by_aliasID($aliasID);
|
||||
$this->delete_alias($aliasID);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
@ -316,7 +327,7 @@ class Artists extends Extension
|
||||
$this->update_alias();
|
||||
$aliasID = int_escape($_POST['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));
|
||||
break;
|
||||
}
|
||||
@ -332,7 +343,7 @@ class Artists extends Extension
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_urls();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
@ -341,7 +352,7 @@ class Artists extends Extension
|
||||
$urlID = $event->get_arg(2);
|
||||
$artistID = $this->get_artistID_by_urlID($urlID);
|
||||
$this->delete_url($urlID);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
@ -357,7 +368,7 @@ class Artists extends Extension
|
||||
$this->update_url();
|
||||
$urlID = int_escape($_POST['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));
|
||||
break;
|
||||
}
|
||||
@ -372,7 +383,7 @@ class Artists extends Extension
|
||||
{
|
||||
$artistID = $_POST['artistID'];
|
||||
$this->add_members();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
@ -381,7 +392,7 @@ class Artists extends Extension
|
||||
$memberID = int_escape($event->get_arg(2));
|
||||
$artistID = $this->get_artistID_by_memberID($memberID);
|
||||
$this->delete_member($memberID);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("artist/view/".$artistID));
|
||||
break;
|
||||
}
|
||||
@ -397,7 +408,7 @@ class Artists extends Extension
|
||||
$this->update_member();
|
||||
$memberID = int_escape($_POST['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));
|
||||
break;
|
||||
}
|
||||
|
@ -545,4 +545,14 @@ class ArtistsTheme extends Themelet
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("application/json");
|
||||
|
||||
$s = strtolower($_GET["s"]);
|
||||
@ -38,7 +38,9 @@ class AutoComplete extends Extension
|
||||
//$limit = 0;
|
||||
$cache_key = "autocomplete-$s";
|
||||
$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) {
|
||||
$limitSQL = "LIMIT :limit";
|
||||
$SQLarr['limit'] = $_GET["limit"];
|
||||
@ -51,7 +53,8 @@ class AutoComplete extends Extension
|
||||
$database->scoreql_to_sql("
|
||||
SELECT tag, count
|
||||
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
|
||||
ORDER BY count DESC
|
||||
$limitSQL"),
|
||||
|
@ -58,7 +58,7 @@ xanax
|
||||
public function onCommentPosting(CommentPostingEvent $event)
|
||||
{
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("manage_blocks")) {
|
||||
if ($user->can(Permissions::MANAGE_BLOCKS)) {
|
||||
$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 ($user->check_auth_token()) {
|
||||
$database->execute("
|
||||
@ -61,7 +71,7 @@ class Blocks extends Extension
|
||||
", [$_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'].")");
|
||||
$database->cache->delete("blocks");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("blocks/list"));
|
||||
}
|
||||
}
|
||||
@ -81,7 +91,7 @@ class Blocks extends Extension
|
||||
log_info("blocks", "Updated Block #".$_POST['id']." (".$_POST['title'].")");
|
||||
}
|
||||
$database->cache->delete("blocks");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("blocks/list"));
|
||||
}
|
||||
} elseif ($event->get_arg(0) == "list") {
|
||||
|
@ -56,6 +56,17 @@ class Blotter extends Extension
|
||||
$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)
|
||||
{
|
||||
global $user;
|
||||
@ -102,7 +113,7 @@ class Blotter extends Extension
|
||||
[$entry_text, $important]
|
||||
);
|
||||
log_info("blotter", "Added Message: $entry_text");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("blotter/editor"));
|
||||
}
|
||||
break;
|
||||
@ -119,7 +130,7 @@ class Blotter extends Extension
|
||||
}
|
||||
$database->Execute("DELETE FROM blotter WHERE id=:id", ["id"=>$id]);
|
||||
log_info("blotter", "Removed Entry #$id");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("blotter/editor"));
|
||||
}
|
||||
break;
|
||||
|
@ -27,14 +27,14 @@ class BrowserSearch extends Extension
|
||||
|
||||
// Add in header code to let the browser know that the search plugin exists
|
||||
// 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');
|
||||
$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
|
||||
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
|
||||
$search_title = $config->get_string('title');
|
||||
$search_title = $config->get_string(SetupConfig::TITLE);
|
||||
$search_form_url = make_link('post/list/{searchTerms}');
|
||||
$suggenton_url = make_link('browser_search/')."{searchTerms}";
|
||||
$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
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/xml");
|
||||
$page->set_data($xml);
|
||||
} elseif (
|
||||
@ -85,7 +85,7 @@ class BrowserSearch extends Extension
|
||||
|
||||
// And now for the final output
|
||||
$json_string = "[\"$tag_search\",[\"$json_tag_list\"],[],[]]";
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_data($json_string);
|
||||
}
|
||||
}
|
||||
|
@ -14,22 +14,31 @@ class BulkActionBlockBuildingEvent extends Event
|
||||
/** @var array */
|
||||
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) {
|
||||
$block = "";
|
||||
}
|
||||
|
||||
array_push(
|
||||
$this->actions,
|
||||
[
|
||||
if(!empty($access_key)) {
|
||||
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,
|
||||
"access_key" => $access_key,
|
||||
"confirmation_message" => $confirmation_message,
|
||||
"action" => $action,
|
||||
"button_text" => $button_text,
|
||||
"position" => $position
|
||||
]
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +51,7 @@ class BulkActionEvent extends Event
|
||||
/** @var PageRequestEvent */
|
||||
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->page_request = $pageRequestEvent;
|
||||
@ -58,6 +67,8 @@ class BulkActions extends Extension
|
||||
|
||||
if ($user->is_logged_in()) {
|
||||
$babbe = new BulkActionBlockBuildingEvent();
|
||||
$babbe->search_terms = $event->search_terms;
|
||||
|
||||
send_event($babbe);
|
||||
|
||||
if (sizeof($babbe->actions) == 0) {
|
||||
@ -74,16 +85,23 @@ class BulkActions extends Extension
|
||||
{
|
||||
global $user;
|
||||
|
||||
if ($user->can("delete_image")) {
|
||||
$event->add_action("bulk_delete", "Delete", "Delete selected images?", "", 10);
|
||||
if ($user->can(Permissions::DELETE_IMAGE)) {
|
||||
$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")) {
|
||||
$event->add_action("bulk_tag", "Tag", "", $this->theme->render_tag_input(), 10);
|
||||
if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) {
|
||||
|
||||
$event->add_action(
|
||||
"bulk_tag",
|
||||
"Tag",
|
||||
"t",
|
||||
"",
|
||||
$this->theme->render_tag_input(),
|
||||
10);
|
||||
}
|
||||
|
||||
if ($user->can("bulk_edit_image_source")) {
|
||||
$event->add_action("bulk_source", "Set Source", "", $this->theme->render_source_input(), 10);
|
||||
if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) {
|
||||
$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) {
|
||||
case "bulk_delete":
|
||||
if ($user->can("delete_image")) {
|
||||
if ($user->can(Permissions::DELETE_IMAGE)) {
|
||||
$i = $this->delete_items($event->items);
|
||||
flash_message("Deleted $i items");
|
||||
}
|
||||
@ -102,7 +120,7 @@ class BulkActions extends Extension
|
||||
if (!isset($_POST['bulk_tags'])) {
|
||||
return;
|
||||
}
|
||||
if ($user->can("bulk_edit_image_tag")) {
|
||||
if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) {
|
||||
$tags = $_POST['bulk_tags'];
|
||||
$replace = false;
|
||||
if (isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace'] == "true") {
|
||||
@ -117,7 +135,7 @@ class BulkActions extends Extension
|
||||
if (!isset($_POST['bulk_source'])) {
|
||||
return;
|
||||
}
|
||||
if ($user->can("bulk_edit_image_source")) {
|
||||
if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) {
|
||||
$source = $_POST['bulk_source'];
|
||||
$i = $this->set_source($event->items, $source);
|
||||
flash_message("Set source for $i items");
|
||||
@ -129,49 +147,33 @@ class BulkActions extends Extension
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
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'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$action = $_POST['bulk_action'];
|
||||
|
||||
$items = [];
|
||||
$items = null;
|
||||
if (isset($_POST['bulk_selected_ids']) && $_POST['bulk_selected_ids'] != "") {
|
||||
$data = json_decode($_POST['bulk_selected_ids']);
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $id) {
|
||||
if (is_numeric($id)) {
|
||||
array_push($items, int_escape($id));
|
||||
}
|
||||
}
|
||||
if (is_array($data)&&!empty($data)) {
|
||||
$items = $this->yield_items($data);
|
||||
}
|
||||
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||
$query = $_POST['bulk_query'];
|
||||
if ($query != null && $query != "") {
|
||||
$n = 0;
|
||||
$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);
|
||||
}
|
||||
$items = $this->yield_search_results($query);
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeof($items) > 0) {
|
||||
reset($items); // rewind to first element in array.
|
||||
if (is_iterable($items)) {
|
||||
$newEvent = new BulkActionEvent($action, $event, $items);
|
||||
send_event($newEvent);
|
||||
}
|
||||
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
$_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)
|
||||
{
|
||||
return $a["position"] - $b["position"];
|
||||
}
|
||||
|
||||
private function delete_items(array $items): int
|
||||
private function delete_items(iterable $items): int
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($items as $id) {
|
||||
foreach ($items as $image) {
|
||||
try {
|
||||
$image = Image::by_id($id);
|
||||
if ($image==null) {
|
||||
continue;
|
||||
if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) {
|
||||
$reason = $_POST['bulk_ban_reason'];
|
||||
if ($reason) {
|
||||
send_event(new AddImageHashBanEvent($image->hash, $reason));
|
||||
}
|
||||
}
|
||||
|
||||
send_event(new ImageDeletionEvent($image));
|
||||
$total++;
|
||||
} catch (Exception $e) {
|
||||
flash_message("Error while removing $id: " . $e->getMessage(), "error");
|
||||
flash_message("Error while removing {$image->id}: " . $e->getMessage(), "error");
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
@ -219,28 +240,21 @@ class BulkActions extends Extension
|
||||
|
||||
$total = 0;
|
||||
if ($replace) {
|
||||
foreach ($items as $id) {
|
||||
$image = Image::by_id($id);
|
||||
if ($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($items as $image) {
|
||||
send_event(new TagSetEvent($image, $tags));
|
||||
$total++;
|
||||
}
|
||||
} else {
|
||||
foreach ($items as $id) {
|
||||
$image = Image::by_id($id);
|
||||
if ($image==null) {
|
||||
continue;
|
||||
}
|
||||
foreach ($items as $image) {
|
||||
$img_tags = array_map("strtolower",$image->get_tag_array());
|
||||
|
||||
$img_tags = [];
|
||||
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);
|
||||
} else {
|
||||
$img_tags = array_merge($tags, $image->get_tag_array());
|
||||
$img_tags = array_merge($tags, $img_tags);
|
||||
}
|
||||
send_event(new TagSetEvent($image, $img_tags));
|
||||
$total++;
|
||||
@ -250,23 +264,17 @@ class BulkActions extends Extension
|
||||
return $total;
|
||||
}
|
||||
|
||||
private function set_source(array $items, String $source): int
|
||||
private function set_source(iterable $items, String $source): int
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($items as $id) {
|
||||
foreach ($items as $image) {
|
||||
try {
|
||||
$image = Image::by_id($id);
|
||||
if ($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
send_event(new SourceSetEvent($image, $source));
|
||||
$total++;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ function validate_selections(form, confirmationMessage) {
|
||||
function activate_bulk_selector () {
|
||||
set_selected_items([]);
|
||||
if(!bulk_selector_initialized) {
|
||||
$("a.shm-thumb").each(
|
||||
$(".shm-thumb").each(
|
||||
function (index, block) {
|
||||
add_selector_button($(block));
|
||||
}
|
||||
@ -67,11 +67,11 @@ function get_selected_items() {
|
||||
}
|
||||
|
||||
function set_selected_items(items) {
|
||||
$("a.shm-thumb").removeClass('selected');
|
||||
$(".shm-thumb").removeClass('selected');
|
||||
|
||||
$(items).each(
|
||||
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() {
|
||||
var items = [];
|
||||
$("a.shm-thumb").each(
|
||||
$(".shm-thumb").each(
|
||||
function ( index, block ) {
|
||||
block = $(block);
|
||||
var id = block.data("post-id");
|
||||
@ -122,7 +122,7 @@ function select_all() {
|
||||
function select_invert() {
|
||||
var currentItems = get_selected_items();
|
||||
var items = [];
|
||||
$("a.shm-thumb").each(
|
||||
$(".shm-thumb").each(
|
||||
function ( index, block ) {
|
||||
block = $(block);
|
||||
var id = block.data("post-id");
|
||||
@ -141,7 +141,7 @@ function select_none() {
|
||||
function select_range(start, end) {
|
||||
var data = get_selected_items();
|
||||
var selecting = false;
|
||||
$("a.shm-thumb").each(
|
||||
$(".shm-thumb").each(
|
||||
function ( index, block ) {
|
||||
block = $(block);
|
||||
var id = block.data("post-id");
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
class BulkActionsTheme extends Themelet
|
||||
{
|
||||
public function display_selector(Page $page, $actions, $query)
|
||||
public function display_selector(Page $page, array $actions, string $query)
|
||||
{
|
||||
global $user;
|
||||
|
||||
$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;'>
|
||||
<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.
|
||||
<br />
|
||||
<table><tr><td>
|
||||
@ -36,7 +36,7 @@ class BulkActionsTheme extends Themelet
|
||||
"<input type='hidden' name='bulk_selected_ids' />" .
|
||||
"<input type='hidden' name='bulk_action' value='" . $action["action"] . "' />" .
|
||||
$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>";
|
||||
}
|
||||
|
||||
@ -47,6 +47,15 @@ class BulkActionsTheme extends Themelet
|
||||
$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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
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)
|
||||
{
|
||||
if ($event->page_matches("comment")) {
|
||||
@ -178,7 +193,7 @@ class CommentList extends Extension
|
||||
$i_iid = int_escape($_POST['image_id']);
|
||||
$cpe = new CommentPostingEvent($_POST['image_id'], $user, $_POST['comment']);
|
||||
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"));
|
||||
} catch (CommentPostingException $ex) {
|
||||
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
|
||||
@ -189,12 +204,12 @@ class CommentList extends Extension
|
||||
private function onPageRequest_delete(PageRequestEvent $event)
|
||||
{
|
||||
global $user, $page;
|
||||
if ($user->can("delete_comment")) {
|
||||
if ($user->can(Permissions::DELETE_COMMENT)) {
|
||||
// FIXME: post, not args
|
||||
if ($event->count_args() === 3) {
|
||||
send_event(new CommentDeletionEvent($event->get_arg(1)));
|
||||
flash_message("Deleted comment");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
if (!empty($_SERVER['HTTP_REFERER'])) {
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
} else {
|
||||
@ -209,7 +224,7 @@ class CommentList extends Extension
|
||||
private function onPageRequest_bulk_delete()
|
||||
{
|
||||
global $user, $database, $page;
|
||||
if ($user->can("delete_comment") && !empty($_POST["ip"])) {
|
||||
if ($user->can(Permissions::DELETE_COMMENT) && !empty($_POST["ip"])) {
|
||||
$ip = $_POST['ip'];
|
||||
|
||||
$comment_ids = $database->get_col("
|
||||
@ -224,7 +239,7 @@ class CommentList extends Extension
|
||||
}
|
||||
flash_message("Deleted $num comments");
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("admin"));
|
||||
} else {
|
||||
$this->theme->display_permission_denied();
|
||||
@ -288,7 +303,7 @@ class CommentList extends Extension
|
||||
$this->theme->display_image_comments(
|
||||
$event->image,
|
||||
$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 {{{
|
||||
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;
|
||||
|
||||
// sqlite fails at intervals
|
||||
if ($database->get_driver_name() === "sqlite") {
|
||||
if ($database->get_driver_name() === DatabaseDriver::SQLITE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$window = int_escape($config->get_int('comment_window'));
|
||||
$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";
|
||||
} else {
|
||||
$window_sql = "interval '$window minute'";
|
||||
@ -574,7 +599,7 @@ class CommentList extends Extension
|
||||
{
|
||||
global $database, $page;
|
||||
|
||||
if (!$user->can("bypass_comment_checks")) {
|
||||
if (!$user->can(Permissions::BYPASS_COMMENT_CHECKS)) {
|
||||
// will raise an exception if anything is wrong
|
||||
$this->comment_checks($image_id, $user, $comment);
|
||||
}
|
||||
@ -600,7 +625,7 @@ class CommentList extends Extension
|
||||
global $config, $page;
|
||||
|
||||
// basic sanity checks
|
||||
if (!$user->can("create_comment")) {
|
||||
if (!$user->can(Permissions::CREATE_COMMENT)) {
|
||||
throw new CommentPostingException("Anonymous posting has been disabled");
|
||||
} elseif (is_null(Image::by_id($image_id))) {
|
||||
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)) {
|
||||
$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).";'";
|
||||
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) {
|
||||
$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_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 = "";
|
||||
if ($user->can("delete_comment")) {
|
||||
if ($user->can(Permissions::DELETE_COMMENT)) {
|
||||
$comment_preview = substr(html_unescape($tfe->stripped), 0, 50);
|
||||
$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);");
|
||||
@ -290,4 +290,28 @@ class CommentListTheme extends Themelet
|
||||
</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,42 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Name: Cron Uploader
|
||||
* Author: YaoiFox <admin@yaoifox.com>
|
||||
* Authors: YaoiFox <admin@yaoifox.com>, Matthew Barbour <matthew@darkholme.net>
|
||||
* Link: http://www.yaoifox.com/
|
||||
* License: GPLv2
|
||||
* Description: Uploads images automatically using Cron Jobs
|
||||
* Documentation: Installation guide: activate this extension and navigate to www.yoursite.com/cron_upload
|
||||
*/
|
||||
|
||||
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: Change logging to MySQL + display log at /cron_upload
|
||||
// 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
|
||||
* @var string
|
||||
*/
|
||||
private $upload_info = "";
|
||||
|
||||
|
||||
/**
|
||||
* Lists all files & info required to upload.
|
||||
* @var array
|
||||
*/
|
||||
private $image_queue = [];
|
||||
|
||||
|
||||
/**
|
||||
* Cron Uploader root directory
|
||||
* @var string
|
||||
*/
|
||||
private $root_dir = "";
|
||||
|
||||
|
||||
/**
|
||||
* Key used to identify uploader
|
||||
* @var string
|
||||
*/
|
||||
private $upload_key = "";
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the cron upload page has been accessed
|
||||
* and initializes the upload.
|
||||
@ -44,39 +54,50 @@ class CronUploader extends Extension
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $config, $user;
|
||||
|
||||
|
||||
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 ($this->upload_key != "" && $event->get_arg(0) == $this->upload_key) {
|
||||
// log in as admin
|
||||
$this->process_upload(); // Start upload
|
||||
$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
|
||||
} finally {
|
||||
flock($lockfile, LOCK_UN);
|
||||
fclose($lockfile);
|
||||
}
|
||||
} elseif ($user->is_admin()) {
|
||||
$this->set_dir();
|
||||
$this->display_documentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function display_documentation()
|
||||
{
|
||||
global $page;
|
||||
$this->set_dir(); // Determines path to cron_uploader_dir
|
||||
|
||||
|
||||
$queue_dir = $this->root_dir . "/queue";
|
||||
$uploaded_dir = $this->root_dir . "/uploaded";
|
||||
$failed_dir = $this->root_dir . "/failed_to_upload";
|
||||
|
||||
|
||||
|
||||
$queue_dir = $this->root_dir . "/" . self::QUEUE_DIR;
|
||||
$uploaded_dir = $this->root_dir . "/" . self::UPLOADED_DIR;
|
||||
$failed_dir = $this->root_dir . "/" . self::FAILED_DIR;
|
||||
|
||||
$queue_dirinfo = $this->scan_dir($queue_dir);
|
||||
$uploaded_dirinfo = $this->scan_dir($uploaded_dir);
|
||||
$failed_dirinfo = $this->scan_dir($failed_dir);
|
||||
|
||||
|
||||
$cron_url = make_http(make_link("/cron_upload/" . $this->upload_key));
|
||||
$cron_cmd = "curl --silent $cron_url";
|
||||
$log_path = $this->root_dir . "/uploads.log";
|
||||
|
||||
|
||||
$info_html = "<b>Information</b>
|
||||
<br>
|
||||
<table style='width:470px;'>
|
||||
@ -105,7 +126,7 @@ class CronUploader extends Extension
|
||||
<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br>
|
||||
Create a cron job with the command above.<br/>
|
||||
Read the documentation if you're not sure what to do.<br>";
|
||||
|
||||
|
||||
$install_html = "
|
||||
This cron uploader is fairly easy to use but has to be configured first.
|
||||
<br />1. Install & activate this plugin.
|
||||
@ -130,8 +151,10 @@ class CronUploader extends Extension
|
||||
<br />So when you want to manually upload an image, all you have to do is open the link once.
|
||||
<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>)";
|
||||
|
||||
|
||||
|
||||
$page->set_title("Cron Uploader");
|
||||
$page->set_heading("Cron Uploader");
|
||||
|
||||
$block = new Block("Cron Uploader", $info_html, "main", 10);
|
||||
$block_install = new Block("Installation Guide", $install_html, "main", 20);
|
||||
$page->add_block($block);
|
||||
@ -142,36 +165,37 @@ class CronUploader extends Extension
|
||||
{
|
||||
global $config;
|
||||
// Set default values
|
||||
$this->upload_key = $config->get_string("cron_uploader_key", "");
|
||||
if (strlen($this->upload_key)<=0) {
|
||||
$config->set_default_int(self::CONFIG_COUNT, 1);
|
||||
$this->set_dir();
|
||||
|
||||
$this->upload_key = $config->get_string(self::CONFIG_KEY, "");
|
||||
if (empty($this->upload_key)) {
|
||||
$this->upload_key = $this->generate_key();
|
||||
|
||||
$config->set_default_int('cron_uploader_count', 1);
|
||||
$config->set_default_string('cron_uploader_key', $this->upload_key);
|
||||
$this->set_dir();
|
||||
|
||||
$config->set_string(self::CONFIG_KEY, $this->upload_key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$this->set_dir();
|
||||
|
||||
|
||||
$cron_url = make_http(make_link("/cron_upload/" . $this->upload_key));
|
||||
$cron_cmd = "curl --silent $cron_url";
|
||||
$documentation_link = make_http(make_link("cron_upload"));
|
||||
|
||||
|
||||
$sb = new SetupBlock("Cron Uploader");
|
||||
$sb->add_label("<b>Settings</b><br>");
|
||||
$sb->add_int_option("cron_uploader_count", "How many to upload each time");
|
||||
$sb->add_text_option("cron_uploader_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_int_option(self::CONFIG_COUNT, "How many to upload each time");
|
||||
$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' readonly='readonly' value='" . html_escape($cron_cmd) . "'><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.");
|
||||
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generates a unique key for the website to prevent unauthorized access.
|
||||
*/
|
||||
@ -180,14 +204,14 @@ class CronUploader extends Extension
|
||||
$length = 20;
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$randomString = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i ++) {
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters [rand(0, strlen($characters) - 1)];
|
||||
}
|
||||
|
||||
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set the directory for the image queue. If no directory was given, set it to the default directory.
|
||||
*/
|
||||
@ -195,50 +219,50 @@ class CronUploader extends Extension
|
||||
{
|
||||
global $config;
|
||||
// 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
|
||||
if ($dir == "") {
|
||||
$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
|
||||
if (!is_dir($dir . "/queue/")) {
|
||||
mkdir($dir . "/queue/", 0775, true);
|
||||
if (!is_dir($dir . "/" . self::QUEUE_DIR . "/")) {
|
||||
mkdir($dir . "/" . self::QUEUE_DIR . "/", 0775, true);
|
||||
}
|
||||
if (!is_dir($dir . "/uploaded/")) {
|
||||
mkdir($dir . "/uploaded/", 0775, true);
|
||||
if (!is_dir($dir . "/" . self::UPLOADED_DIR . "/")) {
|
||||
mkdir($dir . "/" . self::UPLOADED_DIR . "/", 0775, true);
|
||||
}
|
||||
if (!is_dir($dir . "/failed_to_upload/")) {
|
||||
mkdir($dir . "/failed_to_upload/", 0775, true);
|
||||
if (!is_dir($dir . "/" . self::FAILED_DIR . "/")) {
|
||||
mkdir($dir . "/" . self::FAILED_DIR . "/", 0775, true);
|
||||
}
|
||||
|
||||
|
||||
$this->root_dir = $dir;
|
||||
return $dir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns amount of files & total size of dir.
|
||||
*/
|
||||
public function scan_dir(string $path): array
|
||||
{
|
||||
$bytestotal=0;
|
||||
$nbfiles=0;
|
||||
$bytestotal = 0;
|
||||
$nbfiles = 0;
|
||||
|
||||
$ite=new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
|
||||
foreach (new RecursiveIteratorIterator($ite) as $filename=>$cur) {
|
||||
$ite = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
|
||||
foreach (new RecursiveIteratorIterator($ite) as $filename => $cur) {
|
||||
$filesize = $cur->getSize();
|
||||
$bytestotal += $filesize;
|
||||
$nbfiles++;
|
||||
}
|
||||
|
||||
|
||||
$size_mb = $bytestotal / 1048576; // to mb
|
||||
$size_mb = number_format($size_mb, 2, '.', '');
|
||||
return ['total_files'=>$nbfiles,'total_mb'=>$size_mb];
|
||||
return ['total_files' => $nbfiles, 'total_mb' => $size_mb];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uploads the image & handles everything
|
||||
*/
|
||||
@ -246,24 +270,24 @@ class CronUploader extends Extension
|
||||
{
|
||||
global $config, $database;
|
||||
|
||||
set_time_limit(0);
|
||||
//set_time_limit(0);
|
||||
|
||||
$output_subdir = date('Ymd-His', time())."/";
|
||||
$this->set_dir();
|
||||
|
||||
$output_subdir = date('Ymd-His', time()) . "/";
|
||||
$this->generate_image_queue();
|
||||
|
||||
|
||||
// Gets amount of imgs to upload
|
||||
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
|
||||
if (count($this->image_queue) == 0) {
|
||||
$this->add_upload_info("Your queue is empty so nothing could be uploaded.");
|
||||
$this->handle_log();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Randomize Images
|
||||
//shuffle($this->image_queue);
|
||||
|
||||
@ -271,18 +295,17 @@ class CronUploader extends Extension
|
||||
$added = 0;
|
||||
$failed = 0;
|
||||
|
||||
$failedItems = [];
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
try {
|
||||
$database->beginTransaction();
|
||||
$this->add_upload_info("Adding file: {$img[1]} - tags: {$img[2]}");
|
||||
$result = $this->add_image($img[0], $img[1], $img[2]);
|
||||
$database->commit();
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
|
||||
if ($result==null) {
|
||||
if ($result->merged) {
|
||||
$merged++;
|
||||
} else {
|
||||
$added++;
|
||||
@ -290,13 +313,9 @@ class CronUploader extends Extension
|
||||
} catch (Exception $e) {
|
||||
$failed++;
|
||||
$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());
|
||||
if (strpos($e->getMessage(), 'SQLSTATE') !== false) {
|
||||
// Postgres invalidates the transaction if there is an SQL error,
|
||||
// so all subsequence transactions will fail.
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
$database->rollback();
|
||||
} catch (Exception $e) {
|
||||
@ -310,85 +329,94 @@ class CronUploader extends Extension
|
||||
$msgNumber = $this->add_upload_info("Items failed: $failed");
|
||||
|
||||
|
||||
|
||||
// Display & save upload log
|
||||
$this->handle_log();
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private function move_uploaded($path, $filename, $output_subdir, $corrupt = false)
|
||||
{
|
||||
global $config;
|
||||
|
||||
|
||||
// Create
|
||||
$newDir = $this->root_dir;
|
||||
|
||||
|
||||
$relativeDir = dirname(substr($path, strlen($this->root_dir) + 7));
|
||||
|
||||
// Determine which dir to move to
|
||||
if ($corrupt) {
|
||||
// Move to corrupt dir
|
||||
$newDir .= "/failed_to_upload/".$output_subdir.$relativeDir;
|
||||
$newDir .= "/" . self::FAILED_DIR . "/" . $output_subdir . $relativeDir;
|
||||
$info = "ERROR: Image was not uploaded.";
|
||||
} else {
|
||||
$newDir .= "/uploaded/".$output_subdir.$relativeDir;
|
||||
$newDir .= "/" . self::UPLOADED_DIR . "/" . $output_subdir . $relativeDir;
|
||||
$info = "Image successfully uploaded. ";
|
||||
}
|
||||
$newDir = str_replace("//", "/", $newDir."/");
|
||||
$newDir = str_replace("//", "/", $newDir . "/");
|
||||
|
||||
if (!is_dir($newDir)) {
|
||||
mkdir($newDir, 0775, true);
|
||||
}
|
||||
|
||||
// move file to correct dir
|
||||
rename($path, $newDir.$filename);
|
||||
|
||||
rename($path, $newDir . $filename);
|
||||
|
||||
$this->add_upload_info($info . "Image \"$filename\" moved from queue to \"$newDir\".");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
|
||||
|
||||
$tagArray = Tag::explode($tags);
|
||||
if (count($tagArray)==0) {
|
||||
$tagArray[] = "tagme";
|
||||
}
|
||||
|
||||
$pathinfo = pathinfo($filename);
|
||||
$metadata = [];
|
||||
$metadata ['filename'] = $pathinfo ['basename'];
|
||||
if (array_key_exists('extension', $pathinfo)) {
|
||||
$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;
|
||||
$event = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($event);
|
||||
|
||||
|
||||
// Generate info message
|
||||
$infomsg = ""; // Will contain info message
|
||||
if ($event->image_id == -1) {
|
||||
throw new Exception("File type not recognised. Filename: {$filename}");
|
||||
} elseif ($event->image_id == null) {
|
||||
$infomsg = "Image merged. Filename: {$filename}";
|
||||
} elseif ($event->merged === true) {
|
||||
$infomsg = "Image merged. ID: {$event->image_id} Filename: {$filename}";
|
||||
} else {
|
||||
$infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename}";
|
||||
}
|
||||
$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
|
||||
{
|
||||
$base = $this->root_dir . "/queue";
|
||||
|
||||
if (! is_dir($base)) {
|
||||
$base = $this->root_dir . "/" . self::QUEUE_DIR;
|
||||
|
||||
if (!is_dir($base)) {
|
||||
$this->add_upload_info("Image Queue Directory could not be found at \"$base\".");
|
||||
return;
|
||||
}
|
||||
|
||||
$ite=new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS);
|
||||
foreach (new RecursiveIteratorIterator($ite) as $fullpath=>$cur) {
|
||||
|
||||
$ite = new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS);
|
||||
foreach (new RecursiveIteratorIterator($ite) as $fullpath => $cur) {
|
||||
if (!is_link($fullpath) && !is_dir($fullpath)) {
|
||||
$pathinfo = pathinfo($fullpath);
|
||||
|
||||
@ -396,62 +424,62 @@ class CronUploader extends Extension
|
||||
$tags = path_to_tags($relativePath);
|
||||
|
||||
$img = [
|
||||
0 => $fullpath,
|
||||
1 => $pathinfo ["basename"],
|
||||
2 => $tags
|
||||
0 => $fullpath,
|
||||
1 => $pathinfo ["basename"],
|
||||
2 => $tags
|
||||
];
|
||||
array_push($this->image_queue, $img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a message to the info being published at the end
|
||||
*/
|
||||
private function add_upload_info(string $text, int $addon = 0): int
|
||||
{
|
||||
$info = $this->upload_info;
|
||||
$time = "[" .date('Y-m-d H:i:s'). "]";
|
||||
|
||||
$time = "[" . date('Y-m-d H:i:s') . "]";
|
||||
|
||||
// If addon function is not used
|
||||
if ($addon == 0) {
|
||||
$this->upload_info .= "$time $text\r\n";
|
||||
|
||||
$this->upload_info .= "$time $text\r\n";
|
||||
|
||||
// Returns the number of the current line
|
||||
$currentLine = substr_count($this->upload_info, "\n") -1;
|
||||
$currentLine = substr_count($this->upload_info, "\n") - 1;
|
||||
return $currentLine;
|
||||
}
|
||||
|
||||
|
||||
// else if addon function is used, select the line & modify it
|
||||
$lines = substr($info, "\n"); // Seperate the string to array in lines
|
||||
$lines[$addon] = "$lines[$addon] $text"; // Add the content to the line
|
||||
$this->upload_info = implode("\n", $lines); // Put string back together & update
|
||||
|
||||
|
||||
return $addon; // Return line number
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is run at the end to display & save the log.
|
||||
*/
|
||||
private function handle_log()
|
||||
{
|
||||
global $page;
|
||||
|
||||
|
||||
// Display message
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type("text/plain");
|
||||
$page->set_data($this->upload_info);
|
||||
|
||||
|
||||
// Save log
|
||||
$log_path = $this->root_dir . "/uploads.log";
|
||||
|
||||
|
||||
if (file_exists($log_path)) {
|
||||
$prev_content = file_get_contents($log_path);
|
||||
} else {
|
||||
$prev_content = "";
|
||||
}
|
||||
|
||||
$content = $prev_content ."\r\n".$this->upload_info;
|
||||
|
||||
$content = $prev_content . "\r\n" . $this->upload_info;
|
||||
file_put_contents($log_path, $content);
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class custom_html_headers extends Extension
|
||||
global $config, $page;
|
||||
|
||||
// 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");
|
||||
|
||||
// if feature is enabled & sitename isn't already in title
|
||||
|
@ -60,7 +60,7 @@ class DanbooruApi extends Extension
|
||||
private function api_danbooru(PageRequestEvent $event)
|
||||
{
|
||||
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'))) {
|
||||
// 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
|
||||
elseif (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) {
|
||||
$fixedlocation = make_link("post/view/" . $event->get_arg(3));
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$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
|
||||
// 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->add_http_header("X-Danbooru-Errors: authentication error");
|
||||
return;
|
||||
|
@ -32,7 +32,7 @@ class Downtime extends Extension
|
||||
global $config, $page, $user;
|
||||
|
||||
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");
|
||||
$this->theme->display_message($msg);
|
||||
if (!defined("UNITTEST")) { // hax D:
|
||||
|
@ -21,12 +21,12 @@ class DowntimeTheme extends Themelet
|
||||
public function display_message(string $message)
|
||||
{
|
||||
global $config, $user, $page;
|
||||
$theme_name = $config->get_string('theme');
|
||||
$theme_name = $config->get_string(SetupConfig::THEME);
|
||||
$data_href = get_base_href();
|
||||
$login_link = make_link("user_admin/login");
|
||||
$auth = $user->get_auth_html();
|
||||
|
||||
$page->set_mode('data');
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_code(503);
|
||||
$page->set_data(
|
||||
<<<EOD
|
||||
|
@ -18,7 +18,7 @@ class EmoticonListTheme extends Themelet
|
||||
}
|
||||
$html .= "</tr></table>";
|
||||
$html .= "</body></html>";
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_data($html);
|
||||
}
|
||||
}
|
||||
|
@ -18,16 +18,28 @@ class ET extends Extension
|
||||
{
|
||||
global $user;
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("view_sysinfo")) {
|
||||
if ($user->can(Permissions::VIEW_SYSINTO)) {
|
||||
$event->add_link("System Info", make_link("system_info"));
|
||||
}
|
||||
}
|
||||
@ -40,8 +52,8 @@ class ET extends Extension
|
||||
global $config, $database;
|
||||
|
||||
$info = [];
|
||||
$info['site_title'] = $config->get_string("title");
|
||||
$info['site_theme'] = $config->get_string("theme");
|
||||
$info['site_title'] = $config->get_string(SetupConfig::TITLE);
|
||||
$info['site_theme'] = $config->get_string(SetupConfig::THEME);
|
||||
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
|
||||
|
||||
$info['sys_shimmie'] = VERSION;
|
||||
@ -52,14 +64,17 @@ class ET extends Extension
|
||||
$info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " .
|
||||
to_shorthand_int(disk_total_space("./"));
|
||||
$info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown';
|
||||
|
||||
$info['thumb_engine'] = $config->get_string("thumb_engine");
|
||||
$info['thumb_quality'] = $config->get_int('thumb_quality');
|
||||
$info['thumb_width'] = $config->get_int('thumb_width');
|
||||
$info['thumb_height'] = $config->get_int('thumb_height');
|
||||
$info['thumb_scaling'] = $config->get_int('thumb_scaling');
|
||||
$info['thumb_type'] = $config->get_string('thumb_type');
|
||||
$info['thumb_mem'] = $config->get_int("thumb_mem_limit");
|
||||
|
||||
$info[MediaConfig::FFMPEG_PATH] = $config->get_string(MediaConfig::FFMPEG_PATH);
|
||||
$info[MediaConfig::CONVERT_PATH] = $config->get_string(MediaConfig::CONVERT_PATH);
|
||||
$info[MediaConfig::MEM_LIMIT] = $config->get_int(MediaConfig::MEM_LIMIT);
|
||||
|
||||
$info[ImageConfig::THUMB_ENGINE] = $config->get_string(ImageConfig::THUMB_ENGINE);
|
||||
$info[ImageConfig::THUMB_QUALITY] = $config->get_int(ImageConfig::THUMB_QUALITY);
|
||||
$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_comments'] = $database->get_one("SELECT COUNT(*) FROM comments");
|
||||
|
@ -35,14 +35,16 @@ Database: {$info['sys_db']}
|
||||
Server: {$info['sys_server']}
|
||||
Disk use: {$info['sys_disk']}
|
||||
|
||||
Media System:
|
||||
Memory Limit: {$info[MediaConfig::MEM_LIMIT]}
|
||||
|
||||
Thumbnail Generation:
|
||||
Engine: {$info['thumb_engine']}
|
||||
Type: {$info['thumb_type']}
|
||||
Memory: {$info['thumb_mem']}
|
||||
Quality: {$info['thumb_quality']}
|
||||
Width: {$info['thumb_width']}
|
||||
Height: {$info['thumb_height']}
|
||||
Scaling: {$info['thumb_scaling']}
|
||||
Engine: {$info[ImageConfig::THUMB_ENGINE]}
|
||||
Type: {$info[ImageConfig::THUMB_TYPE]}
|
||||
Quality: {$info[ImageConfig::THUMB_QUALITY]}
|
||||
Width: {$info[ImageConfig::THUMB_WIDTH]}
|
||||
Height: {$info[ImageConfig::THUMB_HEIGHT]}
|
||||
Scaling: {$info[ImageConfig::THUMB_SCALING]}
|
||||
|
||||
Shimmie stats:
|
||||
Images: {$info['stat_images']}
|
||||
|
@ -22,8 +22,7 @@ class ExtensionInfo
|
||||
public $ext_name;
|
||||
public $name;
|
||||
public $link;
|
||||
public $author;
|
||||
public $email;
|
||||
public $authors;
|
||||
public $description;
|
||||
public $documentation;
|
||||
public $version;
|
||||
@ -39,8 +38,9 @@ class ExtensionInfo
|
||||
$this->ext_name = $matches[1];
|
||||
$this->name = $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];
|
||||
if (preg_match("/Name: (.*)/", $line, $matches)) {
|
||||
$this->name = $matches[1];
|
||||
@ -53,25 +53,29 @@ class ExtensionInfo
|
||||
}
|
||||
} elseif (preg_match("/Version: (.*)/", $line, $matches)) {
|
||||
$this->version = $matches[1];
|
||||
} elseif (preg_match("/Author: (.*) [<\(](.*@.*)[>\)]/", $line, $matches)) {
|
||||
$this->author = $matches[1];
|
||||
$this->email = $matches[2];
|
||||
} elseif (preg_match("/Author: (.*)/", $line, $matches)) {
|
||||
$this->author = $matches[1];
|
||||
} elseif (preg_match("/Authors?: (.*)/", $line, $matches)) {
|
||||
$author_list = explode(',', $matches[1]);
|
||||
foreach ($author_list as $author) {
|
||||
if (preg_match("/(.*) [<\(](.*@.*)[>\)]/", $author, $matches)) {
|
||||
$this->authors[] = new ExtensionAuthor($matches[1], $matches[2]);
|
||||
} else {
|
||||
$this->authors[] = new ExtensionAuthor($author, null);
|
||||
}
|
||||
}
|
||||
} elseif (preg_match("/(.*)Description: ?(.*)/", $line, $matches)) {
|
||||
$this->description = $matches[2];
|
||||
$start = $matches[1]." ";
|
||||
$start = $matches[1] . " ";
|
||||
$start_len = strlen($start);
|
||||
while (substr($lines[$i+1], 0, $start_len) == $start) {
|
||||
$this->description .= " ".substr($lines[$i+1], $start_len);
|
||||
while (substr($lines[$i + 1], 0, $start_len) == $start) {
|
||||
$this->description .= " " . substr($lines[$i + 1], $start_len);
|
||||
$i++;
|
||||
}
|
||||
} elseif (preg_match("/(.*)Documentation: ?(.*)/", $line, $matches)) {
|
||||
$this->documentation = $matches[2];
|
||||
$start = $matches[1]." ";
|
||||
$start = $matches[1] . " ";
|
||||
$start_len = strlen($start);
|
||||
while (substr($lines[$i+1], 0, $start_len) == $start) {
|
||||
$this->documentation .= " ".substr($lines[$i+1], $start_len);
|
||||
while (substr($lines[$i + 1], 0, $start_len) == $start) {
|
||||
$this->documentation .= " " . substr($lines[$i + 1], $start_len);
|
||||
$i++;
|
||||
}
|
||||
$this->documentation = str_replace('$site', make_http(get_base_href()), $this->documentation);
|
||||
@ -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
|
||||
{
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $page, $user;
|
||||
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 (is_writable("data/config")) {
|
||||
$this->set_things($_POST);
|
||||
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"));
|
||||
} else {
|
||||
$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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("manage_extension_list")) {
|
||||
if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) {
|
||||
$event->add_link("Extension Manager", make_link("ext_manager"));
|
||||
} else {
|
||||
$event->add_link("Help", make_link("ext_doc"));
|
||||
@ -166,7 +193,7 @@ class ExtManager extends Extension
|
||||
if ($all) {
|
||||
$exts = zglob("ext/*/main.php");
|
||||
} else {
|
||||
$exts = zglob("ext/{".ENABLED_EXTS."}/main.php");
|
||||
$exts = zglob("ext/{" . ENABLED_EXTS . "}/main.php");
|
||||
}
|
||||
foreach ($exts as $main) {
|
||||
$extensions[] = new ExtensionInfo($main);
|
||||
@ -200,16 +227,14 @@ class ExtManager extends Extension
|
||||
{
|
||||
file_put_contents(
|
||||
"data/config/extensions.conf.php",
|
||||
'<'.'?php'."\n".
|
||||
'define("EXTRA_EXTS", "'.implode(",", $extras).'");'."\n".
|
||||
'?'.">"
|
||||
'<' . '?php' . "\n" .
|
||||
'define("EXTRA_EXTS", "' . implode(",", $extras) . '");' . "\n" .
|
||||
'?' . ">"
|
||||
);
|
||||
|
||||
// when the list of active extensions changes, we can be
|
||||
// pretty sure that the list of who reacts to what will
|
||||
// change too
|
||||
if (file_exists("data/cache/event_listeners.php")) {
|
||||
unlink("data/cache/event_listeners.php");
|
||||
}
|
||||
_clear_cached_event_listeners();
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class ExtManagerTheme extends Themelet
|
||||
{
|
||||
$h_en = $editable ? "<th>Enabled</th>" : "";
|
||||
$html = "
|
||||
".make_form(make_link("ext_manager/set"))."
|
||||
" . make_form(make_link("ext_manager/set")) . "
|
||||
<table id='extensions' class='zebra sortable'>
|
||||
<thead>
|
||||
<tr>
|
||||
@ -26,17 +26,17 @@ class ExtManagerTheme extends Themelet
|
||||
continue;
|
||||
}
|
||||
|
||||
$h_name = html_escape(empty($extension->name) ? $extension->ext_name : $extension->name);
|
||||
$h_name = html_escape(empty($extension->name) ? $extension->ext_name : $extension->name);
|
||||
$h_description = html_escape($extension->description);
|
||||
$h_link = make_link("ext_doc/".url_escape($extension->ext_name));
|
||||
$h_enabled = ($extension->enabled === true ? " checked='checked'" : ($extension->enabled === false ? "" : " disabled checked='checked'"));
|
||||
$h_enabled_box = $editable ? "<td><input type='checkbox' name='ext_".html_escape($extension->ext_name)."' id='ext_".html_escape($extension->ext_name)."'$h_enabled></td>" : "";
|
||||
$h_docs = ($extension->documentation ? "<a href='$h_link'>■</a>" : ""); //TODO: A proper "docs" symbol would be preferred here.
|
||||
$h_link = make_link("ext_doc/" . url_escape($extension->ext_name));
|
||||
$h_enabled = ($extension->enabled === true ? " checked='checked'" : ($extension->enabled === false ? "" : " disabled checked='checked'"));
|
||||
$h_enabled_box = $editable ? "<td><input type='checkbox' name='ext_" . html_escape($extension->ext_name) . "' id='ext_" . html_escape($extension->ext_name) . "'$h_enabled></td>" : "";
|
||||
$h_docs = ($extension->documentation ? "<a href='$h_link'>■</a>" : ""); //TODO: A proper "docs" symbol would be preferred here.
|
||||
|
||||
$html .= "
|
||||
<tr data-ext='{$extension->ext_name}'>
|
||||
{$h_enabled_box}
|
||||
<td><label for='ext_".html_escape($extension->ext_name)."'>{$h_name}</label></td>
|
||||
<td><label for='ext_" . html_escape($extension->ext_name) . "'>{$h_name}</label></td>
|
||||
<td>{$h_docs}</td>
|
||||
<td style='text-align: left;'>{$h_description}</td>
|
||||
</tr>";
|
||||
@ -116,15 +116,23 @@ class ExtManagerTheme extends Themelet
|
||||
public function display_doc(Page $page, ExtensionInfo $info)
|
||||
{
|
||||
$author = "";
|
||||
if ($info->author) {
|
||||
if ($info->email) {
|
||||
$author = "<br><b>Author:</b> <a href=\"mailto:".html_escape($info->email)."\">".html_escape($info->author)."</a>";
|
||||
} else {
|
||||
$author = "<br><b>Author:</b> ".html_escape($info->author);
|
||||
if (count($info->authors) > 0) {
|
||||
$author = "<br /><b>Author";
|
||||
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 {
|
||||
$author .= html_escape($auth->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
$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>" : "";
|
||||
|
||||
$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>" : "";
|
||||
$doc = $info->documentation;
|
||||
$html = "
|
||||
<div style='margin: auto; text-align: left; width: 512px;'>
|
||||
@ -133,10 +141,10 @@ class ExtManagerTheme extends Themelet
|
||||
$link
|
||||
<p>$doc
|
||||
<hr>
|
||||
<p><a href='".make_link("ext_manager")."'>Back to the list</a>
|
||||
<p><a href='" . make_link("ext_manager") . "'>Back to the list</a>
|
||||
</div>";
|
||||
|
||||
$page->set_title("Documentation for ".html_escape($info->name));
|
||||
$page->set_title("Documentation for " . html_escape($info->name));
|
||||
$page->set_heading(html_escape($info->name));
|
||||
$page->add_block(new NavBlock());
|
||||
$page->add_block(new Block("Documentation", $html));
|
||||
|
@ -81,7 +81,7 @@ class Favorites extends Extension
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
{
|
||||
|
@ -34,4 +34,28 @@ class FavoritesTheme extends Themelet
|
||||
|
||||
$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;
|
||||
if ($event->page_matches("featured_image")) {
|
||||
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']);
|
||||
if ($id > 0) {
|
||||
$config->set_int("featured_id", $id);
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@ -45,7 +45,7 @@ class Featured extends Extension
|
||||
if ($event->get_arg(0) == "download") {
|
||||
$image = Image::by_id($config->get_int("featured_id"));
|
||||
if (!is_null($image)) {
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_type($image->get_mime_type());
|
||||
$page->set_data(file_get_contents($image->get_image_filename()));
|
||||
}
|
||||
@ -86,7 +86,7 @@ class Featured extends Extension
|
||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("edit_feature")) {
|
||||
if ($user->can(Permissions::EDIT_FEATURE)) {
|
||||
$event->add_part($this->theme->get_buttons_html($event->image->id));
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ class Forum extends Extension
|
||||
$redirectTo = "forum/view/".$newThreadID."/1";
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link($redirectTo));
|
||||
|
||||
break;
|
||||
@ -151,7 +151,7 @@ class Forum extends Extension
|
||||
$this->delete_post($postID);
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("forum/view/".$threadID));
|
||||
break;
|
||||
case "nuke":
|
||||
@ -161,7 +161,7 @@ class Forum extends Extension
|
||||
$this->delete_thread($threadID);
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("forum/index"));
|
||||
break;
|
||||
case "answer":
|
||||
@ -176,11 +176,11 @@ class Forum extends Extension
|
||||
}
|
||||
$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));
|
||||
break;
|
||||
default:
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("forum/index"));
|
||||
//$this->theme->display_error(400, "Invalid action", "You should check forum/index.");
|
||||
break;
|
||||
|
@ -14,7 +14,7 @@ class Handle404 extends Extension
|
||||
{
|
||||
global $config, $page;
|
||||
// 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));
|
||||
log_debug("handle_404", "Hit 404: $h_pagename");
|
||||
$page->set_code(404);
|
||||
|
@ -8,12 +8,32 @@
|
||||
|
||||
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;
|
||||
|
||||
if (!create_thumbnail_ffmpeg($hash)) {
|
||||
copy("ext/handle_flash/thumb.jpg", warehouse_path("thumbs", $hash));
|
||||
if (!Media::create_thumbnail_ffmpeg($hash)) {
|
||||
copy("ext/handle_flash/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -35,13 +55,7 @@ class FlashFileHandler extends DataHandlerExtension
|
||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
$info = getimagesize($filename);
|
||||
if (!$info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$image->width = $info[0];
|
||||
$image->height = $info[1];
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
@ -5,65 +5,54 @@
|
||||
* 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)) {
|
||||
$hash = $event->hash;
|
||||
$ha = substr($hash, 0, 2);
|
||||
move_upload_to_archive($event);
|
||||
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");
|
||||
if(in_array($event->ext, self::SUPPORTED_EXTENSIONS)) {
|
||||
$event->lossless = true;
|
||||
$event->video = false;
|
||||
$event->audio = false;
|
||||
|
||||
$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));
|
||||
} finally {
|
||||
fclose($fp);
|
||||
}
|
||||
$iae = new ImageAdditionEvent($image);
|
||||
send_event($iae);
|
||||
$event->image_id = $iae->image->id;
|
||||
|
||||
$width = $subheader['width'];
|
||||
$height = $subheader['height'];
|
||||
$event->width = $width == 0 ? 256 : $width;
|
||||
$event->height = $height == 0 ? 256 : $height;
|
||||
}
|
||||
}
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
|
||||
protected function supported_ext(string $ext): bool
|
||||
{
|
||||
global $page;
|
||||
if ($this->supported_ext($event->image->ext)) {
|
||||
$this->theme->display_image($page, $event->image);
|
||||
}
|
||||
return in_array(strtolower($ext), self::SUPPORTED_EXTENSIONS);
|
||||
}
|
||||
|
||||
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)
|
||||
protected function create_image_from_data(string $filename, array $metadata)
|
||||
{
|
||||
$image = new Image();
|
||||
|
||||
$fp = fopen($filename, "r");
|
||||
$header = unpack("Snull/Stype/Scount", fread($fp, 6));
|
||||
|
||||
$subheader = unpack("Cwidth/Cheight/Ccolours/Cnull/Splanes/Sbpp/Lsize/loffset", fread($fp, 16));
|
||||
fclose($fp);
|
||||
|
||||
$width = $subheader['width'];
|
||||
$height = $subheader['height'];
|
||||
$image->width = $width == 0 ? 256 : $width;
|
||||
$image->height = $height == 0 ? 256 : $height;
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = $metadata['filename'];
|
||||
$image->ext = $metadata['extension'];
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = $metadata['filename'];
|
||||
$image->ext = $metadata['extension'];
|
||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function check_contents(string $file): bool
|
||||
protected function check_contents(string $file): bool
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
@ -74,27 +63,14 @@ class IcoFileHandler extends Extension
|
||||
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;
|
||||
|
||||
$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);
|
||||
try {
|
||||
create_image_thumb($hash, $type, MediaEngine::IMAGICK);
|
||||
return true;
|
||||
} catch (MediaException $e) {
|
||||
log_warning("handle_ico", "Could not generate thumbnail. " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,22 @@
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -37,6 +50,7 @@ class MP3FileHandler extends DataHandlerExtension
|
||||
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
|
@ -8,25 +8,56 @@
|
||||
|
||||
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
|
||||
{
|
||||
$exts = ["jpg", "jpeg", "gif", "png", "webp"];
|
||||
$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)
|
||||
{
|
||||
$image = new Image();
|
||||
|
||||
$info = getimagesize($filename);
|
||||
if (!$info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$image->width = $info[0];
|
||||
$image->height = $info[1];
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename'];
|
||||
@ -53,26 +84,24 @@ class PixelFileHandler extends DataHandlerExtension
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function create_thumb(string $hash): bool
|
||||
protected function create_thumb(string $hash, string $type): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
$inname = warehouse_path("images", $hash);
|
||||
$outname = warehouse_path("thumbs", $hash);
|
||||
|
||||
$ok = false;
|
||||
|
||||
switch ($config->get_string("thumb_engine")) {
|
||||
default:
|
||||
case 'gd':
|
||||
$ok = $this->make_thumb_gd($inname, $outname);
|
||||
break;
|
||||
case 'convert':
|
||||
$ok = create_thumbnail_convert($hash);
|
||||
break;
|
||||
try {
|
||||
create_image_thumb($hash, $type);
|
||||
return true;
|
||||
} 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 $ok;
|
||||
}
|
||||
|
||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||
@ -89,38 +118,4 @@ class PixelFileHandler extends DataHandlerExtension
|
||||
", 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;
|
||||
|
||||
$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?
|
||||
$exif = @exif_read_data($image->get_image_filename(), 0, true);
|
||||
if ($exif) {
|
||||
|
@ -14,10 +14,10 @@ class HandleStatic extends Extension
|
||||
{
|
||||
global $config, $page;
|
||||
// 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));
|
||||
$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";
|
||||
$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('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));
|
||||
if (endsWith($filename, ".ico")) {
|
||||
$page->set_type("image/x-icon");
|
||||
|
@ -7,12 +7,13 @@ TD>INPUT[type="text"],
|
||||
TD>INPUT[type="password"],
|
||||
TD>INPUT[type="email"],
|
||||
TD>SELECT,
|
||||
TD>TEXTAREA {width: 100%;}
|
||||
TD>TEXTAREA,
|
||||
TD>BUTTON {width: 100%;}
|
||||
|
||||
TABLE.form {width: 300px;}
|
||||
TABLE.form TD, TABLE.form TH {vertical-align: middle;}
|
||||
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;}
|
||||
|
||||
*[onclick],
|
||||
|
@ -10,6 +10,24 @@ use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
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)
|
||||
{
|
||||
if ($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
|
||||
@ -19,25 +37,30 @@ class SVGFileHandler extends DataHandlerExtension
|
||||
$sanitizer->removeRemoteReferences(true);
|
||||
$dirtySVG = file_get_contents($event->tmpname);
|
||||
$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));
|
||||
$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)) {
|
||||
throw new UploadException("SVG handler failed to create image object from data");
|
||||
}
|
||||
$iae = new ImageAdditionEvent($image);
|
||||
send_event($iae);
|
||||
$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)) {
|
||||
copy("ext/handle_svg/thumb.jpg", warehouse_path("thumbs", $hash));
|
||||
try {
|
||||
create_image_thumb($hash, $type, MediaEngine::IMAGICK);
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
@ -57,11 +80,11 @@ class SVGFileHandler extends DataHandlerExtension
|
||||
$hash = $image->hash;
|
||||
|
||||
$page->set_type("image/svg+xml");
|
||||
$page->set_mode("data");
|
||||
$page->set_mode(PageMode::DATA);
|
||||
|
||||
$sanitizer = new Sanitizer();
|
||||
$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);
|
||||
$page->set_data($cleanSVG);
|
||||
}
|
||||
@ -77,10 +100,6 @@ class SVGFileHandler extends DataHandlerExtension
|
||||
{
|
||||
$image = new Image();
|
||||
|
||||
$msp = new MiniSVGParser($filename);
|
||||
$image->width = $msp->width;
|
||||
$image->height = $msp->height;
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = $metadata['filename'];
|
||||
|
@ -16,20 +16,21 @@
|
||||
|
||||
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)
|
||||
{
|
||||
global $config;
|
||||
|
||||
if ($config->get_int("ext_handle_video_version") < 1) {
|
||||
if ($ffmpeg = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffmpeg')) {
|
||||
//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', '');
|
||||
}
|
||||
|
||||
// This used to set the ffmpeg path. It does not do this anymore, that is now in the base graphic extension.
|
||||
$config->set_int("ext_handle_video_version", 1);
|
||||
log_info("handle_video", "extension installed");
|
||||
}
|
||||
@ -41,37 +42,83 @@ class VideoFileHandler extends DataHandlerExtension
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$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_label("<br>");
|
||||
$sb->add_bool_option("video_playback_loop", "Loop: ");
|
||||
$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.
|
||||
*/
|
||||
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
|
||||
{
|
||||
$exts = ["flv", "mp4", "m4v", "ogv", "webm"];
|
||||
return in_array(strtolower($ext), $exts);
|
||||
return in_array(strtolower($ext), self::SUPPORTED_EXT);
|
||||
}
|
||||
|
||||
protected function create_image_from_data(string $filename, array $metadata): Image
|
||||
{
|
||||
$image = new Image();
|
||||
|
||||
$size = video_size($filename);
|
||||
$image->width = $size[0];
|
||||
$image->height = $size[1];
|
||||
|
||||
switch (getMimeType($filename)) {
|
||||
case "video/webm":
|
||||
$image->ext = "webm";
|
||||
@ -103,13 +150,7 @@ class VideoFileHandler extends DataHandlerExtension
|
||||
{
|
||||
return (
|
||||
file_exists($tmpname) &&
|
||||
in_array(getMimeType($tmpname), [
|
||||
'video/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/flv',
|
||||
'video/x-flv'
|
||||
])
|
||||
in_array(getMimeType($tmpname), self::SUPPORTED_MIME)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,15 @@ class VideoFileHandlerTheme extends Themelet
|
||||
$loop = $config->get_bool("video_playback_loop");
|
||||
$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/>";
|
||||
|
||||
//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' : '');
|
||||
|
||||
$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]}'>
|
||||
|
||||
<!-- If browser doesn't support filetype, fallback to flash -->
|
||||
|
@ -9,9 +9,9 @@ class HellBan extends Extension
|
||||
{
|
||||
global $page, $user;
|
||||
|
||||
if ($user->can("hellbanned")) {
|
||||
if ($user->can(Permissions::HELLBANNED)) {
|
||||
$s = "";
|
||||
} elseif ($user->can("view_hellbanned")) {
|
||||
} elseif ($user->can(Permissions::VIEW_HELLBANNED)) {
|
||||
$s = "DIV.hb, TR.hb TD {border: 1px solid red !important;}";
|
||||
} else {
|
||||
$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;
|
||||
if ($event->page_matches("home")) {
|
||||
$base_href = get_base_href();
|
||||
$sitename = $config->get_string('title');
|
||||
$theme_name = $config->get_string('theme');
|
||||
$sitename = $config->get_string(SetupConfig::TITLE);
|
||||
$theme_name = $config->get_string(SetupConfig::THEME);
|
||||
|
||||
$body = $this->get_body();
|
||||
|
||||
@ -52,7 +52,7 @@ class Home extends Extension
|
||||
// returns just the contents of the body
|
||||
global $config;
|
||||
$base_href = get_base_href();
|
||||
$sitename = $config->get_string('title');
|
||||
$sitename = $config->get_string(SetupConfig::TITLE);
|
||||
$contact_link = contact_link();
|
||||
if (is_null($contact_link)) {
|
||||
$contact_link = "";
|
||||
|
@ -4,7 +4,7 @@ class HomeTheme extends Themelet
|
||||
{
|
||||
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();
|
||||
$hh = $page->get_all_html_headers();
|
||||
$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.
|
||||
*/
|
||||
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)
|
||||
{
|
||||
global $config;
|
||||
$config->set_default_int('thumb_width', 192);
|
||||
$config->set_default_int('thumb_height', 192);
|
||||
$config->set_default_int('thumb_scaling', 100);
|
||||
$config->set_default_int('thumb_quality', 75);
|
||||
$config->set_default_string('thumb_type', 'jpg');
|
||||
$config->set_default_int('thumb_mem_limit', parse_shorthand_int('8MB'));
|
||||
$config->set_default_string('thumb_convert_path', 'convert');
|
||||
$config->set_default_int(ImageConfig::THUMB_WIDTH, 192);
|
||||
$config->set_default_int(ImageConfig::THUMB_HEIGHT, 192);
|
||||
$config->set_default_int(ImageConfig::THUMB_SCALING, 100);
|
||||
$config->set_default_int(ImageConfig::THUMB_QUALITY, 75);
|
||||
$config->set_default_string(ImageConfig::THUMB_TYPE, 'jpg');
|
||||
|
||||
if (function_exists("exif_read_data")) {
|
||||
$config->set_default_bool('image_show_meta', false);
|
||||
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||
$config->set_default_bool(ImageConfig::SHOW_META, false);
|
||||
}
|
||||
$config->set_default_string('image_ilink', '');
|
||||
$config->set_default_string('image_tlink', '');
|
||||
$config->set_default_string('image_tip', '$tags // $size // $filesize');
|
||||
$config->set_default_string('upload_collision_handler', 'error');
|
||||
$config->set_default_int('image_expires', (60*60*24*31)); // defaults to one month
|
||||
$config->set_default_string(ImageConfig::ILINK, '');
|
||||
$config->set_default_string(ImageConfig::TLINK, '');
|
||||
$config->set_default_string(ImageConfig::TIP, '$tags // $size // $filesize');
|
||||
$config->set_default_string(ImageConfig::UPLOAD_COLLISION_HANDLER, ImageConfig::COLLISION_ERROR);
|
||||
$config->set_default_int(ImageConfig::EXPIRES, (60*60*24*31)); // defaults to one month
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
if ($event->page_matches("image/delete")) {
|
||||
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']);
|
||||
if ($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')) {
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
} else {
|
||||
@ -53,10 +87,10 @@ class ImageIO extends Extension
|
||||
}
|
||||
} elseif ($event->page_matches("image/replace")) {
|
||||
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']);
|
||||
if ($image) {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link('upload/replace/'.$image->id));
|
||||
} else {
|
||||
/* Invalid image ID */
|
||||
@ -76,11 +110,11 @@ class ImageIO extends Extension
|
||||
{
|
||||
global $user;
|
||||
|
||||
if ($user->can("delete_image")) {
|
||||
if ($user->can(Permissions::DELETE_IMAGE)) {
|
||||
$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... */
|
||||
if ($user->can("replace_image")) {
|
||||
if ($user->can(Permissions::REPLACE_IMAGE)) {
|
||||
$event->add_part($this->theme->get_replace_html($event->image->id));
|
||||
}
|
||||
}
|
||||
@ -88,7 +122,7 @@ class ImageIO extends Extension
|
||||
public function onImageAddition(ImageAdditionEvent $event)
|
||||
{
|
||||
try {
|
||||
$this->add_image($event->image);
|
||||
$this->add_image($event);
|
||||
} catch (ImageAdditionException $e) {
|
||||
throw new UploadException($e->error);
|
||||
}
|
||||
@ -125,60 +159,48 @@ class ImageIO extends Extension
|
||||
$sb = new SetupBlock("Image Options");
|
||||
$sb->position = 30;
|
||||
// advanced only
|
||||
//$sb->add_text_option("image_ilink", "Image link: ");
|
||||
//$sb->add_text_option("image_tlink", "<br>Thumbnail link: ");
|
||||
$sb->add_text_option("image_tip", "Image tooltip: ");
|
||||
$sb->add_choice_option("upload_collision_handler", ['Error'=>'error', 'Merge'=>'merge'], "<br>Upload collision handler: ");
|
||||
if (function_exists("exif_read_data")) {
|
||||
$sb->add_bool_option("image_show_meta", "<br>Show metadata: ");
|
||||
//$sb->add_text_option(ImageConfig::ILINK, "Image link: ");
|
||||
//$sb->add_text_option(ImageConfig::TLINK, "<br>Thumbnail link: ");
|
||||
$sb->add_text_option(ImageConfig::TIP, "Image tooltip: ");
|
||||
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "<br>Upload collision handler: ");
|
||||
if (function_exists(self::EXIF_READ_FUNCTION)) {
|
||||
$sb->add_bool_option(ImageConfig::SHOW_META, "<br>Show metadata: ");
|
||||
}
|
||||
|
||||
$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->add_choice_option("thumb_engine", $thumbers, "Engine: ");
|
||||
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine: ");
|
||||
$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_int_option("thumb_width");
|
||||
$sb->add_int_option(ImageConfig::THUMB_WIDTH);
|
||||
$sb->add_label(" x ");
|
||||
$sb->add_int_option("thumb_height");
|
||||
$sb->add_int_option(ImageConfig::THUMB_HEIGHT);
|
||||
$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("<br>High-DPI scaling ");
|
||||
$sb->add_int_option("thumb_scaling");
|
||||
$sb->add_int_option(ImageConfig::THUMB_SCALING);
|
||||
$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);
|
||||
}
|
||||
|
||||
|
||||
// add image {{{
|
||||
private function add_image(Image $image)
|
||||
private function add_image(ImageAdditionEvent $event)
|
||||
{
|
||||
global $user, $database, $config;
|
||||
|
||||
$image = $event->image;
|
||||
|
||||
/*
|
||||
* Validate things
|
||||
*/
|
||||
@ -191,8 +213,8 @@ class ImageIO extends Extension
|
||||
*/
|
||||
$existing = Image::by_hash($image->hash);
|
||||
if (!is_null($existing)) {
|
||||
$handler = $config->get_string("upload_collision_handler");
|
||||
if ($handler == "merge" || isset($_GET['update'])) {
|
||||
$handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER);
|
||||
if ($handler == ImageConfig::COLLISION_MERGE || isset($_GET['update'])) {
|
||||
$merged = array_merge($image->get_tag_array(), $existing->get_tag_array());
|
||||
send_event(new TagSetEvent($existing, $merged));
|
||||
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'])) {
|
||||
send_event(new SourceSetEvent($existing, $_GET['source']));
|
||||
}
|
||||
return null;
|
||||
$event->merged = true;
|
||||
$event->image = Image::by_id($existing->id);
|
||||
return;
|
||||
} else {
|
||||
$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);
|
||||
@ -217,12 +241,12 @@ class ImageIO extends Extension
|
||||
)
|
||||
VALUES (
|
||||
: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,
|
||||
"hash"=>$image->hash, "ext"=>strtolower($image->ext), "width"=>$image->width, "height"=>$image->height, "source"=>$image->source
|
||||
]
|
||||
"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), "source" => $image->source
|
||||
]
|
||||
);
|
||||
$image->id = $database->get_last_insert_id('images_id_seq');
|
||||
|
||||
@ -240,6 +264,13 @@ class ImageIO extends Extension
|
||||
if ($image->source !== null) {
|
||||
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
|
||||
|
||||
@ -251,15 +282,14 @@ class ImageIO extends Extension
|
||||
|
||||
global $page;
|
||||
if (!is_null($image)) {
|
||||
$page->set_mode("data");
|
||||
if ($type == "thumb") {
|
||||
$ext = $config->get_string("thumb_type");
|
||||
$ext = $config->get_string(ImageConfig::THUMB_TYPE);
|
||||
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
||||
$page->set_type(MIME_TYPE_MAP[$ext]);
|
||||
} else {
|
||||
$page->set_type("image/jpeg");
|
||||
}
|
||||
|
||||
|
||||
$file = $image->get_thumb_filename();
|
||||
} else {
|
||||
$page->set_type($image->get_mime_type());
|
||||
@ -274,26 +304,29 @@ class ImageIO extends Extension
|
||||
$gmdate_mod = gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT';
|
||||
|
||||
if ($if_modified_since == $gmdate_mod) {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_code(304);
|
||||
$page->set_data("");
|
||||
} else {
|
||||
$page->set_mode(PageMode::FILE);
|
||||
$page->add_http_header("Last-Modified: $gmdate_mod");
|
||||
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));
|
||||
|
||||
$page->set_file($file);
|
||||
|
||||
if ($config->get_int("image_expires")) {
|
||||
$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 {
|
||||
$expires = 'Fri, 2 Sep 2101 12:42:42 GMT'; // War was beginning
|
||||
}
|
||||
$page->add_http_header('Expires: '.$expires);
|
||||
$page->add_http_header('Expires: ' . $expires);
|
||||
}
|
||||
} else {
|
||||
$page->set_title("Not Found");
|
||||
$page->set_heading("Not Found");
|
||||
$page->add_block(new Block("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0));
|
||||
$page->add_block(new Block("Navigation", "<a href='" . make_link() . "'>Index</a>", "left", 0));
|
||||
$page->add_block(new Block(
|
||||
"Image not in database",
|
||||
"The requested image was not found in the database"
|
||||
@ -314,33 +347,50 @@ class ImageIO extends Extension
|
||||
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) {
|
||||
$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
|
||||
and have it stored in a 'replaced images' list that could be
|
||||
inspected later by an admin?
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
// Update the data in the database.
|
||||
$database->Execute(
|
||||
"UPDATE images SET
|
||||
filename = :filename, filesize = :filesize, hash = :hash,
|
||||
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
|
||||
]
|
||||
);
|
||||
|
||||
try {
|
||||
Media::update_image_media_properties($image->hash, $image->ext);
|
||||
} catch(MediaException $e) {
|
||||
log_warning("image_replace","Error while running update_image_media_properties: ".$e->getMessage());
|
||||
}
|
||||
|
||||
/* Generate new thumbnail */
|
||||
send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->ext)));
|
||||
|
@ -64,7 +64,7 @@ class ImageBan extends Extension
|
||||
global $database, $page, $user;
|
||||
|
||||
if ($event->page_matches("image_hash_ban")) {
|
||||
if ($user->can("ban_image")) {
|
||||
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||
if ($event->get_arg(0) == "add") {
|
||||
$image = isset($_POST['image_id']) ? Image::by_id(int_escape($_POST['image_id'])) : null;
|
||||
$hash = isset($_POST["hash"]) ? $_POST["hash"] : $image->hash;
|
||||
@ -79,7 +79,7 @@ class ImageBan extends Extension
|
||||
flash_message("Image deleted");
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
} elseif ($event->get_arg(0) == "remove") {
|
||||
@ -87,7 +87,7 @@ class ImageBan extends Extension
|
||||
send_event(new RemoveImageHashBanEvent($_POST['hash']));
|
||||
|
||||
flash_message("Image ban removed");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
} 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)
|
||||
{
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@ -130,7 +141,7 @@ class ImageBan extends Extension
|
||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("ban_image")) {
|
||||
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||
$event->add_part($this->theme->get_buttons_html($event->image));
|
||||
}
|
||||
}
|
||||
|
@ -233,16 +233,16 @@ class Index extends Extension
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $database, $page;
|
||||
global $database, $page, $user;
|
||||
if ($event->page_matches("post/list")) {
|
||||
if (isset($_GET['search'])) {
|
||||
// implode(explode()) to resolve aliases and sanitise
|
||||
$search = url_escape(Tag::implode(Tag::explode($_GET['search'], false)));
|
||||
if (empty($search)) {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/list/1"));
|
||||
} else {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link('post/list/'.$search.'/1'));
|
||||
}
|
||||
return;
|
||||
@ -257,13 +257,32 @@ class Index extends Extension
|
||||
try {
|
||||
#log_debug("index", "Search for ".Tag::implode($search_terms), false, array("terms"=>$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 = $database->cache->get("post-list:$page_number");
|
||||
if (!$images) {
|
||||
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
|
||||
$database->cache->set("post-list:$page_number", $images, 60);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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");
|
||||
if (!$images) {
|
||||
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
|
||||
$database->cache->set("post-list:$page_number", $images, 60);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$images) {
|
||||
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
|
||||
}
|
||||
} catch (SearchTermParseException $stpe) {
|
||||
@ -278,7 +297,7 @@ class Index extends Extension
|
||||
$this->theme->display_intro($page);
|
||||
send_event(new PostListBuildingEvent($search_terms));
|
||||
} 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));
|
||||
} else {
|
||||
$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)
|
||||
{
|
||||
$matches = [];
|
||||
@ -361,6 +403,7 @@ class Index extends Extension
|
||||
$event->add_querylet(new Querylet('images.source LIKE :src', ["src"=>"%$source%"]));
|
||||
}
|
||||
} 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], ":") ?: "=";
|
||||
$val = $matches[2];
|
||||
$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;
|
||||
$db = $database->get_driver_name();
|
||||
if ($db == "pgsql" || $db == "sqlite") {
|
||||
if ($db == DatabaseDriver::PGSQL || $db == DatabaseDriver::SQLITE) {
|
||||
$this->markTestIncomplete();
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ and of course start organising your images :-)
|
||||
global $config;
|
||||
|
||||
if (count($this->search_terms) == 0) {
|
||||
$page_title = $config->get_string('title');
|
||||
$page_title = $config->get_string(SetupConfig::TITLE);
|
||||
} else {
|
||||
$search_string = implode(' ', $this->search_terms);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
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")) {
|
||||
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 (isset($_POST['ip']) && isset($_POST['reason']) && isset($_POST['end'])) {
|
||||
if (empty($_POST['end'])) {
|
||||
@ -77,7 +77,7 @@ class IPBan extends Extension
|
||||
send_event(new AddIPBanEvent($_POST['ip'], $_POST['reason'], $end));
|
||||
|
||||
flash_message("Ban for {$_POST['ip']} added");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("ip_ban/list"));
|
||||
}
|
||||
} elseif ($event->get_arg(0) == "remove" && $user->check_auth_token()) {
|
||||
@ -85,7 +85,7 @@ class IPBan extends Extension
|
||||
send_event(new RemoveIPBanEvent($_POST['id']));
|
||||
|
||||
flash_message("Ban removed");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("ip_ban/list"));
|
||||
}
|
||||
} elseif ($event->get_arg(0) == "list") {
|
||||
@ -105,10 +105,20 @@ class IPBan extends Extension
|
||||
$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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("ban_ip")) {
|
||||
if ($user->can(Permissions::BAN_IP)) {
|
||||
$event->add_link("IP Bans", make_link("ip_ban/list"));
|
||||
}
|
||||
}
|
||||
@ -235,7 +245,7 @@ class IPBan extends Extension
|
||||
{
|
||||
global $config, $database;
|
||||
|
||||
$prefix = ($database->get_driver_name() == "sqlite" ? "bans." : "");
|
||||
$prefix = ($database->get_driver_name() == DatabaseDriver::SQLITE ? "bans." : "");
|
||||
|
||||
$bans = $this->get_active_bans();
|
||||
|
||||
|
@ -16,7 +16,7 @@ class IPBanTheme extends Themelet
|
||||
{
|
||||
global $database, $user;
|
||||
$h_bans = "";
|
||||
$prefix = ($database->get_driver_name() == "sqlite" ? "bans." : "");
|
||||
$prefix = ($database->get_driver_name() == DatabaseDriver::SQLITE ? "bans." : "");
|
||||
foreach ($bans as $ban) {
|
||||
$end_human = date('Y-m-d', $ban[$prefix.'end_timestamp']);
|
||||
$h_bans .= "
|
||||
|
@ -48,7 +48,7 @@ class LogDatabase extends Extension
|
||||
{
|
||||
global $database, $user;
|
||||
if ($event->page_matches("log/view")) {
|
||||
if ($user->can("view_eventlog")) {
|
||||
if ($user->can(Permissions::VIEW_EVENTLOG)) {
|
||||
$wheres = [];
|
||||
$args = [];
|
||||
$page_num = int_escape($event->get_arg(0));
|
||||
@ -68,7 +68,7 @@ class LogDatabase extends Extension
|
||||
$args["module"] = $_GET["module"];
|
||||
}
|
||||
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"])) {
|
||||
$wheres[] = "(username = :user1 OR text(address) = :user2)";
|
||||
$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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("view_eventlog")) {
|
||||
if ($user->can(Permissions::VIEW_EVENTLOG)) {
|
||||
$event->add_link("Event Log", make_link("log/view"));
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class MailTest extends Extension
|
||||
{
|
||||
if ($event->page_matches("mail/test")) {
|
||||
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 "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'])) {
|
||||
$_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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("ban_image")) {
|
||||
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||
$event->add_link("UnTags", make_link("untag/list/1"));
|
||||
}
|
||||
}
|
||||
@ -71,7 +81,7 @@ class NotATag extends Extension
|
||||
global $database, $page, $user;
|
||||
|
||||
if ($event->page_matches("untag")) {
|
||||
if ($user->can("ban_image")) {
|
||||
if ($user->can(Permissions::BAN_IMAGE)) {
|
||||
if ($event->get_arg(0) == "add") {
|
||||
$tag = $_POST["tag"];
|
||||
$redirect = isset($_POST['redirect']) ? $_POST['redirect'] : "DNP";
|
||||
@ -81,14 +91,14 @@ class NotATag extends Extension
|
||||
[$tag, $redirect]
|
||||
);
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
} elseif ($event->get_arg(0) == "remove") {
|
||||
if (isset($_POST['tag'])) {
|
||||
$database->Execute("DELETE FROM untags WHERE tag = ?", [$_POST['tag']]);
|
||||
|
||||
flash_message("Image ban removed");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
} elseif ($event->get_arg(0) == "list") {
|
||||
|
@ -100,7 +100,7 @@ class Notes extends Extension
|
||||
$this->revert_history($noteID, $reviewID);
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("note/updated"));
|
||||
break;
|
||||
case "add_note":
|
||||
@ -108,7 +108,7 @@ class Notes extends Extension
|
||||
$this->add_new_note();
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||
break;
|
||||
case "add_request":
|
||||
@ -116,7 +116,7 @@ class Notes extends Extension
|
||||
$this->add_note_request();
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||
break;
|
||||
case "nuke_notes":
|
||||
@ -124,7 +124,7 @@ class Notes extends Extension
|
||||
$this->nuke_notes();
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||
break;
|
||||
case "nuke_requests":
|
||||
@ -132,25 +132,25 @@ class Notes extends Extension
|
||||
$this->nuke_requests();
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||
break;
|
||||
case "edit_note":
|
||||
if (!$user->is_anonymous()) {
|
||||
$this->update_note();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/" . $_POST["image_id"]));
|
||||
}
|
||||
break;
|
||||
case "delete_note":
|
||||
if ($user->is_admin()) {
|
||||
$this->delete_note();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("note/list"));
|
||||
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)"));
|
||||
} elseif (preg_match("/^notes_by_userno[=|:](\d+)$/i", $event->term, $matches)) {
|
||||
$user_id = int_escape($matches[1]);
|
||||
} elseif (preg_match("/^(notes_by_userno|notes_by_user_id)[=|:](\d+)$/i", $event->term, $matches)) {
|
||||
$user_id = int_escape($matches[2]);
|
||||
$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.
|
||||
|
@ -247,4 +247,29 @@ class NotesTheme extends Themelet
|
||||
|
||||
$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)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("edit_other_vote")) {
|
||||
if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
|
||||
$this->theme->get_nuller($event->display_user);
|
||||
}
|
||||
|
||||
@ -94,11 +94,11 @@ class NumericScore extends Extension
|
||||
if (!is_null($score) && $image_id>0) {
|
||||
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"));
|
||||
}
|
||||
} 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']);
|
||||
$database->execute(
|
||||
"DELETE FROM numeric_score_votes WHERE image_id=?",
|
||||
@ -108,13 +108,13 @@ class NumericScore extends Extension
|
||||
"UPDATE images SET numeric_score=0 WHERE id=?",
|
||||
[$image_id]
|
||||
);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/$image_id"));
|
||||
}
|
||||
} 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']));
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link());
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$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()
|
||||
{
|
||||
global $database;
|
||||
|
@ -32,7 +32,7 @@ class NumericScoreTheme extends Themelet
|
||||
<input type='submit' value='Vote Down'>
|
||||
</form>
|
||||
";
|
||||
if ($user->can("edit_other_vote")) {
|
||||
if ($user->can(Permissions::EDIT_OTHER_VOTE)) {
|
||||
$html .= "
|
||||
<form action='".make_link("numeric_score/remove_votes_on")."' method='POST'>
|
||||
".$user->get_auth_html()."
|
||||
@ -87,8 +87,51 @@ class NumericScoreTheme extends Themelet
|
||||
|
||||
$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(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;
|
||||
|
||||
if ($event->page_matches("oekaki")) {
|
||||
if ($user->can("create_image")) {
|
||||
if ($user->can(Permissions::CREATE_IMAGE)) {
|
||||
if ($event->get_arg(0) == "create") {
|
||||
$this->theme->display_page();
|
||||
$this->theme->display_block();
|
||||
@ -41,7 +41,7 @@ class Oekaki extends Extension
|
||||
throw new UploadException("File type not recognised");
|
||||
} else {
|
||||
unlink($tmpname);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/".$duev->image_id));
|
||||
}
|
||||
}
|
||||
@ -84,7 +84,7 @@ class Oekaki extends Extension
|
||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("create_image")) {
|
||||
if ($user->can(Permissions::CREATE_IMAGE)) {
|
||||
$this->theme->display_block();
|
||||
}
|
||||
}
|
||||
|
@ -231,8 +231,8 @@ class _SafeOuroborosImage
|
||||
$this->has_notes = false;
|
||||
|
||||
// thumb
|
||||
$this->preview_height = $config->get_int('thumb_height');
|
||||
$this->preview_width = $config->get_int('thumb_width');
|
||||
$this->preview_height = $config->get_int(ImageConfig::THUMB_HEIGHT);
|
||||
$this->preview_width = $config->get_int(ImageConfig::THUMB_WIDTH);
|
||||
$this->preview_url = make_http($img->get_thumb_link());
|
||||
|
||||
// sample (use the full image here)
|
||||
@ -404,13 +404,13 @@ class OuroborosAPI extends Extension
|
||||
} elseif ($this->type == 'xml') {
|
||||
$page->set_type('text/xml; charset=utf-8');
|
||||
}
|
||||
$page->set_mode('data');
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$this->tryAuth();
|
||||
|
||||
if ($event->page_matches('post')) {
|
||||
if ($this->match('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;
|
||||
$this->postCreate(new OuroborosPost($_REQUEST['post']), $md5);
|
||||
} else {
|
||||
@ -464,7 +464,7 @@ class OuroborosAPI extends Extension
|
||||
}
|
||||
}
|
||||
} 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->display();
|
||||
die();
|
||||
@ -481,8 +481,8 @@ class OuroborosAPI extends Extension
|
||||
protected function postCreate(OuroborosPost $post, string $md5 = '')
|
||||
{
|
||||
global $config;
|
||||
$handler = $config->get_string("upload_collision_handler");
|
||||
if (!empty($md5) && !($handler == 'merge')) {
|
||||
$handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER);
|
||||
if (!empty($md5) && !($handler == ImageConfig::COLLISION_MERGE)) {
|
||||
$img = Image::by_hash($md5);
|
||||
if (!is_null($img)) {
|
||||
$this->sendResponse(420, self::ERROR_POST_CREATE_DUPE);
|
||||
@ -524,8 +524,8 @@ class OuroborosAPI extends Extension
|
||||
if (!empty($meta['hash'])) {
|
||||
$img = Image::by_hash($meta['hash']);
|
||||
if (!is_null($img)) {
|
||||
$handler = $config->get_string("upload_collision_handler");
|
||||
if ($handler == "merge") {
|
||||
$handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER);
|
||||
if ($handler == ImageConfig::COLLISION_MERGE) {
|
||||
$postTags = is_array($post->tags) ? $post->tags : Tag::explode($post->tags);
|
||||
$merged = array_merge($postTags, $img->get_tag_array());
|
||||
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)
|
||||
{
|
||||
global $user;
|
||||
@ -108,7 +121,7 @@ class PrivMsg extends Extension
|
||||
global $page, $user;
|
||||
$duser = $event->display_user;
|
||||
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));
|
||||
}
|
||||
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]);
|
||||
if (is_null($pm)) {
|
||||
$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"]));
|
||||
if ($pm["to_id"] == $user->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]);
|
||||
if (is_null($pm)) {
|
||||
$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->cache->delete("pm-count-{$user->id}");
|
||||
log_info("pm", "Deleted PM #$pm_id", "PM deleted");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER["HTTP_REFERER"]);
|
||||
}
|
||||
}
|
||||
@ -162,7 +175,7 @@ class PrivMsg extends Extension
|
||||
$message = $_POST["message"];
|
||||
send_event(new SendPMEvent(new PM($from_id, $_SERVER["REMOTE_ADDR"], $to_id, $subject, $message)));
|
||||
flash_message("PM sent");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect($_SERVER["HTTP_REFERER"]);
|
||||
}
|
||||
break;
|
||||
|
@ -27,7 +27,7 @@ class PrivMsgTheme extends Themelet
|
||||
$h_subject = "<b>$h_subject</b>";
|
||||
$readYN = "N";
|
||||
}
|
||||
$hb = $from->can("hellbanned") ? "hb" : "";
|
||||
$hb = $from->can(Permissions::HELLBANNED) ? "hb" : "";
|
||||
$html .= "<tr class='$hb'>
|
||||
<td>$readYN</td>
|
||||
<td><a href='$pm_url'>$h_subject</a></td>
|
||||
|
@ -9,6 +9,18 @@
|
||||
* 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.
|
||||
*/
|
||||
@ -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
|
||||
{
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
@ -30,14 +77,14 @@ class Pools extends Extension
|
||||
global $config, $database;
|
||||
|
||||
// Set the defaults for the pools extension
|
||||
$config->set_default_int("poolsMaxImportResults", 1000);
|
||||
$config->set_default_int("poolsImagesPerPage", 20);
|
||||
$config->set_default_int("poolsListsPerPage", 20);
|
||||
$config->set_default_int("poolsUpdatedPerPage", 20);
|
||||
$config->set_default_bool("poolsInfoOnViewImage", false);
|
||||
$config->set_default_bool("poolsAdderOnViewImage", false);
|
||||
$config->set_default_bool("poolsShowNavLinks", false);
|
||||
$config->set_default_bool("poolsAutoIncrementOrder", false);
|
||||
$config->set_default_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000);
|
||||
$config->set_default_int(PoolsConfig::IMAGES_PER_PAGE, 20);
|
||||
$config->set_default_int(PoolsConfig::LISTS_PER_PAGE, 20);
|
||||
$config->set_default_int(PoolsConfig::UPDATED_PER_PAGE, 20);
|
||||
$config->set_default_bool(PoolsConfig::INFO_ON_VIEW_IMAGE, false);
|
||||
$config->set_default_bool(PoolsConfig::ADDER_ON_VIEW_IMAGE, false);
|
||||
$config->set_default_bool(PoolsConfig::SHOW_NAV_LINKS, false);
|
||||
$config->set_default_bool(PoolsConfig::AUTO_INCREMENT_ORDER, false);
|
||||
|
||||
// Create the database tables
|
||||
if ($config->get_int("ext_pools_version") < 1) {
|
||||
@ -86,22 +133,39 @@ class Pools extends Extension
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
$sb = new SetupBlock("Pools");
|
||||
$sb->add_int_option("poolsMaxImportResults", "Max results on import: ");
|
||||
$sb->add_int_option("poolsImagesPerPage", "<br>Images per page: ");
|
||||
$sb->add_int_option("poolsListsPerPage", "<br>Index list items per page: ");
|
||||
$sb->add_int_option("poolsUpdatedPerPage", "<br>Updated list items per page: ");
|
||||
$sb->add_bool_option("poolsInfoOnViewImage", "<br>Show pool info on image: ");
|
||||
$sb->add_bool_option("poolsShowNavLinks", "<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("poolsAdderOnViewImage", "<br>Show pool adder on image: ");
|
||||
$sb->add_int_option(PoolsConfig::MAX_IMPORT_RESULTS, "Max results on import: ");
|
||||
$sb->add_int_option(PoolsConfig::IMAGES_PER_PAGE, "<br>Images per page: ");
|
||||
$sb->add_int_option(PoolsConfig::LISTS_PER_PAGE, "<br>Index list items per page: ");
|
||||
$sb->add_int_option(PoolsConfig::UPDATED_PER_PAGE, "<br>Updated list items per page: ");
|
||||
$sb->add_bool_option(PoolsConfig::INFO_ON_VIEW_IMAGE, "<br>Show pool info on image: ");
|
||||
$sb->add_bool_option(PoolsConfig::SHOW_NAV_LINKS, "<br>Show 'Prev' & 'Next' links when viewing pool images: ");
|
||||
$sb->add_bool_option(PoolsConfig::AUTO_INCREMENT_ORDER, "<br>Autoincrement order when post is added to pool:");
|
||||
//$sb->add_bool_option(PoolsConfig::ADDER_ON_VIEW_IMAGE, "<br>Show pool adder on image: ");
|
||||
|
||||
$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)
|
||||
{
|
||||
global $page, $user;
|
||||
|
||||
global $page, $user, $database;
|
||||
|
||||
if ($event->page_matches("pool")) {
|
||||
$pool_id = 0;
|
||||
$pool = [];
|
||||
@ -111,7 +175,7 @@ class Pools extends Extension
|
||||
$pool_id = int_escape($_POST["pool_id"]);
|
||||
$pool = $this->get_single_pool($pool_id);
|
||||
}
|
||||
|
||||
|
||||
// What action are we trying to perform?
|
||||
switch ($event->get_arg(0)) {
|
||||
case "list": //index
|
||||
@ -129,9 +193,16 @@ class Pools extends Extension
|
||||
|
||||
case "create": // ADD _POST
|
||||
try {
|
||||
$newPoolID = $this->add_pool();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("pool/view/".$newPoolID));
|
||||
$title = $_POST["title"];
|
||||
$event = new PoolCreationEvent(
|
||||
$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) {
|
||||
$this->theme->display_error(400, "Error", $e->error);
|
||||
}
|
||||
@ -150,7 +221,7 @@ class Pools extends Extension
|
||||
if (!$user->is_anonymous()) {
|
||||
$historyID = int_escape($event->get_arg(1));
|
||||
$this->revert_history($historyID);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("pool/updated"));
|
||||
}
|
||||
break;
|
||||
@ -159,8 +230,8 @@ class Pools extends Extension
|
||||
if ($this->have_permission($user, $pool)) {
|
||||
$this->theme->edit_pool($page, $this->get_pool($pool_id), $this->edit_posts($pool_id));
|
||||
} else {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("pool/view/".$pool_id));
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||
}
|
||||
break;
|
||||
|
||||
@ -169,14 +240,14 @@ class Pools extends Extension
|
||||
if ($this->have_permission($user, $pool)) {
|
||||
$this->theme->edit_order($page, $this->get_pool($pool_id), $this->edit_order($pool_id));
|
||||
} else {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("pool/view/".$pool_id));
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||
}
|
||||
} else {
|
||||
if ($this->have_permission($user, $pool)) {
|
||||
$this->order_posts();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("pool/view/".$pool_id));
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||
} else {
|
||||
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
||||
}
|
||||
@ -193,9 +264,13 @@ class Pools extends Extension
|
||||
|
||||
case "add_posts":
|
||||
if ($this->have_permission($user, $pool)) {
|
||||
$this->add_posts();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("pool/view/".$pool_id));
|
||||
$images = [];
|
||||
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));
|
||||
} else {
|
||||
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
||||
}
|
||||
@ -204,8 +279,8 @@ class Pools extends Extension
|
||||
case "remove_posts":
|
||||
if ($this->have_permission($user, $pool)) {
|
||||
$this->remove_posts();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("pool/view/".$pool_id));
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||
} else {
|
||||
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
|
||||
}
|
||||
@ -215,8 +290,8 @@ class Pools extends Extension
|
||||
case "edit_description":
|
||||
if ($this->have_permission($user, $pool)) {
|
||||
$this->edit_description();
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("pool/view/".$pool_id));
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("pool/view/" . $pool_id));
|
||||
} else {
|
||||
$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
|
||||
if ($user->is_admin() || $user->id == $pool['user_id']) {
|
||||
$this->nuke_pool($pool_id);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("pool/list"));
|
||||
} else {
|
||||
$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;
|
||||
|
||||
default:
|
||||
$page->set_mode("redirect");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("pool/list"));
|
||||
break;
|
||||
}
|
||||
@ -260,11 +335,11 @@ class Pools extends Extension
|
||||
{
|
||||
global $config;
|
||||
|
||||
if ($config->get_bool("poolsInfoOnViewImage")) {
|
||||
if ($config->get_bool(PoolsConfig::INFO_ON_VIEW_IMAGE)) {
|
||||
$imageID = $event->image->id;
|
||||
$poolsIDs = $this->get_pool_ids($imageID);
|
||||
|
||||
$show_nav = $config->get_bool("poolsShowNavLinks", false);
|
||||
$show_nav = $config->get_bool(PoolsConfig::SHOW_NAV_LINKS, false);
|
||||
|
||||
$navInfo = [];
|
||||
foreach ($poolsIDs as $poolID) {
|
||||
@ -285,11 +360,11 @@ class Pools extends Extension
|
||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||
{
|
||||
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()) {
|
||||
$pools = $database->get_all("SELECT * FROM pools");
|
||||
} else {
|
||||
$pools = $database->get_all("SELECT * FROM pools WHERE user_id=:id", ["id"=>$user->id]);
|
||||
$pools = $database->get_all("SELECT * FROM pools WHERE user_id=:id", ["id" => $user->id]);
|
||||
}
|
||||
if (count($pools) > 0) {
|
||||
$event->add_part($this->theme->get_adder_html($event->image, $pools));
|
||||
@ -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)
|
||||
{
|
||||
$matches = [];
|
||||
@ -327,7 +413,7 @@ class Pools extends Extension
|
||||
|
||||
if (preg_match("/^pool[=|:]([^:]*|lastcreated):?([0-9]*)$/i", $event->term, $matches)) {
|
||||
global $user;
|
||||
$poolTag = (string) str_replace("_", " ", $matches[1]);
|
||||
$poolTag = (string)str_replace("_", " ", $matches[1]);
|
||||
|
||||
$pool = null;
|
||||
if ($poolTag == 'lastcreated') {
|
||||
@ -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 -------------- */
|
||||
/* ------------------------------------------------- */
|
||||
@ -378,7 +504,7 @@ class Pools extends Extension
|
||||
|
||||
$pageNumber = clamp($pageNumber, 1, null) - 1;
|
||||
|
||||
$poolsPerPage = $config->get_int("poolsListsPerPage");
|
||||
$poolsPerPage = $config->get_int(PoolsConfig::LISTS_PER_PAGE);
|
||||
|
||||
$order_by = "";
|
||||
$order = $page->get_cookie("ui-order-pool");
|
||||
@ -400,7 +526,7 @@ class Pools extends Extension
|
||||
ON p.user_id = u.id
|
||||
$order_by
|
||||
LIMIT :l OFFSET :o
|
||||
", ["l"=>$poolsPerPage, "o"=>$pageNumber * $poolsPerPage]);
|
||||
", ["l" => $poolsPerPage, "o" => $pageNumber * $poolsPerPage]);
|
||||
|
||||
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM pools") / $poolsPerPage);
|
||||
|
||||
@ -411,31 +537,32 @@ class Pools extends Extension
|
||||
/**
|
||||
* HERE WE CREATE A NEW POOL
|
||||
*/
|
||||
private function add_pool(): int
|
||||
public function onPoolCreation(PoolCreationEvent $event)
|
||||
{
|
||||
global $user, $database;
|
||||
|
||||
if ($user->is_anonymous()) {
|
||||
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.");
|
||||
}
|
||||
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.");
|
||||
}
|
||||
|
||||
$public = $_POST["public"] === "Y" ? "Y" : "N";
|
||||
|
||||
$database->execute(
|
||||
"
|
||||
INSERT INTO pools (user_id, public, title, description, date)
|
||||
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');
|
||||
log_info("pools", "Pool {$poolID} created by {$user->name}");
|
||||
return $poolID;
|
||||
|
||||
$event->new_id = $poolID;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -446,7 +573,7 @@ class Pools extends Extension
|
||||
private function get_pool(int $poolID): array
|
||||
{
|
||||
global $database;
|
||||
return $database->get_all("SELECT * FROM pools WHERE id=:id", ["id"=>$poolID]);
|
||||
return $database->get_all("SELECT * FROM pools WHERE id=:id", ["id" => $poolID]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -455,16 +582,16 @@ class Pools extends Extension
|
||||
private function get_single_pool(int $poolID): array
|
||||
{
|
||||
global $database;
|
||||
return $database->get_row("SELECT * FROM pools WHERE id=:id", ["id"=>$poolID]);
|
||||
return $database->get_row("SELECT * FROM pools WHERE id=:id", ["id" => $poolID]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return $database->get_row("SELECT * FROM pools WHERE title=:title", ["title"=>$poolTitle]);
|
||||
return $database->get_row("SELECT * FROM pools WHERE title=:title", ["title" => $poolTitle]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -474,7 +601,7 @@ class Pools extends Extension
|
||||
private function get_pool_ids(int $imageID): array
|
||||
{
|
||||
global $database;
|
||||
return $database->get_col("SELECT pool_id FROM pool_images WHERE image_id=:iid", ["iid"=>$imageID]);
|
||||
return $database->get_col("SELECT pool_id FROM pool_images WHERE image_id=:iid", ["iid" => $imageID]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -483,7 +610,7 @@ class Pools extends Extension
|
||||
private function get_last_userpool(int $userID): array
|
||||
{
|
||||
global $database;
|
||||
return $database->get_row("SELECT * FROM pools WHERE user_id=:uid ORDER BY id DESC", ["uid"=>$userID]);
|
||||
return $database->get_row("SELECT * FROM pools WHERE user_id=:uid ORDER BY id DESC", ["uid" => $userID]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -493,8 +620,8 @@ class Pools extends Extension
|
||||
{
|
||||
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"]));
|
||||
$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
|
||||
*
|
||||
* 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;
|
||||
|
||||
$poolID = int_escape($_POST['pool_id']);
|
||||
$images = "";
|
||||
$pool = $this->get_single_pool($event->pool_id);
|
||||
if (!$this->have_permission($user, $pool)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($_POST['check'] as $imageID) {
|
||||
if (!$this->check_post($poolID, $imageID)) {
|
||||
$database->execute(
|
||||
"
|
||||
INSERT INTO pool_images (pool_id, image_id)
|
||||
VALUES (:pid, :iid)",
|
||||
["pid"=>$poolID, "iid"=>$imageID]
|
||||
);
|
||||
|
||||
$images .= " ".$imageID;
|
||||
$images = " ";
|
||||
foreach ($event->posts as $post_id) {
|
||||
if ($this->add_post($event->pool_id, $post_id, false)) {
|
||||
$images .= " " . $post_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strlen($images) == 0) {
|
||||
$count = int_escape($database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid"=>$poolID]));
|
||||
$this->add_history($poolID, 1, $images, $count);
|
||||
$count = int_escape($database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid", ["pid" => $event->pool_id]));
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -556,7 +669,7 @@ class Pools extends Extension
|
||||
UPDATE pool_images
|
||||
SET image_order = :ord
|
||||
WHERE pool_id = :pid AND image_id = :iid",
|
||||
["ord"=>$imageORDER, "pid"=>$poolID, "iid"=>$imageID]
|
||||
["ord" => $imageORDER, "pid" => $poolID, "iid" => $imageID]
|
||||
);
|
||||
}
|
||||
|
||||
@ -576,11 +689,11 @@ class Pools extends Extension
|
||||
$images = "";
|
||||
|
||||
foreach ($_POST['check'] as $imageID) {
|
||||
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid", ["pid"=>$poolID, "iid"=>$imageID]);
|
||||
$images .= " ".$imageID;
|
||||
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid", ["pid" => $poolID, "iid" => $imageID]);
|
||||
$images .= " " . $imageID;
|
||||
}
|
||||
|
||||
$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, 0, $images, $count);
|
||||
return $poolID;
|
||||
}
|
||||
@ -593,7 +706,7 @@ class Pools extends Extension
|
||||
global $database;
|
||||
|
||||
$poolID = int_escape($_POST['pool_id']);
|
||||
$database->execute("UPDATE pools SET description=:dsc WHERE id=:pid", ["dsc"=>$_POST['description'], "pid"=>$poolID]);
|
||||
$database->execute("UPDATE pools SET description=:dsc WHERE id=:pid", ["dsc" => $_POST['description'], "pid" => $poolID]);
|
||||
|
||||
return $poolID;
|
||||
}
|
||||
@ -605,7 +718,7 @@ class Pools extends Extension
|
||||
private function check_post(int $poolID, int $imageID): bool
|
||||
{
|
||||
global $database;
|
||||
$result = $database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid AND image_id=:iid", ["pid"=>$poolID, "iid"=>$imageID]);
|
||||
$result = $database->get_one("SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid AND image_id=:iid", ["pid" => $poolID, "iid" => $imageID]);
|
||||
return ($result != 0);
|
||||
}
|
||||
|
||||
@ -621,7 +734,7 @@ class Pools extends Extension
|
||||
if (empty($pool) || empty($imageID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$result = $database->get_row(
|
||||
"
|
||||
SELECT (
|
||||
@ -652,7 +765,7 @@ class Pools extends Extension
|
||||
) AS next
|
||||
|
||||
LIMIT 1",
|
||||
["pid"=>$pool['id'], "iid"=>$imageID]
|
||||
["pid" => $pool['id'], "iid" => $imageID]
|
||||
);
|
||||
|
||||
if (empty($result)) {
|
||||
@ -682,46 +795,41 @@ class Pools extends Extension
|
||||
$poolID = int_escape($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
|
||||
// WORKS TO SHOW/HIDE SAFE, QUESTIONABLE, EXPLICIT AND UNRATED IMAGES FROM USER
|
||||
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)) {
|
||||
$result = $database->get_all(
|
||||
"
|
||||
SELECT p.image_id
|
||||
FROM pool_images AS p
|
||||
INNER JOIN images AS i ON i.id = p.image_id
|
||||
WHERE p.pool_id = :pid AND i.rating IN ($rating)
|
||||
if(ext_is_live("trash")) {
|
||||
$query .= $database->scoreql_to_sql(" AND trash = SCORE_BOOL_N ");
|
||||
}
|
||||
|
||||
$result = $database->get_all(
|
||||
"
|
||||
SELECT p.image_id FROM pool_images p
|
||||
$query
|
||||
ORDER BY p.image_order ASC
|
||||
LIMIT :l OFFSET :o",
|
||||
["pid"=>$poolID, "l"=>$imagesPerPage, "o"=>$pageNumber * $imagesPerPage]
|
||||
);
|
||||
["pid" => $poolID, "l" => $imagesPerPage, "o" => $pageNumber * $imagesPerPage]
|
||||
);
|
||||
|
||||
$totalPages = ceil($database->get_one(
|
||||
$totalPages = ceil($database->get_one(
|
||||
"
|
||||
SELECT COUNT(*)
|
||||
FROM pool_images AS p
|
||||
INNER JOIN images AS i ON i.id = p.image_id
|
||||
WHERE pool_id=:pid AND i.rating IN ($rating)",
|
||||
["pid"=>$poolID]
|
||||
SELECT COUNT(*) FROM pool_images p
|
||||
$query",
|
||||
["pid" => $poolID]
|
||||
) / $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 = [];
|
||||
foreach ($result as $singleResult) {
|
||||
@ -740,14 +848,14 @@ class Pools extends Extension
|
||||
{
|
||||
global $database;
|
||||
|
||||
$result = $database->Execute("SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid"=>$poolID]);
|
||||
$result = $database->Execute("SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid" => $poolID]);
|
||||
$images = [];
|
||||
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
$image = Image::by_id($row["image_id"]);
|
||||
$images[] = [$image];
|
||||
}
|
||||
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
@ -761,21 +869,21 @@ class Pools extends Extension
|
||||
{
|
||||
global $database;
|
||||
|
||||
$result = $database->Execute("SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid"=>$poolID]);
|
||||
$result = $database->Execute("SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid" => $poolID]);
|
||||
$images = [];
|
||||
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
$image = $database->get_row(
|
||||
"
|
||||
SELECT * FROM images AS i
|
||||
INNER JOIN pool_images AS p ON i.id = p.image_id
|
||||
WHERE pool_id=:pid AND i.id=:iid",
|
||||
["pid"=>$poolID, "iid"=>$row['image_id']]
|
||||
["pid" => $poolID, "iid" => $row['image_id']]
|
||||
);
|
||||
$image = ($image ? new Image($image) : null);
|
||||
$images[] = [$image];
|
||||
}
|
||||
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
@ -787,15 +895,15 @@ class Pools extends Extension
|
||||
{
|
||||
global $user, $database;
|
||||
|
||||
$p_id = $database->get_one("SELECT user_id FROM pools WHERE id = :pid", ["pid"=>$poolID]);
|
||||
$p_id = $database->get_one("SELECT user_id FROM pools WHERE id = :pid", ["pid" => $poolID]);
|
||||
if ($user->is_admin()) {
|
||||
$database->execute("DELETE FROM pool_history WHERE pool_id = :pid", ["pid"=>$poolID]);
|
||||
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid", ["pid"=>$poolID]);
|
||||
$database->execute("DELETE FROM pools WHERE id = :pid", ["pid"=>$poolID]);
|
||||
$database->execute("DELETE FROM pool_history WHERE pool_id = :pid", ["pid" => $poolID]);
|
||||
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid", ["pid" => $poolID]);
|
||||
$database->execute("DELETE FROM pools WHERE id = :pid", ["pid" => $poolID]);
|
||||
} elseif ($user->id == $p_id) {
|
||||
$database->execute("DELETE FROM pool_history WHERE pool_id = :pid", ["pid"=>$poolID]);
|
||||
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid", ["pid"=>$poolID]);
|
||||
$database->execute("DELETE FROM pools WHERE id = :pid AND user_id = :uid", ["pid"=>$poolID, "uid"=>$user->id]);
|
||||
$database->execute("DELETE FROM pool_history WHERE pool_id = :pid", ["pid" => $poolID]);
|
||||
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid", ["pid" => $poolID]);
|
||||
$database->execute("DELETE FROM pools WHERE id = :pid AND user_id = :uid", ["pid" => $poolID, "uid" => $user->id]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -812,7 +920,7 @@ class Pools extends Extension
|
||||
"
|
||||
INSERT INTO pool_history (pool_id, user_id, action, images, count, date)
|
||||
VALUES (:pid, :uid, :act, :img, :count, now())",
|
||||
["pid"=>$poolID, "uid"=>$user->id, "act"=>$action, "img"=>$images, "count"=>$count]
|
||||
["pid" => $poolID, "uid" => $user->id, "act" => $action, "img" => $images, "count" => $count]
|
||||
);
|
||||
}
|
||||
|
||||
@ -831,7 +939,7 @@ class Pools extends Extension
|
||||
$pageNumber--;
|
||||
}
|
||||
|
||||
$historiesPerPage = $config->get_int("poolsUpdatedPerPage");
|
||||
$historiesPerPage = $config->get_int(PoolsConfig::UPDATED_PER_PAGE);
|
||||
|
||||
$history = $database->get_all("
|
||||
SELECT h.id, h.pool_id, h.user_id, h.action, h.images,
|
||||
@ -843,7 +951,7 @@ class Pools extends Extension
|
||||
ON h.user_id = u.id
|
||||
ORDER BY h.date DESC
|
||||
LIMIT :l OFFSET :o
|
||||
", ["l"=>$historiesPerPage, "o"=>$pageNumber * $historiesPerPage]);
|
||||
", ["l" => $historiesPerPage, "o" => $pageNumber * $historiesPerPage]);
|
||||
|
||||
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM pool_history") / $historiesPerPage);
|
||||
|
||||
@ -856,7 +964,7 @@ class Pools extends Extension
|
||||
private function revert_history(int $historyID)
|
||||
{
|
||||
global $database;
|
||||
$status = $database->get_all("SELECT * FROM pool_history WHERE id=:hid", ["hid"=>$historyID]);
|
||||
$status = $database->get_all("SELECT * FROM pool_history WHERE id=:hid", ["hid" => $historyID]);
|
||||
|
||||
foreach ($status as $entry) {
|
||||
$images = trim($entry['images']);
|
||||
@ -871,7 +979,7 @@ class Pools extends Extension
|
||||
$imageID = $image;
|
||||
$this->add_post($poolID, $imageID);
|
||||
|
||||
$imageArray .= " ".$imageID;
|
||||
$imageArray .= " " . $imageID;
|
||||
$newAction = 1;
|
||||
}
|
||||
} elseif ($entry['action'] == 1) {
|
||||
@ -880,7 +988,7 @@ class Pools extends Extension
|
||||
$imageID = $image;
|
||||
$this->delete_post($poolID, $imageID);
|
||||
|
||||
$imageArray .= " ".$imageID;
|
||||
$imageArray .= " " . $imageID;
|
||||
$newAction = 0;
|
||||
}
|
||||
} else {
|
||||
@ -889,7 +997,7 @@ class Pools extends Extension
|
||||
continue; // go on to the next one.
|
||||
}
|
||||
|
||||
$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, $newAction, $imageArray, $count);
|
||||
}
|
||||
}
|
||||
@ -898,18 +1006,18 @@ class Pools extends Extension
|
||||
* HERE WE ADD A SIMPLE POST FROM POOL.
|
||||
* 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;
|
||||
|
||||
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(
|
||||
"
|
||||
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
|
||||
WHERE pool_id = :pid",
|
||||
["pid"=>$poolID]
|
||||
WHERE pool_id = :pid AND image_order IS NOT NULL",
|
||||
["pid" => $poolID]
|
||||
);
|
||||
}
|
||||
|
||||
@ -917,31 +1025,43 @@ class Pools extends Extension
|
||||
"
|
||||
INSERT INTO pool_images (pool_id, image_id, image_order)
|
||||
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) {
|
||||
$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);
|
||||
}
|
||||
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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* HERE WE REMOVE A SIMPLE POST FROM POOL.
|
||||
* USED WITH FOREACH IN revert_history() & onTagTermParse().
|
||||
*/
|
||||
private function delete_post(int $poolID, int $imageID, bool $history=false)
|
||||
private function delete_post(int $poolID, int $imageID, bool $history = false)
|
||||
{
|
||||
global $database;
|
||||
|
||||
$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]);
|
||||
$database->execute("DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid", ["pid" => $poolID, "iid" => $imageID]);
|
||||
|
||||
$this->update_count($poolID);
|
||||
|
||||
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, 0, $imageID, $count);
|
||||
}
|
||||
}
|
||||
|
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