This commit is contained in:
Shish 2019-11-03 16:28:00 +00:00
commit ee3754ae79
166 changed files with 1780 additions and 1772 deletions

View File

@ -74,8 +74,7 @@ if (is_readable("data/config/shimmie.conf.php")) {
do_install();
// utilities {{{
// TODO: Can some of these be pushed into "core/???.inc.php" ?
// TODO: Can some of these be pushed into "core/???.inc.php" ?
function check_gd_version(): int
{
@ -99,10 +98,9 @@ function check_im_version(): int
return (empty($convert_check) ? 0 : 1);
}
// }}}
function do_install()
{ // {{{
{
if (file_exists("data/config/auto_install.conf.php")) {
require_once "data/config/auto_install.conf.php";
} elseif (@$_POST["database_type"] == DatabaseDriver::SQLITE) {
@ -118,10 +116,10 @@ function do_install()
define("CACHE_DSN", null);
define("DATABASE_KA", true);
install_process();
} // }}}
}
function ask_questions()
{ // {{{
{
$warnings = [];
$errors = [];
@ -232,21 +230,21 @@ function ask_questions()
</div>
</div>
EOD;
} // }}}
}
/**
* This is where the install really takes place.
*/
function install_process()
{ // {{{
{
build_dirs();
create_tables();
insert_defaults();
write_config();
} // }}}
}
function create_tables()
{ // {{{
{
try {
$db = new Database();
@ -331,10 +329,10 @@ EOD;
} catch (Exception $e) {
handle_db_errors(false, "An unknown error occurred while trying to insert data into the database.", $e->getMessage(), 4);
}
} // }}}
}
function insert_defaults()
{ // {{{
{
try {
$db = new Database();
@ -350,10 +348,10 @@ function insert_defaults()
} catch (Exception $e) {
handle_db_errors(false, "An unknown error occurred while trying to insert data into the database.", $e->getMessage(), 6);
}
} // }}}
}
function build_dirs()
{ // {{{
{
$data_exists = file_exists("data") || mkdir("data");
$data_writable = is_writable("data") || chmod("data", 0755);
@ -373,10 +371,10 @@ function build_dirs()
";
exit(7);
}
} // }}}
}
function write_config()
{ // {{{
{
$file_content = '<' . '?php' . "\n" .
"define('DATABASE_DSN', '".DATABASE_DSN."');\n" .
'?' . '>';
@ -417,7 +415,7 @@ EOD;
EOD;
}
echo "\n";
} // }}}
}
function handle_db_errors(bool $isPDO, string $errorMessage1, string $errorMessage2, int $exitCode)
{

View File

@ -178,9 +178,18 @@ class Database
$this->dbtime += $dur;
}
public function execute(string $query, array $args=[]): PDOStatement
public function set_timeout(int $time): void
{
$this->engine->set_timeout($this->db, $time);
}
public function execute(string $query, array $args=[], bool $scoreql = false): PDOStatement
{
try {
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
if (is_null($this->db)) {
$this->connect_db();
}
@ -211,8 +220,12 @@ class Database
/**
* Execute an SQL query and return a 2D array.
*/
public function get_all(string $query, array $args=[]): array
public function get_all(string $query, array $args=[], bool $scoreql = false): array
{
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
$_start = microtime(true);
$data = $this->execute($query, $args)->fetchAll();
$this->count_time("get_all", $_start, $query, $args);
@ -222,8 +235,11 @@ class Database
/**
* Execute an SQL query and return a iterable object for use with generators.
*/
public function get_all_iterable(string $query, array $args=[]): PDOStatement
public function get_all_iterable(string $query, array $args=[], bool $scoreql = false): PDOStatement
{
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
$_start = microtime(true);
$data = $this->execute($query, $args);
$this->count_time("get_all_iterable", $_start, $query, $args);
@ -233,19 +249,40 @@ class Database
/**
* Execute an SQL query and return a single row.
*/
public function get_row(string $query, array $args=[]): ?array
public function get_row(string $query, array $args=[], bool $scoreql = false): ?array
{
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
$_start = microtime(true);
$row = $this->execute($query, $args)->fetch();
$this->count_time("get_row", $_start, $query, $args);
return $row ? $row : null;
}
/**
* Execute an SQL query and return a boolean based on whether it returns a result
*/
public function exists(string $query, array $args=[], bool $scoreql = false): bool
{
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
$_start = microtime(true);
$result = $this->execute($query, $args);
$this->count_time("exists", $_start, $query, $args);
return $result->rowCount()>0;
}
/**
* Execute an SQL query and return the first column of each row.
*/
public function get_col(string $query, array $args=[]): array
public function get_col(string $query, array $args=[], bool $scoreql = false): array
{
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
$_start = microtime(true);
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_COLUMN);
$this->count_time("get_col", $_start, $query, $args);
@ -255,8 +292,11 @@ class Database
/**
* Execute an SQL query and return the first column of each row as a single iterable object.
*/
public function get_col_iterable(string $query, array $args=[]): Generator
public function get_col_iterable(string $query, array $args=[], bool $scoreql = false): Generator
{
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
$_start = microtime(true);
$stmt = $this->execute($query, $args);
$this->count_time("get_col_iterable", $_start, $query, $args);
@ -268,8 +308,11 @@ class Database
/**
* Execute an SQL query and return the the first column => the second column.
*/
public function get_pairs(string $query, array $args=[]): array
public function get_pairs(string $query, array $args=[], bool $scoreql = false): array
{
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
$_start = microtime(true);
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_KEY_PAIR);
$this->count_time("get_pairs", $_start, $query, $args);
@ -279,8 +322,11 @@ class Database
/**
* Execute an SQL query and return a single value.
*/
public function get_one(string $query, array $args=[])
public function get_one(string $query, array $args=[], bool $scoreql = false)
{
if ($scoreql===true) {
$query = $this->scoreql_to_sql($query);
}
$_start = microtime(true);
$row = $this->execute($query, $args)->fetch();
$this->count_time("get_one", $_start, $query, $args);
@ -354,7 +400,7 @@ class MockDatabase extends Database
$this->responses = $responses;
}
public function execute(string $query, array $params=[]): PDOStatement
public function execute(string $query, array $params=[], bool $scoreql = false): PDOStatement
{
log_debug(
"mock-database",
@ -376,23 +422,23 @@ class MockDatabase extends Database
return $this->responses[$this->query_id++];
}
public function get_all(string $query, array $args=[]): array
public function get_all(string $query, array $args=[], bool $scoreql = false): array
{
return $this->_execute($query, $args);
}
public function get_row(string $query, array $args=[]): ?array
public function get_row(string $query, array $args=[], bool $scoreql = false): ?array
{
return $this->_execute($query, $args);
}
public function get_col(string $query, array $args=[]): array
public function get_col(string $query, array $args=[], bool $scoreql = false): array
{
return $this->_execute($query, $args);
}
public function get_pairs(string $query, array $args=[]): array
public function get_pairs(string $query, array $args=[], bool $scoreql = false): array
{
return $this->_execute($query, $args);
}
public function get_one(string $query, array $args=[])
public function get_one(string $query, array $args=[], bool $scoreql = false)
{
return $this->_execute($query, $args);
}

View File

@ -12,7 +12,7 @@ abstract class SCORE
const ILIKE = "SCORE_ILIKE";
}
class DBEngine
abstract class DBEngine
{
/** @var null|string */
public $name = null;
@ -33,6 +33,8 @@ class DBEngine
{
return 'CREATE TABLE '.$name.' ('.$data.')';
}
abstract public function set_timeout(PDO $db, int $time);
}
class MySQL extends DBEngine
@ -68,6 +70,12 @@ class MySQL extends DBEngine
$ctes = "ENGINE=InnoDB DEFAULT CHARSET='utf8'";
return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes;
}
public function set_timeout(PDO $db, int $time): void
{
// These only apply to read-only queries, which appears to be the best we can to mysql-wise
$db->exec("SET SESSION MAX_EXECUTION_TIME=".$time.";");
}
}
class PostgreSQL extends DBEngine
@ -87,7 +95,7 @@ class PostgreSQL extends DBEngine
} else {
$db->exec("SET application_name TO 'shimmie [local]';");
}
$db->exec("SET statement_timeout TO ".DATABASE_TIMEOUT.";");
$this->set_timeout($db, DATABASE_TIMEOUT);
}
public function scoreql_to_sql(string $data): string
@ -109,6 +117,11 @@ class PostgreSQL extends DBEngine
$data = $this->scoreql_to_sql($data);
return "CREATE TABLE $name ($data)";
}
public function set_timeout(PDO $db, int $time): void
{
$db->exec("SET statement_timeout TO ".$time.";");
}
}
// shimmie functions for export to sqlite
@ -213,4 +226,9 @@ class SQLite extends DBEngine
$cols_redone = implode(", ", $cols);
return "CREATE TABLE $name ($cols_redone); $extras";
}
public function set_timeout(PDO $db, int $time): void
{
// There doesn't seem to be such a thing for SQLite, so it does nothing
}
}

View File

@ -154,7 +154,7 @@ class PageRequestEvent extends Event
public function get_page_size(): int
{
global $config;
return $config->get_int('index_images');
return $config->get_int(IndexConfig::IMAGES);
}
}

View File

@ -115,7 +115,7 @@ class Image
if ($max < 1) {
return null;
} // From Issue #22 - opened by HungryFeline on May 30, 2011.
if ($max > $limit_range) {
if ($limit_range > 0 && $max > $limit_range) {
$max = $limit_range;
}
$rand = mt_rand(0, $max-1);
@ -150,7 +150,7 @@ class Image
$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"))));
$querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string(IndexConfig::ORDER))));
if ($limit!=null) {
$querylet->append(new Querylet(" LIMIT :limit ", ["limit" => $limit]));
$querylet->append(new Querylet(" OFFSET :offset ", ["offset"=>$start]));
@ -334,7 +334,7 @@ class Image
public static function count_pages(array $tags=[]): float
{
global $config;
return ceil(Image::count_images($tags) / $config->get_int('index_images'));
return ceil(Image::count_images($tags) / $config->get_int(IndexConfig::IMAGES));
}
private static function terms_to_conditions(array $terms): array
@ -731,8 +731,10 @@ class Image
["tag"=>$tag]
);
$database->execute(
$database->scoreql_to_sql(
"INSERT INTO image_tags(image_id, tag_id)
VALUES(:id, (SELECT id FROM tags WHERE tag = :tag))",
VALUES(:id, (SELECT id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)))"
),
["id"=>$this->id, "tag"=>$tag]
);
} else {

View File

@ -29,23 +29,7 @@ class Tag
$tags = explode(' ', trim($tags));
/* sanitise by removing invisible / dodgy characters */
$tag_array = [];
foreach ($tags as $tag) {
$tag = preg_replace("/\s/", "", $tag); # whitespace
$tag = preg_replace('/\x20(\x0e|\x0f)/', '', $tag); # unicode RTL
$tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
$tag = trim($tag, ", \t\n\r\0\x0B");
if (mb_strlen($tag, 'UTF-8') > 255) {
flash_message("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n");
continue;
}
if (!empty($tag)) {
$tag_array[] = $tag;
}
}
$tag_array = self::sanitize_array($tags);
/* if user supplied a blank string, add "tagme" */
if (count($tag_array) === 0 && $tagme) {
@ -101,6 +85,74 @@ class Tag
return $tag_array;
}
public static function sanitize(string $tag): string
{
$tag = preg_replace("/\s/", "", $tag); # whitespace
$tag = preg_replace('/\x20(\x0e|\x0f)/', '', $tag); # unicode RTL
$tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
$tag = trim($tag, ", \t\n\r\0\x0B");
if (mb_strlen($tag, 'UTF-8') > 255) {
throw new Exception("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n");
}
return $tag;
}
public static function compare(array $tags1, array $tags2): bool
{
if (count($tags1)!==count($tags2)) {
return false;
}
$tags1 = array_map("strtolower", $tags1);
$tags2 = array_map("strtolower", $tags2);
natcasesort($tags1);
natcasesort($tags2);
for ($i = 0; $i < count($tags1); $i++) {
if ($tags1[$i]!==$tags2[$i]) {
var_dump($tags1);
var_dump($tags2);
return false;
}
}
return true;
}
public static function get_diff_tags(array $source, array $remove): array
{
$before = array_map('strtolower', $source);
$remove = array_map('strtolower', $remove);
$after = [];
foreach ($before as $tag) {
if (!in_array($tag, $remove)) {
$after[] = $tag;
}
}
return $after;
}
public static function sanitize_array(array $tags): array
{
$tag_array = [];
foreach ($tags as $tag) {
try {
$tag = Tag::sanitize($tag);
} catch (Exception $e) {
flash_message($e->getMessage());
continue;
}
if (!empty($tag)) {
$tag_array[] = $tag;
}
}
return $tag_array;
}
public static function sqlify(string $term): string
{
global $database;

View File

@ -80,4 +80,7 @@ abstract class Permissions
public const NOTES_ADMIN = "notes_admin";
public const POOLS_ADMIN = "pools_admin";
public const TIPS_ADMIN = "tips_admin";
public const CRON_ADMIN = "cron_admin";
public const APPROVE_IMAGE = "approve_image";
public const APPROVE_COMMENT = "approve_comment";
}

View File

@ -502,7 +502,7 @@ function bool_escape($input): bool
*/
if (is_bool($input)) {
return $input;
} elseif (is_int($input)) {
} elseif (is_numeric($input)) {
return ($input === 1);
} else {
$value = filter_var($input, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

View File

@ -150,6 +150,10 @@ new UserClass("base", null, [
Permissions::NOTES_ADMIN => false,
Permissions::POOLS_ADMIN => false,
Permissions::TIPS_ADMIN => false,
Permissions::CRON_ADMIN => false,
Permissions::APPROVE_IMAGE => false,
Permissions::APPROVE_COMMENT => false,
]);
new UserClass("anonymous", "base", [
@ -226,6 +230,9 @@ new UserClass("admin", "base", [
Permissions::NOTES_ADMIN => true,
Permissions::POOLS_ADMIN => true,
Permissions::TIPS_ADMIN => true,
Permissions::CRON_ADMIN => true,
Permissions::APPROVE_IMAGE => true,
Permissions::APPROVE_COMMENT => true,
]);
new UserClass("hellbanned", "user", [

View File

@ -350,6 +350,54 @@ function join_url(string $base, string ...$paths)
return $output;
}
function get_dir_contents(string $dir): array
{
if (empty($dir)) {
throw new Exception("dir required");
}
if (!is_dir($dir)) {
return [];
}
$results = array_diff(
scandir(
$dir
),
['..', '.']
);
return $results;
}
/**
* Returns amount of files & total size of dir.
*/
function scan_dir(string $path): array
{
$bytestotal = 0;
$nbfiles = 0;
$ite = new RecursiveDirectoryIterator(
$path,
FilesystemIterator::KEY_AS_PATHNAME |
FilesystemIterator::CURRENT_AS_FILEINFO |
FilesystemIterator::SKIP_DOTS
);
foreach (new RecursiveIteratorIterator($ite) as $filename => $cur) {
try {
$filesize = $cur->getSize();
$bytestotal += $filesize;
$nbfiles++;
} catch (RuntimeException $e) {
// This usually just means that the file got eaten by the import
continue;
}
}
$size_mb = $bytestotal / 1048576; // to mb
$size_mb = number_format($size_mb, 2, '.', '');
return ['path' => $path, 'total_files' => $nbfiles, 'total_mb' => $size_mb];
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Debugging functions *

View File

@ -1,15 +1,5 @@
<?php
/**
* Name: Admin Controls
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Various things to make admins' lives easier
* Documentation:
*/
class AdminPageInfo extends ExtensionInfo
{
public const KEY = "admin";

View File

@ -1,14 +1,5 @@
<?php
/**
* Name: Alias Editor
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Edit the alias list
* Documentation:
*/
class AliasEditorInfo extends ExtensionInfo
{
public const KEY = "alias_editor";

13
ext/approval/info.php Normal file
View File

@ -0,0 +1,13 @@
<?php
class ApprovalInfo extends ExtensionInfo
{
public const KEY = "approval";
public $key = self::KEY;
public $name = "Approval";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Adds an approval step to the upload/import process.";
public $db_support = [DatabaseDriver::MYSQL, DatabaseDriver::PGSQL];
}

260
ext/approval/main.php Normal file
View File

@ -0,0 +1,260 @@
<?php
abstract class ApprovalConfig
{
const VERSION = "ext_approval_version";
const IMAGES = "approve_images";
const COMMENTS = "approve_comments";
}
class Approval extends Extension
{
public function onInitExt(InitExtEvent $event)
{
global $config;
$config->set_default_bool(ApprovalConfig::IMAGES, false);
$config->set_default_bool(ApprovalConfig::COMMENTS, false);
if ($config->get_int(ApprovalConfig::VERSION) < 1) {
$this->install();
}
}
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
if ($event->page_matches("approve_image") && $user->can(Permissions::APPROVE_IMAGE)) {
// Try to get the image ID
$image_id = int_escape($event->get_arg(0));
if (empty($image_id)) {
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
}
if (empty($image_id)) {
throw new SCoreException("Can not approve image: No valid Image ID given.");
}
self::approve_image($image_id);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/view/" . $image_id));
}
if ($event->page_matches("disapprove_image") && $user->can(Permissions::APPROVE_IMAGE)) {
// Try to get the image ID
$image_id = int_escape($event->get_arg(0));
if (empty($image_id)) {
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
}
if (empty($image_id)) {
throw new SCoreException("Can not disapprove image: No valid Image ID given.");
}
self::disapprove_image($image_id);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/view/".$image_id));
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
{
$this->theme->display_admin_block($event);
}
public function onAdminBuilding(AdminBuildingEvent $event)
{
global $config;
$this->theme->display_admin_form();
}
public function onAdminAction(AdminActionEvent $event)
{
global $database, $user;
$action = $event->action;
$event->redirect = true;
if ($action==="approval") {
$approval_action = $_POST["approval_action"];
switch ($approval_action) {
case "approve_all":
$database->set_timeout(300000); // These updates can take a little bit
$database->execute(
$database->scoreql_to_sql(
"UPDATE images SET approved = SCORE_BOOL_Y, approved_by_id = :approved_by_id WHERE approved = SCORE_BOOL_N"
),
["approved_by_id"=>$user->id]
);
break;
case "disapprove_all":
$database->set_timeout(300000); // These updates can take a little bit
$database->execute($database->scoreql_to_sql(
"UPDATE images SET approved = SCORE_BOOL_N, approved_by_id = NULL WHERE approved = SCORE_BOOL_Y"
));
break;
default:
break;
}
}
}
public function onDisplayingImage(DisplayingImageEvent $event)
{
global $user, $page, $config;
if ($config->get_bool(ApprovalConfig::IMAGES) && $event->image->approved===false && !$user->can(Permissions::APPROVE_IMAGE)) {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/list"));
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
global $user;
if ($event->parent=="posts") {
if ($user->can(Permissions::APPROVE_IMAGE)) {
$event->add_nav_link("posts_unapproved", new Link('/post/list/approved%3Ano/1'), "Pending Approval", null, 60);
}
}
}
const SEARCH_REGEXP = "/^approved:(yes|no)/";
public function onSearchTermParse(SearchTermParseEvent $event)
{
global $user, $database, $config;
if ($config->get_bool(ApprovalConfig::IMAGES)) {
$matches = [];
if (is_null($event->term) && $this->no_approval_query($event->context)) {
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_Y ")));
}
if (preg_match(self::SEARCH_REGEXP, strtolower($event->term), $matches)) {
if ($user->can(Permissions::APPROVE_IMAGE) && $matches[1] == "no") {
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_N ")));
} else {
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_Y ")));
}
}
}
}
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
{
global $user, $config;
if ($event->key===HelpPages::SEARCH) {
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
$block = new Block();
$block->header = "Approval";
$block->body = $this->theme->get_help_html();
$event->add_block($block);
}
}
}
private function no_approval_query(array $context): bool
{
foreach ($context as $term) {
if (preg_match(self::SEARCH_REGEXP, $term)) {
return false;
}
}
return true;
}
public static function approve_image($image_id)
{
global $database, $user;
$database->execute(
$database->scoreql_to_sql(
"UPDATE images SET approved = SCORE_BOOL_Y, approved_by_id = :approved_by_id WHERE id = :id AND approved = SCORE_BOOL_N"
),
["approved_by_id"=>$user->id, "id"=>$image_id]
);
}
public static function disapprove_image($image_id)
{
global $database, $user;
$database->execute(
$database->scoreql_to_sql(
"UPDATE images SET approved = SCORE_BOOL_N, approved_by_id = NULL WHERE id = :id AND approved = SCORE_BOOL_Y"
),
["id"=>$image_id]
);
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
{
global $user, $config;
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
$event->add_part($this->theme->get_image_admin_html($event->image));
}
}
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
{
global $user, $config;
if ($user->can(Permissions::APPROVE_IMAGE)&& $config->get_bool(ApprovalConfig::IMAGES)) {
if (in_array("approved:no", $event->search_terms)) {
$event->add_action("bulk_approve_image", "Approve", "a");
} else {
$event->add_action("bulk_disapprove_image", "Disapprove");
}
}
}
public function onBulkAction(BulkActionEvent $event)
{
global $user;
switch ($event->action) {
case "bulk_approve_image":
if ($user->can(Permissions::APPROVE_IMAGE)) {
$total = 0;
foreach ($event->items as $image) {
self::approve_image($image->id);
$total++;
}
flash_message("Approved $total items");
}
break;
case "bulk_disapprove_image":
if ($user->can(Permissions::APPROVE_IMAGE)) {
$total = 0;
foreach ($event->items as $image) {
self::disapprove_image($image->id);
$total++;
}
flash_message("Disapproved $total items");
}
break;
}
}
private function install()
{
global $database, $config;
if ($config->get_int(ApprovalConfig::VERSION) < 1) {
$database->Execute($database->scoreql_to_sql(
"ALTER TABLE images ADD COLUMN approved SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N"
));
$database->Execute($database->scoreql_to_sql(
"ALTER TABLE images ADD COLUMN approved_by_id INTEGER NULL"
));
$database->Execute("CREATE INDEX images_approved_idx ON images(approved)");
$config->set_int(ApprovalConfig::VERSION, 1);
}
}
}

58
ext/approval/theme.php Normal file
View File

@ -0,0 +1,58 @@
<?php
class ApprovalTheme extends Themelet
{
public function get_image_admin_html(Image $image)
{
if ($image->approved===true) {
$html = "
".make_form(make_link('disapprove_image/'.$image->id), 'POST')."
<input type='hidden' name='image_id' value='$image->id'>
<input type='submit' value='Disapprove'>
</form>
";
} else {
$html = "
".make_form(make_link('approve_image/'.$image->id), 'POST')."
<input type='hidden' name='image_id' value='$image->id'>
<input type='submit' value='Approve'>
</form>
";
}
return $html;
}
public function get_help_html()
{
return '<p>Search for images that are approved/not approved.</p>
<div class="command_example">
<pre>approved:yes</pre>
<p>Returns images that have been approved.</p>
</div>
<div class="command_example">
<pre>approved:no</pre>
<p>Returns images that have not been approved.</p>
</div>
';
}
public function display_admin_block(SetupBuildingEvent $event)
{
$sb = new SetupBlock("Approval");
$sb->add_bool_option(ApprovalConfig::IMAGES, "Images: ");
$event->panel->add_block($sb);
}
public function display_admin_form()
{
global $page;
$html = make_form(make_link("admin/approval"), "POST");
$html .= "<button name='approval_action' value='approve_all'>Approve All Images</button><br/>";
$html .= "<button name='approval_action' value='disapprove_all'>Disapprove All Images</button>";
$html .= "</form>\n";
$page->add_block(new Block("Approval", $html));
}
}

View File

@ -1,13 +1,5 @@
<?php
/**
* Name: Arrow Key Navigation
* Author: Drudex Software <support@drudexsoftware.com>
* Link: http://www.drudexsoftware.com/
* License: GPLv2
* Description: Allows viewers no navigate between images using the left & right arrow keys.
* Documentation:
* Simply enable this extention in the extention manager to enable arrow key navigation.
*/
class ArrowkeyNavigationInfo extends ExtensionInfo
{
public const KEY = "arrowkey_navigation";

View File

@ -52,7 +52,7 @@ class ArrowkeyNavigation extends Extension
global $config, $database;
// get the amount of images per page
$images_per_page = $config->get_int('index_images');
$images_per_page = $config->get_int(IndexConfig::IMAGES);
// if there are no tags, use default
if (is_null($event->get_arg(1))) {

View File

@ -1,14 +1,5 @@
<?php
/**
* Name: [Beta] Artists System
* Author: Sein Kraft <mail@seinkraft.info>
* Alpha <alpha@furries.com.ar>
* License: GPLv2
* Description: Simple artists extension
* Documentation:
*
*/
class ArtistsInfo extends ExtensionInfo
{
public const KEY = "artists";

View File

@ -905,7 +905,7 @@ class Artists extends Extension
$pageNumber * $artistsPerPage
, $artistsPerPage
]
);
);
$number_of_listings = count($listing);

View File

@ -1,11 +1,5 @@
<?php
/*
* Name: Autocomplete
* Author: Daku <admin@codeanimu.net>
* Description: Adds autocomplete to search & tagging.
*/
class AutoCompleteInfo extends ExtensionInfo
{
public const KEY = "autocomplete";

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: Comment Word Ban
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: For stopping spam and other comment abuse
* Documentation:
*
*/
class BanWordsInfo extends ExtensionInfo
{
public const KEY = "ban_words";

View File

@ -1,13 +1,5 @@
<?php
/**
* Name: BBCode
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Turns BBCode into HTML
*/
class BBCodeInfo extends ExtensionInfo
{
public const KEY = "bbcode";

View File

@ -1,13 +1,5 @@
<?php
/*
* Name: Generic Blocks
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Add HTML to some space (News, Ads, etc)
*/
class BlocksInfo extends ExtensionInfo
{
public const KEY = "blocks";

View File

@ -1,11 +1,5 @@
<?php
/*
* Name: Blotter
* Author: Zach Hall <zach@sosguy.net> [http://seemslegit.com/]
* License: GPLv2
* Description:
*/
class BlotterInfo extends ExtensionInfo
{
public const KEY = "blotter";

View File

@ -1,17 +1,5 @@
<?php
/*
* Name: Browser Search
* Author: ATravelingGeek <atg@atravelinggeek.com>
* Some code (and lots of help) by Artanis (Erik Youngren <artanis.00@gmail.com>) from the 'tagger' extention - Used with permission
* Link: http://atravelinggeek.com/
* License: GPLv2
* Description: Allows the user to add a browser 'plugin' to search the site with real-time suggestions
* Version: 0.1c, October 26, 2007
* Documentation:
*
*/
class BrowserSearchInfo extends ExtensionInfo
{
public const KEY = "browser_search";

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: Bulk Actions
* Author: Matthew Barbour
* License: WTFPL
* Description: Provides query and selection-based bulk action support
* Documentation: Provides bulk action section in list view. Allows performing actions against a set of images based on query or manual selection.
* Based on Mass Tagger by Christian Walde <walde.christian@googlemail.com>, contributions by Shish and Agasa.
*/
class BulkActionsInfo extends ExtensionInfo
{
public const KEY = "bulk_actions";

View File

@ -1,17 +1,8 @@
<?php
/*
* Name: Bulk Add
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Bulk add server-side images
* Documentation:
*/
class BulkAddInfo extends ExtensionInfo
{
public const KEY = "builk_add";
public const KEY = "bulk_add";
public $key = self::KEY;
public $name = "Bulk Add";
@ -20,7 +11,7 @@ class BulkAddInfo extends ExtensionInfo
public $license = self::LICENSE_GPLV2;
public $description = "Bulk add server-side images";
public $documentation =
" Upload the images into a new directory via ftp or similar, go to
"Upload the images into a new directory via ftp or similar, go to
shimmie's admin page and put that directory in the bulk add box.
If there are subdirectories, they get used as tags (eg if you
upload into <code>/home/bob/uploads/holiday/2008/</code> and point

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: Bulk Add CSV
* Author: velocity37 <velocity37@gmail.com>
* License: GPLv2
* Description: Bulk add server-side images with metadata from CSV file
* Documentation:
*
*
*/
class BulkAddCSVInfo extends ExtensionInfo
{
public const KEY = "bulk_add_csv";

View File

@ -1,14 +1,5 @@
<?php
/*
* Name: [Beta] Bulk Remove
* Author: Drudex Software <support@drudexsoftware.com>
* Link: http://www.drudexsoftware.com/
* License: GPLv2
* Description: Allows admin to delete many images at once through Board Admin.
* Documentation:
*
*/
class BulkRemoveInfo extends ExtensionInfo
{
public const KEY = "bulk_remove";

View File

@ -123,7 +123,7 @@ class BulkRemove extends Extension
$page->add_block(new Block(
"Bulk Remove Error",
"Please use Board Admin to use bulk remove."
));
));
}
//

View File

@ -1,15 +1,5 @@
<?php
/**
* Name: Image Comments
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Allow users to make comments on images
* Documentation:
* Formatting is done with the standard formatting API (normally BBCode)
*/
class CommentListInfo extends ExtensionInfo
{
public const KEY = "comment";

View File

@ -367,7 +367,6 @@ class CommentList extends Extension
}
}
// page building {{{
private function build_page(int $current_page)
{
global $cache, $database, $user;
@ -417,9 +416,7 @@ class CommentList extends Extension
$this->theme->display_comment_list($images, $current_page, $total_pages, $user->can(Permissions::CREATE_COMMENT));
}
// }}}
// get comments {{{
/**
* #return Comment[]
*/
@ -488,9 +485,7 @@ class CommentList extends Extension
ORDER BY comments.id ASC
", ["image_id"=>$image_id]);
}
// }}}
// add / remove / edit comments {{{
private function is_comment_limit_hit(): bool
{
global $config, $database;
@ -651,5 +646,4 @@ class CommentList extends Extension
throw new CommentPostingException("Akismet thinks that your comment is spam. Try rewriting the comment, or logging in.");
}
}
// }}}
}

View File

@ -0,0 +1,97 @@
<?php
abstract class CronUploaderConfig
{
const DEFAULT_PATH = "cron_uploader";
const KEY = "cron_uploader_key";
const COUNT = "cron_uploader_count";
const DIR = "cron_uploader_dir";
const USER = "cron_uploader_user";
public static function set_defaults(): void
{
global $config;
$config->set_default_int(self::COUNT, 1);
$config->set_default_string(self::DIR, data_path(self::DEFAULT_PATH));
$upload_key = $config->get_string(self::KEY, "");
if (empty($upload_key)) {
$upload_key = self::generate_key();
$config->set_string(self::KEY, $upload_key);
}
}
public static function get_user(): int
{
global $config;
return $config->get_int(self::USER);
}
public static function set_user(int $value): void
{
global $config;
$config->set_int(self::USER, $value);
}
public static function get_key(): string
{
global $config;
return $config->get_string(self::KEY);
}
public static function set_key(string $value): void
{
global $config;
$config->set_string(self::KEY, $value);
}
public static function get_count(): int
{
global $config;
return $config->get_int(self::COUNT);
}
public static function set_count(int $value): int
{
global $config;
$config->get_int(self::COUNT, $value);
}
public static function get_dir(): string
{
global $config;
$value = $config->get_string(self::DIR);
if (empty($value)) {
$value = data_path("cron_uploader");
self::set_dir($value);
}
return $value;
}
public static function set_dir(string $value): void
{
global $config;
$config->set_string(self::DIR, $value);
}
/*
* Generates a unique key for the website to prevent unauthorized access.
*/
private static function generate_key()
{
$length = 20;
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters [rand(0, strlen($characters) - 1)];
}
return $randomString;
}
}

View File

@ -1,43 +1,31 @@
<?php
require_once "config.php";
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
public const NAME = "cron_uploader";
// TODO: Checkbox option to only allow localhost + a list of additional IP addresses that can be set in /cron_upload
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";
public $output_buffer = [];
/**
* Lists all log events this session
* @var string
*/
private $upload_info = "";
public function onInitExt(InitExtEvent $event)
{
// Set default values
CronUploaderConfig::set_defaults();
}
/**
* 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 = "";
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
if ($event->parent=="system") {
$event->add_nav_link("cron_docs", new Link('cron_upload'), "Cron Upload");
}
}
/**
* Checks if the cron upload page has been accessed
@ -45,308 +33,354 @@ class CronUploader extends Extension
*/
public function onPageRequest(PageRequestEvent $event)
{
global $config, $user;
global $user;
if ($event->page_matches("cron_upload")) {
$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->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->can(Permissions::BULK_ADD)) {
$this->set_dir();
$key = $event->get_arg(0);
if (!empty($key)) {
$this->process_upload($key); // Start upload
} elseif ($user->can(Permissions::CRON_ADMIN)) {
$this->display_documentation();
}
}
}
private function display_documentation()
{
global $page;
$this->set_dir(); // Determines path to cron_uploader_dir
$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;'>
<tr>
<td style='width:90px;'><b>Directory</b></td>
<td style='width:90px;'><b>Files</b></td>
<td style='width:90px;'><b>Size (MB)</b></td>
<td style='width:200px;'><b>Directory Path</b></td>
</tr><tr>
<td>Queue</td>
<td>{$queue_dirinfo['total_files']}</td>
<td>{$queue_dirinfo['total_mb']}</td>
<td><input type='text' style='width:150px;' value='$queue_dir'></td>
</tr><tr>
<td>Uploaded</td>
<td>{$uploaded_dirinfo['total_files']}</td>
<td>{$uploaded_dirinfo['total_mb']}</td>
<td><input type='text' style='width:150px;' value='$uploaded_dir'></td>
</tr><tr>
<td>Failed</td>
<td>{$failed_dirinfo['total_files']}</td>
<td>{$failed_dirinfo['total_mb']}</td>
<td><input type='text' style='width:150px;' value='$failed_dir'></td>
</tr></table>
<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.
<br />
<br />2. Upload your images you want to be uploaded to the queue directory using your FTP client.
<br />(<b>$queue_dir</b>)
<br />This also supports directory names to be used as tags.
<br />
<br />3. Go to the Board Config to the Cron Uploader menu and copy the Cron Command.
<br />(<b>$cron_cmd</b>)
<br />
<br />4. Create a cron job or something else that can open a url on specified times.
<br />If you're not sure how to do this, you can give the command to your web host and you can ask them to create the cron job for you.
<br />When you create the cron job, you choose when to upload new images.
<br />
<br />5. When the cron command is set up, your image queue will upload x file(s) at the specified times.
<br />You can see any uploads or failed uploads in the log file. (<b>$log_path</b>)
<br />Your uploaded images will be moved to the 'uploaded' directory, it's recommended that you remove everything out of this directory from time to time.
<br />(<b>$uploaded_dir</b>)
<br />
<br />Whenever the url in that cron job command is opened, a new file will upload from the queue.
<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);
$page->add_block($block_install);
}
public function onInitExt(InitExtEvent $event)
{
global $config;
// Set default values
$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_string(self::CONFIG_KEY, $this->upload_key);
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
{
$this->set_dir();
global $database;
$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(self::CONFIG_COUNT, "How many to upload each time");
$sb->add_text_option(self::CONFIG_DIR, "<br>Set Cron Uploader root directory<br>");
$users = $database->get_pairs("SELECT name, id FROM users UNION ALL SELECT '', null order by name");
$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.");
$sb = new SetupBlock("Cron Uploader");
$sb->start_table();
$sb->add_int_option(CronUploaderConfig::COUNT, "Upload per run", true);
$sb->add_text_option(CronUploaderConfig::DIR, "Root dir", true);
$sb->add_text_option(CronUploaderConfig::KEY, "Key", true);
$sb->add_choice_option(CronUploaderConfig::USER, $users, "User", true);
$sb->end_table();
$sb->add_label("<a href='$documentation_link'>Read the documentation</a> for cron setup instructions.");
$event->panel->add_block($sb);
}
/*
* Generates a unique key for the website to prevent unauthorized access.
*/
private function generate_key()
public function onAdminBuilding(AdminBuildingEvent $event)
{
$length = 20;
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
$failed_dir = $this->get_failed_dir();
$results = get_dir_contents($failed_dir);
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters [rand(0, strlen($characters) - 1)];
$failed_dirs = [];
foreach ($results as $result) {
$path = join_path($failed_dir, $result);
if (is_dir($path)) {
$failed_dirs[] = $result;
}
}
return $randomString;
$this->theme->display_form($failed_dirs);
}
/*
* Set the directory for the image queue. If no directory was given, set it to the default directory.
*/
private function set_dir()
public function onAdminAction(AdminActionEvent $event)
{
global $config;
// Determine directory (none = default)
$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(self::CONFIG_DIR, $dir);
$action = $event->action;
switch ($action) {
case "cron_uploader_clear_queue":
$event->redirect = true;
$this->clear_folder(self::QUEUE_DIR);
break;
case "cron_uploader_clear_uploaded":
$event->redirect = true;
$this->clear_folder(self::UPLOADED_DIR);
break;
case "cron_uploader_clear_failed":
$event->redirect = true;
$this->clear_folder(self::FAILED_DIR);
break;
case "cron_uploader_restage":
$event->redirect = true;
if (array_key_exists("failed_dir", $_POST) && !empty($_POST["failed_dir"])) {
$this->restage_folder($_POST["failed_dir"]);
}
break;
}
}
private function restage_folder(string $folder)
{
if (empty($folder)) {
throw new Exception("folder empty");
}
$queue_dir = $this->get_queue_dir();
$stage_dir = join_path($this->get_failed_dir(), $folder);
if (!is_dir($stage_dir)) {
throw new Exception("Could not find $stage_dir");
}
$this->prep_root_dir();
$results = get_dir_contents($queue_dir);
if (count($results) > 0) {
flash_message("Queue folder must be empty to re-stage", "error");
return;
}
$results = get_dir_contents($stage_dir);
if (count($results) == 0) {
if (rmdir($stage_dir)===false) {
flash_message("Nothing to stage from $folder, cannot remove folder");
} else {
flash_message("Nothing to stage from $folder, removing folder");
}
return;
}
foreach ($results as $result) {
$original_path = join_path($stage_dir, $result);
$new_path = join_path($queue_dir, $result);
rename($original_path, $new_path);
}
flash_message("Re-staged $folder to queue");
rmdir($stage_dir);
}
private function clear_folder($folder)
{
$path = join_path(CronUploaderConfig::get_dir(), $folder);
deltree($path);
flash_message("Cleared $path");
}
private function get_cron_url()
{
return make_http(make_link("/cron_upload/" . CronUploaderConfig::get_key()));
}
private function get_cron_cmd()
{
return "curl --silent " . $this->get_cron_url();
}
private function display_documentation()
{
global $database;
$this->prep_root_dir();
$queue_dir = $this->get_queue_dir();
$uploaded_dir = $this->get_uploaded_dir();
$failed_dir = $this->get_failed_dir();
$queue_dirinfo = scan_dir($queue_dir);
$uploaded_dirinfo = scan_dir($uploaded_dir);
$failed_dirinfo = scan_dir($failed_dir);
$running = false;
$lockfile = fopen($this->get_lock_file(), "w");
try {
if (!flock($lockfile, LOCK_EX | LOCK_NB)) {
$running = true;
} else {
flock($lockfile, LOCK_UN);
}
} finally {
fclose($lockfile);
}
$logs = [];
if (Extension::is_enabled(LogDatabaseInfo::KEY)) {
$logs = $database->get_all(
"SELECT * FROM score_log WHERE section = :section ORDER BY date_sent DESC LIMIT 100",
["section" => self::NAME]
);
}
$this->theme->display_documentation(
$running,
$queue_dirinfo,
$uploaded_dirinfo,
$failed_dirinfo,
$this->get_cron_cmd(),
$this->get_cron_url(),
$logs
);
}
public function get_queue_dir()
{
$dir = CronUploaderConfig::get_dir();
return join_path($dir, self::QUEUE_DIR);
}
public function get_uploaded_dir()
{
$dir = CronUploaderConfig::get_dir();
return join_path($dir, self::UPLOADED_DIR);
}
public function get_failed_dir()
{
$dir = CronUploaderConfig::get_dir();
return join_path($dir, self::FAILED_DIR);
}
private function prep_root_dir(): string
{
// Determine directory (none = default)
$dir = CronUploaderConfig::get_dir();
// Make the directory if it doesn't exist yet
if (!is_dir($dir . "/" . self::QUEUE_DIR . "/")) {
mkdir($dir . "/" . self::QUEUE_DIR . "/", 0775, true);
if (!is_dir($this->get_queue_dir())) {
mkdir($this->get_queue_dir(), 0775, true);
}
if (!is_dir($dir . "/" . self::UPLOADED_DIR . "/")) {
mkdir($dir . "/" . self::UPLOADED_DIR . "/", 0775, true);
if (!is_dir($this->get_uploaded_dir())) {
mkdir($this->get_uploaded_dir(), 0775, true);
}
if (!is_dir($dir . "/" . self::FAILED_DIR . "/")) {
mkdir($dir . "/" . self::FAILED_DIR . "/", 0775, true);
if (!is_dir($this->get_failed_dir())) {
mkdir($this->get_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
private function get_lock_file(): string
{
$bytestotal = 0;
$nbfiles = 0;
$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];
$root_dir = CronUploaderConfig::get_dir();
return join_path($root_dir, ".lock");
}
/**
* Uploads the image & handles everything
*/
public function process_upload(int $upload_count = 0): bool
public function process_upload(string $key, ?int $upload_count = null): bool
{
global $config, $database;
global $database;
//set_time_limit(0);
$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(self::CONFIG_COUNT, 1);
if ($key!=CronUploaderConfig::get_key()) {
throw new SCoreException("Cron upload key incorrect");
}
$user_id = CronUploaderConfig::get_user();
if (empty($user_id)) {
throw new SCoreException("Cron upload user not set");
}
$user = User::by_id($user_id);
if ($user == null) {
throw new SCoreException("No user found for cron upload user $user_id");
}
// 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;
send_event(new UserLoginEvent($user));
$this->log_message(SCORE_LOG_INFO, "Logged in as user {$user->name}");
$lockfile = fopen($this->get_lock_file(), "w");
if (!flock($lockfile, LOCK_EX | LOCK_NB)) {
throw new SCoreException("Cron upload process is already running");
}
// Randomize Images
//shuffle($this->image_queue);
try {
//set_time_limit(0);
$merged = 0;
$added = 0;
$failed = 0;
// Upload the file(s)
for ($i = 0; $i < $upload_count && sizeof($this->image_queue) > 0; $i++) {
$img = array_pop($this->image_queue);
$database->beginTransaction();
try {
$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->merged) {
$merged++;
} else {
$added++;
}
} catch (Exception $e) {
$database->rollback();
$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($e->getTraceAsString());
// Gets amount of imgs to upload
if ($upload_count == null) {
$upload_count = CronUploaderConfig::get_count();
}
$output_subdir = date('Ymd-His', time());
$image_queue = $this->generate_image_queue($upload_count);
// Throw exception if there's nothing in the queue
if (count($image_queue) == 0) {
$this->log_message(SCORE_LOG_WARNING, "Your queue is empty so nothing could be uploaded.");
$this->handle_log();
return false;
}
// Randomize Images
//shuffle($this->image_queue);
$merged = 0;
$added = 0;
$failed = 0;
// Upload the file(s)
for ($i = 0; $i < $upload_count && sizeof($image_queue) > 0; $i++) {
$img = array_pop($image_queue);
try {
$database->beginTransaction();
$this->log_message(SCORE_LOG_INFO, "Adding file: {$img[0]} - 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->merged) {
$merged++;
} else {
$added++;
}
} catch (Exception $e) {
try {
$database->rollback();
} catch (Exception $e) {
}
$failed++;
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
$this->log_message(SCORE_LOG_ERROR, "(" . gettype($e) . ") " . $e->getMessage());
$this->log_message(SCORE_LOG_ERROR, $e->getTraceAsString());
}
}
$this->log_message(SCORE_LOG_INFO, "Items added: $added");
$this->log_message(SCORE_LOG_INFO, "Items merged: $merged");
$this->log_message(SCORE_LOG_INFO, "Items failed: $failed");
// Display upload log
$this->handle_log();
return true;
} finally {
flock($lockfile, LOCK_UN);
fclose($lockfile);
}
$msgNumber = $this->add_upload_info("Items added: $added");
$msgNumber = $this->add_upload_info("Items merged: $merged");
$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)
private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false)
{
// Create
$newDir = $this->root_dir;
$relativeDir = dirname(substr($path, strlen(CronUploaderConfig::get_dir()) + 7));
$relativeDir = dirname(substr($path, strlen($this->root_dir) + 7));
if ($relativeDir==".") {
$relativeDir = "";
}
// Determine which dir to move to
if ($corrupt) {
// Move to corrupt dir
$newDir .= "/" . self::FAILED_DIR . "/" . $output_subdir . $relativeDir;
$info = "ERROR: Image was not uploaded.";
$newDir = join_path($this->get_failed_dir(), $output_subdir, $relativeDir);
$info = "ERROR: Image was not uploaded. ";
} else {
$newDir .= "/" . self::UPLOADED_DIR . "/" . $output_subdir . $relativeDir;
$newDir = join_path($this->get_uploaded_dir(), $output_subdir, $relativeDir);
$info = "Image successfully uploaded. ";
}
$newDir = str_replace("//", "/", $newDir . "/");
$newDir = str_replace(DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $newDir);
if (!is_dir($newDir)) {
mkdir($newDir, 0775, true);
}
$newFile = join_path($newDir, $filename);
// move file to correct dir
rename($path, $newDir . $filename);
rename($path, $newFile);
$this->add_upload_info($info . "Image \"$filename\" moved from queue to \"$newDir\".");
$this->log_message(SCORE_LOG_INFO, $info . "Image \"$filename\" moved from queue to \"$newDir\".");
}
/**
@ -357,7 +391,7 @@ class CronUploader extends Extension
assert(file_exists($tmpname));
$tagArray = Tag::explode($tags);
if (count($tagArray)==0) {
if (count($tagArray) == 0) {
$tagArray[] = "tagme";
}
@ -377,11 +411,11 @@ class CronUploader extends Extension
if ($event->image_id == -1) {
throw new Exception("File type not recognised. Filename: {$filename}");
} elseif ($event->merged === true) {
$infomsg = "Image merged. ID: {$event->image_id} Filename: {$filename}";
$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);
$this->log_message(SCORE_LOG_INFO, $infomsg);
// Set tags
$img = Image::by_id($event->image_id);
@ -390,18 +424,32 @@ class CronUploader extends Extension
return $event;
}
private function generate_image_queue(): void
private const PARTIAL_DOWNLOAD_EXTENSIONS = ['crdownload','part'];
private function is_skippable_file(string $path)
{
$base = $this->root_dir . "/" . self::QUEUE_DIR;
$info = pathinfo($path);
if (in_array(strtolower($info['extension']), self::PARTIAL_DOWNLOAD_EXTENSIONS)) {
return true;
}
return false;
}
private function generate_image_queue(string $root_dir, ?int $limit = null): array
{
$base = $this->get_queue_dir();
$output = [];
if (!is_dir($base)) {
$this->add_upload_info("Image Queue Directory could not be found at \"$base\".");
return;
$this->log_message(SCORE_LOG_WARNING, "Image Queue Directory could not be found at \"$base\".");
return [];
}
$ite = new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS);
foreach (new RecursiveIteratorIterator($ite) as $fullpath => $cur) {
if (!is_link($fullpath) && !is_dir($fullpath)) {
if (!is_link($fullpath) && !is_dir($fullpath) && !$this->is_skippable_file($fullpath)) {
$pathinfo = pathinfo($fullpath);
$relativePath = substr($fullpath, strlen($base));
@ -412,34 +460,33 @@ class CronUploader extends Extension
1 => $pathinfo ["basename"],
2 => $tags
];
array_push($this->image_queue, $img);
$output[] = $img;
if (!empty($limit) && count($output) >= $limit) {
break;
}
}
}
return $output;
}
/**
* Adds a message to the info being published at the end
*/
private function add_upload_info(string $text, int $addon = 0): int
private function log_message(int $severity, string $message): void
{
$info = $this->upload_info;
global $database;
log_msg(self::NAME, $severity, $message);
$time = "[" . date('Y-m-d H:i:s') . "]";
$this->output_buffer[] = $time . " " . $message;
// If addon function is not used
if ($addon == 0) {
$this->upload_info .= "$time $text\r\n";
$log_path = $this->get_log_file();
// Returns the number of the current line
$currentLine = substr_count($this->upload_info, "\n") - 1;
return $currentLine;
}
file_put_contents($log_path, $time . " " . $message);
}
// 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
private function get_log_file(): string
{
return join_path(CronUploaderConfig::get_dir(), "uploads.log");
}
/**
@ -452,18 +499,6 @@ class CronUploader extends Extension
// Display message
$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;
file_put_contents($log_path, $content);
$page->set_data(implode("\r\n", $this->output_buffer));
}
}

View File

@ -0,0 +1,3 @@
table.log th {
width: 200px;
}

132
ext/cron_uploader/theme.php Normal file
View File

@ -0,0 +1,132 @@
<?php
class CronUploaderTheme extends Themelet
{
public function display_documentation(
bool $running,
array $queue_dirinfo,
array $uploaded_dirinfo,
array $failed_dirinfo,
string $cron_cmd,
string $cron_url,
?array $log_entries
) {
global $page;
$info_html = "<b>Information</b>
<br>
<table style='width:470px;'>
" . ($running ? "<tr><td colspan='4'><b style='color:red'>Cron upload is currently running</b></td></tr>" : "") . "
<tr>
<td style='width:90px;'><b>Directory</b></td>
<td style='width:90px;'><b>Files</b></td>
<td style='width:90px;'><b>Size (MB)</b></td>
<td style='width:200px;'><b>Directory Path</b></td>
</tr><tr>
<td>Queue</td>
<td>{$queue_dirinfo['total_files']}</td>
<td>{$queue_dirinfo['total_mb']}</td>
<td>{$queue_dirinfo['path']}</td>
</tr><tr>
<td>Uploaded</td>
<td>{$uploaded_dirinfo['total_files']}</td>
<td>{$uploaded_dirinfo['total_mb']}</td>
<td>{$uploaded_dirinfo['path']}</td>
</tr><tr>
<td>Failed</td>
<td>{$failed_dirinfo['total_files']}</td>
<td>{$failed_dirinfo['total_mb']}</td>
<td>{$failed_dirinfo['path']}</td>
</tr></table>
<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.
<ol>
<li>Install & activate this plugin.</li>
<li>Go to the <a href='".make_link("setup")."'>Board Config</a> and change any settings to match your preference.</li>
<li>Copy the cron command above.</li>
<li>Create a cron job or something else that can open a url on specified times.
<br/>cron is a service that runs commands over and over again on a a schedule. You can set up cron (or any similar tool) to run the command above to trigger the import on whatever schedule you desire.
<br />If you're not sure how to do this, you can give the command to your web host and you can ask them to create the cron job for you.
<br />When you create the cron job, you choose when to upload new images.</li>
</ol>";
$usage_html = "Upload your images you want to be uploaded to the queue directory using your FTP client or other means.
<br />(<b>{$queue_dirinfo['path']}</b>)
<ol>
<li>Any sub-folders will be turned into tags.</li>
<li>If the file name matches \"## - tag1 tag2.png\" the tags will be used.</li>
<li>If both are found, they will all be used.</li>
<li>The character \";\" will be changed into \":\" in any tags.</li>
<li>You can inherit categories by creating a folder that ends with \";\". For instance category;\\tag1 would result in the tag category:tag1. This allows creating a category folder, then creating many subfolders that will use that category.</li>
</ol>
The cron uploader works by importing files from the queue folder whenever this url is visited:
<br/><pre><a href='$cron_url'>$cron_url</a></pre>
<ul>
<li>If an import is already running, another cannot start until it is done.</li>
<li>Each time it runs it will import up to ".CronUploaderConfig::get_count()." file(s). This is controlled from <a href='".make_link("setup")."'>Board Config</a>.</li>
<li>Uploaded images will be moved to the 'uploaded' directory into a subfolder named after the time the import started. It's recommended that you remove everything out of this directory from time to time. If you have admin controls enabled, this can be done from <a href='".make_link("admin")."'>Board Admin</a>.</li>
<li>If you enable the db logging extension, you can view the log output on this screen. Otherwise the log will be written to a file at ".CronUploaderConfig::get_dir().DIRECTORY_SEPARATOR."uploads.log</li>
</ul>
";
$page->set_title("Cron Uploader");
$page->set_heading("Cron Uploader");
$block = new Block("Cron Uploader", $info_html, "main", 10);
$block_install = new Block("Setup Guide", $install_html, "main", 30);
$block_usage= new Block("Usage Guide", $usage_html, "main", 20);
$page->add_block($block);
$page->add_block($block_install);
$page->add_block($block_usage);
if (!empty($log_entries)) {
$log_html = "<table class='log'>";
foreach ($log_entries as $entry) {
$log_html .= "<tr><th>{$entry["date_sent"]}</th><td>{$entry["message"]}</td></tr>";
}
$log_html .= "</table>";
$block = new Block("Log", $log_html, "main", 40);
$page->add_block($block);
}
}
public function display_form(array $failed_dirs)
{
global $page, $database;
$link = make_http(make_link("cron_upload"));
$html = "<a href='$link'>Cron uploader documentation</a>";
$html .= make_form(make_link("admin/cron_uploader_restage"));
$html .= "<table class='form'>";
$html .= "<tr><th>Failed dir</th><td><select name='failed_dir' required='required'><option></option>";
foreach ($failed_dirs as $dir) {
$html .= "<option value='$dir'>$dir</option>";
}
$html .= "</select></td></tr>";
$html .= "<tr><td colspan='2'><input type='submit' value='Re-stage files to queue' /></td></tr>";
$html .= "</table></form>";
$html .= make_form(make_link("admin/cron_uploader_clear_queue"), "POST", false, "", "return confirm('Are you sure you want to delete everything in the queue folder?');")
."<table class='form'><tr><td>"
."<input type='submit' value='Clear queue folder'></td></tr></table></form>";
$html .= make_form(make_link("admin/cron_uploader_clear_uploaded"), "POST", false, "", "return confirm('Are you sure you want to delete everything in the uploaded folder?');")
."<table class='form'><tr><td>"
."<input type='submit' value='Clear uploaded folder'></td></tr></table></form>";
$html .= make_form(make_link("admin/cron_uploader_clear_failed"), "POST", false, "", "return confirm('Are you sure you want to delete everything in the failed folder?');")
."<table class='form'><tr><td>"
."<input type='submit' value='Clear failed folder'></td></tr></table></form>";
$html .= "</table>\n";
$page->add_block(new Block("Cron Upload", $html));
}
}

View File

@ -1,14 +1,5 @@
<?php
/**
* Name: Custom HTML Headers
* Author: Drudex Software <support@drudexsoftware.com>
* Link: http://www.drudexsoftware.com
* License: GPLv2
* Description: Allows admins to modify & set custom &lt;head&gt; content
* Documentation:
*
*/
class CustomHtmlHeadersInfo extends ExtensionInfo
{
public const KEY = "custom_html_headers";

View File

@ -1,13 +1,5 @@
<?php
/*
Name: Danbooru Client API
Author: JJS <jsutinen@gmail.com>
Description: Allow Danbooru apps like Danbooru Uploader for Firefox to communicate with Shimmie
Documentation:
*/
class DanbooruApiInfo extends ExtensionInfo
{
public const KEY = "danbooru_api";

View File

@ -91,7 +91,9 @@ class DanbooruApi extends Extension
$namelist = explode(",", $_GET['name']);
foreach ($namelist as $name) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE tag = ?",
$database->scoreql_to_sql(
"SELECT id,tag,count FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(?)"
),
[$name]
);
foreach ($sqlresult as $row) {

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: Downtime
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Show a "down for maintenance" page
* Documentation:
*
*/
class DowntimeInfo extends ExtensionInfo
{
public const KEY = "downtime";

View File

@ -66,6 +66,6 @@ class DowntimeTheme extends Themelet
</body>
</html>
EOD
);
);
}
}

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: Emoticon Filter
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Lets users use graphical smilies
* Documentation:
*
*/
class EmoticonsInfo extends ExtensionInfo
{
public const KEY = "emoticons";

View File

@ -1,13 +1,5 @@
<?php
/*
* Name: System Info
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Show various bits of system information
* Documentation:
*/
class ETInfo extends ExtensionInfo
{
public const KEY = "et";

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

View File

@ -1,15 +1,5 @@
<?php
/**
* Name: Extension Manager
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Visibility: admin
* Description: A thing for point & click extension management
* Documentation:
*/
class ExtManagerInfo extends ExtensionInfo
{
public const KEY = "ext_manager";

View File

@ -37,7 +37,7 @@ class ExtManagerTheme extends Themelet
//baseline_open_in_new_black_18dp.png
$h_enabled_box = $editable ? "<td><input type='checkbox' name='ext_" . html_escape($extension->key) . "' id='ext_" . html_escape($extension->key) . "'$h_disabled $h_enabled></td>" : "";
$h_docs = ($extension->documentation ? "<a href='$h_link'></a>" : ""); //TODO: A proper "docs" symbol would be preferred here.
$h_docs = ($extension->documentation ? "<a href='$h_link'><img src='ext/ext_manager/baseline_open_in_new_black_18dp.png'/></a>" : ""); //TODO: A proper "docs" symbol would be preferred here.
$html .= "
<tr data-ext='{$extension->name}'>

View File

@ -1,13 +1,5 @@
<?php
/*
* Name: Favorites
* Author: Daniel Marschall <info@daniel-marschall.de>
* License: GPLv2
* Description: Allow users to favorite images
* Documentation:
*/
class FavoritesInfo extends ExtensionInfo
{
public const KEY = "favorites";

View File

@ -167,6 +167,45 @@ class Favorites extends Extension
}
}
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
{
global $user;
if (!$user->is_anonymous()) {
$event->add_action("bulk_favorite", "Favorite");
$event->add_action("bulk_unfavorite", "Un-Favorite");
}
}
public function onBulkAction(BulkActionEvent $event)
{
global $user;
switch ($event->action) {
case "bulk_favorite":
if (!$user->is_anonymous()) {
$total = 0;
foreach ($event->items as $image) {
send_event(new FavoriteSetEvent($image->id, $user, true));
$total++;
}
flash_message("Added $total items to favorites");
}
break;
case "bulk_unfavorite":
if (!$user->is_anonymous()) {
$total = 0;
foreach ($event->items as $image) {
send_event(new FavoriteSetEvent($image->id, $user, false));
$total++;
}
flash_message("Removed $total items from favorites");
}
break;
}
}
private function install()
{
global $database;
@ -203,10 +242,12 @@ class Favorites extends Extension
{
global $database;
if ($do_set) {
$database->Execute(
"INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(:image_id, :user_id, NOW())",
["image_id"=>$image_id, "user_id"=>$user_id]
);
if (!$database->exists("select 1 from user_favorites where image_id=:image_id and user_id=:user_id", ["image_id"=>$image_id, "user_id"=>$user_id])) {
$database->Execute(
"INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(:image_id, :user_id, NOW())",
["image_id"=>$image_id, "user_id"=>$user_id]
);
}
} else {
$database->Execute(
"DELETE FROM user_favorites WHERE image_id = :image_id AND user_id = :user_id",

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: Featured Image
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Bring a specific image to the users' attentions
* Documentation:
*
*/
class FeaturedInfo extends ExtensionInfo
{
public const KEY = "featured";

View File

@ -1,14 +1,5 @@
<?php
/**
* Name: [Beta] Forum
* Author: Sein Kraft <mail@seinkraft.info>
* Alpha <alpha@furries.com.ar>
* License: GPLv2
* Description: Rough forum extension
* Documentation:
*/
class ForumInfo extends ExtensionInfo
{
public const KEY = "dorum";

View File

@ -269,7 +269,7 @@ class Forum extends Extension
"GROUP BY f.id, f.sticky, f.title, f.date, u.name, u.email, u.class ".
"ORDER BY f.sticky ASC, f.uptodate DESC LIMIT :limit OFFSET :offset",
["limit"=>$threadsPerPage, "offset"=>$pageNumber * $threadsPerPage]
);
);
$this->theme->display_thread_list($page, $threads, $showAdminOptions, $pageNumber + 1, $totalPages);
}
@ -302,7 +302,7 @@ class Forum extends Extension
"ORDER BY p.date ASC ".
"LIMIT :limit OFFSET :offset",
["thread_id"=>$threadID, "offset"=>$pageNumber * $postsPerPage, "limit"=>$postsPerPage]
);
);
$this->theme->display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber + 1, $totalPages);
}

View File

@ -1,14 +1,5 @@
<?php
/**
* Name: Google Analytics
* Author: Drudex Software <support@drudexsoftware.com>
* Link: http://drudexsoftware.com
* License: GPLv2
* Description: Integrates Google Analytics tracking
* Documentation:
*
*/
class GoogleAnalyticsInfo extends ExtensionInfo
{
public const KEY = "google_analytics";

View File

@ -1,13 +1,5 @@
<?php
/**
* Name: 404 Detector
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Visibility: admin
* Description: If no other extension puts anything onto the page, show 404
*/
class Handle404Info extends ExtensionInfo
{
public const KEY = "handle_404";

View File

@ -1,13 +1,5 @@
<?php
/*
* Name: Handle Archives
* Author: Shish <webmaster@shishnet.org>
* Description: Allow users to upload archives (zip, etc)
* Documentation:
*
*/
class ArchiveFileHandlerInfo extends ExtensionInfo
{
public const KEY = "handle_archive";

View File

@ -1,12 +1,5 @@
<?php
/*
* Name: Handle Flash
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* Description: Handle Flash files.
*/
class FlashFileHandlerInfo extends ExtensionInfo
{
public const KEY = "handle_flash";

View File

@ -13,6 +13,7 @@ class FlashFileHandler extends DataHandlerExtension
if (!$info) {
return null;
}
$event->image = false;
$event->width = $info[0];
$event->height = $info[1];

View File

@ -1,11 +1,5 @@
<?php
/*
* Name: Handle ICO
* Author: Shish <webmaster@shishnet.org>
* Description: Handle windows icons
*/
class IcoFileHandlerInfo extends ExtensionInfo
{
public const KEY = "handle_ico";

View File

@ -11,6 +11,8 @@ class IcoFileHandler extends DataHandlerExtension
$event->lossless = true;
$event->video = false;
$event->audio = false;
$event->image = ($event->ext!="ani");
$fp = fopen($event->file_name, "r");
try {

View File

@ -1,11 +1,5 @@
<?php
/*
* Name: Handle MP3
* Author: Shish <webmaster@shishnet.org>
* Description: Handle MP3 files
*/
class MP3FileHandlerInfo extends ExtensionInfo
{
public const KEY = "handle_mp3";

View File

@ -9,6 +9,7 @@ class MP3FileHandler extends DataHandlerExtension
$event->audio = true;
$event->video = false;
$event->lossless = false;
$event->image = false;
break;
}
// TODO: Buff out audio format support, length scanning

View File

@ -1,12 +1,5 @@
<?php
/**
* Name: Handle Pixel
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* Description: Handle JPEG, PNG, GIF, WEBP, etc files
*/
class PixelFileHandlerInfo extends ExtensionInfo
{
public const KEY = "handle_pixel";

View File

@ -29,6 +29,7 @@ class PixelFileHandler extends DataHandlerExtension
$event->video = false;
break;
}
$event->image = !$event->video;
$info = getimagesize($event->file_name);
if (!$info) {

View File

@ -1,14 +1,5 @@
<?php
/**
* Name: Static File Handler
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Visibility: admin
* Description: If Shimmie can't handle a request, check static files ($theme/static/$filename, then ext/handle_static/static/$filename)
*/
class HandleStaticInfo extends ExtensionInfo
{
public const KEY = "handle_static";

View File

@ -1,12 +1,5 @@
<?php
/*
* Name: Handle SVG
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* Description: Handle static SVG files.
*/
class SVGFileHandlerInfo extends ExtensionInfo
{
public const KEY = "handle_svg";

View File

@ -10,6 +10,7 @@ class SVGFileHandler extends DataHandlerExtension
$event->lossless = true;
$event->video = false;
$event->audio = false;
$event->image = true;
$msp = new MiniSVGParser($event->file_name);
$event->width = $msp->width;

View File

@ -1,14 +1,5 @@
<?php
/*
* Name: Handle Video
* Author: velocity37 <velocity37@gmail.com>
* Modified By: Shish <webmaster@shishnet.org>, jgen <jeffgenovy@gmail.com>, im-mi <im.mi.mail.mi@gmail.com>
* License: GPLv2
* Description: Handle FLV, MP4, OGV and WEBM video files.
* Documentation:
*/
class VideoFileHandlerInfo extends ExtensionInfo
{
public const KEY = "handle_video";

View File

@ -38,6 +38,7 @@ class VideoFileHandler extends DataHandlerExtension
{
if (in_array($event->ext, self::SUPPORTED_EXT)) {
$event->video = true;
$event->image = false;
try {
$data = Media::get_ffprobe_data($event->file_name);

View File

@ -1,11 +1,5 @@
<?php
/**
* Name: Help Pages
* Author: Matthew Barbour <matthew@darkholme.net>
* Description: Provides documentation screens
*/
class HelpPagesInfo extends ExtensionInfo
{
public const KEY = "help_pages";

View File

@ -33,22 +33,38 @@ class HelpPages extends Extension
{
public const SEARCH = "search";
private $pages;
private function get_pages(): array
{
if ($this->pages==null) {
$e = new HelpPageListBuildingEvent();
send_event($e);
$this->pages = $e->pages;
}
return $this->pages;
}
public function onPageRequest(PageRequestEvent $event)
{
global $page;
if ($event->page_matches("help")) {
$e = new HelpPageListBuildingEvent();
send_event($e);
$page->set_mode(PageMode::PAGE);
$pages = $this->get_pages();
if ($event->page_matches("help")) {
if ($event->count_args() == 0) {
$this->theme->display_list_page($e->pages);
$name = array_key_first($pages);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("help/".$name));
return;
} else {
$page->set_mode(PageMode::PAGE);
$name = $event->get_arg(0);
$title = $name;
if (array_key_exists($name, $e->pages)) {
$title = $e->pages[$name];
if (array_key_exists($name, $pages)) {
$title = $pages[$name];
} else {
return;
}
$this->theme->display_help_page($title);
@ -77,6 +93,16 @@ class HelpPages extends Extension
$event->add_nav_link("help", new Link('help'), "Help");
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
if ($event->parent=="help") {
$pages = $this->get_pages();
foreach ($pages as $key=>$value) {
$event->add_nav_link("help_".$key, new Link('help/'.$key), $value);
}
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
$event->add_link("Help", make_link("help"));

View File

@ -1,12 +1,5 @@
<?php
/**
* Name: Holiday Theme
* Author: DakuTree <thedakutree@codeanimu.net>
* Link: http://www.codeanimu.net
* License: GPLv2
* Description: Use an additional stylesheet on certain holidays.
*/
class HolidayInfo extends ExtensionInfo
{
public const KEY = "holiday";

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: Home Page
* Author: Bzchan <bzchan@animemahou.com>
* License: GPLv2
* Visibility: admin
* Description: Displays a front page with logo, search box and image count
* Documentation:
*
*/
class HomeInfo extends ExtensionInfo
{
public const KEY = "home";

View File

@ -22,7 +22,7 @@ class HomeTheme extends Themelet
</body>
</html>
EOD
);
);
}
public function build_body(string $sitename, string $main_links, string $main_text, string $contact_link, $num_comma, string $counter_text)

View File

@ -1,14 +1,5 @@
<?php
/*
* Name: Image Manager
* Author: Shish <webmaster@shishnet.org>
* Modified by: jgen <jgen.tech@gmail.com>
* Link: http://code.shishnet.org/shimmie2/
* Description: Handle the image database
* Visibility: admin
*/
class ImageIOInfo extends ExtensionInfo
{
public const KEY = "image";

View File

@ -159,8 +159,6 @@ class ImageIO extends Extension
$event->panel->add_block($sb);
}
// add image {{{
private function add_image(ImageAdditionEvent $event)
{
global $user, $database, $config;
@ -237,9 +235,7 @@ class ImageIO extends Extension
log_warning("add_image", "Error while running update_image_media_properties: ".$e->getMessage());
}
}
// }}} end add
// fetch image {{{
private function send_file(int $image_id, string $type)
{
global $config;
@ -298,9 +294,7 @@ class ImageIO extends Extension
));
}
}
// }}} end fetch
// replace image {{{
private function replace_image(int $id, Image $image)
{
global $database;
@ -362,5 +356,4 @@ class ImageIO extends Extension
log_info("image", "Replaced Image #{$id} with ({$image->hash})");
}
// }}} end replace
} // end of class ImageIO
}

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: Image Hash Ban
* Author: ATravelingGeek <atg@atravelinggeek.com>
* Link: http://atravelinggeek.com/
* License: GPLv2
* Description: Ban images based on their hash
* Based on the ResolutionLimit and IPban extensions by Shish
* Version 0.1, October 21, 2007
*/
class ImageBanInfo extends ExtensionInfo
{
public const KEY = "image_hash_ban";

View File

@ -1,6 +1,5 @@
<?php
// RemoveImageHashBanEvent {{{
class RemoveImageHashBanEvent extends Event
{
public $hash;
@ -10,8 +9,7 @@ class RemoveImageHashBanEvent extends Event
$this->hash = $hash;
}
}
// }}}
// AddImageHashBanEvent {{{
class AddImageHashBanEvent extends Event
{
public $hash;
@ -23,7 +21,7 @@ class AddImageHashBanEvent extends Event
$this->reason = $reason;
}
}
// }}}
class ImageBan extends Extension
{
public function onInitExt(InitExtEvent $event)

View File

@ -1,14 +1,4 @@
<?php
/**
* Name: Image Hash Ban
* Author: ATravelingGeek <atg@atravelinggeek.com>
* Link: http://atravelinggeek.com/
* License: GPLv2
* Description: Ban images based on their hash
* Based on the ResolutionLimit and IPban extensions by Shish
* Version 0.1
* October 21, 2007
*/
class ImageBanTheme extends Themelet
{

View File

@ -1,14 +1,5 @@
<?php
/**
* Name: Image View Counter
* Author: Drudex Software <support@drudexsoftware.com>
* Link: http://www.drudexsoftware.com/
* License: GPLv2
* Description: Tracks & displays how many times an image is viewed
* Documentation:
*
*/
class ImageViewCounterInfo extends ExtensionInfo
{
public const KEY = "image_view_counter";

11
ext/index/config.php Normal file
View File

@ -0,0 +1,11 @@
<?php
class IndexConfig
{
public const IMAGES = "index_images";
public const SHOW_PAGE_SIZES = "index_show_page_sizes";
public const PAGE_SIZES = "index_page_sizes";
public const TIPS = "index_tips";
public const ORDER = "index_order";
}

65
ext/index/events.php Normal file
View File

@ -0,0 +1,65 @@
<?php
/*
* SearchTermParseEvent:
* Signal that a search term needs parsing
*/
class SearchTermParseEvent extends Event
{
/** @var null|string */
public $term = null;
/** @var string[] */
public $context = [];
/** @var Querylet[] */
public $querylets = [];
public function __construct(string $term=null, array $context=[])
{
$this->term = $term;
$this->context = $context;
}
public function is_querylet_set(): bool
{
return (count($this->querylets) > 0);
}
public function get_querylets(): array
{
return $this->querylets;
}
public function add_querylet(Querylet $q)
{
$this->querylets[] = $q;
}
}
class SearchTermParseException extends SCoreException
{
}
class PostListBuildingEvent extends Event
{
/** @var array */
public $search_terms = [];
/** @var array */
public $parts = [];
/**
* #param string[] $search
*/
public function __construct(array $search)
{
$this->search_terms = $search;
}
public function add_control(string $html, int $position=50)
{
while (isset($this->parts[$position])) {
$position++;
}
$this->parts[$position] = $html;
}
}

View File

@ -1,160 +1,5 @@
<?php
/**
* Name: Image List
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Show a list of uploaded images
* Documentation:
* Here is a list of the search methods available out of the box;
* Shimmie extensions may provide other filters:
* <ul>
* <li>by tag, eg
* <ul>
* <li>cat
* <li>pie
* <li>somethi* -- wildcards are supported
* </ul>
* <li>size (=, &lt;, &gt;, &lt;=, &gt;=) width x height, eg
* <ul>
* <li>size=1024x768 -- a specific wallpaper size
* <li>size&gt;=500x500 -- no small images
* <li>size&lt;1000x1000 -- no large images
* </ul>
* <li>width (=, &lt;, &gt;, &lt;=, &gt;=) width, eg
* <ul>
* <li>width=1024 -- find images with 1024 width
* <li>width>2000 -- find images bigger than 2000 width
* </ul>
* <li>height (=, &lt;, &gt;, &lt;=, &gt;=) height, eg
* <ul>
* <li>height=768 -- find images with 768 height
* <li>height>1000 -- find images bigger than 1000 height
* </ul>
* <li>ratio (=, &lt;, &gt;, &lt;=, &gt;=) width : height, eg
* <ul>
* <li>ratio=4:3, ratio=16:9 -- standard wallpaper
* <li>ratio=1:1 -- square images
* <li>ratio<1:1 -- tall images
* <li>ratio>1:1 -- wide images
* </ul>
* <li>filesize (=, &lt;, &gt;, &lt;=, &gt;=) size, eg
* <ul>
* <li>filesize&gt;1024 -- no images under 1KB
* <li>filesize&lt=3MB -- shorthand filesizes are supported too
* </ul>
* <li>id (=, &lt;, &gt;, &lt;=, &gt;=) number, eg
* <ul>
* <li>id<20 -- search only the first few images
* <li>id>=500 -- search later images
* </ul>
* <li>user=Username & poster=Username, eg
* <ul>
* <li>user=Shish -- find all of Shish's posts
* <li>poster=Shish -- same as above
* </ul>
* <li>user_id=userID & poster_id=userID, eg
* <ul>
* <li>user_id=2 -- find all posts by user id 2
* <li>poster_id=2 -- same as above
* </ul>
* <li>hash=md5sum & md5=md5sum, eg
* <ul>
* <li>hash=bf5b59173f16b6937a4021713dbfaa72 -- find the "Taiga want up!" image
* <li>md5=bf5b59173f16b6937a4021713dbfaa72 -- same as above
* </ul>
* <li>filetype=type & ext=type, eg
* <ul>
* <li>filetype=png -- find all PNG images
* <li>ext=png -- same as above
* </ul>
* <li>filename=blah & name=blah, eg
* <ul>
* <li>filename=kitten -- find all images with "kitten" in the original filename
* <li>name=kitten -- same as above
* </ul>
* <li>posted (=, &lt;, &gt;, &lt;=, &gt;=) date, eg
* <ul>
* <li>posted&gt;=2009-12-25 posted&lt;=2010-01-01 -- find images posted between christmas and new year
* </ul>
* <li>tags (=, &lt;, &gt;, &lt;=, &gt;=) count, eg
* <ul>
* <li>tags=1 -- search for images with only 1 tag
* <li>tags>=10 -- search for images with 10 or more tags
* <li>tags<25 -- search for images with less than 25 tags
* </ul>
* <li>source=(URL, any, none) eg
* <ul>
* <li>source=http://example.com -- find all images with "http://example.com" in the source
* <li>source=any -- find all images with a source
* <li>source=none -- find all images without a source
* </ul>
* <li>order=(id, width, height, filesize, filename)_(ASC, DESC), eg
* <ul>
* <li>order=width -- find all images sorted from highest > lowest width
* <li>order=filesize_asc -- find all images sorted from lowest > highest filesize
* </ul>
* <li>order=random_####, eg
* <ul>
* <li>order=random_8547 -- find all images sorted randomly using 8547 as a seed
* </ul>
* </ul>
* <p>Search items can be combined to search for images which match both,
* or you can stick "-" in front of an item to search for things that don't
* match it.
* <p>Metatags can be followed by ":" rather than "=" if you prefer.
* <br />I.E: "posted:2014-01-01", "id:>=500" etc.
* <p>Some search methods provided by extensions:
* <ul>
* <li>Numeric Score
* <ul>
* <li>score (=, &lt;, &gt;, &lt;=, &gt;=) number -- seach by score
* <li>upvoted_by=Username -- search for a user's likes
* <li>downvoted_by=Username -- search for a user's dislikes
* <li>upvoted_by_id=UserID -- search for a user's likes by user ID
* <li>downvoted_by_id=UserID -- search for a user's dislikes by user ID
* <li>order=score_(ASC, DESC) -- find all images sorted from by score
* </ul>
* <li>Image Rating
* <ul>
* <li>rating=se -- find safe and explicit images, ignore questionable and unknown
* </ul>
* <li>Favorites
* <ul>
* <li>favorites (=, &lt;, &gt;, &lt;=, &gt;=) number -- search for images favourited a certain number of times
* <li>favourited_by=Username -- search for a user's choices by username
* <li>favorited_by_userno=UserID -- search for a user's choice by userID
* </ul>
* <li>Notes
* <ul>
* <li>notes (=, &lt;, &gt;, &lt;=, &gt;=) number -- search by the number of notes an image has
* <li>notes_by=Username -- search for images containing notes created by username
* <li>notes_by_userno=UserID -- search for images containing notes created by userID
* </ul>
* <li>Artists
* <ul>
* <li>author=ArtistName -- search for images by artist
* </ul>
* <li>Image Comments
* <ul>
* <li>comments (=, &lt;, &gt;, &lt;=, &gt;=) number -- search for images by number of comments
* <li>commented_by=Username -- search for images containing user's comments by username
* <li>commented_by_userno=UserID -- search for images containing user's comments by userID
* </ul>
* <li>Pools
* <ul>
* <li>pool=(PoolID, any, none) -- search for images in a pool by PoolID.
* <li>pool_by_name=PoolName -- search for images in a pool by PoolName. underscores are replaced with spaces
* </ul>
* <li>Post Relationships
* <ul>
* <li>parent=(parentID, any, none) -- search for images by parentID / if they have, do not have a parent
* <li>child=(any, none) -- search for images which have, or do not have children
* </ul>
* </ul>
*/
class IndexInfo extends ExtensionInfo
{
public const KEY = "index";
@ -166,4 +11,151 @@ class IndexInfo extends ExtensionInfo
public $license = self::LICENSE_GPLV2;
public $description = "Show a list of uploaded images";
public $core = true;
public $documentation = "Here is a list of the search methods available out of the box;
Shimmie extensions may provide other filters:
<ul>
<li>by tag, eg
<ul>
<li>cat
<li>pie
<li>somethi* -- wildcards are supported
</ul>
<li>size (=, &lt;, &gt;, &lt;=, &gt;=) width x height, eg
<ul>
<li>size=1024x768 -- a specific wallpaper size
<li>size&gt;=500x500 -- no small images
<li>size&lt;1000x1000 -- no large images
</ul>
<li>width (=, &lt;, &gt;, &lt;=, &gt;=) width, eg
<ul>
<li>width=1024 -- find images with 1024 width
<li>width>2000 -- find images bigger than 2000 width
</ul>
<li>height (=, &lt;, &gt;, &lt;=, &gt;=) height, eg
<ul>
<li>height=768 -- find images with 768 height
<li>height>1000 -- find images bigger than 1000 height
</ul>
<li>ratio (=, &lt;, &gt;, &lt;=, &gt;=) width : height, eg
<ul>
<li>ratio=4:3, ratio=16:9 -- standard wallpaper
<li>ratio=1:1 -- square images
<li>ratio<1:1 -- tall images
<li>ratio>1:1 -- wide images
</ul>
<li>filesize (=, &lt;, &gt;, &lt;=, &gt;=) size, eg
<ul>
<li>filesize&gt;1024 -- no images under 1KB
<li>filesize&lt=3MB -- shorthand filesizes are supported too
</ul>
<li>id (=, &lt;, &gt;, &lt;=, &gt;=) number, eg
<ul>
<li>id<20 -- search only the first few images
<li>id>=500 -- search later images
</ul>
<li>user=Username & poster=Username, eg
<ul>
<li>user=Shish -- find all of Shish's posts
<li>poster=Shish -- same as above
</ul>
<li>user_id=userID & poster_id=userID, eg
<ul>
<li>user_id=2 -- find all posts by user id 2
<li>poster_id=2 -- same as above
</ul>
<li>hash=md5sum & md5=md5sum, eg
<ul>
<li>hash=bf5b59173f16b6937a4021713dbfaa72 -- find the \"Taiga want up!\" image
<li>md5=bf5b59173f16b6937a4021713dbfaa72 -- same as above
</ul>
<li>filetype=type & ext=type, eg
<ul>
<li>filetype=png -- find all PNG images
<li>ext=png -- same as above
</ul>
<li>filename=blah & name=blah, eg
<ul>
<li>filename=kitten -- find all images with \"kitten\" in the original filename
<li>name=kitten -- same as above
</ul>
<li>posted (=, &lt;, &gt;, &lt;=, &gt;=) date, eg
<ul>
<li>posted&gt;=2009-12-25 posted&lt;=2010-01-01 -- find images posted between christmas and new year
</ul>
<li>tags (=, &lt;, &gt;, &lt;=, &gt;=) count, eg
<ul>
<li>tags=1 -- search for images with only 1 tag
<li>tags>=10 -- search for images with 10 or more tags
<li>tags<25 -- search for images with less than 25 tags
</ul>
<li>source=(URL, any, none) eg
<ul>
<li>source=http://example.com -- find all images with \"http://example.com\" in the source
<li>source=any -- find all images with a source
<li>source=none -- find all images without a source
</ul>
<li>order=(id, width, height, filesize, filename)_(ASC, DESC), eg
<ul>
<li>order=width -- find all images sorted from highest > lowest width
<li>order=filesize_asc -- find all images sorted from lowest > highest filesize
</ul>
<li>order=random_####, eg
<ul>
<li>order=random_8547 -- find all images sorted randomly using 8547 as a seed
</ul>
</ul>
<p>Search items can be combined to search for images which match both,
or you can stick \"-\" in front of an item to search for things that don't
match it.
<p>Metatags can be followed by \":\" rather than \"=\" if you prefer.
<br />I.E: \"posted:2014-01-01\", \"id:>=500\" etc.
<p>Some search methods provided by extensions:
<ul>
<li>Numeric Score
<ul>
<li>score (=, &lt;, &gt;, &lt;=, &gt;=) number -- seach by score
<li>upvoted_by=Username -- search for a user's likes
<li>downvoted_by=Username -- search for a user's dislikes
<li>upvoted_by_id=UserID -- search for a user's likes by user ID
<li>downvoted_by_id=UserID -- search for a user's dislikes by user ID
<li>order=score_(ASC, DESC) -- find all images sorted from by score
</ul>
<li>Image Rating
<ul>
<li>rating=se -- find safe and explicit images, ignore questionable and unknown
</ul>
<li>Favorites
<ul>
<li>favorites (=, &lt;, &gt;, &lt;=, &gt;=) number -- search for images favourited a certain number of times
<li>favourited_by=Username -- search for a user's choices by username
<li>favorited_by_userno=UserID -- search for a user's choice by userID
</ul>
<li>Notes
<ul>
<li>notes (=, &lt;, &gt;, &lt;=, &gt;=) number -- search by the number of notes an image has
<li>notes_by=Username -- search for images containing notes created by username
<li>notes_by_userno=UserID -- search for images containing notes created by userID
</ul>
<li>Artists
<ul>
<li>author=ArtistName -- search for images by artist
</ul>
<li>Image Comments
<ul>
<li>comments (=, &lt;, &gt;, &lt;=, &gt;=) number -- search for images by number of comments
<li>commented_by=Username -- search for images containing user's comments by username
<li>commented_by_userno=UserID -- search for images containing user's comments by userID
</ul>
<li>Pools
<ul>
<li>pool=(PoolID, any, none) -- search for images in a pool by PoolID.
<li>pool_by_name=PoolName -- search for images in a pool by PoolName. underscores are replaced with spaces
</ul>
<li>Post Relationships
<ul>
<li>parent=(parentID, any, none) -- search for images by parentID / if they have, do not have a parent
<li>child=(any, none) -- search for images which have, or do not have children
</ul>
</ul>
";
}

View File

@ -1,68 +1,7 @@
<?php
/*
* SearchTermParseEvent:
* Signal that a search term needs parsing
*/
class SearchTermParseEvent extends Event
{
/** @var null|string */
public $term = null;
/** @var string[] */
public $context = [];
/** @var Querylet[] */
public $querylets = [];
public function __construct(string $term=null, array $context=[])
{
$this->term = $term;
$this->context = $context;
}
public function is_querylet_set(): bool
{
return (count($this->querylets) > 0);
}
public function get_querylets(): array
{
return $this->querylets;
}
public function add_querylet(Querylet $q)
{
$this->querylets[] = $q;
}
}
class SearchTermParseException extends SCoreException
{
}
class PostListBuildingEvent extends Event
{
/** @var array */
public $search_terms = [];
/** @var array */
public $parts = [];
/**
* #param string[] $search
*/
public function __construct(array $search)
{
$this->search_terms = $search;
}
public function add_control(string $html, int $position=50)
{
while (isset($this->parts[$position])) {
$position++;
}
$this->parts[$position] = $html;
}
}
require_once "config.php";
require_once "events.php";
class Index extends Extension
{
@ -72,9 +11,9 @@ class Index extends Extension
public function onInitExt(InitExtEvent $event)
{
global $config;
$config->set_default_int("index_images", 24);
$config->set_default_bool("index_tips", true);
$config->set_default_string("index_order", "id DESC");
$config->set_default_int(IndexConfig::IMAGES, 24);
$config->set_default_bool(IndexConfig::TIPS, true);
$config->set_default_string(IndexConfig::ORDER, "id DESC");
}
public function onPageRequest(PageRequestEvent $event)
@ -167,7 +106,7 @@ class Index extends Extension
$sb->position = 20;
$sb->add_label("Show ");
$sb->add_int_option("index_images");
$sb->add_int_option(IndexConfig::IMAGES);
$sb->add_label(" images on the post list");
$event->panel->add_block($sb);

View File

@ -1,15 +1,5 @@
<?php
/*
* Name: IP Ban
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Ban IP addresses
* Documentation:
*
*/
class IPBanInfo extends ExtensionInfo
{
public const KEY = "ipban";

View File

@ -1,6 +1,5 @@
<?php
// RemoveIPBanEvent {{{
class RemoveIPBanEvent extends Event
{
public $id;
@ -10,8 +9,7 @@ class RemoveIPBanEvent extends Event
$this->id = $id;
}
}
// }}}
// AddIPBanEvent {{{
class AddIPBanEvent extends Event
{
public $ip;
@ -25,7 +23,6 @@ class AddIPBanEvent extends Event
$this->end = trim($end);
}
}
// }}}
class IPBan extends Extension
{
@ -131,7 +128,6 @@ class IPBan extends Extension
}
}
// installer {{{
protected function install()
{
global $database;
@ -209,8 +205,7 @@ class IPBan extends Extension
$config->set_int("ext_ipban_version", 8);
}
}
// }}}
// deal with banned person {{{
private function check_ip_ban()
{
$remote = $_SERVER['REMOTE_ADDR'];
@ -266,8 +261,7 @@ class IPBan extends Extension
log_error("ipban", "block($remote) called but no bans matched");
exit;
}
// }}}
// database {{{
private function get_bans()
{
global $database;
@ -328,5 +322,4 @@ class IPBan extends Extension
$cache->set("ip_bans_sorted", $sorted, 600);
return $sorted;
}
// }}}
}

View File

@ -1,11 +1,5 @@
<?php
/*
* Name: Link to Image
* Author: Artanis <artanis.00@gmail.com>
* Description: Show various forms of link to each image, for copy & paste
*/
class LinkImageInfo extends ExtensionInfo
{
public const KEY = "link_image";
@ -14,4 +8,27 @@ class LinkImageInfo extends ExtensionInfo
public $name = "Link to Image";
public $authors = ["Artanis"=>"artanis.00@gmail.com"];
public $description = "Show various forms of link to each image, for copy & paste";
public $license = self::LICENSE_GPLV2;
public $documentation = "There is one option in Board Config: Text Link Format.
It takes the following arguments as well as plain text.
<pre>
|| arguments || replacement ||
|| \$id || The image ID. ||
|| \$hash || The MD5 hash of the image. ||
|| \$tags || The image's tag list. ||
|| \$base || The base HREF as set in Config. ||
|| \$ext || The image's extension. ||
|| \$size || The image's display size. ||
|| \$filesize || The image's size in KB. ||
|| \$filename || The image's original filename. ||
|| \$title || The site title as set in Config. ||
</pre>
<p>Link to Image will default this option to '\$title - \$id (\$ext \$size \$filesize)'.
<p>To reset to the default, simply clear the current setting. Link to Image
will then fill in the default value after the save.
<p>To leave the setting blank for any reason, leave a space (' ') in it.";
}

View File

@ -1,82 +0,0 @@
Link to Image adds BBCode and HTML link codes to the image view. Offers code for a customizable text link, thumbnail links, and full image inline.
Author: Erik Youngren <artanis.00@gmail.com>
License: GPLv2
Submit a Bug Report or Suggestion for Link to Image:
* http://trac.shishnet.org/shimmie2/newticket?owner=artanis.00@gmail.com&component=third%20party%20extensions&keywords=link_to_image
= Use =
There is one option in Board Config: Text Link Format.
It takes the following arguments as well as plain text.
|| arguments || replacement ||
|| $id || The image ID. ||
|| $hash || The MD5 hash of the image. ||
|| $tags || The image's tag list. ||
|| $base || The base HREF as set in Config. ||
|| $ext || The image's extension. ||
|| $size || The image's display size. ||
|| $filesize || The image's size in KB. ||
|| $filename || The image's original filename. ||
|| $title || The site title as set in Config. ||
Link to Image will default this option to '$title - $id ($ext $size $filesize)'.
To reset to the default, simply clear the current setting. Link to Image will then fill in the default value after the save.
To leave the setting blank for any reason, leave a space (' ') in it.
= Install =
1. Copy the folder {{{contrib/link_image/}}} to {{{ext/}}}.
2. In the Config panel, make sure Base URL is set (you may as well set Data URL while you're there, if you haven't already.)
3. Make sure Image Link, Thumb Link, and Short Link all contain the full path ("http://" and onward,) either by using $base or plain text. Link to Image will not be able to retrieve the correct paths without these variables.
= Change Log =
== Version 0.3.0 ==
* Moved Link to Image over to the official theme engine. This functions basically the same as what the prototype was, but it's more thought out and nicer.
* Cleaned up the insides a bit.
== Version 0.2.0 ==
* Changed the HTML generation to use a prototype theme engine. All HTML generation is now contained within {{{link_image.html.php}}}, which may be copied to the current theme folder and edited from there.
== Version 0.1.4 - 20070510 ==
* Style changes.
* Added output containing only the locations of the thumb, image and post.
* Added a link to wikipedia's HTML page, just as BBCode has a wikipedia link.
== Version 0.1.3b - 20070509 ==
* Renamed style.css to _style.css to avoid the auto loader.
== Version 0.1.3 - 20070508 ==
* Created Readme.txt
* Merged 0.1.2 into 0.1.2b
* Removed uneeded documentation from main.php
* Rewrote the css to be unique. Previously used CSS I wrote for elsewhere. Styled to reduce space consumption.
* Added code to insert the CSS import.
* Updated Nice URLs to allow access to the /ext/ folder. (Why is my stylesheet returning HTML instead of CSS?)
* First SVN update.
== Version 0.1.2b - 20070507 ==
(fairly simultaneous with 0.1.2)
* shish:
* Updated to new extension format
* Created folder link_image in trunk/contrib
* Renamed link_image.ext.php to main.php and moved to /link_image/
* Created style.css {{{ /* 404'd :|*/ }}}.
* Documentation (different from mine.)
* Changed add_text_option() and added add_label() in SetupBuildingEvent because I was using an edited version of the function that shish didn't know about. It was a wonder that didn't throw massive errors.
* Published on SVN.
== Version 0.1.2 - 20070506 ==
* Textboxes now select-all when they gain focus.
* Commenting and documentation.
== Version 0.1.1 - 20070506 ==
* Fixed HTML thumbnail link code. (image tag was being html_escaped twice, resulting in "$gt;" and "&lt;" from the first escape becoming "&amp;gt;" and "&amp;lt;") It turns out that html_escape was completely unnecessary, all I had to do was replace the single-quotes around the attributes with escaped double-quotes ('\"'.)
== Version 0.1.0 - 20070506 ==
* Release.
= Links =
* http://trac.shishnet.org/shimmie2/wiki/Contrib/Extensions/LinkToImage - Home
* http://forum.shishnet.org/viewtopic.php?p=153 - Discussion
* http://trac.shishnet.org/shimmie2/browser/trunk/contrib/link_image - Shimmie2 Trac SVN

View File

@ -1,14 +1,5 @@
<?php
/*
* Name: Live Feed
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Visibility: admin
* Description: Logs user-safe (no IPs) data to a UDP socket, eg IRCCat
* Documentation:
*/
class LiveFeedInfo extends ExtensionInfo
{
public const KEY = "livefeed";

View File

@ -1,13 +1,5 @@
<?php
/*
* Name: Logging (Database)
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* Description: Keep a record of SCore events (in the database).
* Visibility: admin
*/
class LogDatabaseInfo extends ExtensionInfo
{
public const KEY = "log_db";

View File

@ -57,21 +57,21 @@ class LogDatabase extends Extension
$args["time_end"] = $_GET["time-end"];
}
if (!empty($_GET["module"])) {
$wheres[] = "section = :module";
$wheres[] = $database->scoreql_to_sql("SCORE_STRNORM(section) = SCORE_STRNORM(:module)");
$args["module"] = $_GET["module"];
}
if (!empty($_GET["user"])) {
if ($database->get_driver_name() == DatabaseDriver::PGSQL) {
if (preg_match("#\d+\.\d+\.\d+\.\d+(/\d+)?#", $_GET["user"])) {
$wheres[] = "(username = :user1 OR text(address) = :user2)";
$wheres[] = $database->scoreql_to_sql("(SCORE_STRNORM(username) = SCORE_STRNORM(:user1) OR SCORE_STRNORM(text(address)) = SCORE_STRNORM(:user2))");
$args["user1"] = $_GET["user"];
$args["user2"] = $_GET["user"] . "/32";
} else {
$wheres[] = "lower(username) = lower(:user)";
$wheres[] = $database->scoreql_to_sql("SCORE_STRNORM(username) = SCORE_STRNORM(:user)");
$args["user"] = $_GET["user"];
}
} else {
$wheres[] = "(username = :user1 OR address = :user2)";
$wheres[] = $database->scoreql_to_sql("(SCORE_STRNORM(username) = SCORE_STRNORM(:user1) OR SCORE_STRNORM(address) = SCORE_STRNORM(:user2))");
$args["user1"] = $_GET["user"];
$args["user2"] = $_GET["user"];
}

View File

@ -31,7 +31,7 @@ class LogDatabaseTheme extends Themelet
<table class='zebra'>
<thead>
<tr><th>Time</th><th>Module</th><th>User</th><th colspan='3'>Message</th></tr>
<form action='".make_link("log/view")."' method='GET'>
".make_form("log/view", "GET")."
<tr class='sizedinputs'>
<td><input type='date' name='time-start' value='".$this->heie("time-start")."'>
<br><input type='date' name='time-end' value='".$this->heie("time-end")."'></td>
@ -40,11 +40,11 @@ class LogDatabaseTheme extends Themelet
<td><input type='text' name='message' value='".$this->heie("message")."'></td>
<td>
<select name='priority'>
<option value='".SCORE_LOG_DEBUG."'>Debug</option>
<option value='".SCORE_LOG_INFO."' selected>Info</option>
<option value='".SCORE_LOG_WARNING."'>Warning</option>
<option value='".SCORE_LOG_ERROR."'>Error</option>
<option value='".SCORE_LOG_CRITICAL."'>Critical</option>
<option value='".SCORE_LOG_DEBUG."' ".($this->heie("priority")==SCORE_LOG_DEBUG ? "selected" : "").">Debug</option>
<option value='".SCORE_LOG_INFO."' ".($this->heie("priority")==SCORE_LOG_INFO ? "selected" : "").">Info</option>
<option value='".SCORE_LOG_WARNING."' ".($this->heie("priority")==SCORE_LOG_WARNING ? "selected" : "").">Warning</option>
<option value='".SCORE_LOG_ERROR."' ".($this->heie("priority")==SCORE_LOG_ERROR ? "selected" : "").">Error</option>
<option value='".SCORE_LOG_CRITICAL."' ".($this->heie("priority")==SCORE_LOG_CRITICAL ? "selected" : "").">Critical</option>
</select>
</td>
<td><input type='submit' value='Search'></td>

View File

@ -1,13 +1,5 @@
<?php
/*
* Name: Logging (Logstash)
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* Description: Send log events to a network port.
* Visibility: admin
*/
class LogLogstashInfo extends ExtensionInfo
{
public const KEY = "log_logstash";

View File

@ -1,13 +1,5 @@
<?php
/*
* Name: Logging (Network)
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* Description: Send log events to a network port.
* Visibility: admin
*/
class LogNetInfo extends ExtensionInfo
{
public const KEY = "log_net";

View File

@ -1,13 +1,5 @@
<?php
/**
* Name: Mail System
* Author: Zach Hall <zach@sosguy.net>
* Link: http://seemslegit.com
* License: GPLv2
* Description: Provides an interface for sending and receiving mail.
*/
class MailInfo extends ExtensionInfo
{
public const KEY = "mail";

9
ext/media/config.php Normal file
View File

@ -0,0 +1,9 @@
<?php
abstract class MediaConfig
{
const FFMPEG_PATH = "media_ffmpeg_path";
const FFPROBE_PATH = "media_ffprobe_path";
const CONVERT_PATH = "media_convert_path";
const VERSION = "ext_media_version";
const MEM_LIMIT = 'media_mem_limit';
}

62
ext/media/events.php Normal file
View File

@ -0,0 +1,62 @@
<?php
class MediaResizeEvent extends Event
{
public $engine;
public $input_path;
public $input_type;
public $output_path;
public $target_format;
public $target_width;
public $target_height;
public $target_quality;
public $minimize;
public $ignore_aspect_ratio;
public $allow_upscale;
public function __construct(
String $engine,
string $input_path,
string $input_type,
string $output_path,
int $target_width,
int $target_height,
bool $ignore_aspect_ratio = false,
string $target_format = null,
int $target_quality = 80,
bool $minimize = false,
bool $allow_upscale = true
) {
assert(in_array($engine, MediaEngine::ALL));
$this->engine = $engine;
$this->input_path = $input_path;
$this->input_type = $input_type;
$this->output_path = $output_path;
$this->target_height = $target_height;
$this->target_width = $target_width;
$this->target_format = $target_format;
$this->target_quality = $target_quality;
$this->minimize = $minimize;
$this->ignore_aspect_ratio = $ignore_aspect_ratio;
$this->allow_upscale = $allow_upscale;
}
}
class MediaCheckPropertiesEvent extends Event
{
public $file_name;
public $ext;
public $lossless = null;
public $audio = null;
public $video = null;
public $image = null;
public $length = null;
public $height = null;
public $width = null;
public function __construct(string $file_name, string $ext)
{
$this->file_name = $file_name;
$this->ext = $ext;
}
}

View File

@ -1,11 +1,5 @@
<?php
/*
* Name: Media
* Author: Matthew Barbour <matthew@darkholme.net>
* Description: Provides common functions and settings used for media operations.
*/
class MediaInfo extends ExtensionInfo
{
public const KEY = "media";

View File

@ -1,149 +1,16 @@
<?php
require_once "config.php";
require_once "events.php";
require_once "media_engine.php";
/*
* This is used by the media code when there is an error
*/
abstract class MediaConfig
{
const FFMPEG_PATH = "media_ffmpeg_path";
const FFPROBE_PATH = "media_ffprobe_path";
const CONVERT_PATH = "media_convert_path";
const VERSION = "ext_media_version";
const MEM_LIMIT = 'media_mem_limit';
}
abstract class MediaEngine
{
public const GD = "gd";
public const IMAGICK = "convert";
public const FFMPEG = "ffmpeg";
public const ALL = [
MediaEngine::GD,
MediaEngine::FFMPEG,
MediaEngine::IMAGICK
];
public const OUTPUT_SUPPORT = [
MediaEngine::GD => [
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
],
MediaEngine::IMAGICK => [
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS,
],
MediaEngine::FFMPEG => [
"jpg",
"webp",
"png"
]
];
public const INPUT_SUPPORT = [
MediaEngine::GD => [
"bmp",
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS
],
MediaEngine::IMAGICK => [
"bmp",
"gif",
"jpg",
"png",
"psd",
"tiff",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS,
"ico",
],
MediaEngine::FFMPEG => [
"avi",
"mkv",
"webm",
"mp4",
"mov",
"flv"
]
];
}
class MediaException extends SCoreException
{
}
class MediaResizeEvent extends Event
{
public $engine;
public $input_path;
public $input_type;
public $output_path;
public $target_format;
public $target_width;
public $target_height;
public $target_quality;
public $minimize;
public $ignore_aspect_ratio;
public $allow_upscale;
public function __construct(
String $engine,
string $input_path,
string $input_type,
string $output_path,
int $target_width,
int $target_height,
bool $ignore_aspect_ratio = false,
string $target_format = null,
int $target_quality = 80,
bool $minimize = false,
bool $allow_upscale = true
) {
assert(in_array($engine, MediaEngine::ALL));
$this->engine = $engine;
$this->input_path = $input_path;
$this->input_type = $input_type;
$this->output_path = $output_path;
$this->target_height = $target_height;
$this->target_width = $target_width;
$this->target_format = $target_format;
$this->target_quality = $target_quality;
$this->minimize = $minimize;
$this->ignore_aspect_ratio = $ignore_aspect_ratio;
$this->allow_upscale = $allow_upscale;
}
}
class MediaCheckPropertiesEvent extends Event
{
public $file_name;
public $ext;
public $lossless = null;
public $audio = null;
public $video = null;
public $length = null;
public $height = null;
public $width = null;
public function __construct(string $file_name, string $ext)
{
$this->file_name = $file_name;
$this->ext = $ext;
}
}
class Media extends Extension
{
const WEBP_LOSSY = "webp-lossy";
@ -211,41 +78,8 @@ class Media extends Extension
$config->set_default_string(MediaConfig::CONVERT_PATH, 'convert');
if ($config->get_int(MediaConfig::VERSION) < 1) {
$current_value = $config->get_string("thumb_ffmpeg_path");
if (!empty($current_value)) {
$config->set_string(MediaConfig::FFMPEG_PATH, $current_value);
} elseif ($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(MediaConfig::FFMPEG_PATH, 'ffmpeg');
}
}
if ($ffprobe = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffprobe')) {
//ffprobe exists in PATH, check if it's executable, and if so, default to it instead of static
if (is_executable(strtok($ffprobe, PHP_EOL))) {
$config->set_default_string(MediaConfig::FFPROBE_PATH, 'ffprobe');
}
}
$current_value = $config->get_string("thumb_convert_path");
if (!empty($current_value)) {
$config->set_string(MediaConfig::CONVERT_PATH, $current_value);
} elseif ($convert = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' convert')) {
//ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static
if (is_executable(strtok($convert, PHP_EOL))) {
$config->set_default_string(MediaConfig::CONVERT_PATH, 'convert');
}
}
$current_value = $config->get_int("thumb_mem_limit");
if (!empty($current_value)) {
$config->set_int(MediaConfig::MEM_LIMIT, $current_value);
}
$config->set_int(MediaConfig::VERSION, 1);
log_info("media", "extension installed");
if ($config->get_int(MediaConfig::VERSION) < 2) {
$this->setup();
}
}
@ -421,7 +255,7 @@ class Media extends Extension
}
const CONTENT_SEARCH_TERM_REGEX = "/^content[=|:]((video)|(audio))$/i";
const CONTENT_SEARCH_TERM_REGEX = "/^content[=|:]((video)|(audio)|(image)|(unknown))$/i";
public function onSearchTermParse(SearchTermParseEvent $event)
@ -430,8 +264,12 @@ class Media extends Extension
$matches = [];
if (preg_match(self::CONTENT_SEARCH_TERM_REGEX, $event->term, $matches)) {
$field = $matches[2];
$event->add_querylet(new Querylet($database->scoreql_to_sql("$field = SCORE_BOOL_Y")));
$field = $matches[1];
if ($field==="unknown") {
$event->add_querylet(new Querylet($database->scoreql_to_sql("video IS NULL OR audio IS NULL OR image IS NULL")));
} else {
$event->add_querylet(new Querylet($database->scoreql_to_sql("$field = SCORE_BOOL_Y")));
}
}
}
@ -485,7 +323,7 @@ class Media extends Extension
$database->execute(
"UPDATE images SET
lossless = :lossless, video = :video, audio = :audio,
lossless = :lossless, video = :video, audio = :audio,image = :image,
height = :height, width = :width,
length = :length WHERE hash = :hash",
[
@ -494,6 +332,7 @@ class Media extends Extension
"height" => $mcpe->height ?? 0,
"lossless" => $database->scoresql_value_prepare($mcpe->lossless),
"video" => $database->scoresql_value_prepare($mcpe->video),
"image" => $database->scoresql_value_prepare($mcpe->image),
"audio" => $database->scoresql_value_prepare($mcpe->audio),
"length" => $mcpe->length
]
@ -975,7 +814,7 @@ class Media extends Extension
$new_height,
$width,
$height
) === false) {
) === false) {
throw new MediaException("Unable to copy resized image data to new image");
}
@ -1154,4 +993,75 @@ class Media extends Extension
log_debug('Media', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]");
return $size;
}
private function setup()
{
global $config, $database;
if ($config->get_int(MediaConfig::VERSION) < 1) {
$current_value = $config->get_string("thumb_ffmpeg_path");
if (!empty($current_value)) {
$config->set_string(MediaConfig::FFMPEG_PATH, $current_value);
} elseif ($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(MediaConfig::FFMPEG_PATH, 'ffmpeg');
}
}
if ($ffprobe = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffprobe')) {
//ffprobe exists in PATH, check if it's executable, and if so, default to it instead of static
if (is_executable(strtok($ffprobe, PHP_EOL))) {
$config->set_default_string(MediaConfig::FFPROBE_PATH, 'ffprobe');
}
}
$current_value = $config->get_string("thumb_convert_path");
if (!empty($current_value)) {
$config->set_string(MediaConfig::CONVERT_PATH, $current_value);
} elseif ($convert = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' convert')) {
//ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static
if (is_executable(strtok($convert, PHP_EOL))) {
$config->set_default_string(MediaConfig::CONVERT_PATH, 'convert');
}
}
$current_value = $config->get_int("thumb_mem_limit");
if (!empty($current_value)) {
$config->set_int(MediaConfig::MEM_LIMIT, $current_value);
}
$config->set_int(MediaConfig::VERSION, 1);
log_info("media", "extension installed");
}
if ($config->get_int(MediaConfig::VERSION) < 2) {
$database->execute($database->scoreql_to_sql(
"ALTER TABLE images ADD COLUMN image SCORE_BOOL NULL"
));
switch ($database->get_driver_name()) {
case DatabaseDriver::PGSQL:
case DatabaseDriver::SQLITE:
$database->execute('CREATE INDEX images_image_idx ON images(image) WHERE image IS NOT NULL');
break;
default:
$database->execute('CREATE INDEX images_image_idx ON images(image)');
break;
}
$database->set_timeout(300000); // These updates can take a little bit
if ($database->transaction === true) {
$database->commit(); // Each of these commands could hit a lot of data, combining them into one big transaction would not be a good idea.
}
log_info("upgrade", "Setting predictable media values for known file types");
$database->execute($database->scoreql_to_sql("UPDATE images SET image = SCORE_BOOL_N WHERE ext IN ('swf','mp3','ani','flv','mp4','m4v','ogv','webm')"));
$database->execute($database->scoreql_to_sql("UPDATE images SET image = SCORE_BOOL_Y WHERE ext IN ('jpg','jpeg''ico','cur','png')"));
$config->set_int(MediaConfig::VERSION, 2);
log_info("media", "extension at version 2");
$database->beginTransaction();
}
}
}

View File

@ -0,0 +1,67 @@
<?php
abstract class MediaEngine
{
public const GD = "gd";
public const IMAGICK = "convert";
public const FFMPEG = "ffmpeg";
public const ALL = [
MediaEngine::GD,
MediaEngine::FFMPEG,
MediaEngine::IMAGICK
];
public const OUTPUT_SUPPORT = [
MediaEngine::GD => [
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
],
MediaEngine::IMAGICK => [
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS,
],
MediaEngine::FFMPEG => [
"jpg",
"webp",
"png"
]
];
public const INPUT_SUPPORT = [
MediaEngine::GD => [
"bmp",
"gif",
"jpg",
"png",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS
],
MediaEngine::IMAGICK => [
"bmp",
"gif",
"jpg",
"png",
"psd",
"tiff",
"webp",
Media::WEBP_LOSSY,
Media::WEBP_LOSSLESS,
"ico",
],
MediaEngine::FFMPEG => [
"avi",
"mkv",
"webm",
"mp4",
"mov",
"flv"
]
];
}

View File

@ -1,12 +1,5 @@
<?php
/*
* Name: Not A Tag
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Redirect users to the rules if they use bad tags
*/
class NotATagInfo extends ExtensionInfo
{
public const KEY = "not_a_tag";

View File

@ -89,7 +89,7 @@ class NotATag extends Extension
$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']]);
$database->Execute($database->scoreql_to_sql("DELETE FROM untags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(?)"), [$_POST['tag']]);
flash_message("Image ban removed");
$page->set_mode(PageMode::REDIRECT);

Some files were not shown because too many files have changed in this diff Show More