PSR-2. I'm not a huge fan, but ugly consistency beats no consistency...

This commit is contained in:
Shish
2019-05-28 17:59:38 +01:00
parent 5ec3e89884
commit 34b05cca7c
295 changed files with 27094 additions and 24632 deletions

View File

@ -16,6 +16,6 @@ insert_final_newline = true
[*.{js,css,php}] [*.{js,css,php}]
charset = utf-8 charset = utf-8
indent_style = tab indent_style = space
indent_size = 4 indent_size = 4

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ images
thumbs thumbs
*.phar *.phar
*.sqlite *.sqlite
.php_cs.cache
#Composer #Composer
composer.phar composer.phar

18
.php_cs.dist Normal file
View File

@ -0,0 +1,18 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('ext/amazon_s3/lib')
->exclude('vendor')
->in(__DIR__)
;
return PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
//'strict_param' => true,
'array_syntax' => ['syntax' => 'short'],
])
->setFinder($finder)
;
?>

View File

@ -19,14 +19,14 @@ _sanitise_environment();
// load base files // load base files
$_shm_ctx->log_start("Opening files"); $_shm_ctx->log_start("Opening files");
$_shm_files = array_merge( $_shm_files = array_merge(
zglob("core/*.php"), zglob("core/*.php"),
zglob("core/{".ENABLED_MODS."}/*.php"), zglob("core/{".ENABLED_MODS."}/*.php"),
zglob("ext/{".ENABLED_EXTS."}/main.php") zglob("ext/{".ENABLED_EXTS."}/main.php")
); );
foreach($_shm_files as $_shm_filename) { foreach ($_shm_files as $_shm_filename) {
if(basename($_shm_filename)[0] != "_") { if (basename($_shm_filename)[0] != "_") {
require_once $_shm_filename; require_once $_shm_filename;
} }
} }
unset($_shm_files); unset($_shm_files);
unset($_shm_filename); unset($_shm_filename);
@ -40,8 +40,8 @@ $_shm_ctx->log_endok();
// load the theme parts // load the theme parts
$_shm_ctx->log_start("Loading themelets"); $_shm_ctx->log_start("Loading themelets");
foreach(_get_themelet_files(get_theme()) as $themelet) { foreach (_get_themelet_files(get_theme()) as $themelet) {
require_once $themelet; require_once $themelet;
} }
unset($themelet); unset($themelet);
$page = class_exists("CustomPage") ? new CustomPage() : new Page(); $page = class_exists("CustomPage") ? new CustomPage() : new Page();

View File

@ -7,7 +7,7 @@
* @author Shish [webmaster at shishnet.org], jgen [jeffgenovy at gmail.com] * @author Shish [webmaster at shishnet.org], jgen [jeffgenovy at gmail.com]
* @link http://code.shishnet.org/shimmie2/ * @link http://code.shishnet.org/shimmie2/
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
* *
* Initialise the database, check that folder * Initialise the database, check that folder
* permissions are set properly. * permissions are set properly.
* *
@ -30,7 +30,7 @@ date_default_timezone_set('UTC');
<script type="text/javascript" src="vendor/bower-asset/jquery/dist/jquery.min.js"></script> <script type="text/javascript" src="vendor/bower-asset/jquery/dist/jquery.min.js"></script>
</head> </head>
<body> <body>
<?php if(FALSE) { ?> <?php if (false) { ?>
<div id="installer"> <div id="installer">
<h1>Install Error</h1> <h1>Install Error</h1>
<div class="container"> <div class="container">
@ -43,7 +43,7 @@ date_default_timezone_set('UTC');
</div> </div>
</div> </div>
<pre style="display:none"> <pre style="display:none">
<?php } elseif(!file_exists("vendor/")) { ?> <?php } elseif (!file_exists("vendor/")) { ?>
<div id="installer"> <div id="installer">
<h1>Install Error</h1> <h1>Install Error</h1>
<h3>Warning: Composer vendor folder does not exist!</h3> <h3>Warning: Composer vendor folder does not exist!</h3>
@ -65,112 +65,114 @@ require_once "core/cacheengine.php";
require_once "core/dbengine.php"; require_once "core/dbengine.php";
require_once "core/database.php"; require_once "core/database.php";
if(is_readable("data/config/shimmie.conf.php")) die("Shimmie is already installed."); if (is_readable("data/config/shimmie.conf.php")) {
die("Shimmie is already installed.");
}
do_install(); do_install();
// utilities {{{ // 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 { function check_gd_version(): int
$gdversion = 0; {
$gdversion = 0;
if (function_exists('gd_info')){ if (function_exists('gd_info')) {
$gd_info = gd_info(); $gd_info = gd_info();
if (substr_count($gd_info['GD Version'], '2.')) { if (substr_count($gd_info['GD Version'], '2.')) {
$gdversion = 2; $gdversion = 2;
} else if (substr_count($gd_info['GD Version'], '1.')) { } elseif (substr_count($gd_info['GD Version'], '1.')) {
$gdversion = 1; $gdversion = 1;
} }
} }
return $gdversion; return $gdversion;
} }
function check_im_version(): int { function check_im_version(): int
$convert_check = exec("convert"); {
$convert_check = exec("convert");
return (empty($convert_check) ? 0 : 1); return (empty($convert_check) ? 0 : 1);
} }
function eok($name, $value) { function eok($name, $value)
echo "<br>$name ... "; {
if($value) { echo "<br>$name ... ";
echo "<span style='color: green'>ok</span>\n"; if ($value) {
} echo "<span style='color: green'>ok</span>\n";
else { } else {
echo "<span style='color: green'>failed</span>\n"; echo "<span style='color: green'>failed</span>\n";
} }
} }
// }}} // }}}
function do_install() { // {{{ function do_install()
if(file_exists("data/config/auto_install.conf.php")) { { // {{{
require_once "data/config/auto_install.conf.php"; if (file_exists("data/config/auto_install.conf.php")) {
} require_once "data/config/auto_install.conf.php";
else if(@$_POST["database_type"] == "sqlite") { } elseif (@$_POST["database_type"] == "sqlite") {
$id = bin2hex(random_bytes(5)); $id = bin2hex(random_bytes(5));
define('DATABASE_DSN', "sqlite:data/shimmie.{$id}.sqlite"); define('DATABASE_DSN', "sqlite:data/shimmie.{$id}.sqlite");
} } elseif (isset($_POST['database_type']) && isset($_POST['database_host']) && isset($_POST['database_user']) && isset($_POST['database_name'])) {
else if(isset($_POST['database_type']) && isset($_POST['database_host']) && isset($_POST['database_user']) && isset($_POST['database_name'])) { define('DATABASE_DSN', "{$_POST['database_type']}:user={$_POST['database_user']};password={$_POST['database_password']};host={$_POST['database_host']};dbname={$_POST['database_name']}");
define('DATABASE_DSN', "{$_POST['database_type']}:user={$_POST['database_user']};password={$_POST['database_password']};host={$_POST['database_host']};dbname={$_POST['database_name']}"); } else {
} ask_questions();
else { return;
ask_questions(); }
return;
}
define("CACHE_DSN", null); define("CACHE_DSN", null);
define("DEBUG_SQL", false); define("DEBUG_SQL", false);
define("DATABASE_KA", true); define("DATABASE_KA", true);
install_process(); install_process();
} // }}} } // }}}
function ask_questions() { // {{{ function ask_questions()
$warnings = array(); { // {{{
$errors = array(); $warnings = [];
$errors = [];
if(check_gd_version() == 0 && check_im_version() == 0) { if (check_gd_version() == 0 && check_im_version() == 0) {
$errors[] = " $errors[] = "
No thumbnailers could be found - install the imagemagick No thumbnailers could be found - install the imagemagick
tools (or the PHP-GD library, if imagemagick is unavailable). tools (or the PHP-GD library, if imagemagick is unavailable).
"; ";
} } elseif (check_im_version() == 0) {
else if(check_im_version() == 0) { $warnings[] = "
$warnings[] = "
The 'convert' command (from the imagemagick package) The 'convert' command (from the imagemagick package)
could not be found - PHP-GD can be used instead, but could not be found - PHP-GD can be used instead, but
the size of thumbnails will be limited. the size of thumbnails will be limited.
"; ";
} }
if(!function_exists('mb_strlen')) { if (!function_exists('mb_strlen')) {
$errors[] = " $errors[] = "
The mbstring PHP extension is missing - multibyte languages The mbstring PHP extension is missing - multibyte languages
(eg non-english languages) may not work right. (eg non-english languages) may not work right.
"; ";
} }
$drivers = PDO::getAvailableDrivers(); $drivers = PDO::getAvailableDrivers();
if( if (
!in_array("mysql", $drivers) && !in_array("mysql", $drivers) &&
!in_array("pgsql", $drivers) && !in_array("pgsql", $drivers) &&
!in_array("sqlite", $drivers) !in_array("sqlite", $drivers)
) { ) {
$errors[] = " $errors[] = "
No database connection library could be found; shimmie needs No database connection library could be found; shimmie needs
PDO with either Postgres, MySQL, or SQLite drivers PDO with either Postgres, MySQL, or SQLite drivers
"; ";
} }
$db_m = in_array("mysql", $drivers) ? '<option value="mysql">MySQL</option>' : ""; $db_m = in_array("mysql", $drivers) ? '<option value="mysql">MySQL</option>' : "";
$db_p = in_array("pgsql", $drivers) ? '<option value="pgsql">PostgreSQL</option>' : ""; $db_p = in_array("pgsql", $drivers) ? '<option value="pgsql">PostgreSQL</option>' : "";
$db_s = in_array("sqlite", $drivers) ? '<option value="sqlite">SQLite</option>' : ""; $db_s = in_array("sqlite", $drivers) ? '<option value="sqlite">SQLite</option>' : "";
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : ""; $warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : ""; $err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
print <<<EOD print <<<EOD
<div id="installer"> <div id="installer">
<h1>Shimmie Installer</h1> <h1>Shimmie Installer</h1>
@ -243,19 +245,21 @@ EOD;
/** /**
* This is where the install really takes place. * This is where the install really takes place.
*/ */
function install_process() { // {{{ function install_process()
build_dirs(); { // {{{
create_tables(); build_dirs();
insert_defaults(); create_tables();
write_config(); insert_defaults();
write_config();
} // }}} } // }}}
function create_tables() { // {{{ function create_tables()
try { { // {{{
$db = new Database(); try {
$db = new Database();
if ( $db->count_tables() > 0 ) { if ($db->count_tables() > 0) {
print <<<EOD print <<<EOD
<div id="installer"> <div id="installer">
<h1>Shimmie Installer</h1> <h1>Shimmie Installer</h1>
<h3>Warning: The Database schema is not empty!</h3> <h3>Warning: The Database schema is not empty!</h3>
@ -266,22 +270,22 @@ function create_tables() { // {{{
</div> </div>
</div> </div>
EOD; EOD;
exit(2); exit(2);
} }
$db->create_table("aliases", " $db->create_table("aliases", "
oldtag VARCHAR(128) NOT NULL, oldtag VARCHAR(128) NOT NULL,
newtag VARCHAR(128) NOT NULL, newtag VARCHAR(128) NOT NULL,
PRIMARY KEY (oldtag) PRIMARY KEY (oldtag)
"); ");
$db->execute("CREATE INDEX aliases_newtag_idx ON aliases(newtag)", array()); $db->execute("CREATE INDEX aliases_newtag_idx ON aliases(newtag)", []);
$db->create_table("config", " $db->create_table("config", "
name VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL,
value TEXT, value TEXT,
PRIMARY KEY (name) PRIMARY KEY (name)
"); ");
$db->create_table("users", " $db->create_table("users", "
id SCORE_AIPK, id SCORE_AIPK,
name VARCHAR(32) UNIQUE NOT NULL, name VARCHAR(32) UNIQUE NOT NULL,
pass VARCHAR(250), pass VARCHAR(250),
@ -289,9 +293,9 @@ EOD;
class VARCHAR(32) NOT NULL DEFAULT 'user', class VARCHAR(32) NOT NULL DEFAULT 'user',
email VARCHAR(128) email VARCHAR(128)
"); ");
$db->execute("CREATE INDEX users_name_idx ON users(name)", array()); $db->execute("CREATE INDEX users_name_idx ON users(name)", []);
$db->create_table("images", " $db->create_table("images", "
id SCORE_AIPK, id SCORE_AIPK,
owner_id INTEGER NOT NULL, owner_id INTEGER NOT NULL,
owner_ip SCORE_INET NOT NULL, owner_ip SCORE_INET NOT NULL,
@ -306,69 +310,72 @@ EOD;
locked SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N, locked SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N,
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT
"); ");
$db->execute("CREATE INDEX images_owner_id_idx ON images(owner_id)", array()); $db->execute("CREATE INDEX images_owner_id_idx ON images(owner_id)", []);
$db->execute("CREATE INDEX images_width_idx ON images(width)", array()); $db->execute("CREATE INDEX images_width_idx ON images(width)", []);
$db->execute("CREATE INDEX images_height_idx ON images(height)", array()); $db->execute("CREATE INDEX images_height_idx ON images(height)", []);
$db->execute("CREATE INDEX images_hash_idx ON images(hash)", array()); $db->execute("CREATE INDEX images_hash_idx ON images(hash)", []);
$db->create_table("tags", " $db->create_table("tags", "
id SCORE_AIPK, id SCORE_AIPK,
tag VARCHAR(64) UNIQUE NOT NULL, tag VARCHAR(64) UNIQUE NOT NULL,
count INTEGER NOT NULL DEFAULT 0 count INTEGER NOT NULL DEFAULT 0
"); ");
$db->execute("CREATE INDEX tags_tag_idx ON tags(tag)", array()); $db->execute("CREATE INDEX tags_tag_idx ON tags(tag)", []);
$db->create_table("image_tags", " $db->create_table("image_tags", "
image_id INTEGER NOT NULL, image_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL, tag_id INTEGER NOT NULL,
UNIQUE(image_id, tag_id), UNIQUE(image_id, tag_id),
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE, FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
"); ");
$db->execute("CREATE INDEX images_tags_image_id_idx ON image_tags(image_id)", array()); $db->execute("CREATE INDEX images_tags_image_id_idx ON image_tags(image_id)", []);
$db->execute("CREATE INDEX images_tags_tag_id_idx ON image_tags(tag_id)", array()); $db->execute("CREATE INDEX images_tags_tag_id_idx ON image_tags(tag_id)", []);
$db->execute("INSERT INTO config(name, value) VALUES('db_version', 11)"); $db->execute("INSERT INTO config(name, value) VALUES('db_version', 11)");
$db->commit(); $db->commit();
} } catch (PDOException $e) {
catch(PDOException $e) { handle_db_errors(true, "An error occurred while trying to create the database tables necessary for Shimmie.", $e->getMessage(), 3);
handle_db_errors(TRUE, "An error occurred while trying to create the database tables necessary for Shimmie.", $e->getMessage(), 3); } catch (Exception $e) {
} catch (Exception $e) { handle_db_errors(false, "An unknown error occurred while trying to insert data into the database.", $e->getMessage(), 4);
handle_db_errors(FALSE, "An unknown error occurred while trying to insert data into the database.", $e->getMessage(), 4); }
}
} // }}} } // }}}
function insert_defaults() { // {{{ function insert_defaults()
try { { // {{{
$db = new Database(); try {
$db = new Database();
$db->execute("INSERT INTO users(name, pass, joindate, class) VALUES(:name, :pass, now(), :class)", Array("name" => 'Anonymous', "pass" => null, "class" => 'anonymous')); $db->execute("INSERT INTO users(name, pass, joindate, class) VALUES(:name, :pass, now(), :class)", ["name" => 'Anonymous', "pass" => null, "class" => 'anonymous']);
$db->execute("INSERT INTO config(name, value) VALUES(:name, :value)", Array("name" => 'anon_id', "value" => $db->get_last_insert_id('users_id_seq'))); $db->execute("INSERT INTO config(name, value) VALUES(:name, :value)", ["name" => 'anon_id', "value" => $db->get_last_insert_id('users_id_seq')]);
if(check_im_version() > 0) { if (check_im_version() > 0) {
$db->execute("INSERT INTO config(name, value) VALUES(:name, :value)", Array("name" => 'thumb_engine', "value" => 'convert')); $db->execute("INSERT INTO config(name, value) VALUES(:name, :value)", ["name" => 'thumb_engine', "value" => 'convert']);
} }
$db->commit(); $db->commit();
} } catch (PDOException $e) {
catch(PDOException $e) { handle_db_errors(true, "An error occurred while trying to insert data into the database.", $e->getMessage(), 5);
handle_db_errors(TRUE, "An error occurred while trying to insert data into the database.", $e->getMessage(), 5); } catch (Exception $e) {
} handle_db_errors(false, "An unknown error occurred while trying to insert data into the database.", $e->getMessage(), 6);
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() { // {{{ function build_dirs()
// *try* and make default dirs. Ignore any errors -- { // {{{
// if something is amiss, we'll tell the user later // *try* and make default dirs. Ignore any errors --
if(!file_exists("data")) @mkdir("data"); // if something is amiss, we'll tell the user later
if(!is_writable("data")) @chmod("data", 0755); if (!file_exists("data")) {
@mkdir("data");
}
if (!is_writable("data")) {
@chmod("data", 0755);
}
// Clear file status cache before checking again. // Clear file status cache before checking again.
clearstatcache(); clearstatcache();
if(!file_exists("data") || !is_writable("data")) { if (!file_exists("data") || !is_writable("data")) {
print " print "
<div id='installer'> <div id='installer'>
<h1>Shimmie Installer</h1> <h1>Shimmie Installer</h1>
<h3>Directory Permissions Error:</h3> <h3>Directory Permissions Error:</h3>
@ -381,22 +388,23 @@ function build_dirs() { // {{{
</div> </div>
</div> </div>
"; ";
exit(7); exit(7);
} }
} // }}} } // }}}
function write_config() { // {{{ function write_config()
$file_content = '<' . '?php' . "\n" . { // {{{
"define('DATABASE_DSN', '".DATABASE_DSN."');\n" . $file_content = '<' . '?php' . "\n" .
'?' . '>'; "define('DATABASE_DSN', '".DATABASE_DSN."');\n" .
'?' . '>';
if(!file_exists("data/config")) { if (!file_exists("data/config")) {
mkdir("data/config", 0755, true); mkdir("data/config", 0755, true);
} }
if(file_put_contents("data/config/shimmie.conf.php", $file_content, LOCK_EX)) { if (file_put_contents("data/config/shimmie.conf.php", $file_content, LOCK_EX)) {
header("Location: index.php"); header("Location: index.php");
print <<<EOD print <<<EOD
<div id="installer"> <div id="installer">
<h1>Shimmie Installer</h1> <h1>Shimmie Installer</h1>
<h3>Things are OK \o/</h3> <h3>Things are OK \o/</h3>
@ -405,10 +413,9 @@ function write_config() { // {{{
</div> </div>
</div> </div>
EOD; EOD;
} } else {
else { $h_file_content = htmlentities($file_content);
$h_file_content = htmlentities($file_content); print <<<EOD
print <<<EOD
<div id="installer"> <div id="installer">
<h1>Shimmie Installer</h1> <h1>Shimmie Installer</h1>
<h3>File Permissions Error:</h3> <h3>File Permissions Error:</h3>
@ -425,13 +432,14 @@ EOD;
</div> </div>
</div> </div>
EOD; EOD;
} }
echo "\n"; echo "\n";
} // }}} } // }}}
function handle_db_errors(bool $isPDO, string $errorMessage1, string $errorMessage2, int $exitCode) { function handle_db_errors(bool $isPDO, string $errorMessage1, string $errorMessage2, int $exitCode)
$errorMessage1Extra = ($isPDO ? "Please check and ensure that the database configuration options are all correct." : "Please check the server log files for more information."); {
print <<<EOD $errorMessage1Extra = ($isPDO ? "Please check and ensure that the database configuration options are all correct." : "Please check the server log files for more information.");
print <<<EOD
<div id="installer"> <div id="installer">
<h1>Shimmie Installer</h1> <h1>Shimmie Installer</h1>
<h3>Unknown Error:</h3> <h3>Unknown Error:</h3>
@ -442,7 +450,7 @@ function handle_db_errors(bool $isPDO, string $errorMessage1, string $errorMessa
</div> </div>
</div> </div>
EOD; EOD;
exit($exitCode); exit($exitCode);
} }
?> ?>
</body> </body>

View File

@ -5,117 +5,135 @@
* *
* A collection of common functions for theme parts * A collection of common functions for theme parts
*/ */
class BaseThemelet { class BaseThemelet
{
/** /**
* Generic error message display * Generic error message display
*/ */
public function display_error(int $code, string $title, string $message): void { public function display_error(int $code, string $title, string $message): void
global $page; {
$page->set_code($code); global $page;
$page->set_title($title); $page->set_code($code);
$page->set_heading($title); $page->set_title($title);
$has_nav = false; $page->set_heading($title);
foreach($page->blocks as $block) { $has_nav = false;
if($block->header == "Navigation") { foreach ($page->blocks as $block) {
$has_nav = true; if ($block->header == "Navigation") {
break; $has_nav = true;
} break;
} }
if(!$has_nav) { }
$page->add_block(new NavBlock()); if (!$has_nav) {
} $page->add_block(new NavBlock());
$page->add_block(new Block("Error", $message)); }
} $page->add_block(new Block("Error", $message));
}
/** /**
* A specific, common error message * A specific, common error message
*/ */
public function display_permission_denied(): void { public function display_permission_denied(): void
$this->display_error(403, "Permission Denied", "You do not have permission to access this page"); {
} $this->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
/** /**
* Generic thumbnail code; returns HTML rather than adding * Generic thumbnail code; returns HTML rather than adding
* a block since thumbs tend to go inside blocks... * a block since thumbs tend to go inside blocks...
*/ */
public function build_thumb_html(Image $image): string { public function build_thumb_html(Image $image): string
global $config; {
global $config;
$i_id = (int) $image->id; $i_id = (int) $image->id;
$h_view_link = make_link('post/view/'.$i_id); $h_view_link = make_link('post/view/'.$i_id);
$h_thumb_link = $image->get_thumb_link(); $h_thumb_link = $image->get_thumb_link();
$h_tip = html_escape($image->get_tooltip()); $h_tip = html_escape($image->get_tooltip());
$h_tags = html_escape(strtolower($image->get_tag_list())); $h_tags = html_escape(strtolower($image->get_tag_list()));
$extArr = array_flip(array('swf', 'svg', 'mp3')); //List of thumbless filetypes $extArr = array_flip(['swf', 'svg', 'mp3']); //List of thumbless filetypes
if(!isset($extArr[$image->ext])){ if (!isset($extArr[$image->ext])) {
$tsize = get_thumbnail_size($image->width, $image->height); $tsize = get_thumbnail_size($image->width, $image->height);
}else{ } else {
//Use max thumbnail size if using thumbless filetype //Use max thumbnail size if using thumbless filetype
$tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height')); $tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height'));
} }
$custom_classes = ""; $custom_classes = "";
if(class_exists("Relationships")){ if (class_exists("Relationships")) {
if(property_exists($image, 'parent_id') && $image->parent_id !== NULL){ $custom_classes .= "shm-thumb-has_parent "; } if (property_exists($image, 'parent_id') && $image->parent_id !== null) {
if(property_exists($image, 'has_children') && bool_escape($image->has_children)){ $custom_classes .= "shm-thumb-has_child "; } $custom_classes .= "shm-thumb-has_parent ";
} }
if (property_exists($image, 'has_children') && bool_escape($image->has_children)) {
$custom_classes .= "shm-thumb-has_child ";
}
}
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>". return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>".
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>". "<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>".
"</a>\n"; "</a>\n";
} }
public function display_paginator(Page $page, string $base, string $query=null, int $page_number, int $total_pages, bool $show_random = FALSE) { public function display_paginator(Page $page, string $base, string $query=null, int $page_number, int $total_pages, bool $show_random = false)
if($total_pages == 0) $total_pages = 1; {
$body = $this->build_paginator($page_number, $total_pages, $base, $query, $show_random); if ($total_pages == 0) {
$page->add_block(new Block(null, $body, "main", 90, "paginator")); $total_pages = 1;
} }
$body = $this->build_paginator($page_number, $total_pages, $base, $query, $show_random);
$page->add_block(new Block(null, $body, "main", 90, "paginator"));
}
private function gen_page_link(string $base_url, string $query=null, string $page, string $name): string { private function gen_page_link(string $base_url, string $query=null, string $page, string $name): string
$link = make_link($base_url.'/'.$page, $query); {
return '<a href="'.$link.'">'.$name.'</a>'; $link = make_link($base_url.'/'.$page, $query);
} return '<a href="'.$link.'">'.$name.'</a>';
}
private function gen_page_link_block(string $base_url, string $query=null, string $page, int $current_page, string $name): string { private function gen_page_link_block(string $base_url, string $query=null, string $page, int $current_page, string $name): string
$paginator = ""; {
if($page == $current_page) $paginator .= "<b>"; $paginator = "";
$paginator .= $this->gen_page_link($base_url, $query, $page, $name); if ($page == $current_page) {
if($page == $current_page) $paginator .= "</b>"; $paginator .= "<b>";
return $paginator; }
} $paginator .= $this->gen_page_link($base_url, $query, $page, $name);
if ($page == $current_page) {
$paginator .= "</b>";
}
return $paginator;
}
private function build_paginator(int $current_page, int $total_pages, string $base_url, string $query=null, bool $show_random): string { private function build_paginator(int $current_page, int $total_pages, string $base_url, string $query=null, bool $show_random): string
$next = $current_page + 1; {
$prev = $current_page - 1; $next = $current_page + 1;
$prev = $current_page - 1;
$at_start = ($current_page <= 1 || $total_pages <= 1); $at_start = ($current_page <= 1 || $total_pages <= 1);
$at_end = ($current_page >= $total_pages); $at_end = ($current_page >= $total_pages);
$first_html = $at_start ? "First" : $this->gen_page_link($base_url, $query, 1, "First"); $first_html = $at_start ? "First" : $this->gen_page_link($base_url, $query, 1, "First");
$prev_html = $at_start ? "Prev" : $this->gen_page_link($base_url, $query, $prev, "Prev"); $prev_html = $at_start ? "Prev" : $this->gen_page_link($base_url, $query, $prev, "Prev");
$random_html = "-"; $random_html = "-";
if($show_random) { if ($show_random) {
$rand = mt_rand(1, $total_pages); $rand = mt_rand(1, $total_pages);
$random_html = $this->gen_page_link($base_url, $query, $rand, "Random"); $random_html = $this->gen_page_link($base_url, $query, $rand, "Random");
} }
$next_html = $at_end ? "Next" : $this->gen_page_link($base_url, $query, $next, "Next"); $next_html = $at_end ? "Next" : $this->gen_page_link($base_url, $query, $next, "Next");
$last_html = $at_end ? "Last" : $this->gen_page_link($base_url, $query, $total_pages, "Last"); $last_html = $at_end ? "Last" : $this->gen_page_link($base_url, $query, $total_pages, "Last");
$start = $current_page-5 > 1 ? $current_page-5 : 1; $start = $current_page-5 > 1 ? $current_page-5 : 1;
$end = $start+10 < $total_pages ? $start+10 : $total_pages; $end = $start+10 < $total_pages ? $start+10 : $total_pages;
$pages = array(); $pages = [];
foreach(range($start, $end) as $i) { foreach (range($start, $end) as $i) {
$pages[] = $this->gen_page_link_block($base_url, $query, $i, $current_page, $i); $pages[] = $this->gen_page_link_block($base_url, $query, $i, $current_page, $i);
} }
$pages_html = implode(" | ", $pages); $pages_html = implode(" | ", $pages);
return $first_html.' | '.$prev_html.' | '.$random_html.' | '.$next_html.' | '.$last_html return $first_html.' | '.$prev_html.' | '.$random_html.' | '.$next_html.' | '.$last_html
.'<br>&lt;&lt; '.$pages_html.' &gt;&gt;'; .'<br>&lt;&lt; '.$pages_html.' &gt;&gt;';
} }
} }

View File

@ -5,79 +5,86 @@
* *
* A basic chunk of a page. * A basic chunk of a page.
*/ */
class Block { class Block
/** {
* The block's title. /**
* * The block's title.
* @var string *
*/ * @var string
public $header; */
public $header;
/** /**
* The content of the block. * The content of the block.
* *
* @var string * @var string
*/ */
public $body; public $body;
/** /**
* Where the block should be placed. The default theme supports * Where the block should be placed. The default theme supports
* "main" and "left", other themes can add their own areas. * "main" and "left", other themes can add their own areas.
* *
* @var string * @var string
*/ */
public $section; public $section;
/** /**
* How far down the section the block should appear, higher * How far down the section the block should appear, higher
* numbers appear lower. The scale is 0-100 by convention, * numbers appear lower. The scale is 0-100 by convention,
* though any number or string will work. * though any number or string will work.
* *
* @var int * @var int
*/ */
public $position; public $position;
/** /**
* A unique ID for the block. * A unique ID for the block.
* *
* @var string * @var string
*/ */
public $id; public $id;
/** /**
* Should this block count as content for the sake of * Should this block count as content for the sake of
* the 404 handler * the 404 handler
* *
* @var boolean * @var boolean
*/ */
public $is_content = true; public $is_content = true;
public function __construct(string $header=null, string $body=null, string $section="main", int $position=50, string $id=null) { public function __construct(string $header=null, string $body=null, string $section="main", int $position=50, string $id=null)
$this->header = $header; {
$this->body = $body; $this->header = $header;
$this->section = $section; $this->body = $body;
$this->position = $position; $this->section = $section;
$this->position = $position;
if(is_null($id)) { if (is_null($id)) {
$id = (empty($header) ? md5($body) : $header) . $section; $id = (empty($header) ? md5($body) : $header) . $section;
} }
$this->id = preg_replace('/[^\w]/', '',str_replace(' ', '_', $id)); $this->id = preg_replace('/[^\w]/', '', str_replace(' ', '_', $id));
} }
/** /**
* Get the HTML for this block. * Get the HTML for this block.
*/ */
public function get_html(bool $hidable=false): string { public function get_html(bool $hidable=false): string
$h = $this->header; {
$b = $this->body; $h = $this->header;
$i = $this->id; $b = $this->body;
$html = "<section id='$i'>"; $i = $this->id;
$h_toggler = $hidable ? " shm-toggler" : ""; $html = "<section id='$i'>";
if(!empty($h)) $html .= "<h3 data-toggle-sel='#$i' class='$h_toggler'>$h</h3>"; $h_toggler = $hidable ? " shm-toggler" : "";
if(!empty($b)) $html .= "<div class='blockbody'>$b</div>"; if (!empty($h)) {
$html .= "</section>\n"; $html .= "<h3 data-toggle-sel='#$i' class='$h_toggler'>$h</h3>";
return $html; }
} if (!empty($b)) {
$html .= "<div class='blockbody'>$b</div>";
}
$html .= "</section>\n";
return $html;
}
} }
@ -89,8 +96,10 @@ class Block {
* Used because "new NavBlock()" is easier than "new Block('Navigation', ..." * Used because "new NavBlock()" is easier than "new Block('Navigation', ..."
* *
*/ */
class NavBlock extends Block { class NavBlock extends Block
public function __construct() { {
parent::__construct("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0); public function __construct()
} {
parent::__construct("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0);
}
} }

View File

@ -1,196 +1,228 @@
<?php <?php
interface CacheEngine { interface CacheEngine
public function get(string $key); {
public function set(string $key, $val, int $time=0); public function get(string $key);
public function delete(string $key); public function set(string $key, $val, int $time=0);
public function delete(string $key);
} }
class NoCache implements CacheEngine { class NoCache implements CacheEngine
public function get(string $key) {return false;} {
public function set(string $key, $val, int $time=0) {} public function get(string $key)
public function delete(string $key) {} {
return false;
}
public function set(string $key, $val, int $time=0)
{
}
public function delete(string $key)
{
}
} }
class MemcacheCache implements CacheEngine { class MemcacheCache implements CacheEngine
/** @var \Memcache|null */ {
public $memcache=null; /** @var \Memcache|null */
public $memcache=null;
public function __construct(string $args) { public function __construct(string $args)
$hp = explode(":", $args); {
$this->memcache = new Memcache; $hp = explode(":", $args);
@$this->memcache->pconnect($hp[0], $hp[1]); $this->memcache = new Memcache;
} @$this->memcache->pconnect($hp[0], $hp[1]);
}
public function get(string $key) { public function get(string $key)
return $this->memcache->get($key); {
} return $this->memcache->get($key);
}
public function set(string $key, $val, int $time=0) { public function set(string $key, $val, int $time=0)
$this->memcache->set($key, $val, false, $time); {
} $this->memcache->set($key, $val, false, $time);
}
public function delete(string $key) { public function delete(string $key)
$this->memcache->delete($key); {
} $this->memcache->delete($key);
}
} }
class MemcachedCache implements CacheEngine { class MemcachedCache implements CacheEngine
/** @var \Memcached|null */ {
public $memcache=null; /** @var \Memcached|null */
public $memcache=null;
public function __construct(string $args) { public function __construct(string $args)
$hp = explode(":", $args); {
$this->memcache = new Memcached; $hp = explode(":", $args);
#$this->memcache->setOption(Memcached::OPT_COMPRESSION, False); $this->memcache = new Memcached;
#$this->memcache->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP); #$this->memcache->setOption(Memcached::OPT_COMPRESSION, False);
#$this->memcache->setOption(Memcached::OPT_PREFIX_KEY, phpversion()); #$this->memcache->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP);
$this->memcache->addServer($hp[0], $hp[1]); #$this->memcache->setOption(Memcached::OPT_PREFIX_KEY, phpversion());
} $this->memcache->addServer($hp[0], $hp[1]);
}
public function get(string $key) { public function get(string $key)
$key = urlencode($key); {
$key = urlencode($key);
$val = $this->memcache->get($key); $val = $this->memcache->get($key);
$res = $this->memcache->getResultCode(); $res = $this->memcache->getResultCode();
if($res == Memcached::RES_SUCCESS) { if ($res == Memcached::RES_SUCCESS) {
return $val; return $val;
} } elseif ($res == Memcached::RES_NOTFOUND) {
else if($res == Memcached::RES_NOTFOUND) { return false;
return false; } else {
} error_log("Memcached error during get($key): $res");
else { return false;
error_log("Memcached error during get($key): $res"); }
return false; }
}
}
public function set(string $key, $val, int $time=0) { public function set(string $key, $val, int $time=0)
$key = urlencode($key); {
$key = urlencode($key);
$this->memcache->set($key, $val, $time); $this->memcache->set($key, $val, $time);
$res = $this->memcache->getResultCode(); $res = $this->memcache->getResultCode();
if($res != Memcached::RES_SUCCESS) { if ($res != Memcached::RES_SUCCESS) {
error_log("Memcached error during set($key): $res"); error_log("Memcached error during set($key): $res");
} }
} }
public function delete(string $key) { public function delete(string $key)
$key = urlencode($key); {
$key = urlencode($key);
$this->memcache->delete($key); $this->memcache->delete($key);
$res = $this->memcache->getResultCode(); $res = $this->memcache->getResultCode();
if($res != Memcached::RES_SUCCESS && $res != Memcached::RES_NOTFOUND) { if ($res != Memcached::RES_SUCCESS && $res != Memcached::RES_NOTFOUND) {
error_log("Memcached error during delete($key): $res"); error_log("Memcached error during delete($key): $res");
} }
} }
} }
class APCCache implements CacheEngine { class APCCache implements CacheEngine
public function __construct(string $args) { {
// $args is not used, but is passed in when APC cache is created. public function __construct(string $args)
} {
// $args is not used, but is passed in when APC cache is created.
}
public function get(string $key) { public function get(string $key)
return apc_fetch($key); {
} return apc_fetch($key);
}
public function set(string $key, $val, int $time=0) { public function set(string $key, $val, int $time=0)
apc_store($key, $val, $time); {
} apc_store($key, $val, $time);
}
public function delete(string $key) { public function delete(string $key)
apc_delete($key); {
} apc_delete($key);
}
} }
class RedisCache implements CacheEngine { class RedisCache implements CacheEngine
private $redis=null; {
private $redis=null;
public function __construct(string $args) { public function __construct(string $args)
$this->redis = new Redis(); {
$hp = explode(":", $args); $this->redis = new Redis();
$this->redis->pconnect($hp[0], $hp[1]); $hp = explode(":", $args);
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); $this->redis->pconnect($hp[0], $hp[1]);
$this->redis->setOption(Redis::OPT_PREFIX, 'shm:'); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
} $this->redis->setOption(Redis::OPT_PREFIX, 'shm:');
}
public function get(string $key) { public function get(string $key)
return $this->redis->get($key); {
} return $this->redis->get($key);
}
public function set(string $key, $val, int $time=0) { public function set(string $key, $val, int $time=0)
if($time > 0) { {
$this->redis->setEx($key, $time, $val); if ($time > 0) {
} $this->redis->setEx($key, $time, $val);
else { } else {
$this->redis->set($key, $val); $this->redis->set($key, $val);
} }
} }
public function delete(string $key) { public function delete(string $key)
$this->redis->delete($key); {
} $this->redis->delete($key);
}
} }
class Cache { class Cache
public $engine; {
public $hits=0, $misses=0; public $engine;
public $time=0; public $hits=0;
public $misses=0;
public $time=0;
public function __construct(?string $dsn) { public function __construct(?string $dsn)
$matches = array(); {
if($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches)) { $matches = [];
if($matches[1] == "memcache") { if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches)) {
$c = new MemcacheCache($matches[2]); if ($matches[1] == "memcache") {
} $c = new MemcacheCache($matches[2]);
else if($matches[1] == "memcached") { } elseif ($matches[1] == "memcached") {
$c = new MemcachedCache($matches[2]); $c = new MemcachedCache($matches[2]);
} } elseif ($matches[1] == "apc") {
else if($matches[1] == "apc") { $c = new APCCache($matches[2]);
$c = new APCCache($matches[2]); } elseif ($matches[1] == "redis") {
} $c = new RedisCache($matches[2]);
else if($matches[1] == "redis") { }
$c = new RedisCache($matches[2]); } else {
} $c = new NoCache();
} }
else { $this->engine = $c;
$c = new NoCache(); }
}
$this->engine = $c;
}
public function get(string $key) { public function get(string $key)
$val = $this->engine->get($key); {
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { $val = $this->engine->get($key);
$hit = $val === false ? "hit" : "miss"; if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
file_put_contents("data/cache.log", "Cache $hit: $key\n", FILE_APPEND); $hit = $val === false ? "hit" : "miss";
} file_put_contents("data/cache.log", "Cache $hit: $key\n", FILE_APPEND);
if($val !== false) { }
$this->hits++; if ($val !== false) {
return $val; $this->hits++;
} return $val;
else { } else {
$this->misses++; $this->misses++;
return false; return false;
} }
} }
public function set(string $key, $val, int $time=0) { public function set(string $key, $val, int $time=0)
$this->engine->set($key, $val, $time); {
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { $this->engine->set($key, $val, $time);
file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND); if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
} file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND);
} }
}
public function delete(string $key) { public function delete(string $key)
$this->engine->delete($key); {
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { $this->engine->delete($key);
file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND); if ((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
} file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND);
} }
}
public function get_hits(): int {return $this->hits;} public function get_hits(): int
public function get_misses(): int {return $this->misses;} {
return $this->hits;
}
public function get_misses(): int
{
return $this->misses;
}
} }

View File

@ -3,53 +3,56 @@
* CAPTCHA abstraction * * CAPTCHA abstraction *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function captcha_get_html(): string { function captcha_get_html(): string
global $config, $user; {
global $config, $user;
if(DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) return ""; if (DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) {
return "";
}
$captcha = ""; $captcha = "";
if($user->is_anonymous() && $config->get_bool("comment_captcha")) { if ($user->is_anonymous() && $config->get_bool("comment_captcha")) {
$r_publickey = $config->get_string("api_recaptcha_pubkey"); $r_publickey = $config->get_string("api_recaptcha_pubkey");
if(!empty($r_publickey)) { if (!empty($r_publickey)) {
$captcha = " $captcha = "
<div class=\"g-recaptcha\" data-sitekey=\"{$r_publickey}\"></div> <div class=\"g-recaptcha\" data-sitekey=\"{$r_publickey}\"></div>
<script type=\"text/javascript\" src=\"https://www.google.com/recaptcha/api.js\"></script>"; <script type=\"text/javascript\" src=\"https://www.google.com/recaptcha/api.js\"></script>";
} else { } else {
session_start(); session_start();
$captcha = Securimage::getCaptchaHtml(['securimage_path' => './vendor/dapphp/securimage/']); $captcha = Securimage::getCaptchaHtml(['securimage_path' => './vendor/dapphp/securimage/']);
} }
} }
return $captcha; return $captcha;
} }
function captcha_check(): bool { function captcha_check(): bool
global $config, $user; {
global $config, $user;
if(DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) return true; if (DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) {
return true;
}
if($user->is_anonymous() && $config->get_bool("comment_captcha")) { if ($user->is_anonymous() && $config->get_bool("comment_captcha")) {
$r_privatekey = $config->get_string('api_recaptcha_privkey'); $r_privatekey = $config->get_string('api_recaptcha_privkey');
if(!empty($r_privatekey)) { if (!empty($r_privatekey)) {
$recaptcha = new \ReCaptcha\ReCaptcha($r_privatekey); $recaptcha = new \ReCaptcha\ReCaptcha($r_privatekey);
$resp = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']); $resp = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
if(!$resp->isSuccess()) { if (!$resp->isSuccess()) {
log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes())); log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes()));
return false; return false;
} }
} } else {
else { session_start();
session_start(); $securimg = new Securimage();
$securimg = new Securimage(); if ($securimg->check($_POST['captcha_code']) === false) {
if($securimg->check($_POST['captcha_code']) === false) { log_info("core", "Captcha failed (Securimage)");
log_info("core", "Captcha failed (Securimage)"); return false;
return false; }
} }
} }
}
return true; return true;
} }

View File

@ -5,100 +5,101 @@
* *
* An abstract interface for altering a name:value pair list. * An abstract interface for altering a name:value pair list.
*/ */
interface Config { interface Config
/** {
* Save the list of name:value pairs to wherever they came from, /**
* so that the next time a page is loaded it will use the new * Save the list of name:value pairs to wherever they came from,
* configuration. * so that the next time a page is loaded it will use the new
*/ * configuration.
public function save(string $name=null): void; */
public function save(string $name=null): void;
//@{ /*--------------------------------- SET ------------------------------------------------------*/ //@{ /*--------------------------------- SET ------------------------------------------------------*/
/** /**
* Set a configuration option to a new value, regardless of what the value is at the moment. * Set a configuration option to a new value, regardless of what the value is at the moment.
*/ */
public function set_int(string $name, ?int $value): void; public function set_int(string $name, ?int $value): void;
/** /**
* Set a configuration option to a new value, regardless of what the value is at the moment. * Set a configuration option to a new value, regardless of what the value is at the moment.
*/ */
public function set_string(string $name, ?string $value): void; public function set_string(string $name, ?string $value): void;
/** /**
* Set a configuration option to a new value, regardless of what the value is at the moment. * Set a configuration option to a new value, regardless of what the value is at the moment.
* @param null|bool|string $value * @param null|bool|string $value
*/ */
public function set_bool(string $name, $value): void; public function set_bool(string $name, $value): void;
/** /**
* Set a configuration option to a new value, regardless of what the value is at the moment. * Set a configuration option to a new value, regardless of what the value is at the moment.
*/ */
public function set_array(string $name, array $value): void; public function set_array(string $name, array $value): void;
//@} /*--------------------------------------------------------------------------------------------*/ //@} /*--------------------------------------------------------------------------------------------*/
//@{ /*-------------------------------- SET DEFAULT -----------------------------------------------*/ //@{ /*-------------------------------- SET DEFAULT -----------------------------------------------*/
/** /**
* Set a configuration option to a new value, if there is no value currently. * Set a configuration option to a new value, if there is no value currently.
* *
* Extensions should generally call these from their InitExtEvent handlers. * Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup * This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default" * page where they can be modified, while calling get_* with a "default"
* parameter won't show up. * parameter won't show up.
*/ */
public function set_default_int(string $name, int $value): void; public function set_default_int(string $name, int $value): void;
/** /**
* Set a configuration option to a new value, if there is no value currently. * Set a configuration option to a new value, if there is no value currently.
* *
* Extensions should generally call these from their InitExtEvent handlers. * Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup * This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default" * page where they can be modified, while calling get_* with a "default"
* parameter won't show up. * parameter won't show up.
*/ */
public function set_default_string(string $name, string $value): void; public function set_default_string(string $name, string $value): void;
/** /**
* Set a configuration option to a new value, if there is no value currently. * Set a configuration option to a new value, if there is no value currently.
* *
* Extensions should generally call these from their InitExtEvent handlers. * Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup * This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default" * page where they can be modified, while calling get_* with a "default"
* parameter won't show up. * parameter won't show up.
*/ */
public function set_default_bool(string $name, bool $value): void; public function set_default_bool(string $name, bool $value): void;
/** /**
* Set a configuration option to a new value, if there is no value currently. * Set a configuration option to a new value, if there is no value currently.
* *
* Extensions should generally call these from their InitExtEvent handlers. * Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup * This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default" * page where they can be modified, while calling get_* with a "default"
* parameter won't show up. * parameter won't show up.
*/ */
public function set_default_array(string $name, array $value): void; public function set_default_array(string $name, array $value): void;
//@} /*--------------------------------------------------------------------------------------------*/ //@} /*--------------------------------------------------------------------------------------------*/
//@{ /*--------------------------------- GET ------------------------------------------------------*/ //@{ /*--------------------------------- GET ------------------------------------------------------*/
/** /**
* Pick a value out of the table by name, cast to the appropriate data type. * Pick a value out of the table by name, cast to the appropriate data type.
*/ */
public function get_int(string $name, ?int $default=null): ?int; public function get_int(string $name, ?int $default=null): ?int;
/** /**
* Pick a value out of the table by name, cast to the appropriate data type. * Pick a value out of the table by name, cast to the appropriate data type.
*/ */
public function get_string(string $name, ?string $default=null): ?string; public function get_string(string $name, ?string $default=null): ?string;
/** /**
* Pick a value out of the table by name, cast to the appropriate data type. * Pick a value out of the table by name, cast to the appropriate data type.
*/ */
public function get_bool(string $name, ?bool $default=null): ?bool; public function get_bool(string $name, ?bool $default=null): ?bool;
/** /**
* Pick a value out of the table by name, cast to the appropriate data type. * Pick a value out of the table by name, cast to the appropriate data type.
*/ */
public function get_array(string $name, ?array $default=array()): ?array; public function get_array(string $name, ?array $default=[]): ?array;
//@} /*--------------------------------------------------------------------------------------------*/ //@} /*--------------------------------------------------------------------------------------------*/
} }
@ -108,77 +109,90 @@ interface Config {
* Common methods for manipulating the list, loading and saving is * Common methods for manipulating the list, loading and saving is
* left to the concrete implementation * left to the concrete implementation
*/ */
abstract class BaseConfig implements Config { abstract class BaseConfig implements Config
public $values = array(); {
public $values = [];
public function set_int(string $name, $value) { public function set_int(string $name, $value)
$this->values[$name] = parse_shorthand_int($value); {
$this->save($name); $this->values[$name] = parse_shorthand_int($value);
} $this->save($name);
}
public function set_string(string $name, $value) { public function set_string(string $name, $value)
$this->values[$name] = $value; {
$this->save($name); $this->values[$name] = $value;
} $this->save($name);
}
public function set_bool(string $name, $value) { public function set_bool(string $name, $value)
$this->values[$name] = bool_escape($value) ? 'Y' : 'N'; {
$this->save($name); $this->values[$name] = bool_escape($value) ? 'Y' : 'N';
} $this->save($name);
}
public function set_array(string $name, array $value) { public function set_array(string $name, array $value)
$this->values[$name] = implode(",", $value); {
$this->save($name); $this->values[$name] = implode(",", $value);
} $this->save($name);
}
public function set_default_int(string $name, int $value) { public function set_default_int(string $name, int $value)
if(is_null($this->get($name))) { {
$this->values[$name] = $value; if (is_null($this->get($name))) {
} $this->values[$name] = $value;
} }
}
public function set_default_string(string $name, string $value) { public function set_default_string(string $name, string $value)
if(is_null($this->get($name))) { {
$this->values[$name] = $value; if (is_null($this->get($name))) {
} $this->values[$name] = $value;
} }
}
public function set_default_bool(string $name, bool $value) { public function set_default_bool(string $name, bool $value)
if(is_null($this->get($name))) { {
$this->values[$name] = $value ? 'Y' : 'N'; if (is_null($this->get($name))) {
} $this->values[$name] = $value ? 'Y' : 'N';
} }
}
public function set_default_array(string $name, array $value) { public function set_default_array(string $name, array $value)
if(is_null($this->get($name))) { {
$this->values[$name] = implode(",", $value); if (is_null($this->get($name))) {
} $this->values[$name] = implode(",", $value);
} }
}
public function get_int(string $name, $default=null) { public function get_int(string $name, $default=null)
return (int)($this->get($name, $default)); {
} return (int)($this->get($name, $default));
}
public function get_string(string $name, $default=null) { public function get_string(string $name, $default=null)
return $this->get($name, $default); {
} return $this->get($name, $default);
}
public function get_bool(string $name, $default=null) { public function get_bool(string $name, $default=null)
return bool_escape($this->get($name, $default)); {
} return bool_escape($this->get($name, $default));
}
public function get_array(string $name, array $default=array()): array { public function get_array(string $name, array $default=[]): array
return explode(",", $this->get($name, "")); {
} return explode(",", $this->get($name, ""));
}
private function get(string $name, $default=null) { private function get(string $name, $default=null)
if(isset($this->values[$name])) { {
return $this->values[$name]; if (isset($this->values[$name])) {
} return $this->values[$name];
else { } else {
return $default; return $default;
} }
} }
} }
@ -187,14 +201,17 @@ abstract class BaseConfig implements Config {
* *
* For testing, mostly. * For testing, mostly.
*/ */
class HardcodeConfig extends BaseConfig { class HardcodeConfig extends BaseConfig
public function __construct(array $dict) { {
$this->values = $dict; public function __construct(array $dict)
} {
$this->values = $dict;
}
public function save(string $name=null) { public function save(string $name=null)
// static config is static {
} // static config is static
}
} }
@ -208,26 +225,27 @@ class HardcodeConfig extends BaseConfig {
* $config['baz'] = "qux"; * $config['baz'] = "qux";
* ?> * ?>
*/ */
class StaticConfig extends BaseConfig { class StaticConfig extends BaseConfig
public function __construct(string $filename) { {
if(file_exists($filename)) { public function __construct(string $filename)
$config = array(); {
require_once $filename; if (file_exists($filename)) {
if(!empty($config)) { $config = [];
$this->values = $config; require_once $filename;
} if (!empty($config)) {
else { $this->values = $config;
throw new Exception("Config file '$filename' doesn't contain any config"); } else {
} throw new Exception("Config file '$filename' doesn't contain any config");
} }
else { } else {
throw new Exception("Config file '$filename' missing"); throw new Exception("Config file '$filename' missing");
} }
} }
public function save(string $name=null) { public function save(string $name=null)
// static config is static {
} // static config is static
}
} }
@ -244,51 +262,53 @@ class StaticConfig extends BaseConfig {
* ); * );
* \endcode * \endcode
*/ */
class DatabaseConfig extends BaseConfig { class DatabaseConfig extends BaseConfig
/** @var Database */ {
private $database = null; /** @var Database */
private $database = null;
public function __construct(Database $database) { public function __construct(Database $database)
$this->database = $database; {
$this->database = $database;
$cached = $this->database->cache->get("config"); $cached = $this->database->cache->get("config");
if($cached) { if ($cached) {
$this->values = $cached; $this->values = $cached;
} } else {
else { $this->values = [];
$this->values = array(); foreach ($this->database->get_all("SELECT name, value FROM config") as $row) {
foreach($this->database->get_all("SELECT name, value FROM config") as $row) { $this->values[$row["name"]] = $row["value"];
$this->values[$row["name"]] = $row["value"]; }
} $this->database->cache->set("config", $this->values);
$this->database->cache->set("config", $this->values); }
} }
}
public function save(string $name=null) { public function save(string $name=null)
if(is_null($name)) { {
reset($this->values); // rewind the array to the first element if (is_null($name)) {
foreach($this->values as $name => $value) { reset($this->values); // rewind the array to the first element
$this->save($name); foreach ($this->values as $name => $value) {
} $this->save($name);
} }
else { } else {
$this->database->Execute("DELETE FROM config WHERE name = :name", array("name"=>$name)); $this->database->Execute("DELETE FROM config WHERE name = :name", ["name"=>$name]);
$this->database->Execute("INSERT INTO config VALUES (:name, :value)", array("name"=>$name, "value"=>$this->values[$name])); $this->database->Execute("INSERT INTO config VALUES (:name, :value)", ["name"=>$name, "value"=>$this->values[$name]]);
} }
// rather than deleting and having some other request(s) do a thundering // rather than deleting and having some other request(s) do a thundering
// herd of race-conditioned updates, just save the updated version once here // herd of race-conditioned updates, just save the updated version once here
$this->database->cache->set("config", $this->values); $this->database->cache->set("config", $this->values);
} }
} }
/** /**
* Class MockConfig * Class MockConfig
*/ */
class MockConfig extends HardcodeConfig { class MockConfig extends HardcodeConfig
public function __construct(array $config=array()) { {
$config["db_version"] = "999"; public function __construct(array $config=[])
$config["anon_id"] = "0"; {
parent::__construct($config); $config["db_version"] = "999";
} $config["anon_id"] = "0";
parent::__construct($config);
}
} }

View File

@ -2,353 +2,418 @@
/** /**
* A class for controlled database access * A class for controlled database access
*/ */
class Database { class Database
/** {
* The PDO database connection object, for anyone who wants direct access. /**
* @var null|PDO * The PDO database connection object, for anyone who wants direct access.
*/ * @var null|PDO
private $db = null; */
private $db = null;
/**
* @var float /**
*/ * @var float
public $dbtime = 0.0; */
public $dbtime = 0.0;
/** /**
* Meta info about the database engine. * Meta info about the database engine.
* @var DBEngine|null * @var DBEngine|null
*/ */
private $engine = null; private $engine = null;
/** /**
* The currently active cache engine. * The currently active cache engine.
* @var Cache|null * @var Cache|null
*/ */
public $cache = null; public $cache = null;
/** /**
* A boolean flag to track if we already have an active transaction. * A boolean flag to track if we already have an active transaction.
* (ie: True if beginTransaction() already called) * (ie: True if beginTransaction() already called)
* *
* @var bool * @var bool
*/ */
public $transaction = false; public $transaction = false;
/** /**
* How many queries this DB object has run * How many queries this DB object has run
*/ */
public $query_count = 0; public $query_count = 0;
/** /**
* For now, only connect to the cache, as we will pretty much certainly * For now, only connect to the cache, as we will pretty much certainly
* need it. There are some pages where all the data is in cache, so the * need it. There are some pages where all the data is in cache, so the
* DB connection is on-demand. * DB connection is on-demand.
*/ */
public function __construct() { public function __construct()
$this->cache = new Cache(CACHE_DSN); {
} $this->cache = new Cache(CACHE_DSN);
}
private function connect_db() { private function connect_db()
# FIXME: detect ADODB URI, automatically translate PDO DSN {
# FIXME: detect ADODB URI, automatically translate PDO DSN
/* /*
* Why does the abstraction layer act differently depending on the * Why does the abstraction layer act differently depending on the
* back-end? Because PHP is deliberately retarded. * back-end? Because PHP is deliberately retarded.
* *
* http://stackoverflow.com/questions/237367 * http://stackoverflow.com/questions/237367
*/ */
$matches = array(); $db_user=null; $db_pass=null; $matches = [];
if(preg_match("/user=([^;]*)/", DATABASE_DSN, $matches)) $db_user=$matches[1]; $db_user=null;
if(preg_match("/password=([^;]*)/", DATABASE_DSN, $matches)) $db_pass=$matches[1]; $db_pass=null;
if (preg_match("/user=([^;]*)/", DATABASE_DSN, $matches)) {
$db_user=$matches[1];
}
if (preg_match("/password=([^;]*)/", DATABASE_DSN, $matches)) {
$db_pass=$matches[1];
}
// https://bugs.php.net/bug.php?id=70221 // https://bugs.php.net/bug.php?id=70221
$ka = DATABASE_KA; $ka = DATABASE_KA;
if(version_compare(PHP_VERSION, "6.9.9") == 1 && $this->get_driver_name() == "sqlite") { if (version_compare(PHP_VERSION, "6.9.9") == 1 && $this->get_driver_name() == "sqlite") {
$ka = false; $ka = false;
} }
$db_params = array( $db_params = [
PDO::ATTR_PERSISTENT => $ka, PDO::ATTR_PERSISTENT => $ka,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
); ];
$this->db = new PDO(DATABASE_DSN, $db_user, $db_pass, $db_params); $this->db = new PDO(DATABASE_DSN, $db_user, $db_pass, $db_params);
$this->connect_engine(); $this->connect_engine();
$this->engine->init($this->db); $this->engine->init($this->db);
$this->beginTransaction(); $this->beginTransaction();
} }
private function connect_engine() { private function connect_engine()
if(preg_match("/^([^:]*)/", DATABASE_DSN, $matches)) $db_proto=$matches[1]; {
else throw new SCoreException("Can't figure out database engine"); if (preg_match("/^([^:]*)/", DATABASE_DSN, $matches)) {
$db_proto=$matches[1];
} else {
throw new SCoreException("Can't figure out database engine");
}
if($db_proto === "mysql") { if ($db_proto === "mysql") {
$this->engine = new MySQL(); $this->engine = new MySQL();
} } elseif ($db_proto === "pgsql") {
else if($db_proto === "pgsql") { $this->engine = new PostgreSQL();
$this->engine = new PostgreSQL(); } elseif ($db_proto === "sqlite") {
} $this->engine = new SQLite();
else if($db_proto === "sqlite") { } else {
$this->engine = new SQLite(); die('Unknown PDO driver: '.$db_proto);
} }
else { }
die('Unknown PDO driver: '.$db_proto);
}
}
public function beginTransaction() { public function beginTransaction()
if ($this->transaction === false) { {
$this->db->beginTransaction(); if ($this->transaction === false) {
$this->transaction = true; $this->db->beginTransaction();
} $this->transaction = true;
} }
}
public function commit(): bool { public function commit(): bool
if(!is_null($this->db)) { {
if ($this->transaction === true) { if (!is_null($this->db)) {
$this->transaction = false; if ($this->transaction === true) {
return $this->db->commit(); $this->transaction = false;
} return $this->db->commit();
else { } else {
throw new SCoreException("<p><b>Database Transaction Error:</b> Unable to call commit() as there is no transaction currently open."); throw new SCoreException("<p><b>Database Transaction Error:</b> Unable to call commit() as there is no transaction currently open.");
} }
} } else {
else { throw new SCoreException("<p><b>Database Transaction Error:</b> Unable to call commit() as there is no connection currently open.");
throw new SCoreException("<p><b>Database Transaction Error:</b> Unable to call commit() as there is no connection currently open."); }
} }
}
public function rollback(): bool { public function rollback(): bool
if(!is_null($this->db)) { {
if ($this->transaction === true) { if (!is_null($this->db)) {
$this->transaction = false; if ($this->transaction === true) {
return $this->db->rollback(); $this->transaction = false;
} return $this->db->rollback();
else { } else {
throw new SCoreException("<p><b>Database Transaction Error:</b> Unable to call rollback() as there is no transaction currently open."); throw new SCoreException("<p><b>Database Transaction Error:</b> Unable to call rollback() as there is no transaction currently open.");
} }
} } else {
else { throw new SCoreException("<p><b>Database Transaction Error:</b> Unable to call rollback() as there is no connection currently open.");
throw new SCoreException("<p><b>Database Transaction Error:</b> Unable to call rollback() as there is no connection currently open."); }
} }
}
public function escape(string $input): string { public function escape(string $input): string
if(is_null($this->db)) $this->connect_db(); {
return $this->db->Quote($input); if (is_null($this->db)) {
} $this->connect_db();
}
return $this->db->Quote($input);
}
public function scoreql_to_sql(string $input): string { public function scoreql_to_sql(string $input): string
if(is_null($this->engine)) $this->connect_engine(); {
return $this->engine->scoreql_to_sql($input); if (is_null($this->engine)) {
} $this->connect_engine();
}
return $this->engine->scoreql_to_sql($input);
}
public function get_driver_name(): string { public function get_driver_name(): string
if(is_null($this->engine)) $this->connect_engine(); {
return $this->engine->name; if (is_null($this->engine)) {
} $this->connect_engine();
}
return $this->engine->name;
}
private function count_execs(string $sql, array $inputarray) { private function count_execs(string $sql, array $inputarray)
if((DEBUG_SQL === true) || (is_null(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) { {
$sql = trim(preg_replace('/\s+/msi', ' ', $sql)); if ((DEBUG_SQL === true) || (is_null(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) {
if(isset($inputarray) && is_array($inputarray) && !empty($inputarray)) { $sql = trim(preg_replace('/\s+/msi', ' ', $sql));
$text = $sql." -- ".join(", ", $inputarray)."\n"; if (isset($inputarray) && is_array($inputarray) && !empty($inputarray)) {
} $text = $sql." -- ".join(", ", $inputarray)."\n";
else { } else {
$text = $sql."\n"; $text = $sql."\n";
} }
file_put_contents("data/sql.log", $text, FILE_APPEND); file_put_contents("data/sql.log", $text, FILE_APPEND);
} }
if(!is_array($inputarray)) $this->query_count++; if (!is_array($inputarray)) {
# handle 2-dimensional input arrays $this->query_count++;
else if(is_array(reset($inputarray))) $this->query_count += sizeof($inputarray); }
else $this->query_count++; # handle 2-dimensional input arrays
} elseif (is_array(reset($inputarray))) {
$this->query_count += sizeof($inputarray);
} else {
$this->query_count++;
}
}
private function count_time(string $method, float $start) { private function count_time(string $method, float $start)
if((DEBUG_SQL === true) || (is_null(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) { {
$text = $method.":".(microtime(true) - $start)."\n"; if ((DEBUG_SQL === true) || (is_null(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) {
file_put_contents("data/sql.log", $text, FILE_APPEND); $text = $method.":".(microtime(true) - $start)."\n";
} file_put_contents("data/sql.log", $text, FILE_APPEND);
$this->dbtime += microtime(true) - $start; }
} $this->dbtime += microtime(true) - $start;
}
public function execute(string $query, array $args=array()): PDOStatement { public function execute(string $query, array $args=[]): PDOStatement
try { {
if(is_null($this->db)) $this->connect_db(); try {
$this->count_execs($query, $args); if (is_null($this->db)) {
$stmt = $this->db->prepare( $this->connect_db();
"-- " . str_replace("%2F", "/", urlencode(@$_GET['q'])). "\n" . }
$query $this->count_execs($query, $args);
); $stmt = $this->db->prepare(
// $stmt = $this->db->prepare($query); "-- " . str_replace("%2F", "/", urlencode(@$_GET['q'])). "\n" .
if (!array_key_exists(0, $args)) { $query
foreach($args as $name=>$value) { );
if(is_numeric($value)) { // $stmt = $this->db->prepare($query);
$stmt->bindValue(':'.$name, $value, PDO::PARAM_INT); if (!array_key_exists(0, $args)) {
} foreach ($args as $name=>$value) {
else { if (is_numeric($value)) {
$stmt->bindValue(':'.$name, $value, PDO::PARAM_STR); $stmt->bindValue(':'.$name, $value, PDO::PARAM_INT);
} } else {
} $stmt->bindValue(':'.$name, $value, PDO::PARAM_STR);
$stmt->execute(); }
} }
else { $stmt->execute();
$stmt->execute($args); } else {
} $stmt->execute($args);
return $stmt; }
} return $stmt;
catch(PDOException $pdoe) { } catch (PDOException $pdoe) {
throw new SCoreException($pdoe->getMessage()."<p><b>Query:</b> ".$query); throw new SCoreException($pdoe->getMessage()."<p><b>Query:</b> ".$query);
} }
} }
/** /**
* Execute an SQL query and return a 2D array. * Execute an SQL query and return a 2D array.
*/ */
public function get_all(string $query, array $args=array()): array { public function get_all(string $query, array $args=[]): array
$_start = microtime(true); {
$data = $this->execute($query, $args)->fetchAll(); $_start = microtime(true);
$this->count_time("get_all", $_start); $data = $this->execute($query, $args)->fetchAll();
return $data; $this->count_time("get_all", $_start);
} return $data;
}
/** /**
* Execute an SQL query and return a single row. * 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=[])
$_start = microtime(true); {
$row = $this->execute($query, $args)->fetch(); $_start = microtime(true);
$this->count_time("get_row", $_start); $row = $this->execute($query, $args)->fetch();
return $row ? $row : null; $this->count_time("get_row", $_start);
} return $row ? $row : null;
}
/** /**
* Execute an SQL query and return the first column of each row. * Execute an SQL query and return the first column of each row.
*/ */
public function get_col(string $query, array $args=array()): array { public function get_col(string $query, array $args=[]): array
$_start = microtime(true); {
$stmt = $this->execute($query, $args); $_start = microtime(true);
$res = array(); $stmt = $this->execute($query, $args);
foreach($stmt as $row) { $res = [];
$res[] = $row[0]; foreach ($stmt as $row) {
} $res[] = $row[0];
$this->count_time("get_col", $_start); }
return $res; $this->count_time("get_col", $_start);
} return $res;
}
/** /**
* Execute an SQL query and return the the first row => the second row. * Execute an SQL query and return the the first row => the second row.
*/ */
public function get_pairs(string $query, array $args=array()): array { public function get_pairs(string $query, array $args=[]): array
$_start = microtime(true); {
$stmt = $this->execute($query, $args); $_start = microtime(true);
$res = array(); $stmt = $this->execute($query, $args);
foreach($stmt as $row) { $res = [];
$res[$row[0]] = $row[1]; foreach ($stmt as $row) {
} $res[$row[0]] = $row[1];
$this->count_time("get_pairs", $_start); }
return $res; $this->count_time("get_pairs", $_start);
} return $res;
}
/** /**
* Execute an SQL query and return a single value. * Execute an SQL query and return a single value.
*/ */
public function get_one(string $query, array $args=array()) { public function get_one(string $query, array $args=[])
$_start = microtime(true); {
$row = $this->execute($query, $args)->fetch(); $_start = microtime(true);
$this->count_time("get_one", $_start); $row = $this->execute($query, $args)->fetch();
return $row[0]; $this->count_time("get_one", $_start);
} return $row[0];
}
/** /**
* Get the ID of the last inserted row. * Get the ID of the last inserted row.
*/ */
public function get_last_insert_id(string $seq): int { public function get_last_insert_id(string $seq): int
if($this->engine->name == "pgsql") { {
return $this->db->lastInsertId($seq); if ($this->engine->name == "pgsql") {
} return $this->db->lastInsertId($seq);
else { } else {
return $this->db->lastInsertId(); return $this->db->lastInsertId();
} }
} }
/** /**
* Create a table from pseudo-SQL. * Create a table from pseudo-SQL.
*/ */
public function create_table(string $name, string $data): void { public function create_table(string $name, string $data): void
if(is_null($this->engine)) { $this->connect_engine(); } {
$data = trim($data, ", \t\n\r\0\x0B"); // mysql doesn't like trailing commas if (is_null($this->engine)) {
$this->execute($this->engine->create_table_sql($name, $data)); $this->connect_engine();
} }
$data = trim($data, ", \t\n\r\0\x0B"); // mysql doesn't like trailing commas
$this->execute($this->engine->create_table_sql($name, $data));
}
/** /**
* Returns the number of tables present in the current database. * Returns the number of tables present in the current database.
* *
* @throws SCoreException * @throws SCoreException
*/ */
public function count_tables(): int { public function count_tables(): int
if(is_null($this->db) || is_null($this->engine)) $this->connect_db(); {
if (is_null($this->db) || is_null($this->engine)) {
$this->connect_db();
}
if($this->engine->name === "mysql") { if ($this->engine->name === "mysql") {
return count( return count(
$this->get_all("SHOW TABLES") $this->get_all("SHOW TABLES")
); );
} else if ($this->engine->name === "pgsql") { } elseif ($this->engine->name === "pgsql") {
return count( return count(
$this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'") $this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
); );
} else if ($this->engine->name === "sqlite") { } elseif ($this->engine->name === "sqlite") {
return count( return count(
$this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'") $this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'")
); );
} else { } else {
throw new SCoreException("Can't count tables for database type {$this->engine->name}"); throw new SCoreException("Can't count tables for database type {$this->engine->name}");
} }
} }
} }
class MockDatabase extends Database { class MockDatabase extends Database
/** @var int */ {
private $query_id = 0; /** @var int */
/** @var array */ private $query_id = 0;
private $responses = array(); /** @var array */
/** @var \NoCache|null */ private $responses = [];
public $cache = null; /** @var \NoCache|null */
public $cache = null;
public function __construct(array $responses = array()) { public function __construct(array $responses = [])
$this->cache = new NoCache(); {
$this->responses = $responses; $this->cache = new NoCache();
} $this->responses = $responses;
}
public function execute(string $query, array $params=array()): PDOStatement { public function execute(string $query, array $params=[]): PDOStatement
log_debug("mock-database", {
"QUERY: " . $query . log_debug(
"\nARGS: " . var_export($params, true) . "mock-database",
"\nRETURN: " . var_export($this->responses[$this->query_id], true) "QUERY: " . $query .
); "\nARGS: " . var_export($params, true) .
return $this->responses[$this->query_id++]; "\nRETURN: " . var_export($this->responses[$this->query_id], true)
} );
public function _execute(string $query, array $params=array()) { return $this->responses[$this->query_id++];
log_debug("mock-database", }
"QUERY: " . $query . public function _execute(string $query, array $params=[])
"\nARGS: " . var_export($params, true) . {
"\nRETURN: " . var_export($this->responses[$this->query_id], true) log_debug(
); "mock-database",
return $this->responses[$this->query_id++]; "QUERY: " . $query .
} "\nARGS: " . var_export($params, true) .
"\nRETURN: " . var_export($this->responses[$this->query_id], true)
);
return $this->responses[$this->query_id++];
}
public function get_all(string $query, array $args=array()): array {return $this->_execute($query, $args);} public function get_all(string $query, array $args=[]): array
public function get_row(string $query, array $args=array()) {return $this->_execute($query, $args);} {
public function get_col(string $query, array $args=array()): array {return $this->_execute($query, $args);} return $this->_execute($query, $args);
public function get_pairs(string $query, array $args=array()): array {return $this->_execute($query, $args);} }
public function get_one(string $query, array $args=array()) {return $this->_execute($query, $args);} public function get_row(string $query, array $args=[])
{
return $this->_execute($query, $args);
}
public function get_col(string $query, array $args=[]): array
{
return $this->_execute($query, $args);
}
public function get_pairs(string $query, array $args=[]): array
{
return $this->_execute($query, $args);
}
public function get_one(string $query, array $args=[])
{
return $this->_execute($query, $args);
}
public function get_last_insert_id(string $seq): int {return $this->query_id;} public function get_last_insert_id(string $seq): int
{
return $this->query_id;
}
public function scoreql_to_sql(string $sql): string {return $sql;} public function scoreql_to_sql(string $sql): string
public function create_table(string $name, string $def) {} {
public function connect_engine() {} return $sql;
}
public function create_table(string $name, string $def)
{
}
public function connect_engine()
{
}
} }

View File

@ -1,142 +1,188 @@
<?php <?php
class DBEngine { class DBEngine
/** @var null|string */ {
public $name = null; /** @var null|string */
public $name = null;
public function init(PDO $db) {} public function init(PDO $db)
{
}
public function scoreql_to_sql(string $scoreql): string { public function scoreql_to_sql(string $scoreql): string
return $scoreql; {
} return $scoreql;
}
public function create_table_sql(string $name, string $data): string { public function create_table_sql(string $name, string $data): string
return 'CREATE TABLE '.$name.' ('.$data.')'; {
} return 'CREATE TABLE '.$name.' ('.$data.')';
}
} }
class MySQL extends DBEngine { class MySQL extends DBEngine
/** @var string */ {
public $name = "mysql"; /** @var string */
public $name = "mysql";
public function init(PDO $db) { public function init(PDO $db)
$db->exec("SET NAMES utf8;"); {
} $db->exec("SET NAMES utf8;");
}
public function scoreql_to_sql(string $data): string { public function scoreql_to_sql(string $data): string
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY auto_increment", $data); {
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data); $data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY auto_increment", $data);
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data); $data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
$data = str_replace("SCORE_BOOL_N", "'N'", $data); $data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
$data = str_replace("SCORE_BOOL", "ENUM('Y', 'N')", $data); $data = str_replace("SCORE_BOOL_N", "'N'", $data);
$data = str_replace("SCORE_DATETIME", "DATETIME", $data); $data = str_replace("SCORE_BOOL", "ENUM('Y', 'N')", $data);
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data); $data = str_replace("SCORE_DATETIME", "DATETIME", $data);
$data = str_replace("SCORE_STRNORM", "", $data); $data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
$data = str_replace("SCORE_ILIKE", "LIKE", $data); $data = str_replace("SCORE_STRNORM", "", $data);
return $data; $data = str_replace("SCORE_ILIKE", "LIKE", $data);
} return $data;
}
public function create_table_sql(string $name, string $data): string { public function create_table_sql(string $name, string $data): string
$data = $this->scoreql_to_sql($data); {
$ctes = "ENGINE=InnoDB DEFAULT CHARSET='utf8'"; $data = $this->scoreql_to_sql($data);
return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes; $ctes = "ENGINE=InnoDB DEFAULT CHARSET='utf8'";
} return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes;
}
} }
class PostgreSQL extends DBEngine { class PostgreSQL extends DBEngine
/** @var string */ {
public $name = "pgsql"; /** @var string */
public $name = "pgsql";
public function init(PDO $db) { public function init(PDO $db)
if(array_key_exists('REMOTE_ADDR', $_SERVER)) { {
$db->exec("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';"); if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
} $db->exec("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';");
else { } else {
$db->exec("SET application_name TO 'shimmie [local]';"); $db->exec("SET application_name TO 'shimmie [local]';");
} }
$db->exec("SET statement_timeout TO 10000;"); $db->exec("SET statement_timeout TO 10000;");
} }
public function scoreql_to_sql(string $data): string { public function scoreql_to_sql(string $data): string
$data = str_replace("SCORE_AIPK", "SERIAL PRIMARY KEY", $data); {
$data = str_replace("SCORE_INET", "INET", $data); $data = str_replace("SCORE_AIPK", "SERIAL PRIMARY KEY", $data);
$data = str_replace("SCORE_BOOL_Y", "'t'", $data); $data = str_replace("SCORE_INET", "INET", $data);
$data = str_replace("SCORE_BOOL_N", "'f'", $data); $data = str_replace("SCORE_BOOL_Y", "'t'", $data);
$data = str_replace("SCORE_BOOL", "BOOL", $data); $data = str_replace("SCORE_BOOL_N", "'f'", $data);
$data = str_replace("SCORE_DATETIME", "TIMESTAMP", $data); $data = str_replace("SCORE_BOOL", "BOOL", $data);
$data = str_replace("SCORE_NOW", "current_timestamp", $data); $data = str_replace("SCORE_DATETIME", "TIMESTAMP", $data);
$data = str_replace("SCORE_STRNORM", "lower", $data); $data = str_replace("SCORE_NOW", "current_timestamp", $data);
$data = str_replace("SCORE_ILIKE", "ILIKE", $data); $data = str_replace("SCORE_STRNORM", "lower", $data);
return $data; $data = str_replace("SCORE_ILIKE", "ILIKE", $data);
} return $data;
}
public function create_table_sql(string $name, string $data): string { public function create_table_sql(string $name, string $data): string
$data = $this->scoreql_to_sql($data); {
return "CREATE TABLE $name ($data)"; $data = $this->scoreql_to_sql($data);
} return "CREATE TABLE $name ($data)";
}
} }
// shimmie functions for export to sqlite // shimmie functions for export to sqlite
function _unix_timestamp($date) { return strtotime($date); } function _unix_timestamp($date)
function _now() { return date("Y-m-d h:i:s"); } {
function _floor($a) { return floor($a); } return strtotime($date);
function _log($a, $b=null) {
if(is_null($b)) return log($a);
else return log($a, $b);
} }
function _isnull($a) { return is_null($a); } function _now()
function _md5($a) { return md5($a); } {
function _concat($a, $b) { return $a . $b; } return date("Y-m-d h:i:s");
function _lower($a) { return strtolower($a); } }
function _rand() { return rand(); } function _floor($a)
function _ln($n) { return log($n); } {
return floor($a);
class SQLite extends DBEngine { }
/** @var string */ function _log($a, $b=null)
public $name = "sqlite"; {
if (is_null($b)) {
public function init(PDO $db) { return log($a);
ini_set('sqlite.assoc_case', 0); } else {
$db->exec("PRAGMA foreign_keys = ON;"); return log($a, $b);
$db->sqliteCreateFunction('UNIX_TIMESTAMP', '_unix_timestamp', 1); }
$db->sqliteCreateFunction('now', '_now', 0); }
$db->sqliteCreateFunction('floor', '_floor', 1); function _isnull($a)
$db->sqliteCreateFunction('log', '_log'); {
$db->sqliteCreateFunction('isnull', '_isnull', 1); return is_null($a);
$db->sqliteCreateFunction('md5', '_md5', 1); }
$db->sqliteCreateFunction('concat', '_concat', 2); function _md5($a)
$db->sqliteCreateFunction('lower', '_lower', 1); {
$db->sqliteCreateFunction('rand', '_rand', 0); return md5($a);
$db->sqliteCreateFunction('ln', '_ln', 1); }
} function _concat($a, $b)
{
public function scoreql_to_sql(string $data): string { return $a . $b;
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY", $data); }
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data); function _lower($a)
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data); {
$data = str_replace("SCORE_BOOL_N", "'N'", $data); return strtolower($a);
$data = str_replace("SCORE_BOOL", "CHAR(1)", $data); }
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data); function _rand()
$data = str_replace("SCORE_STRNORM", "lower", $data); {
$data = str_replace("SCORE_ILIKE", "LIKE", $data); return rand();
return $data; }
} function _ln($n)
{
public function create_table_sql(string $name, string $data): string { return log($n);
$data = $this->scoreql_to_sql($data); }
$cols = array();
$extras = ""; class SQLite extends DBEngine
foreach(explode(",", $data) as $bit) { {
$matches = array(); /** @var string */
if(preg_match("/(UNIQUE)? ?INDEX\s*\((.*)\)/", $bit, $matches)) { public $name = "sqlite";
$uni = $matches[1];
$col = $matches[2]; public function init(PDO $db)
$extras .= "CREATE $uni INDEX {$name}_{$col} ON {$name}({$col});"; {
} ini_set('sqlite.assoc_case', 0);
else { $db->exec("PRAGMA foreign_keys = ON;");
$cols[] = $bit; $db->sqliteCreateFunction('UNIX_TIMESTAMP', '_unix_timestamp', 1);
} $db->sqliteCreateFunction('now', '_now', 0);
} $db->sqliteCreateFunction('floor', '_floor', 1);
$cols_redone = implode(", ", $cols); $db->sqliteCreateFunction('log', '_log');
return "CREATE TABLE $name ($cols_redone); $extras"; $db->sqliteCreateFunction('isnull', '_isnull', 1);
} $db->sqliteCreateFunction('md5', '_md5', 1);
$db->sqliteCreateFunction('concat', '_concat', 2);
$db->sqliteCreateFunction('lower', '_lower', 1);
$db->sqliteCreateFunction('rand', '_rand', 0);
$db->sqliteCreateFunction('ln', '_ln', 1);
}
public function scoreql_to_sql(string $data): string
{
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY", $data);
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
$data = str_replace("SCORE_BOOL", "CHAR(1)", $data);
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
$data = str_replace("SCORE_STRNORM", "lower", $data);
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
return $data;
}
public function create_table_sql(string $name, string $data): string
{
$data = $this->scoreql_to_sql($data);
$cols = [];
$extras = "";
foreach (explode(",", $data) as $bit) {
$matches = [];
if (preg_match("/(UNIQUE)? ?INDEX\s*\((.*)\)/", $bit, $matches)) {
$uni = $matches[1];
$col = $matches[2];
$extras .= "CREATE $uni INDEX {$name}_{$col} ON {$name}({$col});";
} else {
$cols[] = $bit;
}
}
$cols_redone = implode(", ", $cols);
return "CREATE TABLE $name ($cols_redone); $extras";
}
} }

View File

@ -5,64 +5,66 @@
* *
* A generic email. * A generic email.
*/ */
class Email { class Email
/** @var string */ {
public $to; /** @var string */
/** @var string */ public $to;
public $subject; /** @var string */
/** @var string */ public $subject;
public $header; /** @var string */
/** @var null|string */ public $header;
public $style; /** @var null|string */
/** @var null|string */ public $style;
public $header_img; /** @var null|string */
/** @var null|string */ public $header_img;
public $sitename; /** @var null|string */
/** @var null|string */ public $sitename;
public $sitedomain; /** @var null|string */
/** @var null|string */ public $sitedomain;
public $siteemail; /** @var null|string */
/** @var string */ public $siteemail;
public $date; /** @var string */
/** @var string */ public $date;
public $body; /** @var string */
/** @var null|string */ public $body;
public $footer; /** @var null|string */
public $footer;
public function __construct(string $to, string $subject, string $header, string $body) { public function __construct(string $to, string $subject, string $header, string $body)
global $config; {
$this->to = $to; global $config;
$this->to = $to;
$sub_prefix = $config->get_string("mail_sub");
$sub_prefix = $config->get_string("mail_sub");
if(!isset($sub_prefix)){
$this->subject = $subject; if (!isset($sub_prefix)) {
} $this->subject = $subject;
else{ } else {
$this->subject = $sub_prefix." ".$subject; $this->subject = $sub_prefix." ".$subject;
} }
$this->style = $config->get_string("mail_style"); $this->style = $config->get_string("mail_style");
$this->header = html_escape($header); $this->header = html_escape($header);
$this->header_img = $config->get_string("mail_img"); $this->header_img = $config->get_string("mail_img");
$this->sitename = $config->get_string("site_title"); $this->sitename = $config->get_string("site_title");
$this->sitedomain = make_http(make_link("")); $this->sitedomain = make_http(make_link(""));
$this->siteemail = $config->get_string("site_email"); $this->siteemail = $config->get_string("site_email");
$this->date = date("F j, Y"); $this->date = date("F j, Y");
$this->body = $body; $this->body = $body;
$this->footer = $config->get_string("mail_fot"); $this->footer = $config->get_string("mail_fot");
} }
public function send(): bool { public function send(): bool
$headers = "From: ".$this->sitename." <".$this->siteemail.">\r\n"; {
$headers .= "Reply-To: ".$this->siteemail."\r\n"; $headers = "From: ".$this->sitename." <".$this->siteemail.">\r\n";
$headers .= "X-Mailer: PHP/" . phpversion(). "\r\n"; $headers .= "Reply-To: ".$this->siteemail."\r\n";
$headers .= "errors-to: ".$this->siteemail."\r\n"; $headers .= "X-Mailer: PHP/" . phpversion(). "\r\n";
$headers .= "Date: " . date(DATE_RFC2822); $headers .= "errors-to: ".$this->siteemail."\r\n";
$headers .= 'MIME-Version: 1.0' . "\r\n"; $headers .= "Date: " . date(DATE_RFC2822);
$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n"; $headers .= 'MIME-Version: 1.0' . "\r\n";
$message = ' $headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
$message = '
<html> <html>
<head> <head>
@ -118,15 +120,13 @@ Copyright (C) <a href="'.$this->sitedomain.'">'.$this->sitename.'</a><br />
</body> </body>
</html> </html>
'; ';
$sent = mail($this->to, $this->subject, $message, $headers); $sent = mail($this->to, $this->subject, $message, $headers);
if($sent){ if ($sent) {
log_info("mail", "Sent message '$this->subject' to '$this->to'"); log_info("mail", "Sent message '$this->subject' to '$this->to'");
} } else {
else{ log_info("mail", "Error sending message '$this->subject' to '$this->to'");
log_info("mail", "Error sending message '$this->subject' to '$this->to'"); }
}
return $sent;
return $sent; }
}
} }

View File

@ -4,8 +4,11 @@
* *
* An event is anything that can be passed around via send_event($blah) * An event is anything that can be passed around via send_event($blah)
*/ */
abstract class Event { abstract class Event
public function __construct() {} {
public function __construct()
{
}
} }
@ -16,7 +19,9 @@ abstract class Event {
* *
* This event is sent before $user is set to anything * This event is sent before $user is set to anything
*/ */
class InitExtEvent extends Event {} class InitExtEvent extends Event
{
}
/** /**
@ -27,188 +32,197 @@ class InitExtEvent extends Event {}
* true and ignores the matched part, such that $event->count_args() = 1 and * true and ignores the matched part, such that $event->count_args() = 1 and
* $event->get_arg(0) = "42" * $event->get_arg(0) = "42"
*/ */
class PageRequestEvent extends Event { class PageRequestEvent extends Event
/** {
* @var array /**
*/ * @var array
public $args; */
public $args;
/** /**
* @var int * @var int
*/ */
public $arg_count; public $arg_count;
/** /**
* @var int * @var int
*/ */
public $part_count; public $part_count;
public function __construct(string $path) { public function __construct(string $path)
global $config; {
global $config;
// trim starting slashes // trim starting slashes
$path = ltrim($path, "/"); $path = ltrim($path, "/");
// if path is not specified, use the default front page // if path is not specified, use the default front page
if(empty($path)) { /* empty is faster than strlen */ if (empty($path)) { /* empty is faster than strlen */
$path = $config->get_string('front_page'); $path = $config->get_string('front_page');
} }
// break the path into parts // break the path into parts
$args = explode('/', $path); $args = explode('/', $path);
// voodoo so that an arg can contain a slash; is // voodoo so that an arg can contain a slash; is
// this still needed? // this still needed?
if(strpos($path, "^") !== FALSE) { if (strpos($path, "^") !== false) {
$unescaped = array(); $unescaped = [];
foreach($args as $part) { foreach ($args as $part) {
$unescaped[] = _decaret($part); $unescaped[] = _decaret($part);
} }
$args = $unescaped; $args = $unescaped;
} }
$this->args = $args; $this->args = $args;
$this->arg_count = count($args); $this->arg_count = count($args);
} }
/** /**
* Test if the requested path matches a given pattern. * Test if the requested path matches a given pattern.
* *
* If it matches, store the remaining path elements in $args * If it matches, store the remaining path elements in $args
*/ */
public function page_matches(string $name): bool { public function page_matches(string $name): bool
$parts = explode("/", $name); {
$this->part_count = count($parts); $parts = explode("/", $name);
$this->part_count = count($parts);
if($this->part_count > $this->arg_count) { if ($this->part_count > $this->arg_count) {
return false; return false;
} }
for($i=0; $i<$this->part_count; $i++) { for ($i=0; $i<$this->part_count; $i++) {
if($parts[$i] != $this->args[$i]) { if ($parts[$i] != $this->args[$i]) {
return false; return false;
} }
} }
return true; return true;
} }
/** /**
* Get the n th argument of the page request (if it exists.) * Get the n th argument of the page request (if it exists.)
*/ */
public function get_arg(int $n): ?string { public function get_arg(int $n): ?string
$offset = $this->part_count + $n; {
if($offset >= 0 && $offset < $this->arg_count) { $offset = $this->part_count + $n;
return $this->args[$offset]; if ($offset >= 0 && $offset < $this->arg_count) {
} return $this->args[$offset];
else { } else {
return null; return null;
} }
} }
/** /**
* Returns the number of arguments the page request has. * Returns the number of arguments the page request has.
*/ */
public function count_args(): int { public function count_args(): int
return int_escape($this->arg_count - $this->part_count); {
} return int_escape($this->arg_count - $this->part_count);
}
/* /*
* Many things use these functions * Many things use these functions
*/ */
public function get_search_terms(): array { public function get_search_terms(): array
$search_terms = array(); {
if($this->count_args() === 2) { $search_terms = [];
$search_terms = Tag::explode($this->get_arg(0)); if ($this->count_args() === 2) {
} $search_terms = Tag::explode($this->get_arg(0));
return $search_terms; }
} return $search_terms;
}
public function get_page_number(): int { public function get_page_number(): int
$page_number = 1; {
if($this->count_args() === 1) { $page_number = 1;
$page_number = int_escape($this->get_arg(0)); if ($this->count_args() === 1) {
} $page_number = int_escape($this->get_arg(0));
else if($this->count_args() === 2) { } elseif ($this->count_args() === 2) {
$page_number = int_escape($this->get_arg(1)); $page_number = int_escape($this->get_arg(1));
} }
if($page_number === 0) $page_number = 1; // invalid -> 0 if ($page_number === 0) {
return $page_number; $page_number = 1;
} } // invalid -> 0
return $page_number;
}
public function get_page_size(): int { public function get_page_size(): int
global $config; {
return $config->get_int('index_images'); global $config;
} return $config->get_int('index_images');
}
} }
/** /**
* Sent when index.php is called from the command line * Sent when index.php is called from the command line
*/ */
class CommandEvent extends Event { class CommandEvent extends Event
/** {
* @var string /**
*/ * @var string
public $cmd = "help"; */
public $cmd = "help";
/** /**
* @var array * @var array
*/ */
public $args = array(); public $args = [];
/** /**
* #param string[] $args * #param string[] $args
*/ */
public function __construct(array $args) { public function __construct(array $args)
global $user; {
global $user;
$opts = array(); $opts = [];
$log_level = SCORE_LOG_WARNING; $log_level = SCORE_LOG_WARNING;
$arg_count = count($args); $arg_count = count($args);
for($i=1; $i<$arg_count; $i++) { for ($i=1; $i<$arg_count; $i++) {
switch($args[$i]) { switch ($args[$i]) {
case '-u': case '-u':
$user = User::by_name($args[++$i]); $user = User::by_name($args[++$i]);
if(is_null($user)) { if (is_null($user)) {
die("Unknown user"); die("Unknown user");
} }
break; break;
case '-q': case '-q':
$log_level += 10; $log_level += 10;
break; break;
case '-v': case '-v':
$log_level -= 10; $log_level -= 10;
break; break;
default: default:
$opts[] = $args[$i]; $opts[] = $args[$i];
break; break;
} }
} }
define("CLI_LOG_LEVEL", $log_level); define("CLI_LOG_LEVEL", $log_level);
if(count($opts) > 0) { if (count($opts) > 0) {
$this->cmd = $opts[0]; $this->cmd = $opts[0];
$this->args = array_slice($opts, 1); $this->args = array_slice($opts, 1);
} } else {
else { print "\n";
print "\n"; print "Usage: php {$args[0]} [flags] [command]\n";
print "Usage: php {$args[0]} [flags] [command]\n"; print "\n";
print "\n"; print "Flags:\n";
print "Flags:\n"; print " -u [username]\n";
print " -u [username]\n"; print " Log in as the specified user\n";
print " Log in as the specified user\n"; print " -q / -v\n";
print " -q / -v\n"; print " Be quieter / more verbose\n";
print " Be quieter / more verbose\n"; print " Scale is debug - info - warning - error - critical\n";
print " Scale is debug - info - warning - error - critical\n"; print " Default is to show warnings and above\n";
print " Default is to show warnings and above\n"; print " \n";
print " \n"; print "Currently known commands:\n";
print "Currently known commands:\n"; }
} }
}
} }
@ -216,82 +230,85 @@ class CommandEvent extends Event {
* A signal that some text needs formatting, the event carries * A signal that some text needs formatting, the event carries
* both the text and the result * both the text and the result
*/ */
class TextFormattingEvent extends Event { class TextFormattingEvent extends Event
/** {
* For reference /**
* * For reference
* @var string *
*/ * @var string
public $original; */
public $original;
/** /**
* with formatting applied * with formatting applied
* *
* @var string * @var string
*/ */
public $formatted; public $formatted;
/** /**
* with formatting removed * with formatting removed
* *
* @var string * @var string
*/ */
public $stripped; public $stripped;
public function __construct(string $text) { public function __construct(string $text)
$h_text = html_escape(trim($text)); {
$this->original = $h_text; $h_text = html_escape(trim($text));
$this->formatted = $h_text; $this->original = $h_text;
$this->stripped = $h_text; $this->formatted = $h_text;
} $this->stripped = $h_text;
}
} }
/** /**
* A signal that something needs logging * A signal that something needs logging
*/ */
class LogEvent extends Event { class LogEvent extends Event
/** {
* a category, normally the extension name /**
* * a category, normally the extension name
* @var string *
*/ * @var string
public $section; */
public $section;
/** /**
* See python... * See python...
* *
* @var int * @var int
*/ */
public $priority = 0; public $priority = 0;
/** /**
* Free text to be logged * Free text to be logged
* *
* @var string * @var string
*/ */
public $message; public $message;
/** /**
* The time that the event was created * The time that the event was created
* *
* @var int * @var int
*/ */
public $time; public $time;
/** /**
* Extra data to be held separate * Extra data to be held separate
* *
* @var array * @var array
*/ */
public $args; public $args;
public function __construct(string $section, int $priority, string $message, array $args) { public function __construct(string $section, int $priority, string $message, array $args)
$this->section = $section; {
$this->priority = $priority; $this->section = $section;
$this->message = $message; $this->priority = $priority;
$this->args = $args; $this->message = $message;
$this->time = time(); $this->args = $args;
} $this->time = time();
}
} }

View File

@ -5,14 +5,18 @@
* *
* A base exception to be caught by the upper levels. * A base exception to be caught by the upper levels.
*/ */
class SCoreException extends Exception {} class SCoreException extends Exception
{
}
/** /**
* Class PermissionDeniedException * Class PermissionDeniedException
* *
* A fairly common, generic exception. * A fairly common, generic exception.
*/ */
class PermissionDeniedException extends SCoreException {} class PermissionDeniedException extends SCoreException
{
}
/** /**
* Class ImageDoesNotExist * Class ImageDoesNotExist
@ -21,9 +25,13 @@ class PermissionDeniedException extends SCoreException {}
* *
* Example: Image::by_id(-1) returns null * Example: Image::by_id(-1) returns null
*/ */
class ImageDoesNotExist extends SCoreException {} class ImageDoesNotExist extends SCoreException
{
}
/* /*
* For validate_input() * For validate_input()
*/ */
class InvalidInput extends SCoreException {} class InvalidInput extends SCoreException
{
}

View File

@ -81,50 +81,53 @@
* Then re-implemented by Shish after he broke the forum and couldn't * Then re-implemented by Shish after he broke the forum and couldn't
* find the thread where the original was posted >_< * find the thread where the original was posted >_<
*/ */
abstract class Extension { abstract class Extension
/** @var array which DBs this ext supports (blank for 'all') */ {
protected $db_support = []; /** @var array which DBs this ext supports (blank for 'all') */
protected $db_support = [];
/** @var Themelet this theme's Themelet object */ /** @var Themelet this theme's Themelet object */
public $theme; public $theme;
public function __construct() { public function __construct()
$this->theme = $this->get_theme_object(get_called_class()); {
} $this->theme = $this->get_theme_object(get_called_class());
}
public function is_live(): bool { public function is_live(): bool
global $database; {
return ( global $database;
empty($this->db_support) || return (
in_array($database->get_driver_name(), $this->db_support) empty($this->db_support) ||
); in_array($database->get_driver_name(), $this->db_support)
} );
}
/** /**
* Find the theme object for a given extension. * Find the theme object for a given extension.
*/ */
private function get_theme_object(string $base): ?Themelet { private function get_theme_object(string $base): ?Themelet
$custom = 'Custom'.$base.'Theme'; {
$normal = $base.'Theme'; $custom = 'Custom'.$base.'Theme';
$normal = $base.'Theme';
if(class_exists($custom)) { if (class_exists($custom)) {
return new $custom(); return new $custom();
} } elseif (class_exists($normal)) {
elseif(class_exists($normal)) { return new $normal();
return new $normal(); } else {
} return null;
else { }
return null; }
}
}
/** /**
* Override this to change the priority of the extension, * Override this to change the priority of the extension,
* lower numbered ones will receive events first. * lower numbered ones will receive events first.
*/ */
public function get_priority(): int { public function get_priority(): int
return 50; {
} return 50;
}
} }
/** /**
@ -132,14 +135,16 @@ abstract class Extension {
* *
* Several extensions have this in common, make a common API. * Several extensions have this in common, make a common API.
*/ */
abstract class FormatterExtension extends Extension { abstract class FormatterExtension extends Extension
public function onTextFormatting(TextFormattingEvent $event) { {
$event->formatted = $this->format($event->formatted); public function onTextFormatting(TextFormattingEvent $event)
$event->stripped = $this->strip($event->stripped); {
} $event->formatted = $this->format($event->formatted);
$event->stripped = $this->strip($event->stripped);
}
abstract public function format(string $text): string; abstract public function format(string $text): string;
abstract public function strip(string $text): string; abstract public function strip(string $text): string;
} }
/** /**
@ -148,100 +153,100 @@ abstract class FormatterExtension extends Extension {
* This too is a common class of extension with many methods in common, * This too is a common class of extension with many methods in common,
* so we have a base class to extend from. * so we have a base class to extend from.
*/ */
abstract class DataHandlerExtension extends Extension { abstract class DataHandlerExtension extends Extension
public function onDataUpload(DataUploadEvent $event) { {
$supported_ext = $this->supported_ext($event->type); public function onDataUpload(DataUploadEvent $event)
$check_contents = $this->check_contents($event->tmpname); {
if($supported_ext && $check_contents) { $supported_ext = $this->supported_ext($event->type);
move_upload_to_archive($event); $check_contents = $this->check_contents($event->tmpname);
send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); if ($supported_ext && $check_contents) {
move_upload_to_archive($event);
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
/* Check if we are replacing an image */ /* Check if we are replacing an image */
if(array_key_exists('replace', $event->metadata) && isset($event->metadata['replace'])) { if (array_key_exists('replace', $event->metadata) && isset($event->metadata['replace'])) {
/* hax: This seems like such a dirty way to do this.. */ /* hax: This seems like such a dirty way to do this.. */
/* Validate things */ /* Validate things */
$image_id = int_escape($event->metadata['replace']); $image_id = int_escape($event->metadata['replace']);
/* Check to make sure the image exists. */ /* Check to make sure the image exists. */
$existing = Image::by_id($image_id); $existing = Image::by_id($image_id);
if(is_null($existing)) { if (is_null($existing)) {
throw new UploadException("Image to replace does not exist!"); throw new UploadException("Image to replace does not exist!");
} }
if ($existing->hash === $event->metadata['hash']) { if ($existing->hash === $event->metadata['hash']) {
throw new UploadException("The uploaded image is the same as the one to replace."); throw new UploadException("The uploaded image is the same as the one to replace.");
} }
// even more hax.. // even more hax..
$event->metadata['tags'] = $existing->get_tag_list(); $event->metadata['tags'] = $existing->get_tag_list();
$image = $this->create_image_from_data(warehouse_path("images", $event->metadata['hash']), $event->metadata); $image = $this->create_image_from_data(warehouse_path("images", $event->metadata['hash']), $event->metadata);
if(is_null($image)) { if (is_null($image)) {
throw new UploadException("Data handler failed to create image object from data"); throw new UploadException("Data handler failed to create image object from data");
} }
$ire = new ImageReplaceEvent($image_id, $image); $ire = new ImageReplaceEvent($image_id, $image);
send_event($ire); send_event($ire);
$event->image_id = $image_id; $event->image_id = $image_id;
} } else {
else { $image = $this->create_image_from_data(warehouse_path("images", $event->hash), $event->metadata);
$image = $this->create_image_from_data(warehouse_path("images", $event->hash), $event->metadata); if (is_null($image)) {
if(is_null($image)) { throw new UploadException("Data handler failed to create image object from data");
throw new UploadException("Data handler failed to create image object from data"); }
} $iae = new ImageAdditionEvent($image);
$iae = new ImageAdditionEvent($image); send_event($iae);
send_event($iae); $event->image_id = $iae->image->id;
$event->image_id = $iae->image->id;
// Rating Stuff. // Rating Stuff.
if(!empty($event->metadata['rating'])){ if (!empty($event->metadata['rating'])) {
$rating = $event->metadata['rating']; $rating = $event->metadata['rating'];
send_event(new RatingSetEvent($image, $rating)); send_event(new RatingSetEvent($image, $rating));
} }
// Locked Stuff. // Locked Stuff.
if(!empty($event->metadata['locked'])){ if (!empty($event->metadata['locked'])) {
$locked = $event->metadata['locked']; $locked = $event->metadata['locked'];
send_event(new LockSetEvent($image, !empty($locked))); send_event(new LockSetEvent($image, !empty($locked)));
} }
} }
} } elseif ($supported_ext && !$check_contents) {
elseif($supported_ext && !$check_contents){ throw new UploadException("Invalid or corrupted file");
throw new UploadException("Invalid or corrupted file"); }
} }
}
public function onThumbnailGeneration(ThumbnailGenerationEvent $event) { public function onThumbnailGeneration(ThumbnailGenerationEvent $event)
if($this->supported_ext($event->type)) { {
if (method_exists($this, 'create_thumb_force') && $event->force == true) { if ($this->supported_ext($event->type)) {
$this->create_thumb_force($event->hash); if (method_exists($this, 'create_thumb_force') && $event->force == true) {
} $this->create_thumb_force($event->hash);
else { } else {
$this->create_thumb($event->hash); $this->create_thumb($event->hash);
} }
} }
} }
public function onDisplayingImage(DisplayingImageEvent $event) { public function onDisplayingImage(DisplayingImageEvent $event)
global $page; {
if($this->supported_ext($event->image->ext)) { global $page;
$this->theme->display_image($page, $event->image); if ($this->supported_ext($event->image->ext)) {
} $this->theme->display_image($page, $event->image);
} }
}
/* /*
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = $this->setup(); $sb = $this->setup();
if($sb) $event->panel->add_block($sb); if($sb) $event->panel->add_block($sb);
} }
protected function setup() {} protected function setup() {}
*/ */
abstract protected function supported_ext(string $ext): bool; abstract protected function supported_ext(string $ext): bool;
abstract protected function check_contents(string $tmpname): bool; abstract protected function check_contents(string $tmpname): bool;
abstract protected function create_image_from_data(string $filename, array $metadata); abstract protected function create_image_from_data(string $filename, array $metadata);
abstract protected function create_thumb(string $hash): bool; abstract protected function create_thumb(string $hash): bool;
} }

View File

@ -3,99 +3,111 @@
/** /**
* An image is being added to the database. * An image is being added to the database.
*/ */
class ImageAdditionEvent extends Event { class ImageAdditionEvent extends Event
/** @var User */ {
public $user; /** @var User */
public $user;
/** @var Image */ /** @var Image */
public $image; public $image;
/** /**
* Inserts a new image into the database with its associated * Inserts a new image into the database with its associated
* information. Also calls TagSetEvent to set the tags for * information. Also calls TagSetEvent to set the tags for
* this new image. * this new image.
*/ */
public function __construct(Image $image) { public function __construct(Image $image)
$this->image = $image; {
} $this->image = $image;
}
} }
class ImageAdditionException extends SCoreException { class ImageAdditionException extends SCoreException
public $error; {
public $error;
public function __construct(string $error) { public function __construct(string $error)
$this->error = $error; {
} $this->error = $error;
}
} }
/** /**
* An image is being deleted. * An image is being deleted.
*/ */
class ImageDeletionEvent extends Event { class ImageDeletionEvent extends Event
/** @var \Image */ {
public $image; /** @var \Image */
public $image;
/** /**
* Deletes an image. * Deletes an image.
* *
* Used by things like tags and comments handlers to * Used by things like tags and comments handlers to
* clean out related rows in their tables. * clean out related rows in their tables.
*/ */
public function __construct(Image $image) { public function __construct(Image $image)
$this->image = $image; {
} $this->image = $image;
}
} }
/** /**
* An image is being replaced. * An image is being replaced.
*/ */
class ImageReplaceEvent extends Event { class ImageReplaceEvent extends Event
/** @var int */ {
public $id; /** @var int */
/** @var \Image */ public $id;
public $image; /** @var \Image */
public $image;
/** /**
* Replaces an image. * Replaces an image.
* *
* Updates an existing ID in the database to use a new image * Updates an existing ID in the database to use a new image
* file, leaving the tags and such unchanged. Also removes * file, leaving the tags and such unchanged. Also removes
* the old image file and thumbnail from the disk. * the old image file and thumbnail from the disk.
*/ */
public function __construct(int $id, Image $image) { public function __construct(int $id, Image $image)
$this->id = $id; {
$this->image = $image; $this->id = $id;
} $this->image = $image;
}
} }
class ImageReplaceException extends SCoreException { class ImageReplaceException extends SCoreException
/** @var string */ {
public $error; /** @var string */
public $error;
public function __construct(string $error) { public function __construct(string $error)
$this->error = $error; {
} $this->error = $error;
}
} }
/** /**
* Request a thumbnail be made for an image object. * Request a thumbnail be made for an image object.
*/ */
class ThumbnailGenerationEvent extends Event { class ThumbnailGenerationEvent extends Event
/** @var string */ {
public $hash; /** @var string */
/** @var string */ public $hash;
public $type; /** @var string */
/** @var bool */ public $type;
public $force; /** @var bool */
public $force;
/** /**
* Request a thumbnail be made for an image object * Request a thumbnail be made for an image object
*/ */
public function __construct(string $hash, string $type, bool $force=false) { public function __construct(string $hash, string $type, bool $force=false)
$this->hash = $hash; {
$this->type = $type; $this->hash = $hash;
$this->force = $force; $this->type = $type;
} $this->force = $force;
}
} }
@ -105,21 +117,24 @@ class ThumbnailGenerationEvent extends Event {
* $original -- the formatting string, for reference * $original -- the formatting string, for reference
* $image -- the image who's link is being parsed * $image -- the image who's link is being parsed
*/ */
class ParseLinkTemplateEvent extends Event { class ParseLinkTemplateEvent extends Event
/** @var string */ {
public $link; /** @var string */
/** @var string */ public $link;
public $original; /** @var string */
/** @var \Image */ public $original;
public $image; /** @var \Image */
public $image;
public function __construct(string $link, Image $image) { public function __construct(string $link, Image $image)
$this->link = $link; {
$this->original = $link; $this->link = $link;
$this->image = $image; $this->original = $link;
} $this->image = $image;
}
public function replace(string $needle, string $replace): void { public function replace(string $needle, string $replace): void
$this->link = str_replace($needle, $replace, $this->link); {
} $this->link = str_replace($needle, $replace, $this->link);
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,15 +9,16 @@
* *
* @throws UploadException * @throws UploadException
*/ */
function move_upload_to_archive(DataUploadEvent $event) { function move_upload_to_archive(DataUploadEvent $event)
$target = warehouse_path("images", $event->hash); {
if(!@copy($event->tmpname, $target)) { $target = warehouse_path("images", $event->hash);
$errors = error_get_last(); if (!@copy($event->tmpname, $target)) {
throw new UploadException( $errors = error_get_last();
"Failed to copy file from uploads ({$event->tmpname}) to archive ($target): ". throw new UploadException(
"{$errors['type']} / {$errors['message']}" "Failed to copy file from uploads ({$event->tmpname}) to archive ($target): ".
); "{$errors['type']} / {$errors['message']}"
} );
}
} }
/** /**
@ -25,45 +26,46 @@ function move_upload_to_archive(DataUploadEvent $event) {
* *
* #return string[] * #return string[]
*/ */
function add_dir(string $base): array { function add_dir(string $base): array
$results = array(); {
$results = [];
foreach(list_files($base) as $full_path) { foreach (list_files($base) as $full_path) {
$short_path = str_replace($base, "", $full_path); $short_path = str_replace($base, "", $full_path);
$filename = basename($full_path); $filename = basename($full_path);
$tags = path_to_tags($short_path); $tags = path_to_tags($short_path);
$result = "$short_path (".str_replace(" ", ", ", $tags).")... "; $result = "$short_path (".str_replace(" ", ", ", $tags).")... ";
try { try {
add_image($full_path, $filename, $tags); add_image($full_path, $filename, $tags);
$result .= "ok"; $result .= "ok";
} } catch (UploadException $ex) {
catch(UploadException $ex) { $result .= "failed: ".$ex->getMessage();
$result .= "failed: ".$ex->getMessage(); }
} $results[] = $result;
$results[] = $result; }
}
return $results; return $results;
} }
function add_image(string $tmpname, string $filename, string $tags): void { function add_image(string $tmpname, string $filename, string $tags): void
assert(file_exists($tmpname)); {
assert(file_exists($tmpname));
$pathinfo = pathinfo($filename); $pathinfo = pathinfo($filename);
if(!array_key_exists('extension', $pathinfo)) { if (!array_key_exists('extension', $pathinfo)) {
throw new UploadException("File has no extension"); throw new UploadException("File has no extension");
} }
$metadata = array(); $metadata = [];
$metadata['filename'] = $pathinfo['basename']; $metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension']; $metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = Tag::explode($tags); $metadata['tags'] = Tag::explode($tags);
$metadata['source'] = null; $metadata['source'] = null;
$event = new DataUploadEvent($tmpname, $metadata); $event = new DataUploadEvent($tmpname, $metadata);
send_event($event); send_event($event);
if($event->image_id == -1) { if ($event->image_id == -1) {
throw new UploadException("File type not recognised"); throw new UploadException("File type not recognised");
} }
} }
/** /**
@ -72,26 +74,34 @@ function add_image(string $tmpname, string $filename, string $tags): void {
* *
* #return int[] * #return int[]
*/ */
function get_thumbnail_size(int $orig_width, int $orig_height): array { function get_thumbnail_size(int $orig_width, int $orig_height): array
global $config; {
global $config;
if($orig_width === 0) $orig_width = 192; if ($orig_width === 0) {
if($orig_height === 0) $orig_height = 192; $orig_width = 192;
}
if ($orig_height === 0) {
$orig_height = 192;
}
if($orig_width > $orig_height * 5) $orig_width = $orig_height * 5; if ($orig_width > $orig_height * 5) {
if($orig_height > $orig_width * 5) $orig_height = $orig_width * 5; $orig_width = $orig_height * 5;
}
if ($orig_height > $orig_width * 5) {
$orig_height = $orig_width * 5;
}
$max_width = $config->get_int('thumb_width'); $max_width = $config->get_int('thumb_width');
$max_height = $config->get_int('thumb_height'); $max_height = $config->get_int('thumb_height');
$xscale = ($max_height / $orig_height); $xscale = ($max_height / $orig_height);
$yscale = ($max_width / $orig_width); $yscale = ($max_width / $orig_width);
$scale = ($xscale < $yscale) ? $xscale : $yscale; $scale = ($xscale < $yscale) ? $xscale : $yscale;
if($scale > 1 && $config->get_bool('thumb_upscale')) { if ($scale > 1 && $config->get_bool('thumb_upscale')) {
return array((int)$orig_width, (int)$orig_height); return [(int)$orig_width, (int)$orig_height];
} } else {
else { return [(int)($orig_width*$scale), (int)($orig_height*$scale)];
return array((int)($orig_width*$scale), (int)($orig_height*$scale)); }
}
} }

View File

@ -1,49 +1,58 @@
<?php <?php
class Querylet { class Querylet
/** @var string */ {
public $sql; /** @var string */
/** @var array */ public $sql;
public $variables; /** @var array */
public $variables;
public function __construct(string $sql, array $variables=array()) { public function __construct(string $sql, array $variables=[])
$this->sql = $sql; {
$this->variables = $variables; $this->sql = $sql;
} $this->variables = $variables;
}
public function append(Querylet $querylet) { public function append(Querylet $querylet)
$this->sql .= $querylet->sql; {
$this->variables = array_merge($this->variables, $querylet->variables); $this->sql .= $querylet->sql;
} $this->variables = array_merge($this->variables, $querylet->variables);
}
public function append_sql(string $sql) { public function append_sql(string $sql)
$this->sql .= $sql; {
} $this->sql .= $sql;
}
public function add_variable($var) { public function add_variable($var)
$this->variables[] = $var; {
} $this->variables[] = $var;
}
} }
class TagQuerylet { class TagQuerylet
/** @var string */ {
public $tag; /** @var string */
/** @var bool */ public $tag;
public $positive; /** @var bool */
public $positive;
public function __construct(string $tag, bool $positive) { public function __construct(string $tag, bool $positive)
$this->tag = $tag; {
$this->positive = $positive; $this->tag = $tag;
} $this->positive = $positive;
}
} }
class ImgQuerylet { class ImgQuerylet
/** @var \Querylet */ {
public $qlet; /** @var \Querylet */
/** @var bool */ public $qlet;
public $positive; /** @var bool */
public $positive;
public function __construct(Querylet $qlet, bool $positive) { public function __construct(Querylet $qlet, bool $positive)
$this->qlet = $qlet; {
$this->positive = $positive; $this->qlet = $qlet;
} $this->positive = $positive;
}
} }

View File

@ -7,96 +7,97 @@
* All the methods are static, one should never actually use a tag object. * All the methods are static, one should never actually use a tag object.
* *
*/ */
class Tag { class Tag
public static function implode(array $tags): string { {
sort($tags); public static function implode(array $tags): string
$tags = implode(' ', $tags); {
sort($tags);
$tags = implode(' ', $tags);
return $tags; return $tags;
} }
/** /**
* Turn a human-supplied string into a valid tag array. * Turn a human-supplied string into a valid tag array.
* *
* #return string[] * #return string[]
*/ */
public static function explode(string $tags, bool $tagme=true): array { public static function explode(string $tags, bool $tagme=true): array
global $database; {
global $database;
$tags = explode(' ', trim($tags)); $tags = explode(' ', trim($tags));
/* sanitise by removing invisible / dodgy characters */ /* sanitise by removing invisible / dodgy characters */
$tag_array = array(); $tag_array = [];
foreach($tags as $tag) { foreach ($tags as $tag) {
$tag = preg_replace("/\s/", "", $tag); # whitespace $tag = preg_replace("/\s/", "", $tag); # whitespace
$tag = preg_replace('/\x20(\x0e|\x0f)/', '', $tag); # unicode RTL $tag = preg_replace('/\x20(\x0e|\x0f)/', '', $tag); # unicode RTL
$tag = preg_replace("/\.+/", ".", $tag); # strings of dots? $tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes? $tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
$tag = trim($tag, ", \t\n\r\0\x0B"); $tag = trim($tag, ", \t\n\r\0\x0B");
if(mb_strlen($tag, 'UTF-8') > 255){ 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"); flash_message("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n");
continue; continue;
} }
if(!empty($tag)) { if (!empty($tag)) {
$tag_array[] = $tag; $tag_array[] = $tag;
} }
} }
/* if user supplied a blank string, add "tagme" */ /* if user supplied a blank string, add "tagme" */
if(count($tag_array) === 0 && $tagme) { if (count($tag_array) === 0 && $tagme) {
$tag_array = array("tagme"); $tag_array = ["tagme"];
} }
/* resolve aliases */ /* resolve aliases */
$new = array(); $new = [];
$i = 0; $i = 0;
$tag_count = count($tag_array); $tag_count = count($tag_array);
while($i<$tag_count) { while ($i<$tag_count) {
$tag = $tag_array[$i]; $tag = $tag_array[$i];
$negative = ''; $negative = '';
if(!empty($tag) && ($tag[0] == '-')) { if (!empty($tag) && ($tag[0] == '-')) {
$negative = '-'; $negative = '-';
$tag = substr($tag, 1); $tag = substr($tag, 1);
} }
$newtags = $database->get_one( $newtags = $database->get_one(
$database->scoreql_to_sql(" $database->scoreql_to_sql("
SELECT newtag SELECT newtag
FROM aliases FROM aliases
WHERE SCORE_STRNORM(oldtag)=SCORE_STRNORM(:tag) WHERE SCORE_STRNORM(oldtag)=SCORE_STRNORM(:tag)
"), "),
array("tag"=>$tag) ["tag"=>$tag]
); );
if(empty($newtags)) { if (empty($newtags)) {
//tag has no alias, use old tag //tag has no alias, use old tag
$aliases = array($tag); $aliases = [$tag];
} } else {
else { $aliases = explode(" ", $newtags); // Tag::explode($newtags); - recursion can be infinite
$aliases = explode(" ", $newtags); // Tag::explode($newtags); - recursion can be infinite }
}
foreach($aliases as $alias) { foreach ($aliases as $alias) {
if(!in_array($alias, $new)) { if (!in_array($alias, $new)) {
if($tag == $alias) { if ($tag == $alias) {
$new[] = $negative.$alias; $new[] = $negative.$alias;
} } elseif (!in_array($alias, $tag_array)) {
elseif(!in_array($alias, $tag_array)) { $tag_array[] = $negative.$alias;
$tag_array[] = $negative.$alias; $tag_count++;
$tag_count++; }
} }
} }
} $i++;
$i++; }
}
/* remove any duplicate tags */ /* remove any duplicate tags */
$tag_array = array_iunique($new); $tag_array = array_iunique($new);
/* tidy up */ /* tidy up */
sort($tag_array); sort($tag_array);
return $tag_array; return $tag_array;
} }
} }

View File

@ -17,39 +17,55 @@ define("SCORE_LOG_NOTSET", 0);
* When taking action, a log event should be stored by the server * When taking action, a log event should be stored by the server
* Quite often, both of these happen at once, hence log_*() having $flash * Quite often, both of these happen at once, hence log_*() having $flash
*/ */
function log_msg(string $section, int $priority, string $message, ?string $flash=null, $args=array()) { function log_msg(string $section, int $priority, string $message, ?string $flash=null, $args=[])
send_event(new LogEvent($section, $priority, $message, $args)); {
$threshold = defined("CLI_LOG_LEVEL") ? CLI_LOG_LEVEL : 0; send_event(new LogEvent($section, $priority, $message, $args));
$threshold = defined("CLI_LOG_LEVEL") ? CLI_LOG_LEVEL : 0;
if((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && ($priority >= $threshold)) { if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && ($priority >= $threshold)) {
print date("c")." $section: $message\n"; print date("c")." $section: $message\n";
} }
if(!is_null($flash)) { if (!is_null($flash)) {
flash_message($flash); flash_message($flash);
} }
} }
// More shorthand ways of logging // More shorthand ways of logging
function log_debug( string $section, string $message, ?string $flash=null, $args=array()) {log_msg($section, SCORE_LOG_DEBUG, $message, $flash, $args);} function log_debug(string $section, string $message, ?string $flash=null, $args=[])
function log_info( string $section, string $message, ?string $flash=null, $args=array()) {log_msg($section, SCORE_LOG_INFO, $message, $flash, $args);} {
function log_warning( string $section, string $message, ?string $flash=null, $args=array()) {log_msg($section, SCORE_LOG_WARNING, $message, $flash, $args);} log_msg($section, SCORE_LOG_DEBUG, $message, $flash, $args);
function log_error( string $section, string $message, ?string $flash=null, $args=array()) {log_msg($section, SCORE_LOG_ERROR, $message, $flash, $args);} }
function log_critical(string $section, string $message, ?string $flash=null, $args=array()) {log_msg($section, SCORE_LOG_CRITICAL, $message, $flash, $args);} function log_info(string $section, string $message, ?string $flash=null, $args=[])
{
log_msg($section, SCORE_LOG_INFO, $message, $flash, $args);
}
function log_warning(string $section, string $message, ?string $flash=null, $args=[])
{
log_msg($section, SCORE_LOG_WARNING, $message, $flash, $args);
}
function log_error(string $section, string $message, ?string $flash=null, $args=[])
{
log_msg($section, SCORE_LOG_ERROR, $message, $flash, $args);
}
function log_critical(string $section, string $message, ?string $flash=null, $args=[])
{
log_msg($section, SCORE_LOG_CRITICAL, $message, $flash, $args);
}
/** /**
* Get a unique ID for this request, useful for grouping log messages. * Get a unique ID for this request, useful for grouping log messages.
*/ */
function get_request_id(): string { function get_request_id(): string
static $request_id = null; {
if(!$request_id) { static $request_id = null;
// not completely trustworthy, as a user can spoof this if (!$request_id) {
if(@$_SERVER['HTTP_X_VARNISH']) { // not completely trustworthy, as a user can spoof this
$request_id = $_SERVER['HTTP_X_VARNISH']; if (@$_SERVER['HTTP_X_VARNISH']) {
} $request_id = $_SERVER['HTTP_X_VARNISH'];
else { } else {
$request_id = "P" . uniqid(); $request_id = "P" . uniqid();
} }
} }
return $request_id; return $request_id;
} }

View File

@ -35,331 +35,352 @@
* The various extensions all add whatever they want to this structure, * The various extensions all add whatever they want to this structure,
* then Layout turns it into HTML. * then Layout turns it into HTML.
*/ */
class Page { class Page
/** @name Overall */ {
//@{ /** @name Overall */
/** @var string */ //@{
public $mode = "page"; /** @var string */
/** @var string */ public $mode = "page";
public $type = "text/html; charset=utf-8"; /** @var string */
public $type = "text/html; charset=utf-8";
/** /**
* Set what this page should do; "page", "data", or "redirect". * Set what this page should do; "page", "data", or "redirect".
*/ */
public function set_mode(string $mode) { public function set_mode(string $mode)
$this->mode = $mode; {
} $this->mode = $mode;
}
/** /**
* Set the page's MIME type. * Set the page's MIME type.
*/ */
public function set_type(string $type) { public function set_type(string $type)
$this->type = $type; {
} $this->type = $type;
}
//@} //@}
// ============================================== // ==============================================
/** @name "data" mode */ /** @name "data" mode */
//@{ //@{
/** @var string; public only for unit test */ /** @var string; public only for unit test */
public $data = ""; public $data = "";
/** @var string; public only for unit test */ /** @var string; public only for unit test */
public $filename = null; public $filename = null;
/** /**
* Set the raw data to be sent. * Set the raw data to be sent.
*/ */
public function set_data(string $data) { public function set_data(string $data)
$this->data = $data; {
} $this->data = $data;
}
/** /**
* Set the recommended download filename. * Set the recommended download filename.
*/ */
public function set_filename(string $filename) { public function set_filename(string $filename)
$this->filename = $filename; {
} $this->filename = $filename;
}
//@} //@}
// ============================================== // ==============================================
/** @name "redirect" mode */ /** @name "redirect" mode */
//@{ //@{
/** @var string */ /** @var string */
private $redirect = ""; private $redirect = "";
/** /**
* Set the URL to redirect to (remember to use make_link() if linking * Set the URL to redirect to (remember to use make_link() if linking
* to a page in the same site). * to a page in the same site).
*/ */
public function set_redirect(string $redirect) { public function set_redirect(string $redirect)
$this->redirect = $redirect; {
} $this->redirect = $redirect;
}
//@} //@}
// ============================================== // ==============================================
/** @name "page" mode */ /** @name "page" mode */
//@{ //@{
/** @var int */ /** @var int */
public $code = 200; public $code = 200;
/** @var string */ /** @var string */
public $title = ""; public $title = "";
/** @var string */ /** @var string */
public $heading = ""; public $heading = "";
/** @var string */ /** @var string */
public $subheading = ""; public $subheading = "";
/** @var string */ /** @var string */
public $quicknav = ""; public $quicknav = "";
/** @var string[] */ /** @var string[] */
public $html_headers = array(); public $html_headers = [];
/** @var string[] */ /** @var string[] */
public $http_headers = array(); public $http_headers = [];
/** @var string[][] */ /** @var string[][] */
public $cookies = array(); public $cookies = [];
/** @var Block[] */ /** @var Block[] */
public $blocks = array(); public $blocks = [];
/** /**
* Set the HTTP status code * Set the HTTP status code
*/ */
public function set_code(int $code): void { public function set_code(int $code): void
$this->code = $code; {
} $this->code = $code;
}
public function set_title(string $title): void { public function set_title(string $title): void
$this->title = $title; {
} $this->title = $title;
}
public function set_heading(string $heading): void { public function set_heading(string $heading): void
$this->heading = $heading; {
} $this->heading = $heading;
}
public function set_subheading(string $subheading): void { public function set_subheading(string $subheading): void
$this->subheading = $subheading; {
} $this->subheading = $subheading;
}
/** /**
* Add a line to the HTML head section. * Add a line to the HTML head section.
*/ */
public function add_html_header(string $line, int $position=50): void { public function add_html_header(string $line, int $position=50): void
while(isset($this->html_headers[$position])) $position++; {
$this->html_headers[$position] = $line; while (isset($this->html_headers[$position])) {
} $position++;
}
$this->html_headers[$position] = $line;
}
/** /**
* Add a http header to be sent to the client. * Add a http header to be sent to the client.
*/ */
public function add_http_header(string $line, int $position=50): void { public function add_http_header(string $line, int $position=50): void
while(isset($this->http_headers[$position])) $position++; {
$this->http_headers[$position] = $line; while (isset($this->http_headers[$position])) {
} $position++;
}
$this->http_headers[$position] = $line;
}
/** /**
* The counterpart for get_cookie, this works like php's * The counterpart for get_cookie, this works like php's
* setcookie method, but prepends the site-wide cookie prefix to * setcookie method, but prepends the site-wide cookie prefix to
* the $name argument before doing anything. * the $name argument before doing anything.
*/ */
public function add_cookie(string $name, string $value, int $time, string $path): void { public function add_cookie(string $name, string $value, int $time, string $path): void
$full_name = COOKIE_PREFIX."_".$name; {
$this->cookies[] = array($full_name, $value, $time, $path); $full_name = COOKIE_PREFIX."_".$name;
} $this->cookies[] = [$full_name, $value, $time, $path];
}
public function get_cookie(string $name): ?string { public function get_cookie(string $name): ?string
$full_name = COOKIE_PREFIX."_".$name; {
if(isset($_COOKIE[$full_name])) { $full_name = COOKIE_PREFIX."_".$name;
return $_COOKIE[$full_name]; if (isset($_COOKIE[$full_name])) {
} return $_COOKIE[$full_name];
else { } else {
return null; return null;
} }
} }
/** /**
* Get all the HTML headers that are currently set and return as a string. * Get all the HTML headers that are currently set and return as a string.
*/ */
public function get_all_html_headers(): string { public function get_all_html_headers(): string
$data = ''; {
ksort($this->html_headers); $data = '';
foreach ($this->html_headers as $line) { ksort($this->html_headers);
$data .= "\t\t" . $line . "\n"; foreach ($this->html_headers as $line) {
} $data .= "\t\t" . $line . "\n";
return $data; }
} return $data;
}
/** /**
* Removes all currently set HTML headers (Be careful..). * Removes all currently set HTML headers (Be careful..).
*/ */
public function delete_all_html_headers(): void { public function delete_all_html_headers(): void
$this->html_headers = array(); {
} $this->html_headers = [];
}
/** /**
* Add a Block of data to the page. * Add a Block of data to the page.
*/ */
public function add_block(Block $block) { public function add_block(Block $block)
$this->blocks[] = $block; {
} $this->blocks[] = $block;
}
//@} //@}
// ============================================== // ==============================================
/** /**
* Display the page according to the mode and data given. * Display the page according to the mode and data given.
*/ */
public function display(): void { public function display(): void
global $page, $user; {
global $page, $user;
header("HTTP/1.0 {$this->code} Shimmie"); header("HTTP/1.0 {$this->code} Shimmie");
header("Content-type: ".$this->type); header("Content-type: ".$this->type);
header("X-Powered-By: SCore-".SCORE_VERSION); header("X-Powered-By: SCore-".SCORE_VERSION);
if (!headers_sent()) { if (!headers_sent()) {
foreach($this->http_headers as $head) { foreach ($this->http_headers as $head) {
header($head); header($head);
} }
foreach($this->cookies as $c) { foreach ($this->cookies as $c) {
setcookie($c[0], $c[1], $c[2], $c[3]); setcookie($c[0], $c[1], $c[2], $c[3]);
} }
} else { } else {
print "Error: Headers have already been sent to the client."; print "Error: Headers have already been sent to the client.";
} }
switch($this->mode) { switch ($this->mode) {
case "page": case "page":
if(CACHE_HTTP) { if (CACHE_HTTP) {
header("Vary: Cookie, Accept-Encoding"); header("Vary: Cookie, Accept-Encoding");
if($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") { if ($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") {
header("Cache-control: public, max-age=600"); header("Cache-control: public, max-age=600");
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT');
} } else {
else { #header("Cache-control: private, max-age=0");
#header("Cache-control: private, max-age=0"); header("Cache-control: no-cache");
header("Cache-control: no-cache"); header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT'); }
} }
} #else {
#else { # header("Cache-control: no-cache");
# header("Cache-control: no-cache"); # header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
# header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT'); #}
#} if ($this->get_cookie("flash_message") !== null) {
if($this->get_cookie("flash_message") !== null) { $this->add_cookie("flash_message", "", -1, "/");
$this->add_cookie("flash_message", "", -1, "/"); }
} usort($this->blocks, "blockcmp");
usort($this->blocks, "blockcmp"); $this->add_auto_html_headers();
$this->add_auto_html_headers(); $layout = new Layout();
$layout = new Layout(); $layout->display_page($page);
$layout->display_page($page); break;
break; case "data":
case "data": header("Content-Length: ".strlen($this->data));
header("Content-Length: ".strlen($this->data)); if (!is_null($this->filename)) {
if(!is_null($this->filename)) { header('Content-Disposition: attachment; filename='.$this->filename);
header('Content-Disposition: attachment; filename='.$this->filename); }
} print $this->data;
print $this->data; break;
break; case "redirect":
case "redirect": header('Location: '.$this->redirect);
header('Location: '.$this->redirect); print 'You should be redirected to <a href="'.$this->redirect.'">'.$this->redirect.'</a>';
print 'You should be redirected to <a href="'.$this->redirect.'">'.$this->redirect.'</a>'; break;
break; default:
default: print "Invalid page mode";
print "Invalid page mode"; break;
break; }
} }
}
/** /**
* This function grabs all the CSS and JavaScript files sprinkled throughout Shimmie's folders, * This function grabs all the CSS and JavaScript files sprinkled throughout Shimmie's folders,
* concatenates them together into two large files (one for CSS and one for JS) and then stores * concatenates them together into two large files (one for CSS and one for JS) and then stores
* them in the /cache/ directory for serving to the user. * them in the /cache/ directory for serving to the user.
* *
* Why do this? Two reasons: * Why do this? Two reasons:
* 1. Reduces the number of files the user's browser needs to download. * 1. Reduces the number of files the user's browser needs to download.
* 2. Allows these cached files to be compressed/minified by the admin. * 2. Allows these cached files to be compressed/minified by the admin.
* *
* TODO: This should really be configurable somehow... * TODO: This should really be configurable somehow...
*/ */
public function add_auto_html_headers(): void { public function add_auto_html_headers(): void
global $config; {
global $config;
$data_href = get_base_href(); $data_href = get_base_href();
$theme_name = $config->get_string('theme', 'default'); $theme_name = $config->get_string('theme', 'default');
$this->add_html_header("<script type='text/javascript'>base_href = '$data_href';</script>", 40); $this->add_html_header("<script type='text/javascript'>base_href = '$data_href';</script>", 40);
# static handler will map these to themes/foo/static/bar.ico or ext/handle_static/static/bar.ico # static handler will map these to themes/foo/static/bar.ico or ext/handle_static/static/bar.ico
$this->add_html_header("<link rel='icon' type='image/x-icon' href='$data_href/favicon.ico'>", 41); $this->add_html_header("<link rel='icon' type='image/x-icon' href='$data_href/favicon.ico'>", 41);
$this->add_html_header("<link rel='apple-touch-icon' href='$data_href/apple-touch-icon.png'>", 42); $this->add_html_header("<link rel='apple-touch-icon' href='$data_href/apple-touch-icon.png'>", 42);
//We use $config_latest to make sure cache is reset if config is ever updated. //We use $config_latest to make sure cache is reset if config is ever updated.
$config_latest = 0; $config_latest = 0;
foreach(zglob("data/config/*") as $conf) { foreach (zglob("data/config/*") as $conf) {
$config_latest = max($config_latest, filemtime($conf)); $config_latest = max($config_latest, filemtime($conf));
} }
/*** Generate CSS cache files ***/ /*** Generate CSS cache files ***/
$css_latest = $config_latest; $css_latest = $config_latest;
$css_files = array_merge( $css_files = array_merge(
zglob("ext/{".ENABLED_EXTS."}/style.css"), zglob("ext/{".ENABLED_EXTS."}/style.css"),
zglob("themes/$theme_name/style.css") zglob("themes/$theme_name/style.css")
); );
foreach($css_files as $css) { foreach ($css_files as $css) {
$css_latest = max($css_latest, filemtime($css)); $css_latest = max($css_latest, filemtime($css));
} }
$css_md5 = md5(serialize($css_files)); $css_md5 = md5(serialize($css_files));
$css_cache_file = data_path("cache/style/{$theme_name}.{$css_latest}.{$css_md5}.css"); $css_cache_file = data_path("cache/style/{$theme_name}.{$css_latest}.{$css_md5}.css");
if(!file_exists($css_cache_file)) { if (!file_exists($css_cache_file)) {
$css_data = ""; $css_data = "";
foreach($css_files as $file) { foreach ($css_files as $file) {
$file_data = file_get_contents($file); $file_data = file_get_contents($file);
$pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/'; $pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/';
$replace = 'url("../../../'.dirname($file).'/$1")'; $replace = 'url("../../../'.dirname($file).'/$1")';
$file_data = preg_replace($pattern, $replace, $file_data); $file_data = preg_replace($pattern, $replace, $file_data);
$css_data .= $file_data . "\n"; $css_data .= $file_data . "\n";
} }
file_put_contents($css_cache_file, $css_data); file_put_contents($css_cache_file, $css_data);
} }
$this->add_html_header("<link rel='stylesheet' href='$data_href/$css_cache_file' type='text/css'>", 43); $this->add_html_header("<link rel='stylesheet' href='$data_href/$css_cache_file' type='text/css'>", 43);
/*** Generate JS cache files ***/ /*** Generate JS cache files ***/
$js_latest = $config_latest; $js_latest = $config_latest;
$js_files = array_merge( $js_files = array_merge(
[ [
"vendor/bower-asset/jquery/dist/jquery.min.js", "vendor/bower-asset/jquery/dist/jquery.min.js",
"vendor/bower-asset/jquery-timeago/jquery.timeago.js", "vendor/bower-asset/jquery-timeago/jquery.timeago.js",
"vendor/bower-asset/tablesorter/jquery.tablesorter.min.js", "vendor/bower-asset/tablesorter/jquery.tablesorter.min.js",
"vendor/bower-asset/js-cookie/src/js.cookie.js", "vendor/bower-asset/js-cookie/src/js.cookie.js",
"ext/handle_static/modernizr-3.3.1.custom.js", "ext/handle_static/modernizr-3.3.1.custom.js",
], ],
zglob("ext/{".ENABLED_EXTS."}/script.js"), zglob("ext/{".ENABLED_EXTS."}/script.js"),
zglob("themes/$theme_name/script.js") zglob("themes/$theme_name/script.js")
); );
foreach($js_files as $js) { foreach ($js_files as $js) {
$js_latest = max($js_latest, filemtime($js)); $js_latest = max($js_latest, filemtime($js));
} }
$js_md5 = md5(serialize($js_files)); $js_md5 = md5(serialize($js_files));
$js_cache_file = data_path("cache/script/{$theme_name}.{$js_latest}.{$js_md5}.js"); $js_cache_file = data_path("cache/script/{$theme_name}.{$js_latest}.{$js_md5}.js");
if(!file_exists($js_cache_file)) { if (!file_exists($js_cache_file)) {
$js_data = ""; $js_data = "";
foreach($js_files as $file) { foreach ($js_files as $file) {
$js_data .= file_get_contents($file) . "\n"; $js_data .= file_get_contents($file) . "\n";
} }
file_put_contents($js_cache_file, $js_data); file_put_contents($js_cache_file, $js_data);
} }
$this->add_html_header("<script src='$data_href/$js_cache_file' type='text/javascript'></script>", 44); $this->add_html_header("<script src='$data_href/$js_cache_file' type='text/javascript'></script>", 44);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -5,90 +5,94 @@
/** @private */ /** @private */
global $_shm_event_listeners; global $_shm_event_listeners;
$_shm_event_listeners = array(); $_shm_event_listeners = [];
function _load_event_listeners() { function _load_event_listeners()
global $_shm_event_listeners, $_shm_ctx; {
global $_shm_event_listeners, $_shm_ctx;
$_shm_ctx->log_start("Loading extensions"); $_shm_ctx->log_start("Loading extensions");
$cache_path = data_path("cache/shm_event_listeners.php"); $cache_path = data_path("cache/shm_event_listeners.php");
if(COMPILE_ELS && file_exists($cache_path)) { if (COMPILE_ELS && file_exists($cache_path)) {
require_once($cache_path); require_once($cache_path);
} } else {
else { _set_event_listeners();
_set_event_listeners();
if(COMPILE_ELS) { if (COMPILE_ELS) {
_dump_event_listeners($_shm_event_listeners, $cache_path); _dump_event_listeners($_shm_event_listeners, $cache_path);
} }
} }
$_shm_ctx->log_endok(); $_shm_ctx->log_endok();
} }
function _set_event_listeners() { function _set_event_listeners()
global $_shm_event_listeners; {
$_shm_event_listeners = array(); global $_shm_event_listeners;
$_shm_event_listeners = [];
foreach(get_declared_classes() as $class) { foreach (get_declared_classes() as $class) {
$rclass = new ReflectionClass($class); $rclass = new ReflectionClass($class);
if($rclass->isAbstract()) { if ($rclass->isAbstract()) {
// don't do anything // don't do anything
} } elseif (is_subclass_of($class, "Extension")) {
elseif(is_subclass_of($class, "Extension")) { /** @var Extension $extension */
/** @var Extension $extension */ $extension = new $class();
$extension = new $class();
// skip extensions which don't support our current database // skip extensions which don't support our current database
if(!$extension->is_live()) continue; if (!$extension->is_live()) {
continue;
}
foreach(get_class_methods($extension) as $method) { foreach (get_class_methods($extension) as $method) {
if(substr($method, 0, 2) == "on") { if (substr($method, 0, 2) == "on") {
$event = substr($method, 2) . "Event"; $event = substr($method, 2) . "Event";
$pos = $extension->get_priority() * 100; $pos = $extension->get_priority() * 100;
while(isset($_shm_event_listeners[$event][$pos])) { while (isset($_shm_event_listeners[$event][$pos])) {
$pos += 1; $pos += 1;
} }
$_shm_event_listeners[$event][$pos] = $extension; $_shm_event_listeners[$event][$pos] = $extension;
} }
} }
} }
} }
} }
function _dump_event_listeners(array $event_listeners, string $path): void { function _dump_event_listeners(array $event_listeners, string $path): void
$p = "<"."?php\n"; {
$p = "<"."?php\n";
foreach(get_declared_classes() as $class) { foreach (get_declared_classes() as $class) {
$rclass = new ReflectionClass($class); $rclass = new ReflectionClass($class);
if($rclass->isAbstract()) {} if ($rclass->isAbstract()) {
elseif(is_subclass_of($class, "Extension")) { } elseif (is_subclass_of($class, "Extension")) {
$p .= "\$$class = new $class(); "; $p .= "\$$class = new $class(); ";
} }
} }
$p .= "\$_shm_event_listeners = array(\n"; $p .= "\$_shm_event_listeners = array(\n";
foreach($event_listeners as $event => $listeners) { foreach ($event_listeners as $event => $listeners) {
$p .= "\t'$event' => array(\n"; $p .= "\t'$event' => array(\n";
foreach($listeners as $id => $listener) { foreach ($listeners as $id => $listener) {
$p .= "\t\t$id => \$".get_class($listener).",\n"; $p .= "\t\t$id => \$".get_class($listener).",\n";
} }
$p .= "\t),\n"; $p .= "\t),\n";
} }
$p .= ");\n"; $p .= ");\n";
$p .= "?".">"; $p .= "?".">";
file_put_contents($path, $p); file_put_contents($path, $p);
} }
function ext_is_live(string $ext_name): bool { function ext_is_live(string $ext_name): bool
if (class_exists($ext_name)) { {
/** @var Extension $ext */ if (class_exists($ext_name)) {
$ext = new $ext_name(); /** @var Extension $ext */
return $ext->is_live(); $ext = new $ext_name();
} return $ext->is_live();
return false; }
return false;
} }
@ -99,26 +103,37 @@ $_shm_event_count = 0;
/** /**
* Send an event to all registered Extensions. * Send an event to all registered Extensions.
*/ */
function send_event(Event $event): void { function send_event(Event $event): void
global $_shm_event_listeners, $_shm_event_count, $_shm_ctx; {
if(!isset($_shm_event_listeners[get_class($event)])) return; global $_shm_event_listeners, $_shm_event_count, $_shm_ctx;
$method_name = "on".str_replace("Event", "", get_class($event)); if (!isset($_shm_event_listeners[get_class($event)])) {
return;
}
$method_name = "on".str_replace("Event", "", get_class($event));
// send_event() is performance sensitive, and with the number // send_event() is performance sensitive, and with the number
// of times context gets called the time starts to add up // of times context gets called the time starts to add up
$ctx_enabled = constant('CONTEXT'); $ctx_enabled = constant('CONTEXT');
if($ctx_enabled) $_shm_ctx->log_start(get_class($event)); if ($ctx_enabled) {
// SHIT: http://bugs.php.net/bug.php?id=35106 $_shm_ctx->log_start(get_class($event));
$my_event_listeners = $_shm_event_listeners[get_class($event)]; }
ksort($my_event_listeners); // SHIT: http://bugs.php.net/bug.php?id=35106
foreach($my_event_listeners as $listener) { $my_event_listeners = $_shm_event_listeners[get_class($event)];
if($ctx_enabled) $_shm_ctx->log_start(get_class($listener)); ksort($my_event_listeners);
if(method_exists($listener, $method_name)) { foreach ($my_event_listeners as $listener) {
$listener->$method_name($event); if ($ctx_enabled) {
} $_shm_ctx->log_start(get_class($listener));
if($ctx_enabled) $_shm_ctx->log_endok(); }
} if (method_exists($listener, $method_name)) {
$_shm_event_count++; $listener->$method_name($event);
if($ctx_enabled) $_shm_ctx->log_endok(); }
if ($ctx_enabled) {
$_shm_ctx->log_endok();
}
}
$_shm_event_count++;
if ($ctx_enabled) {
$_shm_ctx->log_endok();
}
} }

View File

@ -19,7 +19,12 @@
* *
*/ */
function _d(string $name, $value) {if(!defined($name)) define($name, $value);} function _d(string $name, $value)
{
if (!defined($name)) {
define($name, $value);
}
}
_d("DATABASE_DSN", null); // string PDO database connection details _d("DATABASE_DSN", null); // string PDO database connection details
_d("DATABASE_KA", true); // string Keep database connection alive _d("DATABASE_KA", true); // string Keep database connection alive
_d("CACHE_DSN", null); // string cache connection details _d("CACHE_DSN", null); // string cache connection details
@ -50,5 +55,3 @@ _d("ENABLED_MODS", "imageboard");
*/ */
_d("SCORE_VERSION", 'develop/'.VERSION); // string SCore version _d("SCORE_VERSION", 'develop/'.VERSION); // string SCore version
_d("ENABLED_EXTS", CORE_EXTS.",".EXTRA_EXTS); _d("ENABLED_EXTS", CORE_EXTS.",".EXTRA_EXTS);

View File

@ -1,43 +1,48 @@
<?php <?php
require_once "core/polyfills.php"; require_once "core/polyfills.php";
class PolyfillsTest extends \PHPUnit\Framework\TestCase { class PolyfillsTest extends \PHPUnit\Framework\TestCase
public function test_html_escape() { {
$this->assertEquals( public function test_html_escape()
html_escape("Foo & <waffles>"), {
"Foo &amp; &lt;waffles&gt;" $this->assertEquals(
); html_escape("Foo & <waffles>"),
"Foo &amp; &lt;waffles&gt;"
);
$this->assertEquals( $this->assertEquals(
html_unescape("Foo &amp; &lt;waffles&gt;"), html_unescape("Foo &amp; &lt;waffles&gt;"),
"Foo & <waffles>" "Foo & <waffles>"
); );
$x = "Foo &amp; &lt;waffles&gt;"; $x = "Foo &amp; &lt;waffles&gt;";
$this->assertEquals(html_escape(html_unescape($x)), $x); $this->assertEquals(html_escape(html_unescape($x)), $x);
} }
public function test_int_escape() { public function test_int_escape()
$this->assertEquals(int_escape(""), 0); {
$this->assertEquals(int_escape("1"), 1); $this->assertEquals(int_escape(""), 0);
$this->assertEquals(int_escape("-1"), -1); $this->assertEquals(int_escape("1"), 1);
$this->assertEquals(int_escape("-1.5"), -1); $this->assertEquals(int_escape("-1"), -1);
} $this->assertEquals(int_escape("-1.5"), -1);
}
public function test_clamp() { public function test_clamp()
$this->assertEquals(clamp(0, 5, 10), 5); {
$this->assertEquals(clamp(5, 5, 10), 5); $this->assertEquals(clamp(0, 5, 10), 5);
$this->assertEquals(clamp(7, 5, 10), 7); $this->assertEquals(clamp(5, 5, 10), 5);
$this->assertEquals(clamp(10, 5, 10), 10); $this->assertEquals(clamp(7, 5, 10), 7);
$this->assertEquals(clamp(15, 5, 10), 10); $this->assertEquals(clamp(10, 5, 10), 10);
} $this->assertEquals(clamp(15, 5, 10), 10);
}
public function test_shorthand_int() { public function test_shorthand_int()
$this->assertEquals(to_shorthand_int(1231231231), "1.1GB"); {
$this->assertEquals(to_shorthand_int(1231231231), "1.1GB");
$this->assertEquals(parse_shorthand_int("foo"), -1); $this->assertEquals(parse_shorthand_int("foo"), -1);
$this->assertEquals(parse_shorthand_int("32M"), 33554432); $this->assertEquals(parse_shorthand_int("32M"), 33554432);
$this->assertEquals(parse_shorthand_int("43.4KB"), 44441); $this->assertEquals(parse_shorthand_int("43.4KB"), 44441);
$this->assertEquals(parse_shorthand_int("1231231231"), 1231231231); $this->assertEquals(parse_shorthand_int("1231231231"), 1231231231);
} }
} }

View File

@ -9,92 +9,94 @@
* *
* eg make_link("post/list") becomes "/v2/index.php?q=post/list" * eg make_link("post/list") becomes "/v2/index.php?q=post/list"
*/ */
function make_link(?string $page=null, ?string $query=null): string { function make_link(?string $page=null, ?string $query=null): string
global $config; {
global $config;
if(is_null($page)) $page = $config->get_string('main_page'); if (is_null($page)) {
$page = $config->get_string('main_page');
}
if(!is_null(BASE_URL)) { if (!is_null(BASE_URL)) {
$base = BASE_URL; $base = BASE_URL;
} } elseif (NICE_URLS || $config->get_bool('nice_urls', false)) {
elseif(NICE_URLS || $config->get_bool('nice_urls', false)) { $base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $_SERVER["PHP_SELF"]);
$base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $_SERVER["PHP_SELF"]); } else {
} $base = "./".basename($_SERVER["SCRIPT_FILENAME"])."?q=";
else { }
$base = "./".basename($_SERVER["SCRIPT_FILENAME"])."?q=";
}
if(is_null($query)) { if (is_null($query)) {
return str_replace("//", "/", $base.'/'.$page ); return str_replace("//", "/", $base.'/'.$page);
} } else {
else { if (strpos($base, "?")) {
if(strpos($base, "?")) { return $base .'/'. $page .'&'. $query;
return $base .'/'. $page .'&'. $query; } elseif (strpos($query, "#") === 0) {
} return $base .'/'. $page . $query;
else if(strpos($query, "#") === 0) { } else {
return $base .'/'. $page . $query; return $base .'/'. $page .'?'. $query;
} }
else { }
return $base .'/'. $page .'?'. $query;
}
}
} }
/** /**
* Take the current URL and modify some parameters * Take the current URL and modify some parameters
*/ */
function modify_current_url(array $changes): string { function modify_current_url(array $changes): string
return modify_url($_SERVER['QUERY_STRING'], $changes); {
return modify_url($_SERVER['QUERY_STRING'], $changes);
} }
function modify_url(string $url, array $changes): string { function modify_url(string $url, array $changes): string
// SHIT: PHP is officially the worst web API ever because it does not {
// have a built-in function to do this. // SHIT: PHP is officially the worst web API ever because it does not
// have a built-in function to do this.
// SHIT: parse_str is magically retarded; not only is it a useless name, it also // SHIT: parse_str is magically retarded; not only is it a useless name, it also
// didn't return the parsed array, preferring to overwrite global variables with // didn't return the parsed array, preferring to overwrite global variables with
// whatever data the user supplied. Thankfully, 4.0.3 added an extra option to // whatever data the user supplied. Thankfully, 4.0.3 added an extra option to
// give it an array to use... // give it an array to use...
$params = array(); $params = [];
parse_str($url, $params); parse_str($url, $params);
if(isset($changes['q'])) { if (isset($changes['q'])) {
$base = $changes['q']; $base = $changes['q'];
unset($changes['q']); unset($changes['q']);
} } else {
else { $base = _get_query();
$base = _get_query(); }
}
if(isset($params['q'])) { if (isset($params['q'])) {
unset($params['q']); unset($params['q']);
} }
foreach($changes as $k => $v) { foreach ($changes as $k => $v) {
if(is_null($v) and isset($params[$k])) unset($params[$k]); if (is_null($v) and isset($params[$k])) {
$params[$k] = $v; unset($params[$k]);
} }
$params[$k] = $v;
}
return make_link($base, http_build_query($params)); return make_link($base, http_build_query($params));
} }
/** /**
* Turn a relative link into an absolute one, including hostname * Turn a relative link into an absolute one, including hostname
*/ */
function make_http(string $link): string { function make_http(string $link): string
if(strpos($link, "://") > 0) { {
return $link; if (strpos($link, "://") > 0) {
} return $link;
}
if(strlen($link) > 0 && $link[0] != '/') { if (strlen($link) > 0 && $link[0] != '/') {
$link = get_base_href() . '/' . $link; $link = get_base_href() . '/' . $link;
} }
$protocol = is_https_enabled() ? "https://" : "http://"; $protocol = is_https_enabled() ? "https://" : "http://";
$link = $protocol . $_SERVER["HTTP_HOST"] . $link; $link = $protocol . $_SERVER["HTTP_HOST"] . $link;
$link = str_replace("/./", "/", $link); $link = str_replace("/./", "/", $link);
return $link; return $link;
} }

View File

@ -1,7 +1,8 @@
<?php <?php
function _new_user(array $row): User { function _new_user(array $row): User
return new User($row); {
return new User($row);
} }
@ -12,212 +13,229 @@ function _new_user(array $row): User {
* *
* The currently logged in user will always be accessible via the global variable $user. * The currently logged in user will always be accessible via the global variable $user.
*/ */
class User { class User
/** @var int */ {
public $id; /** @var int */
public $id;
/** @var string */ /** @var string */
public $name; public $name;
/** @var string */ /** @var string */
public $email; public $email;
public $join_date; public $join_date;
/** @var string */ /** @var string */
public $passhash; public $passhash;
/** @var UserClass */ /** @var UserClass */
public $class; public $class;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialisation * * Initialisation *
* * * *
* User objects shouldn't be created directly, they should be * * User objects shouldn't be created directly, they should be *
* fetched from the database like so: * * fetched from the database like so: *
* * * *
* $user = User::by_name("bob"); * * $user = User::by_name("bob"); *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/** /**
* One will very rarely construct a user directly, more common * One will very rarely construct a user directly, more common
* would be to use User::by_id, User::by_session, etc. * would be to use User::by_id, User::by_session, etc.
* *
* @throws SCoreException * @throws SCoreException
*/ */
public function __construct(array $row) { public function __construct(array $row)
global $_shm_user_classes; {
global $_shm_user_classes;
$this->id = int_escape($row['id']); $this->id = int_escape($row['id']);
$this->name = $row['name']; $this->name = $row['name'];
$this->email = $row['email']; $this->email = $row['email'];
$this->join_date = $row['joindate']; $this->join_date = $row['joindate'];
$this->passhash = $row['pass']; $this->passhash = $row['pass'];
if(array_key_exists($row["class"], $_shm_user_classes)) { if (array_key_exists($row["class"], $_shm_user_classes)) {
$this->class = $_shm_user_classes[$row["class"]]; $this->class = $_shm_user_classes[$row["class"]];
} } else {
else { throw new SCoreException("User '{$this->name}' has invalid class '{$row["class"]}'");
throw new SCoreException("User '{$this->name}' has invalid class '{$row["class"]}'"); }
} }
}
public static function by_session(string $name, string $session) { public static function by_session(string $name, string $session)
global $config, $database; {
$row = $database->cache->get("user-session:$name-$session"); global $config, $database;
if(!$row) { $row = $database->cache->get("user-session:$name-$session");
if($database->get_driver_name() === "mysql") { if (!$row) {
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess"; if ($database->get_driver_name() === "mysql") {
} $query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
else { } else {
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess"; $query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
} }
$row = $database->get_row($query, array("name"=>$name, "ip"=>get_session_ip($config), "sess"=>$session)); $row = $database->get_row($query, ["name"=>$name, "ip"=>get_session_ip($config), "sess"=>$session]);
$database->cache->set("user-session:$name-$session", $row, 600); $database->cache->set("user-session:$name-$session", $row, 600);
} }
return is_null($row) ? null : new User($row); return is_null($row) ? null : new User($row);
} }
public static function by_id(int $id) { public static function by_id(int $id)
global $database; {
if($id === 1) { global $database;
$cached = $database->cache->get('user-id:'.$id); if ($id === 1) {
if($cached) return new User($cached); $cached = $database->cache->get('user-id:'.$id);
} if ($cached) {
$row = $database->get_row("SELECT * FROM users WHERE id = :id", array("id"=>$id)); return new User($cached);
if($id === 1) $database->cache->set('user-id:'.$id, $row, 600); }
return is_null($row) ? null : new User($row); }
} $row = $database->get_row("SELECT * FROM users WHERE id = :id", ["id"=>$id]);
if ($id === 1) {
$database->cache->set('user-id:'.$id, $row, 600);
}
return is_null($row) ? null : new User($row);
}
public static function by_name(string $name) { public static function by_name(string $name)
global $database; {
$row = $database->get_row($database->scoreql_to_sql("SELECT * FROM users WHERE SCORE_STRNORM(name) = SCORE_STRNORM(:name)"), array("name"=>$name)); global $database;
return is_null($row) ? null : new User($row); $row = $database->get_row($database->scoreql_to_sql("SELECT * FROM users WHERE SCORE_STRNORM(name) = SCORE_STRNORM(:name)"), ["name"=>$name]);
} return is_null($row) ? null : new User($row);
}
public static function by_name_and_pass(string $name, string $pass) { public static function by_name_and_pass(string $name, string $pass)
$user = User::by_name($name); {
if($user) { $user = User::by_name($name);
if($user->passhash == md5(strtolower($name) . $pass)) { if ($user) {
log_info("core-user", "Migrating from md5 to bcrypt for ".html_escape($name)); if ($user->passhash == md5(strtolower($name) . $pass)) {
$user->set_password($pass); log_info("core-user", "Migrating from md5 to bcrypt for ".html_escape($name));
} $user->set_password($pass);
if(password_verify($pass, $user->passhash)) { }
log_info("core-user", "Logged in as ".html_escape($name)." ({$user->class->name})"); if (password_verify($pass, $user->passhash)) {
return $user; log_info("core-user", "Logged in as ".html_escape($name)." ({$user->class->name})");
} return $user;
else { } else {
log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid password)"); log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid password)");
} }
} } else {
else { log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid username)");
log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid username)"); }
} return null;
return null; }
}
/* useful user object functions start here */ /* useful user object functions start here */
public function can(string $ability): bool { public function can(string $ability): bool
return $this->class->can($ability); {
} return $this->class->can($ability);
}
public function is_anonymous(): bool { public function is_anonymous(): bool
global $config; {
return ($this->id === $config->get_int('anon_id')); global $config;
} return ($this->id === $config->get_int('anon_id'));
}
public function is_logged_in(): bool { public function is_logged_in(): bool
global $config; {
return ($this->id !== $config->get_int('anon_id')); global $config;
} return ($this->id !== $config->get_int('anon_id'));
}
public function is_admin(): bool { public function is_admin(): bool
return ($this->class->name === "admin"); {
} return ($this->class->name === "admin");
}
public function set_class(string $class) { public function set_class(string $class)
global $database; {
$database->Execute("UPDATE users SET class=:class WHERE id=:id", array("class"=>$class, "id"=>$this->id)); global $database;
log_info("core-user", 'Set class for '.$this->name.' to '.$class); $database->Execute("UPDATE users SET class=:class WHERE id=:id", ["class"=>$class, "id"=>$this->id]);
} log_info("core-user", 'Set class for '.$this->name.' to '.$class);
}
public function set_name(string $name) { public function set_name(string $name)
global $database; {
if(User::by_name($name)) { global $database;
throw new Exception("Desired username is already in use"); if (User::by_name($name)) {
} throw new Exception("Desired username is already in use");
$old_name = $this->name; }
$this->name = $name; $old_name = $this->name;
$database->Execute("UPDATE users SET name=:name WHERE id=:id", array("name"=>$this->name, "id"=>$this->id)); $this->name = $name;
log_info("core-user", "Changed username for {$old_name} to {$this->name}"); $database->Execute("UPDATE users SET name=:name WHERE id=:id", ["name"=>$this->name, "id"=>$this->id]);
} log_info("core-user", "Changed username for {$old_name} to {$this->name}");
}
public function set_password(string $password) { public function set_password(string $password)
global $database; {
$hash = password_hash($password, PASSWORD_BCRYPT); global $database;
if(is_string($hash)) { $hash = password_hash($password, PASSWORD_BCRYPT);
$this->passhash = $hash; if (is_string($hash)) {
$database->Execute("UPDATE users SET pass=:hash WHERE id=:id", array("hash"=>$this->passhash, "id"=>$this->id)); $this->passhash = $hash;
log_info("core-user", 'Set password for '.$this->name); $database->Execute("UPDATE users SET pass=:hash WHERE id=:id", ["hash"=>$this->passhash, "id"=>$this->id]);
} log_info("core-user", 'Set password for '.$this->name);
else { } else {
throw new SCoreException("Failed to hash password"); throw new SCoreException("Failed to hash password");
} }
} }
public function set_email(string $address) { public function set_email(string $address)
global $database; {
$database->Execute("UPDATE users SET email=:email WHERE id=:id", array("email"=>$address, "id"=>$this->id)); global $database;
log_info("core-user", 'Set email for '.$this->name); $database->Execute("UPDATE users SET email=:email WHERE id=:id", ["email"=>$address, "id"=>$this->id]);
} log_info("core-user", 'Set email for '.$this->name);
}
/** /**
* Get a snippet of HTML which will render the user's avatar, be that * Get a snippet of HTML which will render the user's avatar, be that
* a local file, a remote file, a gravatar, a something else, etc. * a local file, a remote file, a gravatar, a something else, etc.
*/ */
public function get_avatar_html(): string { public function get_avatar_html(): string
// FIXME: configurable {
global $config; // FIXME: configurable
if($config->get_string("avatar_host") === "gravatar") { global $config;
if(!empty($this->email)) { if ($config->get_string("avatar_host") === "gravatar") {
$hash = md5(strtolower($this->email)); if (!empty($this->email)) {
$s = $config->get_string("avatar_gravatar_size"); $hash = md5(strtolower($this->email));
$d = urlencode($config->get_string("avatar_gravatar_default")); $s = $config->get_string("avatar_gravatar_size");
$r = $config->get_string("avatar_gravatar_rating"); $d = urlencode($config->get_string("avatar_gravatar_default"));
$cb = date("Y-m-d"); $r = $config->get_string("avatar_gravatar_rating");
return "<img class=\"avatar gravatar\" src=\"https://www.gravatar.com/avatar/$hash.jpg?s=$s&d=$d&r=$r&cacheBreak=$cb\">"; $cb = date("Y-m-d");
} return "<img class=\"avatar gravatar\" src=\"https://www.gravatar.com/avatar/$hash.jpg?s=$s&d=$d&r=$r&cacheBreak=$cb\">";
} }
return ""; }
} return "";
}
/** /**
* Get an auth token to be used in POST forms * Get an auth token to be used in POST forms
* *
* password = secret, avoid storing directly * password = secret, avoid storing directly
* passhash = bcrypt(password), so someone who gets to the database can't get passwords * passhash = bcrypt(password), so someone who gets to the database can't get passwords
* sesskey = md5(passhash . IP), so if it gets sniffed it can't be used from another IP, * sesskey = md5(passhash . IP), so if it gets sniffed it can't be used from another IP,
* and it can't be used to get the passhash to generate new sesskeys * and it can't be used to get the passhash to generate new sesskeys
* authtok = md5(sesskey, salt), presented to the user in web forms, to make sure that * authtok = md5(sesskey, salt), presented to the user in web forms, to make sure that
* the form was generated within the session. Salted and re-hashed so that * the form was generated within the session. Salted and re-hashed so that
* reading a web page from the user's cache doesn't give access to the session key * reading a web page from the user's cache doesn't give access to the session key
*/ */
public function get_auth_token(): string { public function get_auth_token(): string
global $config; {
$salt = DATABASE_DSN; global $config;
$addr = get_session_ip($config); $salt = DATABASE_DSN;
return md5(md5($this->passhash . $addr) . "salty-csrf-" . $salt); $addr = get_session_ip($config);
} return md5(md5($this->passhash . $addr) . "salty-csrf-" . $salt);
}
public function get_auth_html(): string { public function get_auth_html(): string
$at = $this->get_auth_token(); {
return '<input type="hidden" name="auth_token" value="'.$at.'">'; $at = $this->get_auth_token();
} return '<input type="hidden" name="auth_token" value="'.$at.'">';
}
public function check_auth_token(): bool { public function check_auth_token(): bool
return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token()); {
} return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token());
}
} }

View File

@ -3,191 +3,191 @@
* @global UserClass[] $_shm_user_classes * @global UserClass[] $_shm_user_classes
*/ */
global $_shm_user_classes; global $_shm_user_classes;
$_shm_user_classes = array(); $_shm_user_classes = [];
/** /**
* Class UserClass * Class UserClass
*/ */
class UserClass { class UserClass
{
/** /**
* @var null|string * @var null|string
*/ */
public $name = null; public $name = null;
/** /**
* @var \UserClass|null * @var \UserClass|null
*/ */
public $parent = null; public $parent = null;
/** /**
* @var array * @var array
*/ */
public $abilities = array(); public $abilities = [];
public function __construct(string $name, string $parent=null, array $abilities=array()) { public function __construct(string $name, string $parent=null, array $abilities=[])
global $_shm_user_classes; {
global $_shm_user_classes;
$this->name = $name; $this->name = $name;
$this->abilities = $abilities; $this->abilities = $abilities;
if(!is_null($parent)) { if (!is_null($parent)) {
$this->parent = $_shm_user_classes[$parent]; $this->parent = $_shm_user_classes[$parent];
} }
$_shm_user_classes[$name] = $this; $_shm_user_classes[$name] = $this;
} }
/** /**
* Determine if this class of user can perform an action or has ability. * Determine if this class of user can perform an action or has ability.
* *
* @throws SCoreException * @throws SCoreException
*/ */
public function can(string $ability): bool { public function can(string $ability): bool
if(array_key_exists($ability, $this->abilities)) { {
$val = $this->abilities[$ability]; if (array_key_exists($ability, $this->abilities)) {
return $val; $val = $this->abilities[$ability];
} return $val;
else if(!is_null($this->parent)) { } elseif (!is_null($this->parent)) {
return $this->parent->can($ability); return $this->parent->can($ability);
} } else {
else { global $_shm_user_classes;
global $_shm_user_classes; $min_dist = 9999;
$min_dist = 9999; $min_ability = null;
$min_ability = null; foreach ($_shm_user_classes['base']->abilities as $a => $cando) {
foreach($_shm_user_classes['base']->abilities as $a => $cando) { $v = levenshtein($ability, $a);
$v = levenshtein($ability, $a); if ($v < $min_dist) {
if($v < $min_dist) { $min_dist = $v;
$min_dist = $v; $min_ability = $a;
$min_ability = $a; }
} }
} throw new SCoreException("Unknown ability '".html_escape($ability)."'. Did the developer mean '".html_escape($min_ability)."'?");
throw new SCoreException("Unknown ability '".html_escape($ability)."'. Did the developer mean '".html_escape($min_ability)."'?"); }
} }
}
} }
// action_object_attribute // action_object_attribute
// action = create / view / edit / delete // action = create / view / edit / delete
// object = image / user / tag / setting // object = image / user / tag / setting
new UserClass("base", null, array( new UserClass("base", null, [
"change_setting" => False, # modify web-level settings, eg the config table "change_setting" => false, # modify web-level settings, eg the config table
"override_config" => False, # modify sys-level settings, eg shimmie.conf.php "override_config" => false, # modify sys-level settings, eg shimmie.conf.php
"big_search" => False, # search for more than 3 tags at once (speed mode only) "big_search" => false, # search for more than 3 tags at once (speed mode only)
"manage_extension_list" => False, "manage_extension_list" => false,
"manage_alias_list" => False, "manage_alias_list" => false,
"mass_tag_edit" => False, "mass_tag_edit" => false,
"view_ip" => False, # view IP addresses associated with things "view_ip" => false, # view IP addresses associated with things
"ban_ip" => False, "ban_ip" => false,
"edit_user_name" => False, "edit_user_name" => false,
"edit_user_password" => False, "edit_user_password" => false,
"edit_user_info" => False, # email address, etc "edit_user_info" => false, # email address, etc
"edit_user_class" => False, "edit_user_class" => false,
"delete_user" => False, "delete_user" => false,
"create_comment" => False, "create_comment" => false,
"delete_comment" => False, "delete_comment" => false,
"bypass_comment_checks" => False, # spam etc "bypass_comment_checks" => false, # spam etc
"replace_image" => False, "replace_image" => false,
"create_image" => False, "create_image" => false,
"edit_image_tag" => False, "edit_image_tag" => false,
"edit_image_source" => False, "edit_image_source" => false,
"edit_image_owner" => False, "edit_image_owner" => false,
"edit_image_lock" => False, "edit_image_lock" => false,
"bulk_edit_image_tag" => False, "bulk_edit_image_tag" => false,
"bulk_edit_image_source" => False, "bulk_edit_image_source" => false,
"delete_image" => False, "delete_image" => false,
"ban_image" => False, "ban_image" => false,
"view_eventlog" => False, "view_eventlog" => false,
"ignore_downtime" => False, "ignore_downtime" => false,
"create_image_report" => False, "create_image_report" => false,
"view_image_report" => False, # deal with reported images "view_image_report" => false, # deal with reported images
"edit_wiki_page" => False, "edit_wiki_page" => false,
"delete_wiki_page" => False, "delete_wiki_page" => false,
"manage_blocks" => False, "manage_blocks" => false,
"manage_admintools" => False, "manage_admintools" => false,
"view_other_pms" => False, "view_other_pms" => false,
"edit_feature" => False, "edit_feature" => false,
"bulk_edit_vote" => False, "bulk_edit_vote" => false,
"edit_other_vote" => False, "edit_other_vote" => false,
"view_sysinfo" => False, "view_sysinfo" => false,
"hellbanned" => False, "hellbanned" => false,
"view_hellbanned" => False, "view_hellbanned" => false,
"protected" => False, # only admins can modify protected users (stops a moderator changing an admin's password) "protected" => false, # only admins can modify protected users (stops a moderator changing an admin's password)
)); ]);
new UserClass("anonymous", "base", array( new UserClass("anonymous", "base", [
)); ]);
new UserClass("user", "base", array( new UserClass("user", "base", [
"big_search" => True, "big_search" => true,
"create_image" => True, "create_image" => true,
"create_comment" => True, "create_comment" => true,
"edit_image_tag" => True, "edit_image_tag" => true,
"edit_image_source" => True, "edit_image_source" => true,
"create_image_report" => True, "create_image_report" => true,
)); ]);
new UserClass("admin", "base", array( new UserClass("admin", "base", [
"change_setting" => True, "change_setting" => true,
"override_config" => True, "override_config" => true,
"big_search" => True, "big_search" => true,
"edit_image_lock" => True, "edit_image_lock" => true,
"view_ip" => True, "view_ip" => true,
"ban_ip" => True, "ban_ip" => true,
"edit_user_name" => True, "edit_user_name" => true,
"edit_user_password" => True, "edit_user_password" => true,
"edit_user_info" => True, "edit_user_info" => true,
"edit_user_class" => True, "edit_user_class" => true,
"delete_user" => True, "delete_user" => true,
"create_image" => True, "create_image" => true,
"delete_image" => True, "delete_image" => true,
"ban_image" => True, "ban_image" => true,
"create_comment" => True, "create_comment" => true,
"delete_comment" => True, "delete_comment" => true,
"bypass_comment_checks" => True, "bypass_comment_checks" => true,
"replace_image" => True, "replace_image" => true,
"manage_extension_list" => True, "manage_extension_list" => true,
"manage_alias_list" => True, "manage_alias_list" => true,
"edit_image_tag" => True, "edit_image_tag" => true,
"edit_image_source" => True, "edit_image_source" => true,
"edit_image_owner" => True, "edit_image_owner" => true,
"bulk_edit_image_tag" => True, "bulk_edit_image_tag" => true,
"bulk_edit_image_source" => True, "bulk_edit_image_source" => true,
"mass_tag_edit" => True, "mass_tag_edit" => true,
"create_image_report" => True, "create_image_report" => true,
"view_image_report" => True, "view_image_report" => true,
"edit_wiki_page" => True, "edit_wiki_page" => true,
"delete_wiki_page" => True, "delete_wiki_page" => true,
"view_eventlog" => True, "view_eventlog" => true,
"manage_blocks" => True, "manage_blocks" => true,
"manage_admintools" => True, "manage_admintools" => true,
"ignore_downtime" => True, "ignore_downtime" => true,
"view_other_pms" => True, "view_other_pms" => true,
"edit_feature" => True, "edit_feature" => true,
"bulk_edit_vote" => True, "bulk_edit_vote" => true,
"edit_other_vote" => True, "edit_other_vote" => true,
"view_sysinfo" => True, "view_sysinfo" => true,
"view_hellbanned" => True, "view_hellbanned" => true,
"protected" => True, "protected" => true,
)); ]);
new UserClass("hellbanned", "user", array( new UserClass("hellbanned", "user", [
"hellbanned" => True, "hellbanned" => true,
)); ]);
@include_once "data/config/user-classes.conf.php"; @include_once "data/config/user-classes.conf.php";

View File

@ -5,117 +5,126 @@ require_once "vendor/shish/libcontext-php/context.php";
* Misc * * Misc *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function mtimefile(string $file): string { function mtimefile(string $file): string
$data_href = get_base_href(); {
$mtime = filemtime($file); $data_href = get_base_href();
return "$data_href/$file?$mtime"; $mtime = filemtime($file);
return "$data_href/$file?$mtime";
} }
function get_theme(): string { function get_theme(): string
global $config; {
$theme = $config->get_string("theme", "default"); global $config;
if(!file_exists("themes/$theme")) $theme = "default"; $theme = $config->get_string("theme", "default");
return $theme; if (!file_exists("themes/$theme")) {
$theme = "default";
}
return $theme;
} }
function contact_link(): ?string { function contact_link(): ?string
global $config; {
$text = $config->get_string('contact_link'); global $config;
if(is_null($text)) return null; $text = $config->get_string('contact_link');
if (is_null($text)) {
return null;
}
if( if (
startsWith($text, "http:") || startsWith($text, "http:") ||
startsWith($text, "https:") || startsWith($text, "https:") ||
startsWith($text, "mailto:") startsWith($text, "mailto:")
) { ) {
return $text; return $text;
} }
if(strpos($text, "@")) { if (strpos($text, "@")) {
return "mailto:$text"; return "mailto:$text";
} }
if(strpos($text, "/")) { if (strpos($text, "/")) {
return "http://$text"; return "http://$text";
} }
return $text; return $text;
} }
/** /**
* Check if HTTPS is enabled for the server. * Check if HTTPS is enabled for the server.
*/ */
function is_https_enabled(): bool { function is_https_enabled(): bool
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'); {
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
} }
/** /**
* Compare two Block objects, used to sort them before being displayed * Compare two Block objects, used to sort them before being displayed
*/ */
function blockcmp(Block $a, Block $b): int { function blockcmp(Block $a, Block $b): int
if($a->position == $b->position) { {
return 0; if ($a->position == $b->position) {
} return 0;
else { } else {
return ($a->position > $b->position); return ($a->position > $b->position);
} }
} }
/** /**
* Figure out PHP's internal memory limit * Figure out PHP's internal memory limit
*/ */
function get_memory_limit(): int { function get_memory_limit(): int
global $config; {
global $config;
// thumbnail generation requires lots of memory // thumbnail generation requires lots of memory
$default_limit = 8*1024*1024; // 8 MB of memory is PHP's default. $default_limit = 8*1024*1024; // 8 MB of memory is PHP's default.
$shimmie_limit = parse_shorthand_int($config->get_int("thumb_mem_limit")); $shimmie_limit = parse_shorthand_int($config->get_int("thumb_mem_limit"));
if($shimmie_limit < 3*1024*1024) { if ($shimmie_limit < 3*1024*1024) {
// we aren't going to fit, override // we aren't going to fit, override
$shimmie_limit = $default_limit; $shimmie_limit = $default_limit;
} }
/* /*
Get PHP's configured memory limit. Get PHP's configured memory limit.
Note that this is set to -1 for NO memory limit. Note that this is set to -1 for NO memory limit.
http://ca2.php.net/manual/en/ini.core.php#ini.memory-limit http://ca2.php.net/manual/en/ini.core.php#ini.memory-limit
*/ */
$memory = parse_shorthand_int(ini_get("memory_limit")); $memory = parse_shorthand_int(ini_get("memory_limit"));
if($memory == -1) { if ($memory == -1) {
// No memory limit. // No memory limit.
// Return the larger of the set limits. // Return the larger of the set limits.
return max($shimmie_limit, $default_limit); return max($shimmie_limit, $default_limit);
} } else {
else { // PHP has a memory limit set.
// PHP has a memory limit set. if ($shimmie_limit > $memory) {
if ($shimmie_limit > $memory) { // Shimmie wants more memory than what PHP is currently set for.
// Shimmie wants more memory than what PHP is currently set for.
// Attempt to set PHP's memory limit. // Attempt to set PHP's memory limit.
if ( ini_set("memory_limit", $shimmie_limit) === false ) { if (ini_set("memory_limit", $shimmie_limit) === false) {
/* We can't change PHP's limit, oh well, return whatever its currently set to */ /* We can't change PHP's limit, oh well, return whatever its currently set to */
return $memory; return $memory;
} }
$memory = parse_shorthand_int(ini_get("memory_limit")); $memory = parse_shorthand_int(ini_get("memory_limit"));
} }
// PHP's memory limit is more than Shimmie needs. // PHP's memory limit is more than Shimmie needs.
return $memory; // return the current setting return $memory; // return the current setting
} }
} }
/** /**
* Get the currently active IP, masked to make it not change when the last * Get the currently active IP, masked to make it not change when the last
* octet or two change, for use in session cookies and such * octet or two change, for use in session cookies and such
*/ */
function get_session_ip(Config $config): string { function get_session_ip(Config $config): string
$mask = $config->get_string("session_hash_mask", "255.255.0.0"); {
$addr = $_SERVER['REMOTE_ADDR']; $mask = $config->get_string("session_hash_mask", "255.255.0.0");
$addr = inet_ntop(inet_pton($addr) & inet_pton($mask)); $addr = $_SERVER['REMOTE_ADDR'];
return $addr; $addr = inet_ntop(inet_pton($addr) & inet_pton($mask));
return $addr;
} }
@ -128,146 +137,159 @@ function get_session_ip(Config $config): string {
* the action actually takes place (eg onWhateverElse) - but much of the time, actions * the action actually takes place (eg onWhateverElse) - but much of the time, actions
* are taken from within onPageRequest... * are taken from within onPageRequest...
*/ */
function flash_message(string $text, string $type="info") { function flash_message(string $text, string $type="info")
global $page; {
$current = $page->get_cookie("flash_message"); global $page;
if($current) { $current = $page->get_cookie("flash_message");
$text = $current . "\n" . $text; if ($current) {
} $text = $current . "\n" . $text;
# the message should be viewed pretty much immediately, }
# so 60s timeout should be more than enough # the message should be viewed pretty much immediately,
$page->add_cookie("flash_message", $text, time()+60, "/"); # so 60s timeout should be more than enough
$page->add_cookie("flash_message", $text, time()+60, "/");
} }
/** /**
* A shorthand way to send a TextFormattingEvent and get the results. * A shorthand way to send a TextFormattingEvent and get the results.
*/ */
function format_text(string $string): string { function format_text(string $string): string
$tfe = new TextFormattingEvent($string); {
send_event($tfe); $tfe = new TextFormattingEvent($string);
return $tfe->formatted; send_event($tfe);
return $tfe->formatted;
} }
function warehouse_path(string $base, string $hash, bool $create=true): string { function warehouse_path(string $base, string $hash, bool $create=true): string
$ab = substr($hash, 0, 2); {
$cd = substr($hash, 2, 2); $ab = substr($hash, 0, 2);
if(WH_SPLITS == 2) { $cd = substr($hash, 2, 2);
$pa = 'data/'.$base.'/'.$ab.'/'.$cd.'/'.$hash; if (WH_SPLITS == 2) {
} $pa = 'data/'.$base.'/'.$ab.'/'.$cd.'/'.$hash;
else { } else {
$pa = 'data/'.$base.'/'.$ab.'/'.$hash; $pa = 'data/'.$base.'/'.$ab.'/'.$hash;
} }
if($create && !file_exists(dirname($pa))) mkdir(dirname($pa), 0755, true); if ($create && !file_exists(dirname($pa))) {
return $pa; mkdir(dirname($pa), 0755, true);
}
return $pa;
} }
function data_path(string $filename): string { function data_path(string $filename): string
$filename = "data/" . $filename; {
if(!file_exists(dirname($filename))) mkdir(dirname($filename), 0755, true); $filename = "data/" . $filename;
return $filename; if (!file_exists(dirname($filename))) {
mkdir(dirname($filename), 0755, true);
}
return $filename;
} }
function transload(string $url, string $mfile): ?array { function transload(string $url, string $mfile): ?array
global $config; {
global $config;
if($config->get_string("transload_engine") === "curl" && function_exists("curl_init")) { if ($config->get_string("transload_engine") === "curl" && function_exists("curl_init")) {
$ch = curl_init($url); $ch = curl_init($url);
$fp = fopen($mfile, "w"); $fp = fopen($mfile, "w");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1); curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_REFERER, $url); curl_setopt($ch, CURLOPT_REFERER, $url);
curl_setopt($ch, CURLOPT_USERAGENT, "Shimmie-".VERSION); curl_setopt($ch, CURLOPT_USERAGENT, "Shimmie-".VERSION);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$response = curl_exec($ch); $response = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = http_parse_headers(implode("\n", preg_split('/\R/', rtrim(substr($response, 0, $header_size))))); $headers = http_parse_headers(implode("\n", preg_split('/\R/', rtrim(substr($response, 0, $header_size)))));
$body = substr($response, $header_size); $body = substr($response, $header_size);
curl_close($ch); curl_close($ch);
fwrite($fp, $body); fwrite($fp, $body);
fclose($fp); fclose($fp);
return $headers; return $headers;
} }
if($config->get_string("transload_engine") === "wget") { if ($config->get_string("transload_engine") === "wget") {
$s_url = escapeshellarg($url); $s_url = escapeshellarg($url);
$s_mfile = escapeshellarg($mfile); $s_mfile = escapeshellarg($mfile);
system("wget --no-check-certificate $s_url --output-document=$s_mfile"); system("wget --no-check-certificate $s_url --output-document=$s_mfile");
return file_exists($mfile); return file_exists($mfile);
} }
if($config->get_string("transload_engine") === "fopen") { if ($config->get_string("transload_engine") === "fopen") {
$fp_in = @fopen($url, "r"); $fp_in = @fopen($url, "r");
$fp_out = fopen($mfile, "w"); $fp_out = fopen($mfile, "w");
if(!$fp_in || !$fp_out) { if (!$fp_in || !$fp_out) {
return null; return null;
} }
$length = 0; $length = 0;
while(!feof($fp_in) && $length <= $config->get_int('upload_size')) { while (!feof($fp_in) && $length <= $config->get_int('upload_size')) {
$data = fread($fp_in, 8192); $data = fread($fp_in, 8192);
$length += strlen($data); $length += strlen($data);
fwrite($fp_out, $data); fwrite($fp_out, $data);
} }
fclose($fp_in); fclose($fp_in);
fclose($fp_out); fclose($fp_out);
$headers = http_parse_headers(implode("\n", $http_response_header)); $headers = http_parse_headers(implode("\n", $http_response_header));
return $headers; return $headers;
} }
return null; return null;
} }
/** /**
* Get the active contents of a .php file * Get the active contents of a .php file
*/ */
function manual_include(string $fname): ?string { function manual_include(string $fname): ?string
static $included = array(); {
static $included = [];
if(!file_exists($fname)) return null; if (!file_exists($fname)) {
return null;
}
if(in_array($fname, $included)) return null; if (in_array($fname, $included)) {
return null;
}
$included[] = $fname; $included[] = $fname;
print "$fname\n"; print "$fname\n";
$text = file_get_contents($fname); $text = file_get_contents($fname);
// we want one continuous file // we want one continuous file
$text = str_replace('<'.'?php', '', $text); $text = str_replace('<'.'?php', '', $text);
$text = str_replace('?'.'>', '', $text); $text = str_replace('?'.'>', '', $text);
// most requires are built-in, but we want /lib separately // most requires are built-in, but we want /lib separately
$text = str_replace('require_', '// require_', $text); $text = str_replace('require_', '// require_', $text);
$text = str_replace('// require_once "lib', 'require_once "lib', $text); $text = str_replace('// require_once "lib', 'require_once "lib', $text);
// @include_once is used for user-creatable config files // @include_once is used for user-creatable config files
$text = preg_replace('/@include_once "(.*)";/e', "manual_include('$1')", $text); $text = preg_replace('/@include_once "(.*)";/e', "manual_include('$1')", $text);
return $text; return $text;
} }
function path_to_tags(string $path): string { function path_to_tags(string $path): string
$matches = array(); {
if(preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", basename($path), $matches)) { $matches = [];
$tags = $matches[1]; if (preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", basename($path), $matches)) {
} $tags = $matches[1];
else { } else {
$tags = dirname($path); $tags = dirname($path);
$tags = str_replace("/", " ", $tags); $tags = str_replace("/", " ", $tags);
$tags = str_replace("__", " ", $tags); $tags = str_replace("__", " ", $tags);
$tags = trim($tags); $tags = trim($tags);
} }
return $tags; return $tags;
} }
@ -284,52 +306,54 @@ $_shm_load_start = microtime(true);
* Collects some debug information (execution time, memory usage, queries, etc) * Collects some debug information (execution time, memory usage, queries, etc)
* and formats it to stick in the footer of the page. * and formats it to stick in the footer of the page.
*/ */
function get_debug_info(): string { function get_debug_info(): string
global $config, $_shm_event_count, $database, $_shm_load_start; {
global $config, $_shm_event_count, $database, $_shm_load_start;
$i_mem = sprintf("%5.2f", ((memory_get_peak_usage(true)+512)/1024)/1024); $i_mem = sprintf("%5.2f", ((memory_get_peak_usage(true)+512)/1024)/1024);
if($config->get_string("commit_hash", "unknown") == "unknown"){ if ($config->get_string("commit_hash", "unknown") == "unknown") {
$commit = ""; $commit = "";
} } else {
else { $commit = " (".$config->get_string("commit_hash").")";
$commit = " (".$config->get_string("commit_hash").")"; }
} $time = sprintf("%.2f", microtime(true) - $_shm_load_start);
$time = sprintf("%.2f", microtime(true) - $_shm_load_start); $dbtime = sprintf("%.2f", $database->dbtime);
$dbtime = sprintf("%.2f", $database->dbtime); $i_files = count(get_included_files());
$i_files = count(get_included_files()); $hits = $database->cache->get_hits();
$hits = $database->cache->get_hits(); $miss = $database->cache->get_misses();
$miss = $database->cache->get_misses();
$debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM"; $debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM";
$debug .= "; Used $i_files files and {$database->query_count} queries"; $debug .= "; Used $i_files files and {$database->query_count} queries";
$debug .= "; Sent $_shm_event_count events"; $debug .= "; Sent $_shm_event_count events";
$debug .= "; $hits cache hits and $miss misses"; $debug .= "; $hits cache hits and $miss misses";
$debug .= "; Shimmie version ". VERSION . $commit; // .", SCore Version ". SCORE_VERSION; $debug .= "; Shimmie version ". VERSION . $commit; // .", SCore Version ". SCORE_VERSION;
return $debug; return $debug;
} }
function log_slow() { function log_slow()
global $_shm_load_start; {
if(!is_null(SLOW_PAGES)) { global $_shm_load_start;
$_time = microtime(true) - $_shm_load_start; if (!is_null(SLOW_PAGES)) {
if($_time > SLOW_PAGES) { $_time = microtime(true) - $_shm_load_start;
$_query = _get_query(); if ($_time > SLOW_PAGES) {
$_dbg = get_debug_info(); $_query = _get_query();
file_put_contents("data/slow-pages.log", "$_time $_query $_dbg\n", FILE_APPEND | LOCK_EX); $_dbg = get_debug_info();
} file_put_contents("data/slow-pages.log", "$_time $_query $_dbg\n", FILE_APPEND | LOCK_EX);
} }
}
} }
function score_assert_handler($file, $line, $code, $desc = null) { function score_assert_handler($file, $line, $code, $desc = null)
$file = basename($file); {
print("Assertion failed at $file:$line: $code ($desc)"); $file = basename($file);
/* print("Assertion failed at $file:$line: $code ($desc)");
print("<pre>"); /*
debug_print_backtrace(); print("<pre>");
print("</pre>"); debug_print_backtrace();
*/ print("</pre>");
*/
} }
@ -339,88 +363,94 @@ function score_assert_handler($file, $line, $code, $desc = null) {
/** @privatesection */ /** @privatesection */
function _version_check() { function _version_check()
if(MIN_PHP_VERSION) { {
if(version_compare(phpversion(), MIN_PHP_VERSION, ">=") === FALSE) { if (MIN_PHP_VERSION) {
print " if (version_compare(phpversion(), MIN_PHP_VERSION, ">=") === false) {
print "
Shimmie (SCore Engine) does not support versions of PHP lower than ".MIN_PHP_VERSION." Shimmie (SCore Engine) does not support versions of PHP lower than ".MIN_PHP_VERSION."
(PHP reports that it is version ".phpversion().") (PHP reports that it is version ".phpversion().")
If your web host is running an older version, they are dangerously out of If your web host is running an older version, they are dangerously out of
date and you should plan on moving elsewhere. date and you should plan on moving elsewhere.
"; ";
exit; exit;
} }
} }
} }
function _sanitise_environment() { function _sanitise_environment()
global $_shm_ctx; {
global $_shm_ctx;
if(TIMEZONE) { if (TIMEZONE) {
date_default_timezone_set(TIMEZONE); date_default_timezone_set(TIMEZONE);
} }
# ini_set('zend.assertions', 1); // generate assertions # ini_set('zend.assertions', 1); // generate assertions
ini_set('assert.exception', 1); // throw exceptions when failed ini_set('assert.exception', 1); // throw exceptions when failed
if(DEBUG) { if (DEBUG) {
error_reporting(E_ALL); error_reporting(E_ALL);
assert_options(ASSERT_ACTIVE, 1); assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_BAIL, 1); assert_options(ASSERT_BAIL, 1);
assert_options(ASSERT_WARNING, 0); assert_options(ASSERT_WARNING, 0);
assert_options(ASSERT_QUIET_EVAL, 1); assert_options(ASSERT_QUIET_EVAL, 1);
assert_options(ASSERT_CALLBACK, 'score_assert_handler'); assert_options(ASSERT_CALLBACK, 'score_assert_handler');
} }
$_shm_ctx = new Context(); $_shm_ctx = new Context();
if(CONTEXT) { if (CONTEXT) {
$_shm_ctx->set_log(CONTEXT); $_shm_ctx->set_log(CONTEXT);
} }
if(COVERAGE) { if (COVERAGE) {
_start_coverage(); _start_coverage();
register_shutdown_function("_end_coverage"); register_shutdown_function("_end_coverage");
} }
ob_start(); ob_start();
if(PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') { if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
if(isset($_SERVER['REMOTE_ADDR'])) { if (isset($_SERVER['REMOTE_ADDR'])) {
die("CLI with remote addr? Confused, not taking the risk."); die("CLI with remote addr? Confused, not taking the risk.");
} }
$_SERVER['REMOTE_ADDR'] = "0.0.0.0"; $_SERVER['REMOTE_ADDR'] = "0.0.0.0";
$_SERVER['HTTP_HOST'] = "<cli command>"; $_SERVER['HTTP_HOST'] = "<cli command>";
} }
} }
function _get_themelet_files(string $_theme): array { function _get_themelet_files(string $_theme): array
$base_themelets = array(); {
if(file_exists('themes/'.$_theme.'/custompage.class.php')) $base_themelets[] = 'themes/'.$_theme.'/custompage.class.php'; $base_themelets = [];
$base_themelets[] = 'themes/'.$_theme.'/layout.class.php'; if (file_exists('themes/'.$_theme.'/custompage.class.php')) {
$base_themelets[] = 'themes/'.$_theme.'/themelet.class.php'; $base_themelets[] = 'themes/'.$_theme.'/custompage.class.php';
}
$base_themelets[] = 'themes/'.$_theme.'/layout.class.php';
$base_themelets[] = 'themes/'.$_theme.'/themelet.class.php';
$ext_themelets = zglob("ext/{".ENABLED_EXTS."}/theme.php"); $ext_themelets = zglob("ext/{".ENABLED_EXTS."}/theme.php");
$custom_themelets = zglob('themes/'.$_theme.'/{'.ENABLED_EXTS.'}.theme.php'); $custom_themelets = zglob('themes/'.$_theme.'/{'.ENABLED_EXTS.'}.theme.php');
return array_merge($base_themelets, $ext_themelets, $custom_themelets); return array_merge($base_themelets, $ext_themelets, $custom_themelets);
} }
/** /**
* Used to display fatal errors to the web user. * Used to display fatal errors to the web user.
*/ */
function _fatal_error(Exception $e) { function _fatal_error(Exception $e)
$version = VERSION; {
$message = $e->getMessage(); $version = VERSION;
$message = $e->getMessage();
//$trace = var_dump($e->getTrace()); //$trace = var_dump($e->getTrace());
//$hash = exec("git rev-parse HEAD"); //$hash = exec("git rev-parse HEAD");
//$h_hash = $hash ? "<p><b>Hash:</b> $hash" : ""; //$h_hash = $hash ? "<p><b>Hash:</b> $hash" : "";
//'.$h_hash.' //'.$h_hash.'
header("HTTP/1.0 500 Internal Error"); header("HTTP/1.0 500 Internal Error");
echo ' echo '
<html> <html>
<head> <head>
<title>Internal error - SCore-'.$version.'</title> <title>Internal error - SCore-'.$version.'</title>
@ -440,42 +470,50 @@ function _fatal_error(Exception $e) {
* Necessary because various servers and various clients * Necessary because various servers and various clients
* think that / is special... * think that / is special...
*/ */
function _decaret(string $str): string { function _decaret(string $str): string
$out = ""; {
$length = strlen($str); $out = "";
for($i=0; $i<$length; $i++) { $length = strlen($str);
if($str[$i] == "^") { for ($i=0; $i<$length; $i++) {
$i++; if ($str[$i] == "^") {
if($str[$i] == "^") $out .= "^"; $i++;
if($str[$i] == "s") $out .= "/"; if ($str[$i] == "^") {
if($str[$i] == "b") $out .= "\\"; $out .= "^";
} }
else { if ($str[$i] == "s") {
$out .= $str[$i]; $out .= "/";
} }
} if ($str[$i] == "b") {
return $out; $out .= "\\";
}
} else {
$out .= $str[$i];
}
}
return $out;
} }
function _get_user(): User { function _get_user(): User
global $config, $page; {
$user = null; global $config, $page;
if($page->get_cookie("user") && $page->get_cookie("session")) { $user = null;
$tmp_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session")); if ($page->get_cookie("user") && $page->get_cookie("session")) {
if(!is_null($tmp_user)) { $tmp_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session"));
$user = $tmp_user; if (!is_null($tmp_user)) {
} $user = $tmp_user;
} }
if(is_null($user)) { }
$user = User::by_id($config->get_int("anon_id", 0)); if (is_null($user)) {
} $user = User::by_id($config->get_int("anon_id", 0));
assert(!is_null($user)); }
assert(!is_null($user));
return $user; return $user;
} }
function _get_query(): string { function _get_query(): string
return (@$_POST["q"]?:@$_GET["q"])?:"/"; {
return (@$_POST["q"]?:@$_GET["q"])?:"/";
} }
@ -483,24 +521,30 @@ function _get_query(): string {
* Code coverage * * Code coverage *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function _start_coverage(): void { function _start_coverage(): void
if(function_exists("xdebug_start_code_coverage")) { {
#xdebug_start_code_coverage(XDEBUG_CC_UNUSED|XDEBUG_CC_DEAD_CODE); if (function_exists("xdebug_start_code_coverage")) {
xdebug_start_code_coverage(XDEBUG_CC_UNUSED); #xdebug_start_code_coverage(XDEBUG_CC_UNUSED|XDEBUG_CC_DEAD_CODE);
} xdebug_start_code_coverage(XDEBUG_CC_UNUSED);
}
} }
function _end_coverage(): void { function _end_coverage(): void
if(function_exists("xdebug_get_code_coverage")) { {
// Absolute path is necessary because working directory if (function_exists("xdebug_get_code_coverage")) {
// inside register_shutdown_function is unpredictable. // Absolute path is necessary because working directory
$absolute_path = dirname(dirname(__FILE__)) . "/data/coverage"; // inside register_shutdown_function is unpredictable.
if(!file_exists($absolute_path)) mkdir($absolute_path); $absolute_path = dirname(dirname(__FILE__)) . "/data/coverage";
$n = 0; if (!file_exists($absolute_path)) {
$t = time(); mkdir($absolute_path);
while(file_exists("$absolute_path/$t.$n.log")) $n++; }
file_put_contents("$absolute_path/$t.$n.log", gzdeflate(serialize(xdebug_get_code_coverage()))); $n = 0;
} $t = time();
while (file_exists("$absolute_path/$t.$n.log")) {
$n++;
}
file_put_contents("$absolute_path/$t.$n.log", gzdeflate(serialize(xdebug_get_code_coverage())));
}
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
@ -513,35 +557,36 @@ function _end_coverage(): void {
* *
* FIXME: also check that IP ban ext is installed * FIXME: also check that IP ban ext is installed
*/ */
function show_ip(string $ip, string $ban_reason): string { function show_ip(string $ip, string $ban_reason): string
global $user; {
$u_reason = url_escape($ban_reason); global $user;
$u_end = url_escape("+1 week"); $u_reason = url_escape($ban_reason);
$ban = $user->can("ban_ip") ? ", <a href='".make_link("ip_ban/list", "ip=$ip&reason=$u_reason&end=$u_end#add")."'>Ban</a>" : ""; $u_end = url_escape("+1 week");
$ip = $user->can("view_ip") ? $ip.$ban : ""; $ban = $user->can("ban_ip") ? ", <a href='".make_link("ip_ban/list", "ip=$ip&reason=$u_reason&end=$u_end#add")."'>Ban</a>" : "";
return $ip; $ip = $user->can("view_ip") ? $ip.$ban : "";
return $ip;
} }
/** /**
* Make a form tag with relevant auth token and stuff * Make a form tag with relevant auth token and stuff
*/ */
function make_form(string $target, string $method="POST", bool $multipart=False, string $form_id="", string $onsubmit=""): string { function make_form(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit=""): string
global $user; {
if($method == "GET") { global $user;
$link = html_escape($target); if ($method == "GET") {
$target = make_link($target); $link = html_escape($target);
$extra_inputs = "<input type='hidden' name='q' value='$link'>"; $target = make_link($target);
} $extra_inputs = "<input type='hidden' name='q' value='$link'>";
else { } else {
$extra_inputs = $user->get_auth_html(); $extra_inputs = $user->get_auth_html();
} }
$extra = empty($form_id) ? '' : 'id="'. $form_id .'"'; $extra = empty($form_id) ? '' : 'id="'. $form_id .'"';
if($multipart) { if ($multipart) {
$extra .= " enctype='multipart/form-data'"; $extra .= " enctype='multipart/form-data'";
} }
if($onsubmit) { if ($onsubmit) {
$extra .= ' onsubmit="'.$onsubmit.'"'; $extra .= ' onsubmit="'.$onsubmit.'"';
} }
return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$extra_inputs; return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$extra_inputs;
} }

View File

@ -23,254 +23,268 @@
/** /**
* Sent when the admin page is ready to be added to * Sent when the admin page is ready to be added to
*/ */
class AdminBuildingEvent extends Event { class AdminBuildingEvent extends Event
/** @var \Page */ {
public $page; /** @var \Page */
public $page;
public function __construct(Page $page) { public function __construct(Page $page)
$this->page = $page; {
} $this->page = $page;
}
} }
class AdminActionEvent extends Event { class AdminActionEvent extends Event
/** @var string */ {
public $action; /** @var string */
/** @var bool */ public $action;
public $redirect = true; /** @var bool */
public $redirect = true;
public function __construct(string $action) { public function __construct(string $action)
$this->action = $action; {
} $this->action = $action;
}
} }
class AdminPage extends Extension { class AdminPage extends Extension
public function onPageRequest(PageRequestEvent $event) { {
global $page, $user; public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
if($event->page_matches("admin")) { if ($event->page_matches("admin")) {
if(!$user->can("manage_admintools")) { if (!$user->can("manage_admintools")) {
$this->theme->display_permission_denied(); $this->theme->display_permission_denied();
} } else {
else { if ($event->count_args() == 0) {
if($event->count_args() == 0) { send_event(new AdminBuildingEvent($page));
send_event(new AdminBuildingEvent($page)); } else {
} $action = $event->get_arg(0);
else { $aae = new AdminActionEvent($action);
$action = $event->get_arg(0);
$aae = new AdminActionEvent($action);
if($user->check_auth_token()) { if ($user->check_auth_token()) {
log_info("admin", "Util: $action"); log_info("admin", "Util: $action");
set_time_limit(0); set_time_limit(0);
send_event($aae); send_event($aae);
} }
if($aae->redirect) { if ($aae->redirect) {
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("admin")); $page->set_redirect(make_link("admin"));
} }
} }
} }
} }
} }
public function onCommand(CommandEvent $event) { public function onCommand(CommandEvent $event)
if($event->cmd == "help") { {
print "\tget-page [query string]\n"; if ($event->cmd == "help") {
print "\t\teg 'get-page post/list'\n\n"; print "\tget-page [query string]\n";
print "\tregen-thumb [hash]\n"; print "\t\teg 'get-page post/list'\n\n";
print "\t\tregenerate a thumbnail\n\n"; print "\tregen-thumb [hash]\n";
} print "\t\tregenerate a thumbnail\n\n";
if($event->cmd == "get-page") { }
global $page; if ($event->cmd == "get-page") {
send_event(new PageRequestEvent($event->args[0])); global $page;
$page->display(); send_event(new PageRequestEvent($event->args[0]));
} $page->display();
if($event->cmd == "regen-thumb") { }
$image = Image::by_hash($event->args[0]); if ($event->cmd == "regen-thumb") {
if($image) { $image = Image::by_hash($event->args[0]);
print("Regenerating thumb for image {$image->id} ({$image->hash})\n"); if ($image) {
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true)); print("Regenerating thumb for image {$image->id} ({$image->hash})\n");
} send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
else { } else {
print("Can't find image with hash {$event->args[0]}\n"); print("Can't find image with hash {$event->args[0]}\n");
} }
} }
} }
public function onAdminBuilding(AdminBuildingEvent $event) { public function onAdminBuilding(AdminBuildingEvent $event)
$this->theme->display_page(); {
$this->theme->display_form(); $this->theme->display_page();
} $this->theme->display_form();
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) { public function onUserBlockBuilding(UserBlockBuildingEvent $event)
global $user; {
if($user->can("manage_admintools")) { global $user;
$event->add_link("Board Admin", make_link("admin")); if ($user->can("manage_admintools")) {
} $event->add_link("Board Admin", make_link("admin"));
} }
}
public function onAdminAction(AdminActionEvent $event) { public function onAdminAction(AdminActionEvent $event)
$action = $event->action; {
if(method_exists($this, $action)) { $action = $event->action;
$event->redirect = $this->$action(); if (method_exists($this, $action)) {
} $event->redirect = $this->$action();
} }
}
public function onPostListBuilding(PostListBuildingEvent $event) { public function onPostListBuilding(PostListBuildingEvent $event)
global $user; {
if($user->can("manage_admintools") && !empty($event->search_terms)) { global $user;
$event->add_control($this->theme->dbq_html(Tag::implode($event->search_terms))); if ($user->can("manage_admintools") && !empty($event->search_terms)) {
} $event->add_control($this->theme->dbq_html(Tag::implode($event->search_terms)));
} }
}
private function delete_by_query() { private function delete_by_query()
global $page; {
$query = $_POST['query']; global $page;
$reason = @$_POST['reason']; $query = $_POST['query'];
assert(strlen($query) > 1); $reason = @$_POST['reason'];
assert(strlen($query) > 1);
$images = Image::find_images(0, 1000000, Tag::explode($query)); $images = Image::find_images(0, 1000000, Tag::explode($query));
$count = count($images); $count = count($images);
log_warning("admin", "Mass-deleting $count images from $query", "Mass deleted $count images"); log_warning("admin", "Mass-deleting $count images from $query", "Mass deleted $count images");
foreach($images as $image) { foreach ($images as $image) {
if($reason && class_exists("ImageBan")) { if ($reason && class_exists("ImageBan")) {
send_event(new AddImageHashBanEvent($image->hash, $reason)); send_event(new AddImageHashBanEvent($image->hash, $reason));
} }
send_event(new ImageDeletionEvent($image)); send_event(new ImageDeletionEvent($image));
} }
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/list")); $page->set_redirect(make_link("post/list"));
return false; return false;
} }
private function set_tag_case() { private function set_tag_case()
global $database; {
$database->execute($database->scoreql_to_sql( global $database;
"UPDATE tags SET tag=:tag1 WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag2)" $database->execute($database->scoreql_to_sql(
), array("tag1" => $_POST['tag'], "tag2" => $_POST['tag'])); "UPDATE tags SET tag=:tag1 WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag2)"
log_info("admin", "Fixed the case of ".html_escape($_POST['tag']), "Fixed case"); ), ["tag1" => $_POST['tag'], "tag2" => $_POST['tag']]);
return true; log_info("admin", "Fixed the case of ".html_escape($_POST['tag']), "Fixed case");
} return true;
}
private function lowercase_all_tags() { private function lowercase_all_tags()
global $database; {
$database->execute("UPDATE tags SET tag=lower(tag)"); global $database;
log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase"); $database->execute("UPDATE tags SET tag=lower(tag)");
return true; log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase");
} return true;
}
private function recount_tag_use() { private function recount_tag_use()
global $database; {
$database->Execute(" global $database;
$database->Execute("
UPDATE tags UPDATE tags
SET count = COALESCE( SET count = COALESCE(
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id), (SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
0 0
) )
"); ");
$database->Execute("DELETE FROM tags WHERE count=0"); $database->Execute("DELETE FROM tags WHERE count=0");
log_warning("admin", "Re-counted tags", "Re-counted tags"); log_warning("admin", "Re-counted tags", "Re-counted tags");
return true; return true;
} }
private function database_dump() { private function database_dump()
global $page; {
global $page;
$matches = array(); $matches = [];
preg_match("#^(?P<proto>\w+)\:(?:user=(?P<user>\w+)(?:;|$)|password=(?P<password>\w*)(?:;|$)|host=(?P<host>[\w\.\-]+)(?:;|$)|dbname=(?P<dbname>[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches); preg_match("#^(?P<proto>\w+)\:(?:user=(?P<user>\w+)(?:;|$)|password=(?P<password>\w*)(?:;|$)|host=(?P<host>[\w\.\-]+)(?:;|$)|dbname=(?P<dbname>[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches);
$software = $matches['proto']; $software = $matches['proto'];
$username = $matches['user']; $username = $matches['user'];
$password = $matches['password']; $password = $matches['password'];
$hostname = $matches['host']; $hostname = $matches['host'];
$database = $matches['dbname']; $database = $matches['dbname'];
switch($software) { switch ($software) {
case 'mysql': case 'mysql':
$cmd = "mysqldump -h$hostname -u$username -p$password $database"; $cmd = "mysqldump -h$hostname -u$username -p$password $database";
break; break;
case 'pgsql': case 'pgsql':
putenv("PGPASSWORD=$password"); putenv("PGPASSWORD=$password");
$cmd = "pg_dump -h $hostname -U $username $database"; $cmd = "pg_dump -h $hostname -U $username $database";
break; break;
case 'sqlite': case 'sqlite':
$cmd = "sqlite3 $database .dump"; $cmd = "sqlite3 $database .dump";
break; break;
default: default:
$cmd = false; $cmd = false;
} }
//FIXME: .SQL dump is empty if cmd doesn't exist //FIXME: .SQL dump is empty if cmd doesn't exist
if($cmd) { if ($cmd) {
$page->set_mode("data"); $page->set_mode("data");
$page->set_type("application/x-unknown"); $page->set_type("application/x-unknown");
$page->set_filename('shimmie-'.date('Ymd').'.sql'); $page->set_filename('shimmie-'.date('Ymd').'.sql');
$page->set_data(shell_exec($cmd)); $page->set_data(shell_exec($cmd));
} }
return false; return false;
} }
private function download_all_images() { private function download_all_images()
global $database, $page; {
global $database, $page;
$images = $database->get_all("SELECT hash, ext FROM images"); $images = $database->get_all("SELECT hash, ext FROM images");
$filename = data_path('imgdump-'.date('Ymd').'.zip'); $filename = data_path('imgdump-'.date('Ymd').'.zip');
$zip = new ZipArchive; $zip = new ZipArchive;
if($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === TRUE){ if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
foreach($images as $img){ foreach ($images as $img) {
$img_loc = warehouse_path("images", $img["hash"], FALSE); $img_loc = warehouse_path("images", $img["hash"], false);
$zip->addFile($img_loc, $img["hash"].".".$img["ext"]); $zip->addFile($img_loc, $img["hash"].".".$img["ext"]);
} }
$zip->close(); $zip->close();
} }
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link($filename)); //TODO: Delete file after downloaded? $page->set_redirect(make_link($filename)); //TODO: Delete file after downloaded?
return false; // we do want a redirect, but a manual one return false; // we do want a redirect, but a manual one
} }
private function reset_image_ids() { private function reset_image_ids()
{
global $database; global $database;
//TODO: Make work with PostgreSQL + SQLite //TODO: Make work with PostgreSQL + SQLite
//TODO: Update score_log (Having an optional ID column for score_log would be nice..) //TODO: Update score_log (Having an optional ID column for score_log would be nice..)
preg_match("#^(?P<proto>\w+)\:(?:user=(?P<user>\w+)(?:;|$)|password=(?P<password>\w*)(?:;|$)|host=(?P<host>[\w\.\-]+)(?:;|$)|dbname=(?P<dbname>[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches); preg_match("#^(?P<proto>\w+)\:(?:user=(?P<user>\w+)(?:;|$)|password=(?P<password>\w*)(?:;|$)|host=(?P<host>[\w\.\-]+)(?:;|$)|dbname=(?P<dbname>[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches);
if($matches['proto'] == "mysql"){ if ($matches['proto'] == "mysql") {
$tables = $database->get_col("SELECT TABLE_NAME $tables = $database->get_col("SELECT TABLE_NAME
FROM information_schema.KEY_COLUMN_USAGE FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = :db WHERE TABLE_SCHEMA = :db
AND REFERENCED_COLUMN_NAME = 'id' AND REFERENCED_COLUMN_NAME = 'id'
AND REFERENCED_TABLE_NAME = 'images'", array("db" => $matches['dbname'])); AND REFERENCED_TABLE_NAME = 'images'", ["db" => $matches['dbname']]);
$i = 1; $i = 1;
$ids = $database->get_col("SELECT id FROM images ORDER BY images.id ASC"); $ids = $database->get_col("SELECT id FROM images ORDER BY images.id ASC");
foreach($ids as $id){ foreach ($ids as $id) {
$sql = "SET FOREIGN_KEY_CHECKS=0; $sql = "SET FOREIGN_KEY_CHECKS=0;
UPDATE images SET id={$i} WHERE image_id={$id};"; UPDATE images SET id={$i} WHERE image_id={$id};";
foreach($tables as $table){ foreach ($tables as $table) {
$sql .= "UPDATE {$table} SET image_id={$i} WHERE image_id={$id};"; $sql .= "UPDATE {$table} SET image_id={$i} WHERE image_id={$id};";
} }
$sql .= " SET FOREIGN_KEY_CHECKS=1;"; $sql .= " SET FOREIGN_KEY_CHECKS=1;";
$database->execute($sql); $database->execute($sql);
$i++; $i++;
} }
$database->execute("ALTER TABLE images AUTO_INCREMENT=".(count($ids) + 1)); $database->execute("ALTER TABLE images AUTO_INCREMENT=".(count($ids) + 1));
}elseif($matches['proto'] == "pgsql"){ } elseif ($matches['proto'] == "pgsql") {
//TODO: Make this work with PostgreSQL //TODO: Make this work with PostgreSQL
}elseif($matches['proto'] == "sqlite"){ } elseif ($matches['proto'] == "sqlite") {
//TODO: Make this work with SQLite //TODO: Make this work with SQLite
} }
return true; return true;
} }
} }

View File

@ -1,84 +1,89 @@
<?php <?php
class AdminPageTest extends ShimmiePHPUnitTestCase { class AdminPageTest extends ShimmiePHPUnitTestCase
public function testAuth() { {
$this->get_page('admin'); public function testAuth()
$this->assert_response(403); {
$this->assert_title("Permission Denied"); $this->get_page('admin');
$this->assert_response(403);
$this->assert_title("Permission Denied");
$this->log_in_as_user(); $this->log_in_as_user();
$this->get_page('admin'); $this->get_page('admin');
$this->assert_response(403); $this->assert_response(403);
$this->assert_title("Permission Denied"); $this->assert_title("Permission Denied");
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page('admin'); $this->get_page('admin');
$this->assert_response(200); $this->assert_response(200);
$this->assert_title("Admin Tools"); $this->assert_title("Admin Tools");
} }
public function testLowercase() { public function testLowercase()
$ts = time(); // we need a tag that hasn't been used before {
$ts = time(); // we need a tag that hasn't been used before
$this->log_in_as_admin(); $this->log_in_as_admin();
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "TeStCase$ts"); $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "TeStCase$ts");
$this->get_page("post/view/$image_id_1"); $this->get_page("post/view/$image_id_1");
$this->assert_title("Image $image_id_1: TeStCase$ts"); $this->assert_title("Image $image_id_1: TeStCase$ts");
$this->get_page('admin'); $this->get_page('admin');
$this->assert_title("Admin Tools"); $this->assert_title("Admin Tools");
//$this->click("All tags to lowercase"); //$this->click("All tags to lowercase");
send_event(new AdminActionEvent('lowercase_all_tags')); send_event(new AdminActionEvent('lowercase_all_tags'));
$this->get_page("post/view/$image_id_1"); $this->get_page("post/view/$image_id_1");
$this->assert_title("Image $image_id_1: testcase$ts"); $this->assert_title("Image $image_id_1: testcase$ts");
$this->delete_image($image_id_1); $this->delete_image($image_id_1);
} }
# FIXME: make sure the admin tools actually work # FIXME: make sure the admin tools actually work
public function testRecount() { public function testRecount()
$this->log_in_as_admin(); {
$this->get_page('admin'); $this->log_in_as_admin();
$this->assert_title("Admin Tools"); $this->get_page('admin');
$this->assert_title("Admin Tools");
//$this->click("Recount tag use"); //$this->click("Recount tag use");
send_event(new AdminActionEvent('recount_tag_use')); send_event(new AdminActionEvent('recount_tag_use'));
} }
public function testDump() { public function testDump()
$this->log_in_as_admin(); {
$this->get_page('admin'); $this->log_in_as_admin();
$this->assert_title("Admin Tools"); $this->get_page('admin');
$this->assert_title("Admin Tools");
// this calls mysqldump which jams up travis prompting for a password // this calls mysqldump which jams up travis prompting for a password
//$this->click("Download database contents"); //$this->click("Download database contents");
//send_event(new AdminActionEvent('database_dump')); //send_event(new AdminActionEvent('database_dump'));
//$this->assert_response(200); //$this->assert_response(200);
} }
public function testDBQ() { public function testDBQ()
$this->log_in_as_user(); {
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "test"); $this->log_in_as_user();
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "test2"); $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "test");
$image_id_3 = $this->post_image("tests/favicon.png", "test"); $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "test2");
$image_id_3 = $this->post_image("tests/favicon.png", "test");
$this->get_page("post/list/test/1"); $this->get_page("post/list/test/1");
//$this->click("Delete All These Images"); //$this->click("Delete All These Images");
$_POST['query'] = 'test'; $_POST['query'] = 'test';
//$_POST['reason'] = 'reason'; // non-null-reason = add a hash ban //$_POST['reason'] = 'reason'; // non-null-reason = add a hash ban
send_event(new AdminActionEvent('delete_by_query')); send_event(new AdminActionEvent('delete_by_query'));
$this->get_page("post/view/$image_id_1"); $this->get_page("post/view/$image_id_1");
$this->assert_response(404); $this->assert_response(404);
$this->get_page("post/view/$image_id_2"); $this->get_page("post/view/$image_id_2");
$this->assert_response(200); $this->assert_response(200);
$this->get_page("post/view/$image_id_3"); $this->get_page("post/view/$image_id_3");
$this->assert_response(404); $this->assert_response(404);
$this->delete_image($image_id_1); $this->delete_image($image_id_1);
$this->delete_image($image_id_2); $this->delete_image($image_id_2);
$this->delete_image($image_id_3); $this->delete_image($image_id_3);
} }
} }

View File

@ -1,71 +1,76 @@
<?php <?php
class AdminPageTheme extends Themelet { class AdminPageTheme extends Themelet
/* {
* Show the basics of a page, for other extensions to add to /*
*/ * Show the basics of a page, for other extensions to add to
public function display_page() { */
global $page; public function display_page()
{
global $page;
$page->set_title("Admin Tools"); $page->set_title("Admin Tools");
$page->set_heading("Admin Tools"); $page->set_heading("Admin Tools");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
} }
protected function button(string $name, string $action, bool $protected=false): string { protected function button(string $name, string $action, bool $protected=false): string
$c_protected = $protected ? " protected" : ""; {
$html = make_form(make_link("admin/$action"), "POST", false, "admin$c_protected"); $c_protected = $protected ? " protected" : "";
if($protected) { $html = make_form(make_link("admin/$action"), "POST", false, "admin$c_protected");
$html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>"; if ($protected) {
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>"; $html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>";
} $html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
else { } else {
$html .= "<input type='submit' id='$action' value='$name'>"; $html .= "<input type='submit' id='$action' value='$name'>";
} }
$html .= "</form>\n"; $html .= "</form>\n";
return $html; return $html;
} }
/* /*
* Show a form which links to admin_utils with POST[action] set to one of: * Show a form which links to admin_utils with POST[action] set to one of:
* 'lowercase all tags' * 'lowercase all tags'
* 'recount tag use' * 'recount tag use'
* etc * etc
*/ */
public function display_form() { public function display_form()
global $page, $database; {
global $page, $database;
$html = ""; $html = "";
$html .= $this->button("All tags to lowercase", "lowercase_all_tags", true); $html .= $this->button("All tags to lowercase", "lowercase_all_tags", true);
$html .= $this->button("Recount tag use", "recount_tag_use", false); $html .= $this->button("Recount tag use", "recount_tag_use", false);
if(class_exists('ZipArchive')) if (class_exists('ZipArchive')) {
$html .= $this->button("Download all images", "download_all_images", false); $html .= $this->button("Download all images", "download_all_images", false);
}
$html .= $this->button("Download database contents", "database_dump", false); $html .= $this->button("Download database contents", "database_dump", false);
if($database->get_driver_name() == "mysql") if ($database->get_driver_name() == "mysql") {
$html .= $this->button("Reset image IDs", "reset_image_ids", true); $html .= $this->button("Reset image IDs", "reset_image_ids", true);
$page->add_block(new Block("Misc Admin Tools", $html)); }
$page->add_block(new Block("Misc Admin Tools", $html));
$html = make_form(make_link("admin/set_tag_case"), "POST"); $html = make_form(make_link("admin/set_tag_case"), "POST");
$html .= "<input type='text' name='tag' placeholder='Enter tag with correct case' class='autocomplete_tags' autocomplete='off'>"; $html .= "<input type='text' name='tag' placeholder='Enter tag with correct case' class='autocomplete_tags' autocomplete='off'>";
$html .= "<input type='submit' value='Set Tag Case'>"; $html .= "<input type='submit' value='Set Tag Case'>";
$html .= "</form>\n"; $html .= "</form>\n";
$page->add_block(new Block("Set Tag Case", $html)); $page->add_block(new Block("Set Tag Case", $html));
} }
public function dbq_html($terms) { public function dbq_html($terms)
$h_terms = html_escape($terms); {
$h_reason = ""; $h_terms = html_escape($terms);
if(class_exists("ImageBan")) { $h_reason = "";
$h_reason = "<input type='text' name='reason' placeholder='Ban reason (leave blank to not ban)'>"; if (class_exists("ImageBan")) {
} $h_reason = "<input type='text' name='reason' placeholder='Ban reason (leave blank to not ban)'>";
$html = make_form(make_link("admin/delete_by_query"), "POST") . " }
$html = make_form(make_link("admin/delete_by_query"), "POST") . "
<input type='button' class='shm-unlocker' data-unlock-sel='#dbqsubmit' value='Unlock'> <input type='button' class='shm-unlocker' data-unlock-sel='#dbqsubmit' value='Unlock'>
<input type='hidden' name='query' value='$h_terms'> <input type='hidden' name='query' value='$h_terms'>
$h_reason $h_reason
<input type='submit' id='dbqsubmit' disabled='true' value='Delete All These Images'> <input type='submit' id='dbqsubmit' disabled='true' value='Delete All These Images'>
</form> </form>
"; ";
return $html; return $html;
} }
} }

View File

@ -10,155 +10,156 @@
* site admins can edit it, other people can view and download it * site admins can edit it, other people can view and download it
*/ */
class AddAliasEvent extends Event { class AddAliasEvent extends Event
/** @var string */ {
public $oldtag; /** @var string */
/** @var string */ public $oldtag;
public $newtag; /** @var string */
public $newtag;
public function __construct(string $oldtag, string $newtag) { public function __construct(string $oldtag, string $newtag)
$this->oldtag = trim($oldtag); {
$this->newtag = trim($newtag); $this->oldtag = trim($oldtag);
} $this->newtag = trim($newtag);
}
} }
class AddAliasException extends SCoreException {} class AddAliasException extends SCoreException
{
class AliasEditor extends Extension {
public function onPageRequest(PageRequestEvent $event) {
global $config, $database, $page, $user;
if($event->page_matches("alias")) {
if($event->get_arg(0) == "add") {
if($user->can("manage_alias_list")) {
if(isset($_POST['oldtag']) && isset($_POST['newtag'])) {
try {
$aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']);
send_event($aae);
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
}
catch(AddAliasException $ex) {
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
}
}
}
}
else if($event->get_arg(0) == "remove") {
if($user->can("manage_alias_list")) {
if(isset($_POST['oldtag'])) {
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", array("oldtag" => $_POST['oldtag']));
log_info("alias_editor", "Deleted alias for ".$_POST['oldtag'], "Deleted alias");
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
}
}
}
else if($event->get_arg(0) == "list") {
$page_number = $event->get_arg(1);
if(is_null($page_number) || !is_numeric($page_number)) {
$page_number = 0;
}
else if ($page_number <= 0) {
$page_number = 0;
}
else {
$page_number--;
}
$alias_per_page = $config->get_int('alias_items_per_page', 30);
$query = "SELECT oldtag, newtag FROM aliases ORDER BY newtag ASC LIMIT :limit OFFSET :offset";
$alias = $database->get_pairs($query,
array("limit"=>$alias_per_page, "offset"=>$page_number * $alias_per_page)
);
$total_pages = ceil($database->get_one("SELECT COUNT(*) FROM aliases") / $alias_per_page);
$this->theme->display_aliases($alias, $page_number + 1, $total_pages);
}
else if($event->get_arg(0) == "export") {
$page->set_mode("data");
$page->set_type("text/csv");
$page->set_filename("aliases.csv");
$page->set_data($this->get_alias_csv($database));
}
else if($event->get_arg(0) == "import") {
if($user->can("manage_alias_list")) {
if(count($_FILES) > 0) {
$tmp = $_FILES['alias_file']['tmp_name'];
$contents = file_get_contents($tmp);
$this->add_alias_csv($database, $contents);
log_info("alias_editor", "Imported aliases from file", "Imported aliases"); # FIXME: how many?
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
}
else {
$this->theme->display_error(400, "No File Specified", "You have to upload a file");
}
}
else {
$this->theme->display_error(401, "Admins Only", "Only admins can edit the alias list");
}
}
}
}
public function onAddAlias(AddAliasEvent $event) {
global $database;
$pair = array("oldtag" => $event->oldtag, "newtag" => $event->newtag);
if($database->get_row("SELECT * FROM aliases WHERE oldtag=:oldtag AND lower(newtag)=lower(:newtag)", $pair)) {
throw new AddAliasException("That alias already exists");
}
else if($database->get_row("SELECT * FROM aliases WHERE oldtag=:newtag", array("newtag" => $event->newtag))) {
throw new AddAliasException("{$event->newtag} is itself an alias");
}
else {
$database->execute("INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)", $pair);
log_info("alias_editor", "Added alias for {$event->oldtag} -> {$event->newtag}", "Added alias");
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
global $user;
if($user->can("manage_alias_list")) {
$event->add_link("Alias Editor", make_link("alias/list"));
}
}
private function get_alias_csv(Database $database): string {
$csv = "";
$aliases = $database->get_pairs("SELECT oldtag, newtag FROM aliases ORDER BY newtag");
foreach($aliases as $old => $new) {
$csv .= "\"$old\",\"$new\"\n";
}
return $csv;
}
private function add_alias_csv(Database $database, string $csv) {
$csv = str_replace("\r", "\n", $csv);
foreach(explode("\n", $csv) as $line) {
$parts = str_getcsv($line);
if(count($parts) == 2) {
try {
$aae = new AddAliasEvent($parts[0], $parts[1]);
send_event($aae);
}
catch(AddAliasException $ex) {
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
}
}
}
}
/**
* Get the priority for this extension.
*
* Add alias *after* mass tag editing, else the MTE will
* search for the images and be redirected to the alias,
* missing out the images tagged with the old tag.
*/
public function get_priority(): int {return 60;}
} }
class AliasEditor extends Extension
{
public function onPageRequest(PageRequestEvent $event)
{
global $config, $database, $page, $user;
if ($event->page_matches("alias")) {
if ($event->get_arg(0) == "add") {
if ($user->can("manage_alias_list")) {
if (isset($_POST['oldtag']) && isset($_POST['newtag'])) {
try {
$aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']);
send_event($aae);
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
} catch (AddAliasException $ex) {
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
}
}
}
} elseif ($event->get_arg(0) == "remove") {
if ($user->can("manage_alias_list")) {
if (isset($_POST['oldtag'])) {
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", ["oldtag" => $_POST['oldtag']]);
log_info("alias_editor", "Deleted alias for ".$_POST['oldtag'], "Deleted alias");
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
}
}
} elseif ($event->get_arg(0) == "list") {
$page_number = $event->get_arg(1);
if (is_null($page_number) || !is_numeric($page_number)) {
$page_number = 0;
} elseif ($page_number <= 0) {
$page_number = 0;
} else {
$page_number--;
}
$alias_per_page = $config->get_int('alias_items_per_page', 30);
$query = "SELECT oldtag, newtag FROM aliases ORDER BY newtag ASC LIMIT :limit OFFSET :offset";
$alias = $database->get_pairs(
$query,
["limit"=>$alias_per_page, "offset"=>$page_number * $alias_per_page]
);
$total_pages = ceil($database->get_one("SELECT COUNT(*) FROM aliases") / $alias_per_page);
$this->theme->display_aliases($alias, $page_number + 1, $total_pages);
} elseif ($event->get_arg(0) == "export") {
$page->set_mode("data");
$page->set_type("text/csv");
$page->set_filename("aliases.csv");
$page->set_data($this->get_alias_csv($database));
} elseif ($event->get_arg(0) == "import") {
if ($user->can("manage_alias_list")) {
if (count($_FILES) > 0) {
$tmp = $_FILES['alias_file']['tmp_name'];
$contents = file_get_contents($tmp);
$this->add_alias_csv($database, $contents);
log_info("alias_editor", "Imported aliases from file", "Imported aliases"); # FIXME: how many?
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
} else {
$this->theme->display_error(400, "No File Specified", "You have to upload a file");
}
} else {
$this->theme->display_error(401, "Admins Only", "Only admins can edit the alias list");
}
}
}
}
public function onAddAlias(AddAliasEvent $event)
{
global $database;
$pair = ["oldtag" => $event->oldtag, "newtag" => $event->newtag];
if ($database->get_row("SELECT * FROM aliases WHERE oldtag=:oldtag AND lower(newtag)=lower(:newtag)", $pair)) {
throw new AddAliasException("That alias already exists");
} elseif ($database->get_row("SELECT * FROM aliases WHERE oldtag=:newtag", ["newtag" => $event->newtag])) {
throw new AddAliasException("{$event->newtag} is itself an alias");
} else {
$database->execute("INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)", $pair);
log_info("alias_editor", "Added alias for {$event->oldtag} -> {$event->newtag}", "Added alias");
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
if ($user->can("manage_alias_list")) {
$event->add_link("Alias Editor", make_link("alias/list"));
}
}
private function get_alias_csv(Database $database): string
{
$csv = "";
$aliases = $database->get_pairs("SELECT oldtag, newtag FROM aliases ORDER BY newtag");
foreach ($aliases as $old => $new) {
$csv .= "\"$old\",\"$new\"\n";
}
return $csv;
}
private function add_alias_csv(Database $database, string $csv)
{
$csv = str_replace("\r", "\n", $csv);
foreach (explode("\n", $csv) as $line) {
$parts = str_getcsv($line);
if (count($parts) == 2) {
try {
$aae = new AddAliasEvent($parts[0], $parts[1]);
send_event($aae);
} catch (AddAliasException $ex) {
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
}
}
}
}
/**
* Get the priority for this extension.
*
* Add alias *after* mass tag editing, else the MTE will
* search for the images and be redirected to the alias,
* missing out the images tagged with the old tag.
*/
public function get_priority(): int
{
return 60;
}
}

View File

@ -1,104 +1,107 @@
<?php <?php
class AliasEditorTest extends ShimmiePHPUnitTestCase { class AliasEditorTest extends ShimmiePHPUnitTestCase
public function testAliasList() { {
$this->get_page('alias/list'); public function testAliasList()
$this->assert_response(200); {
$this->assert_title("Alias List"); $this->get_page('alias/list');
} $this->assert_response(200);
$this->assert_title("Alias List");
}
public function testAliasListReadOnly() { public function testAliasListReadOnly()
// Check that normal users can't add aliases. {
$this->log_in_as_user(); // Check that normal users can't add aliases.
$this->get_page('alias/list'); $this->log_in_as_user();
$this->assert_title("Alias List"); $this->get_page('alias/list');
$this->assert_no_text("Add"); $this->assert_title("Alias List");
} $this->assert_no_text("Add");
}
public function testAliasEditor() { public function testAliasEditor()
/* {
********************************************************************** /*
* FIXME: TODO: **********************************************************************
* For some reason the alias tests always fail when they are running * FIXME: TODO:
* inside the TravisCI VM environment. I have tried to determine * For some reason the alias tests always fail when they are running
* the exact cause of this, but have been unable to pin it down. * inside the TravisCI VM environment. I have tried to determine
* * the exact cause of this, but have been unable to pin it down.
* For now, I am commenting them out until I have more time to *
* dig into this and determine exactly what is happening. * For now, I am commenting them out until I have more time to
* * dig into this and determine exactly what is happening.
********************************************************************* *
*/ *********************************************************************
$this->markTestIncomplete(); */
$this->markTestIncomplete();
$this->log_in_as_admin(); $this->log_in_as_admin();
# test one to one # test one to one
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_title("Alias List"); $this->assert_title("Alias List");
$this->set_field('oldtag', "test1"); $this->set_field('oldtag', "test1");
$this->set_field('newtag', "test2"); $this->set_field('newtag', "test2");
$this->clickSubmit('Add'); $this->clickSubmit('Add');
$this->assert_no_text("Error adding alias"); $this->assert_no_text("Error adding alias");
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_text("test1"); $this->assert_text("test1");
$this->get_page("alias/export/aliases.csv"); $this->get_page("alias/export/aliases.csv");
$this->assert_text("test1,test2"); $this->assert_text("test1,test2");
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
$this->get_page("post/view/$image_id"); # check that the tag has been replaced $this->get_page("post/view/$image_id"); # check that the tag has been replaced
$this->assert_title("Image $image_id: test2"); $this->assert_title("Image $image_id: test2");
$this->get_page("post/list/test1/1"); # searching for an alias should find the master tag $this->get_page("post/list/test1/1"); # searching for an alias should find the master tag
$this->assert_title("Image $image_id: test2"); $this->assert_title("Image $image_id: test2");
$this->get_page("post/list/test2/1"); # check that searching for the main tag still works $this->get_page("post/list/test2/1"); # check that searching for the main tag still works
$this->assert_title("Image $image_id: test2"); $this->assert_title("Image $image_id: test2");
$this->delete_image($image_id); $this->delete_image($image_id);
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->click("Remove"); $this->click("Remove");
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_title("Alias List"); $this->assert_title("Alias List");
$this->assert_no_text("test1"); $this->assert_no_text("test1");
# test one to many # test one to many
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_title("Alias List"); $this->assert_title("Alias List");
$this->set_field('oldtag', "onetag"); $this->set_field('oldtag', "onetag");
$this->set_field('newtag', "multi tag"); $this->set_field('newtag', "multi tag");
$this->click("Add"); $this->click("Add");
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_text("multi"); $this->assert_text("multi");
$this->assert_text("tag"); $this->assert_text("tag");
$this->get_page("alias/export/aliases.csv"); $this->get_page("alias/export/aliases.csv");
$this->assert_text("onetag,multi tag"); $this->assert_text("onetag,multi tag");
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "onetag"); $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "onetag");
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "onetag"); $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "onetag");
// FIXME: known broken // FIXME: known broken
//$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases //$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases
//$this->assert_title("onetag"); //$this->assert_title("onetag");
//$this->assert_no_text("No Images Found"); //$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi/1"); $this->get_page("post/list/multi/1");
$this->assert_title("multi"); $this->assert_title("multi");
$this->assert_no_text("No Images Found"); $this->assert_no_text("No Images Found");
$this->get_page("post/list/multi%20tag/1"); $this->get_page("post/list/multi%20tag/1");
$this->assert_title("multi tag"); $this->assert_title("multi tag");
$this->assert_no_text("No Images Found"); $this->assert_no_text("No Images Found");
$this->delete_image($image_id_1); $this->delete_image($image_id_1);
$this->delete_image($image_id_2); $this->delete_image($image_id_2);
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->click("Remove"); $this->click("Remove");
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_title("Alias List"); $this->assert_title("Alias List");
$this->assert_no_text("test1"); $this->assert_no_text("test1");
$this->log_out(); $this->log_out();
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_title("Alias List"); $this->assert_title("Alias List");
$this->assert_no_text("Add"); $this->assert_no_text("Add");
} }
} }

View File

@ -1,18 +1,20 @@
<?php <?php
class AliasEditorTheme extends Themelet { class AliasEditorTheme extends Themelet
/** {
* Show a page of aliases. /**
* * Show a page of aliases.
* Note: $can_manage = whether things like "add new alias" should be shown *
*/ * Note: $can_manage = whether things like "add new alias" should be shown
public function display_aliases(array $aliases, int $pageNumber, int $totalPages): void { */
global $page, $user; public function display_aliases(array $aliases, int $pageNumber, int $totalPages): void
{
global $page, $user;
$can_manage = $user->can("manage_alias_list"); $can_manage = $user->can("manage_alias_list");
if($can_manage) { if ($can_manage) {
$h_action = "<th width='10%'>Action</th>"; $h_action = "<th width='10%'>Action</th>";
$h_add = " $h_add = "
<tr> <tr>
".make_form(make_link("alias/add"))." ".make_form(make_link("alias/add"))."
<td><input type='text' name='oldtag' class='autocomplete_tags' autocomplete='off'></td> <td><input type='text' name='oldtag' class='autocomplete_tags' autocomplete='off'></td>
@ -21,20 +23,19 @@ class AliasEditorTheme extends Themelet {
</form> </form>
</tr> </tr>
"; ";
} } else {
else { $h_action = "";
$h_action = ""; $h_add = "";
$h_add = ""; }
}
$h_aliases = ""; $h_aliases = "";
foreach($aliases as $old => $new) { foreach ($aliases as $old => $new) {
$h_old = html_escape($old); $h_old = html_escape($old);
$h_new = "<a href='".make_link("post/list/".url_escape($new)."/1")."'>".html_escape($new)."</a>"; $h_new = "<a href='".make_link("post/list/".url_escape($new)."/1")."'>".html_escape($new)."</a>";
$h_aliases .= "<tr><td>$h_old</td><td>$h_new</td>"; $h_aliases .= "<tr><td>$h_old</td><td>$h_new</td>";
if($can_manage) { if ($can_manage) {
$h_aliases .= " $h_aliases .= "
<td> <td>
".make_form(make_link("alias/remove"))." ".make_form(make_link("alias/remove"))."
<input type='hidden' name='oldtag' value='$h_old'> <input type='hidden' name='oldtag' value='$h_old'>
@ -42,10 +43,10 @@ class AliasEditorTheme extends Themelet {
</form> </form>
</td> </td>
"; ";
} }
$h_aliases .= "</tr>"; $h_aliases .= "</tr>";
} }
$html = " $html = "
<table id='aliases' class='sortable zebra'> <table id='aliases' class='sortable zebra'>
<thead><tr><th>From</th><th>To</th>$h_action</tr></thead> <thead><tr><th>From</th><th>To</th>$h_action</tr></thead>
<tbody>$h_aliases</tbody> <tbody>$h_aliases</tbody>
@ -54,22 +55,21 @@ class AliasEditorTheme extends Themelet {
<p><a href='".make_link("alias/export/aliases.csv")."' download='aliases.csv'>Download as CSV</a></p> <p><a href='".make_link("alias/export/aliases.csv")."' download='aliases.csv'>Download as CSV</a></p>
"; ";
$bulk_html = " $bulk_html = "
".make_form(make_link("alias/import"), 'post', true)." ".make_form(make_link("alias/import"), 'post', true)."
<input type='file' name='alias_file'> <input type='file' name='alias_file'>
<input type='submit' value='Upload List'> <input type='submit' value='Upload List'>
</form> </form>
"; ";
$page->set_title("Alias List"); $page->set_title("Alias List");
$page->set_heading("Alias List"); $page->set_heading("Alias List");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
$page->add_block(new Block("Aliases", $html)); $page->add_block(new Block("Aliases", $html));
if($can_manage) { if ($can_manage) {
$page->add_block(new Block("Bulk Upload", $bulk_html, "main", 51)); $page->add_block(new Block("Bulk Upload", $bulk_html, "main", 51));
} }
$this->display_paginator($page, "alias/list", null, $pageNumber, $totalPages); $this->display_paginator($page, "alias/list", null, $pageNumber, $totalPages);
} }
} }

View File

@ -9,67 +9,71 @@
require_once "ext/amazon_s3/lib/S3.php"; require_once "ext/amazon_s3/lib/S3.php";
class UploadS3 extends Extension { class UploadS3 extends Extension
public function onInitExt(InitExtEvent $event) { {
global $config; public function onInitExt(InitExtEvent $event)
$config->set_default_string("amazon_s3_access", ""); {
$config->set_default_string("amazon_s3_secret", ""); global $config;
$config->set_default_string("amazon_s3_bucket", ""); $config->set_default_string("amazon_s3_access", "");
} $config->set_default_string("amazon_s3_secret", "");
$config->set_default_string("amazon_s3_bucket", "");
}
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event)
$sb = new SetupBlock("Amazon S3"); {
$sb->add_text_option("amazon_s3_access", "Access key: "); $sb = new SetupBlock("Amazon S3");
$sb->add_text_option("amazon_s3_secret", "<br>Secret key: "); $sb->add_text_option("amazon_s3_access", "Access key: ");
$sb->add_text_option("amazon_s3_bucket", "<br>Bucket: "); $sb->add_text_option("amazon_s3_secret", "<br>Secret key: ");
$event->panel->add_block($sb); $sb->add_text_option("amazon_s3_bucket", "<br>Bucket: ");
} $event->panel->add_block($sb);
}
public function onImageAddition(ImageAdditionEvent $event) { public function onImageAddition(ImageAdditionEvent $event)
global $config; {
$access = $config->get_string("amazon_s3_access"); global $config;
$secret = $config->get_string("amazon_s3_secret"); $access = $config->get_string("amazon_s3_access");
$bucket = $config->get_string("amazon_s3_bucket"); $secret = $config->get_string("amazon_s3_secret");
if(!empty($bucket)) { $bucket = $config->get_string("amazon_s3_bucket");
log_debug("amazon_s3", "Mirroring Image #".$event->image->id." to S3 #$bucket"); if (!empty($bucket)) {
$s3 = new S3($access, $secret); log_debug("amazon_s3", "Mirroring Image #".$event->image->id." to S3 #$bucket");
$s3->putBucket($bucket, S3::ACL_PUBLIC_READ); $s3 = new S3($access, $secret);
$s3->putObjectFile( $s3->putBucket($bucket, S3::ACL_PUBLIC_READ);
warehouse_path("thumbs", $event->image->hash), $s3->putObjectFile(
$bucket, warehouse_path("thumbs", $event->image->hash),
'thumbs/'.$event->image->hash, $bucket,
S3::ACL_PUBLIC_READ, 'thumbs/'.$event->image->hash,
array(), S3::ACL_PUBLIC_READ,
array( [],
"Content-Type" => "image/jpeg", [
"Content-Disposition" => "inline; filename=image-" . $event->image->id . ".jpg", "Content-Type" => "image/jpeg",
) "Content-Disposition" => "inline; filename=image-" . $event->image->id . ".jpg",
); ]
$s3->putObjectFile( );
warehouse_path("images", $event->image->hash), $s3->putObjectFile(
$bucket, warehouse_path("images", $event->image->hash),
'images/'.$event->image->hash, $bucket,
S3::ACL_PUBLIC_READ, 'images/'.$event->image->hash,
array(), S3::ACL_PUBLIC_READ,
array( [],
"Content-Type" => $event->image->get_mime_type(), [
"Content-Disposition" => "inline; filename=image-" . $event->image->id . "." . $event->image->ext, "Content-Type" => $event->image->get_mime_type(),
) "Content-Disposition" => "inline; filename=image-" . $event->image->id . "." . $event->image->ext,
); ]
} );
} }
}
public function onImageDeletion(ImageDeletionEvent $event) { public function onImageDeletion(ImageDeletionEvent $event)
global $config; {
$access = $config->get_string("amazon_s3_access"); global $config;
$secret = $config->get_string("amazon_s3_secret"); $access = $config->get_string("amazon_s3_access");
$bucket = $config->get_string("amazon_s3_bucket"); $secret = $config->get_string("amazon_s3_secret");
if(!empty($bucket)) { $bucket = $config->get_string("amazon_s3_bucket");
log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3"); if (!empty($bucket)) {
$s3 = new S3($access, $secret); log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3");
$s3->deleteObject($bucket, "images/" . $event->image->hash); $s3 = new S3($access, $secret);
$s3->deleteObject($bucket, "thumbs/" . $event->image->hash); $s3->deleteObject($bucket, "images/" . $event->image->hash);
} $s3->deleteObject($bucket, "thumbs/" . $event->image->hash);
} }
}
} }

View File

@ -8,35 +8,39 @@
* Documentation: * Documentation:
* Simply enable this extention in the extention manager to enable arrow key navigation. * Simply enable this extention in the extention manager to enable arrow key navigation.
*/ */
class ArrowkeyNavigation extends Extension { class ArrowkeyNavigation extends Extension
/** {
* Adds functionality for post/view on images. /**
*/ * Adds functionality for post/view on images.
public function onDisplayingImage(DisplayingImageEvent $event) { */
$prev_url = make_http(make_link("post/prev/".$event->image->id)); public function onDisplayingImage(DisplayingImageEvent $event)
$next_url = make_http(make_link("post/next/".$event->image->id)); {
$this->add_arrowkeys_code($prev_url, $next_url); $prev_url = make_http(make_link("post/prev/".$event->image->id));
} $next_url = make_http(make_link("post/next/".$event->image->id));
$this->add_arrowkeys_code($prev_url, $next_url);
}
/** /**
* Adds functionality for post/list. * Adds functionality for post/list.
*/ */
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
if($event->page_matches("post/list")) { {
$pageinfo = $this->get_list_pageinfo($event); if ($event->page_matches("post/list")) {
$prev_url = make_http(make_link("post/list/".$pageinfo["prev"])); $pageinfo = $this->get_list_pageinfo($event);
$next_url = make_http(make_link("post/list/".$pageinfo["next"])); $prev_url = make_http(make_link("post/list/".$pageinfo["prev"]));
$this->add_arrowkeys_code($prev_url, $next_url); $next_url = make_http(make_link("post/list/".$pageinfo["next"]));
} $this->add_arrowkeys_code($prev_url, $next_url);
} }
}
/** /**
* Adds the javascript to the page with the given urls. * Adds the javascript to the page with the given urls.
*/ */
private function add_arrowkeys_code(string $prev_url, string $next_url) { private function add_arrowkeys_code(string $prev_url, string $next_url)
global $page; {
global $page;
$page->add_html_header("<script type=\"text/javascript\"> $page->add_html_header("<script type=\"text/javascript\">
(function($){ (function($){
$(document).keyup(function(e) { $(document).keyup(function(e) {
if($(e.target).is('input', 'textarea')){ return; } if($(e.target).is('input', 'textarea')){ return; }
@ -46,46 +50,53 @@ class ArrowkeyNavigation extends Extension {
}); });
})(jQuery); })(jQuery);
</script>", 60); </script>", 60);
} }
/** /**
* Returns info about the current page number. * Returns info about the current page number.
*/ */
private function get_list_pageinfo(PageRequestEvent $event): array { private function get_list_pageinfo(PageRequestEvent $event): array
global $config, $database; {
global $config, $database;
// get the amount of images per page // get the amount of images per page
$images_per_page = $config->get_int('index_images'); $images_per_page = $config->get_int('index_images');
// if there are no tags, use default // if there are no tags, use default
if (is_null($event->get_arg(1))){ if (is_null($event->get_arg(1))) {
$prefix = ""; $prefix = "";
$page_number = int_escape($event->get_arg(0)); $page_number = int_escape($event->get_arg(0));
$total_pages = ceil($database->get_one( $total_pages = ceil($database->get_one(
"SELECT COUNT(*) FROM images") / $images_per_page); "SELECT COUNT(*) FROM images"
} ) / $images_per_page);
else { // if there are tags, use pages with tags } else { // if there are tags, use pages with tags
$prefix = url_escape($event->get_arg(0)) . "/"; $prefix = url_escape($event->get_arg(0)) . "/";
$page_number = int_escape($event->get_arg(1)); $page_number = int_escape($event->get_arg(1));
$total_pages = ceil($database->get_one( $total_pages = ceil($database->get_one(
"SELECT count FROM tags WHERE tag=:tag", "SELECT count FROM tags WHERE tag=:tag",
array("tag"=>$event->get_arg(0))) / $images_per_page); ["tag"=>$event->get_arg(0)]
} ) / $images_per_page);
}
// creates previous & next values // creates previous & next values
// When previous first page, go to last page // When previous first page, go to last page
if ($page_number <= 1) $prev = $total_pages; if ($page_number <= 1) {
else $prev = $page_number-1; $prev = $total_pages;
if ($page_number >= $total_pages) $next = 1; } else {
else $next = $page_number+1; $prev = $page_number-1;
}
if ($page_number >= $total_pages) {
$next = 1;
} else {
$next = $page_number+1;
}
// Create return array // Create return array
$pageinfo = array( $pageinfo = [
"prev" => $prefix.$prev, "prev" => $prefix.$prev,
"next" => $prefix.$next, "next" => $prefix.$next,
); ];
return $pageinfo; return $pageinfo;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,10 @@
<?php <?php
class ArtistTest extends ShimmiePHPUnitTestCase { class ArtistTest extends ShimmiePHPUnitTestCase
public function testSearch() { {
# FIXME: check that the results are there public function testSearch()
$this->get_page("post/list/author=bob/1"); {
#$this->assert_response(200); # FIXME: check that the results are there
} $this->get_page("post/list/author=bob/1");
#$this->assert_response(200);
}
} }

View File

@ -1,8 +1,10 @@
<?php <?php
class ArtistsTheme extends Themelet { class ArtistsTheme extends Themelet
public function get_author_editor_html(string $author): string { {
$h_author = html_escape($author); public function get_author_editor_html(string $author): string
return " {
$h_author = html_escape($author);
return "
<tr> <tr>
<th>Author</th> <th>Author</th>
<td> <td>
@ -11,22 +13,23 @@ class ArtistsTheme extends Themelet {
</td> </td>
</tr> </tr>
"; ";
} }
public function sidebar_options(string $mode, ?int $artistID=NULL, $is_admin=FALSE): bool { public function sidebar_options(string $mode, ?int $artistID=null, $is_admin=false): bool
global $page, $user; {
global $page, $user;
$html = ""; $html = "";
if($mode == "neutral"){ if ($mode == "neutral") {
$html = "<form method='post' action='".make_link("artist/new_artist")."'> $html = "<form method='post' action='".make_link("artist/new_artist")."'>
".$user->get_auth_html()." ".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='New Artist'/> <input type='submit' name='edit' id='edit' value='New Artist'/>
</form>"; </form>";
} }
if($mode == "editor"){ if ($mode == "editor") {
$html = "<form method='post' action='".make_link("artist/new_artist")."'> $html = "<form method='post' action='".make_link("artist/new_artist")."'>
".$user->get_auth_html()." ".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='New Artist'/> <input type='submit' name='edit' id='edit' value='New Artist'/>
</form> </form>
@ -36,16 +39,16 @@ class ArtistsTheme extends Themelet {
<input type='submit' name='edit' id='edit' value='Edit Artist'/> <input type='submit' name='edit' id='edit' value='Edit Artist'/>
<input type='hidden' name='artist_id' value='".$artistID."'> <input type='hidden' name='artist_id' value='".$artistID."'>
</form>"; </form>";
if($is_admin){ if ($is_admin) {
$html .= "<form method='post' action='".make_link("artist/nuke_artist")."'> $html .= "<form method='post' action='".make_link("artist/nuke_artist")."'>
".$user->get_auth_html()." ".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='Delete Artist'/> <input type='submit' name='edit' id='edit' value='Delete Artist'/>
<input type='hidden' name='artist_id' value='".$artistID."'> <input type='hidden' name='artist_id' value='".$artistID."'>
</form>"; </form>";
} }
$html .= "<form method='post' action='".make_link("artist/add_alias")."'> $html .= "<form method='post' action='".make_link("artist/add_alias")."'>
".$user->get_auth_html()." ".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='Add Alias'/> <input type='submit' name='edit' id='edit' value='Add Alias'/>
<input type='hidden' name='artist_id' value='".$artistID."'> <input type='hidden' name='artist_id' value='".$artistID."'>
@ -62,49 +65,52 @@ class ArtistsTheme extends Themelet {
<input type='submit' name='edit' id='edit' value='Add Url'/> <input type='submit' name='edit' id='edit' value='Add Url'/>
<input type='hidden' name='artist_id' value='".$artistID."'> <input type='hidden' name='artist_id' value='".$artistID."'>
</form>"; </form>";
} }
if($html) $page->add_block(new Block("Manage Artists", $html, "left", 10)); if ($html) {
} $page->add_block(new Block("Manage Artists", $html, "left", 10));
}
}
public function show_artist_editor($artist, $aliases, $members, $urls) { public function show_artist_editor($artist, $aliases, $members, $urls)
global $user; {
global $user;
$artistName = $artist['name']; $artistName = $artist['name'];
$artistNotes = $artist['notes']; $artistNotes = $artist['notes'];
$artistID = $artist['id']; $artistID = $artist['id'];
// aliases // aliases
$aliasesString = ""; $aliasesString = "";
$aliasesIDsString = ""; $aliasesIDsString = "";
foreach ($aliases as $alias) { foreach ($aliases as $alias) {
$aliasesString .= $alias["alias_name"]." "; $aliasesString .= $alias["alias_name"]." ";
$aliasesIDsString .= $alias["alias_id"]." "; $aliasesIDsString .= $alias["alias_id"]." ";
} }
$aliasesString = rtrim($aliasesString); $aliasesString = rtrim($aliasesString);
$aliasesIDsString = rtrim($aliasesIDsString); $aliasesIDsString = rtrim($aliasesIDsString);
// members // members
$membersString = ""; $membersString = "";
$membersIDsString = ""; $membersIDsString = "";
foreach ($members as $member) { foreach ($members as $member) {
$membersString .= $member["name"]." "; $membersString .= $member["name"]." ";
$membersIDsString .= $member["id"]." "; $membersIDsString .= $member["id"]." ";
} }
$membersString = rtrim($membersString); $membersString = rtrim($membersString);
$membersIDsString = rtrim($membersIDsString); $membersIDsString = rtrim($membersIDsString);
// urls // urls
$urlsString = ""; $urlsString = "";
$urlsIDsString = ""; $urlsIDsString = "";
foreach ($urls as $url) { foreach ($urls as $url) {
$urlsString .= $url["url"]."\n"; $urlsString .= $url["url"]."\n";
$urlsIDsString .= $url["id"]." "; $urlsIDsString .= $url["id"]." ";
} }
$urlsString = substr($urlsString, 0, strlen($urlsString) -1); $urlsString = substr($urlsString, 0, strlen($urlsString) -1);
$urlsIDsString = rtrim($urlsIDsString); $urlsIDsString = rtrim($urlsIDsString);
$html = ' $html = '
<form method="POST" action="'.make_link("artist/edited/".$artist['id']).'"> <form method="POST" action="'.make_link("artist/edited/".$artist['id']).'">
'.$user->get_auth_html().' '.$user->get_auth_html().'
<table> <table>
@ -122,14 +128,15 @@ class ArtistsTheme extends Themelet {
</form> </form>
'; ';
global $page; global $page;
$page->add_block(new Block("Edit artist", $html, "main", 10)); $page->add_block(new Block("Edit artist", $html, "main", 10));
} }
public function new_artist_composer() { public function new_artist_composer()
global $page, $user; {
global $page, $user;
$html = "<form action=".make_link("artist/create")." method='POST'> $html = "<form action=".make_link("artist/create")." method='POST'>
".$user->get_auth_html()." ".$user->get_auth_html()."
<table> <table>
<tr><td>Name:</td><td><input type='text' name='name' /></td></tr> <tr><td>Name:</td><td><input type='text' name='name' /></td></tr>
@ -141,86 +148,95 @@ class ArtistsTheme extends Themelet {
</table> </table>
"; ";
$page->set_title("Artists"); $page->set_title("Artists");
$page->set_heading("Artists"); $page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10)); $page->add_block(new Block("Artists", $html, "main", 10));
} }
public function list_artists($artists, $pageNumber, $totalPages) { public function list_artists($artists, $pageNumber, $totalPages)
global $user, $page; {
global $user, $page;
$html = "<table id='poolsList' class='zebra'>". $html = "<table id='poolsList' class='zebra'>".
"<thead><tr>". "<thead><tr>".
"<th>Name</th>". "<th>Name</th>".
"<th>Type</th>". "<th>Type</th>".
"<th>Last updater</th>". "<th>Last updater</th>".
"<th>Posts</th>"; "<th>Posts</th>";
if(!$user->is_anonymous()) $html .= "<th colspan='2'>Action</th>"; // space for edit link if (!$user->is_anonymous()) {
$html .= "<th colspan='2'>Action</th>";
$html .= "</tr></thead>"; } // space for edit link
$html .= "</tr></thead>";
$deletionLinkActionArray = array( $deletionLinkActionArray = [
'artist' => 'artist/nuke/', 'artist' => 'artist/nuke/',
'alias' => 'artist/alias/delete/', 'alias' => 'artist/alias/delete/',
'member' => 'artist/member/delete/', 'member' => 'artist/member/delete/',
); ];
$editionLinkActionArray = array( $editionLinkActionArray = [
'artist' => 'artist/edit/', 'artist' => 'artist/edit/',
'alias' => 'artist/alias/edit/', 'alias' => 'artist/alias/edit/',
'member' => 'artist/member/edit/', 'member' => 'artist/member/edit/',
); ];
$typeTextArray = array( $typeTextArray = [
'artist' => 'Artist', 'artist' => 'Artist',
'alias' => 'Alias', 'alias' => 'Alias',
'member' => 'Member', 'member' => 'Member',
); ];
foreach ($artists as $artist) { foreach ($artists as $artist) {
if ($artist['type'] != 'artist') if ($artist['type'] != 'artist') {
$artist['name'] = str_replace("_", " ", $artist['name']); $artist['name'] = str_replace("_", " ", $artist['name']);
}
$elementLink = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['name'])."</a>"; $elementLink = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['name'])."</a>";
//$artist_link = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['artist_name'])."</a>"; //$artist_link = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['artist_name'])."</a>";
$user_link = "<a href='".make_link("user/".$artist['user_name'])."'>".$artist['user_name']."</a>"; $user_link = "<a href='".make_link("user/".$artist['user_name'])."'>".$artist['user_name']."</a>";
$edit_link = "<a href='".make_link($editionLinkActionArray[$artist['type']].$artist['id'])."'>Edit</a>"; $edit_link = "<a href='".make_link($editionLinkActionArray[$artist['type']].$artist['id'])."'>Edit</a>";
$del_link = "<a href='".make_link($deletionLinkActionArray[$artist['type']].$artist['id'])."'>Delete</a>"; $del_link = "<a href='".make_link($deletionLinkActionArray[$artist['type']].$artist['id'])."'>Delete</a>";
$html .= "<tr>". $html .= "<tr>".
"<td class='left'>".$elementLink; "<td class='left'>".$elementLink;
//if ($artist['type'] == 'member') //if ($artist['type'] == 'member')
// $html .= " (member of ".$artist_link.")"; // $html .= " (member of ".$artist_link.")";
//if ($artist['type'] == 'alias') //if ($artist['type'] == 'alias')
// $html .= " (alias for ".$artist_link.")"; // $html .= " (alias for ".$artist_link.")";
$html .= "</td>". $html .= "</td>".
"<td>".$typeTextArray[$artist['type']]."</td>". "<td>".$typeTextArray[$artist['type']]."</td>".
"<td>".$user_link."</td>". "<td>".$user_link."</td>".
"<td>".$artist['posts']."</td>"; "<td>".$artist['posts']."</td>";
if(!$user->is_anonymous()) $html .= "<td>".$edit_link."</td>"; if (!$user->is_anonymous()) {
if($user->is_admin()) $html .= "<td>".$del_link."</td>"; $html .= "<td>".$edit_link."</td>";
}
if ($user->is_admin()) {
$html .= "<td>".$del_link."</td>";
}
$html .= "</tr>"; $html .= "</tr>";
} }
$html .= "</tbody></table>"; $html .= "</tbody></table>";
$page->set_title("Artists"); $page->set_title("Artists");
$page->set_heading("Artists"); $page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10)); $page->add_block(new Block("Artists", $html, "main", 10));
$this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages); $this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages);
} }
public function show_new_alias_composer($artistID) { public function show_new_alias_composer($artistID)
global $user; {
global $user;
$html = ' $html = '
<form method="POST" action='.make_link("artist/alias/add").'> <form method="POST" action='.make_link("artist/alias/add").'>
'.$user->get_auth_html().' '.$user->get_auth_html().'
<table> <table>
@ -231,14 +247,15 @@ class ArtistsTheme extends Themelet {
</form> </form>
'; ';
global $page; global $page;
$page->add_block(new Block("Artist Aliases", $html, "main", 20)); $page->add_block(new Block("Artist Aliases", $html, "main", 20));
} }
public function show_new_member_composer($artistID) { public function show_new_member_composer($artistID)
global $user; {
global $user;
$html = ' $html = '
<form method="POST" action='.make_link("artist/member/add").'> <form method="POST" action='.make_link("artist/member/add").'>
'.$user->get_auth_html().' '.$user->get_auth_html().'
<table> <table>
@ -249,14 +266,15 @@ class ArtistsTheme extends Themelet {
</form> </form>
'; ';
global $page; global $page;
$page->add_block(new Block("Artist members", $html, "main", 30)); $page->add_block(new Block("Artist members", $html, "main", 30));
} }
public function show_new_url_composer($artistID) { public function show_new_url_composer($artistID)
global $user; {
global $user;
$html = ' $html = '
<form method="POST" action='.make_link("artist/url/add").'> <form method="POST" action='.make_link("artist/url/add").'>
'.$user->get_auth_html().' '.$user->get_auth_html().'
<table> <table>
@ -267,14 +285,15 @@ class ArtistsTheme extends Themelet {
</form> </form>
'; ';
global $page; global $page;
$page->add_block(new Block("Artist URLs", $html, "main", 40)); $page->add_block(new Block("Artist URLs", $html, "main", 40));
} }
public function show_alias_editor($alias) { public function show_alias_editor($alias)
global $user; {
global $user;
$html = ' $html = '
<form method="POST" action="'.make_link("artist/alias/edited/".$alias['id']).'"> <form method="POST" action="'.make_link("artist/alias/edited/".$alias['id']).'">
'.$user->get_auth_html().' '.$user->get_auth_html().'
<label for="alias">Alias:</label> <label for="alias">Alias:</label>
@ -284,14 +303,15 @@ class ArtistsTheme extends Themelet {
</form> </form>
'; ';
global $page; global $page;
$page->add_block(new Block("Edit Alias", $html, "main", 10)); $page->add_block(new Block("Edit Alias", $html, "main", 10));
} }
public function show_url_editor($url) { public function show_url_editor($url)
global $user; {
global $user;
$html = ' $html = '
<form method="POST" action="'.make_link("artist/url/edited/".$url['id']).'"> <form method="POST" action="'.make_link("artist/url/edited/".$url['id']).'">
'.$user->get_auth_html().' '.$user->get_auth_html().'
<label for="url">URL:</label> <label for="url">URL:</label>
@ -301,14 +321,15 @@ class ArtistsTheme extends Themelet {
</form> </form>
'; ';
global $page; global $page;
$page->add_block(new Block("Edit URL", $html, "main", 10)); $page->add_block(new Block("Edit URL", $html, "main", 10));
} }
public function show_member_editor($member) { public function show_member_editor($member)
global $user; {
global $user;
$html = ' $html = '
<form method="POST" action="'.make_link("artist/member/edited/".$member['id']).'"> <form method="POST" action="'.make_link("artist/member/edited/".$member['id']).'">
'.$user->get_auth_html().' '.$user->get_auth_html().'
<label for="member">Member name:</label> <label for="member">Member name:</label>
@ -318,184 +339,210 @@ class ArtistsTheme extends Themelet {
</form> </form>
'; ';
global $page; global $page;
$page->add_block(new Block("Edit Member", $html, "main", 10)); $page->add_block(new Block("Edit Member", $html, "main", 10));
} }
public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin) { public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin)
global $page; {
global $page;
$artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>"; $artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>";
$html = "<table id='poolsList' class='zebra'> $html = "<table id='poolsList' class='zebra'>
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th></th>"; <th></th>";
if ($userIsLogged) $html .= "<th></th>"; if ($userIsLogged) {
if ($userIsAdmin) $html .= "<th></th>"; $html .= "<th></th>";
}
if ($userIsAdmin) {
$html .= "<th></th>";
}
$html .= " <tr> $html .= " <tr>
</thead> </thead>
<tr> <tr>
<td class='left'>Name:</td> <td class='left'>Name:</td>
<td class='left'>".$artist_link."</td>"; <td class='left'>".$artist_link."</td>";
if ($userIsLogged) $html .= "<td></td>"; if ($userIsLogged) {
if ($userIsAdmin) $html .= "<td></td>"; $html .= "<td></td>";
$html .= "</tr>"; }
if ($userIsAdmin) {
$html .= "<td></td>";
}
$html .= "</tr>";
$html .= $this->render_aliases($aliases, $userIsLogged, $userIsAdmin); $html .= $this->render_aliases($aliases, $userIsLogged, $userIsAdmin);
$html .= $this->render_members($members, $userIsLogged, $userIsAdmin); $html .= $this->render_members($members, $userIsLogged, $userIsAdmin);
$html .= $this->render_urls($urls, $userIsLogged, $userIsAdmin); $html .= $this->render_urls($urls, $userIsLogged, $userIsAdmin);
$html .= "<tr> $html .= "<tr>
<td class='left'>Notes:</td> <td class='left'>Notes:</td>
<td class='left'>".$artist["notes"]."</td>"; <td class='left'>".$artist["notes"]."</td>";
if ($userIsLogged) $html .= "<td></td>"; if ($userIsLogged) {
if ($userIsAdmin) $html .= "<td></td>"; $html .= "<td></td>";
//TODO how will notes be edited? On edit artist? (should there be an editartist?) or on a editnotes? }
//same question for deletion if ($userIsAdmin) {
$html .= "</tr> $html .= "<td></td>";
}
//TODO how will notes be edited? On edit artist? (should there be an editartist?) or on a editnotes?
//same question for deletion
$html .= "</tr>
</table>"; </table>";
$page->set_title("Artist"); $page->set_title("Artist");
$page->set_heading("Artist"); $page->set_heading("Artist");
$page->add_block(new Block("Artist", $html, "main", 10)); $page->add_block(new Block("Artist", $html, "main", 10));
//we show the images for the artist //we show the images for the artist
$artist_images = ""; $artist_images = "";
foreach($images as $image) { foreach ($images as $image) {
$thumb_html = $this->build_thumb_html($image); $thumb_html = $this->build_thumb_html($image);
$artist_images .= '<span class="thumb">'. $artist_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'. '<a href="$image_link">'.$thumb_html.'</a>'.
'</span>'; '</span>';
} }
$page->add_block(new Block("Artist Images", $artist_images, "main", 20)); $page->add_block(new Block("Artist Images", $artist_images, "main", 20));
} }
private function render_aliases(array $aliases, bool $userIsLogged, bool $userIsAdmin): string { private function render_aliases(array $aliases, bool $userIsLogged, bool $userIsAdmin): string
$html = ""; {
if(count($aliases) > 0) { $html = "";
$aliasViewLink = str_replace("_", " ", $aliases[0]['alias_name']); // no link anymore if (count($aliases) > 0) {
$aliasEditLink = "<a href='" . make_link("artist/alias/edit/" . $aliases[0]['alias_id']) . "'>Edit</a>"; $aliasViewLink = str_replace("_", " ", $aliases[0]['alias_name']); // no link anymore
$aliasDeleteLink = "<a href='" . make_link("artist/alias/delete/" . $aliases[0]['alias_id']) . "'>Delete</a>"; $aliasEditLink = "<a href='" . make_link("artist/alias/edit/" . $aliases[0]['alias_id']) . "'>Edit</a>";
$aliasDeleteLink = "<a href='" . make_link("artist/alias/delete/" . $aliases[0]['alias_id']) . "'>Delete</a>";
$html .= "<tr> $html .= "<tr>
<td class='left'>Aliases:</td> <td class='left'>Aliases:</td>
<td class='left'>" . $aliasViewLink . "</td>"; <td class='left'>" . $aliasViewLink . "</td>";
if ($userIsLogged) if ($userIsLogged) {
$html .= "<td class='left'>" . $aliasEditLink . "</td>"; $html .= "<td class='left'>" . $aliasEditLink . "</td>";
}
if ($userIsAdmin) if ($userIsAdmin) {
$html .= "<td class='left'>" . $aliasDeleteLink . "</td>"; $html .= "<td class='left'>" . $aliasDeleteLink . "</td>";
}
$html .= "</tr>"; $html .= "</tr>";
if (count($aliases) > 1) { if (count($aliases) > 1) {
for ($i = 1; $i < count($aliases); $i++) { for ($i = 1; $i < count($aliases); $i++) {
$aliasViewLink = str_replace("_", " ", $aliases[$i]['alias_name']); // no link anymore $aliasViewLink = str_replace("_", " ", $aliases[$i]['alias_name']); // no link anymore
$aliasEditLink = "<a href='" . make_link("artist/alias/edit/" . $aliases[$i]['alias_id']) . "'>Edit</a>"; $aliasEditLink = "<a href='" . make_link("artist/alias/edit/" . $aliases[$i]['alias_id']) . "'>Edit</a>";
$aliasDeleteLink = "<a href='" . make_link("artist/alias/delete/" . $aliases[$i]['alias_id']) . "'>Delete</a>"; $aliasDeleteLink = "<a href='" . make_link("artist/alias/delete/" . $aliases[$i]['alias_id']) . "'>Delete</a>";
$html .= "<tr> $html .= "<tr>
<td class='left'>&nbsp;</td> <td class='left'>&nbsp;</td>
<td class='left'>" . $aliasViewLink . "</td>"; <td class='left'>" . $aliasViewLink . "</td>";
if ($userIsLogged) if ($userIsLogged) {
$html .= "<td class='left'>" . $aliasEditLink . "</td>"; $html .= "<td class='left'>" . $aliasEditLink . "</td>";
if ($userIsAdmin) }
$html .= "<td class='left'>" . $aliasDeleteLink . "</td>"; if ($userIsAdmin) {
$html .= "<td class='left'>" . $aliasDeleteLink . "</td>";
}
$html .= "</tr>"; $html .= "</tr>";
} }
} }
} }
return $html; return $html;
} }
private function render_members(array $members, bool $userIsLogged, bool $userIsAdmin): string { private function render_members(array $members, bool $userIsLogged, bool $userIsAdmin): string
$html = ""; {
if(count($members) > 0) { $html = "";
$memberViewLink = str_replace("_", " ", $members[0]['name']); // no link anymore if (count($members) > 0) {
$memberEditLink = "<a href='" . make_link("artist/member/edit/" . $members[0]['id']) . "'>Edit</a>"; $memberViewLink = str_replace("_", " ", $members[0]['name']); // no link anymore
$memberDeleteLink = "<a href='" . make_link("artist/member/delete/" . $members[0]['id']) . "'>Delete</a>"; $memberEditLink = "<a href='" . make_link("artist/member/edit/" . $members[0]['id']) . "'>Edit</a>";
$memberDeleteLink = "<a href='" . make_link("artist/member/delete/" . $members[0]['id']) . "'>Delete</a>";
$html .= "<tr> $html .= "<tr>
<td class='left'>Members:</td> <td class='left'>Members:</td>
<td class='left'>" . $memberViewLink . "</td>"; <td class='left'>" . $memberViewLink . "</td>";
if ($userIsLogged) if ($userIsLogged) {
$html .= "<td class='left'>" . $memberEditLink . "</td>"; $html .= "<td class='left'>" . $memberEditLink . "</td>";
if ($userIsAdmin) }
$html .= "<td class='left'>" . $memberDeleteLink . "</td>"; if ($userIsAdmin) {
$html .= "<td class='left'>" . $memberDeleteLink . "</td>";
}
$html .= "</tr>"; $html .= "</tr>";
if (count($members) > 1) { if (count($members) > 1) {
for ($i = 1; $i < count($members); $i++) { for ($i = 1; $i < count($members); $i++) {
$memberViewLink = str_replace("_", " ", $members[$i]['name']); // no link anymore $memberViewLink = str_replace("_", " ", $members[$i]['name']); // no link anymore
$memberEditLink = "<a href='" . make_link("artist/member/edit/" . $members[$i]['id']) . "'>Edit</a>"; $memberEditLink = "<a href='" . make_link("artist/member/edit/" . $members[$i]['id']) . "'>Edit</a>";
$memberDeleteLink = "<a href='" . make_link("artist/member/delete/" . $members[$i]['id']) . "'>Delete</a>"; $memberDeleteLink = "<a href='" . make_link("artist/member/delete/" . $members[$i]['id']) . "'>Delete</a>";
$html .= "<tr> $html .= "<tr>
<td class='left'>&nbsp;</td> <td class='left'>&nbsp;</td>
<td class='left'>" . $memberViewLink . "</td>"; <td class='left'>" . $memberViewLink . "</td>";
if ($userIsLogged) if ($userIsLogged) {
$html .= "<td class='left'>" . $memberEditLink . "</td>"; $html .= "<td class='left'>" . $memberEditLink . "</td>";
if ($userIsAdmin) }
$html .= "<td class='left'>" . $memberDeleteLink . "</td>"; if ($userIsAdmin) {
$html .= "<td class='left'>" . $memberDeleteLink . "</td>";
}
$html .= "</tr>"; $html .= "</tr>";
} }
} }
} }
return $html; return $html;
} }
private function render_urls(array $urls, bool $userIsLogged, bool $userIsAdmin): string { private function render_urls(array $urls, bool $userIsLogged, bool $userIsAdmin): string
$html = ""; {
if(count($urls) > 0) { $html = "";
$urlViewLink = "<a href='" . str_replace("_", " ", $urls[0]['url']) . "' target='_blank'>" . str_replace("_", " ", $urls[0]['url']) . "</a>"; if (count($urls) > 0) {
$urlEditLink = "<a href='" . make_link("artist/url/edit/" . $urls[0]['id']) . "'>Edit</a>"; $urlViewLink = "<a href='" . str_replace("_", " ", $urls[0]['url']) . "' target='_blank'>" . str_replace("_", " ", $urls[0]['url']) . "</a>";
$urlDeleteLink = "<a href='" . make_link("artist/url/delete/" . $urls[0]['id']) . "'>Delete</a>"; $urlEditLink = "<a href='" . make_link("artist/url/edit/" . $urls[0]['id']) . "'>Edit</a>";
$urlDeleteLink = "<a href='" . make_link("artist/url/delete/" . $urls[0]['id']) . "'>Delete</a>";
$html .= "<tr> $html .= "<tr>
<td class='left'>URLs:</td> <td class='left'>URLs:</td>
<td class='left'>" . $urlViewLink . "</td>"; <td class='left'>" . $urlViewLink . "</td>";
if ($userIsLogged) if ($userIsLogged) {
$html .= "<td class='left'>" . $urlEditLink . "</td>"; $html .= "<td class='left'>" . $urlEditLink . "</td>";
}
if ($userIsAdmin) if ($userIsAdmin) {
$html .= "<td class='left'>" . $urlDeleteLink . "</td>"; $html .= "<td class='left'>" . $urlDeleteLink . "</td>";
}
$html .= "</tr>"; $html .= "</tr>";
if (count($urls) > 1) { if (count($urls) > 1) {
for ($i = 1; $i < count($urls); $i++) { for ($i = 1; $i < count($urls); $i++) {
$urlViewLink = "<a href='" . str_replace("_", " ", $urls[$i]['url']) . "' target='_blank'>" . str_replace("_", " ", $urls[$i]['url']) . "</a>"; $urlViewLink = "<a href='" . str_replace("_", " ", $urls[$i]['url']) . "' target='_blank'>" . str_replace("_", " ", $urls[$i]['url']) . "</a>";
$urlEditLink = "<a href='" . make_link("artist/url/edit/" . $urls[$i]['id']) . "'>Edit</a>"; $urlEditLink = "<a href='" . make_link("artist/url/edit/" . $urls[$i]['id']) . "'>Edit</a>";
$urlDeleteLink = "<a href='" . make_link("artist/url/delete/" . $urls[$i]['id']) . "'>Delete</a>"; $urlDeleteLink = "<a href='" . make_link("artist/url/delete/" . $urls[$i]['id']) . "'>Delete</a>";
$html .= "<tr> $html .= "<tr>
<td class='left'>&nbsp;</td> <td class='left'>&nbsp;</td>
<td class='left'>" . $urlViewLink . "</td>"; <td class='left'>" . $urlViewLink . "</td>";
if ($userIsLogged) if ($userIsLogged) {
$html .= "<td class='left'>" . $urlEditLink . "</td>"; $html .= "<td class='left'>" . $urlEditLink . "</td>";
}
if ($userIsAdmin) if ($userIsAdmin) {
$html .= "<td class='left'>" . $urlDeleteLink . "</td>"; $html .= "<td class='left'>" . $urlDeleteLink . "</td>";
}
$html .= "</tr>";
}
return $html;
}
}
return $html;
}
$html .= "</tr>";
}
return $html;
}
}
return $html;
}
} }

View File

@ -5,55 +5,64 @@
* Description: Adds autocomplete to search & tagging. * Description: Adds autocomplete to search & tagging.
*/ */
class AutoComplete extends Extension { class AutoComplete extends Extension
public function get_priority(): int {return 30;} // before Home {
public function get_priority(): int
{
return 30;
} // before Home
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
global $page, $database; {
global $page, $database;
if($event->page_matches("api/internal/autocomplete")) { if ($event->page_matches("api/internal/autocomplete")) {
if(!isset($_GET["s"])) return; if (!isset($_GET["s"])) {
return;
}
$page->set_mode("data"); $page->set_mode("data");
$page->set_type("application/json"); $page->set_type("application/json");
$s = strtolower($_GET["s"]); $s = strtolower($_GET["s"]);
if( if (
$s == '' || $s == '' ||
$s[0] == '_' || $s[0] == '_' ||
$s[0] == '%' || $s[0] == '%' ||
strlen($s) > 32 strlen($s) > 32
) { ) {
$page->set_data("{}"); $page->set_data("{}");
return; return;
} }
//$limit = 0; //$limit = 0;
$cache_key = "autocomplete-$s"; $cache_key = "autocomplete-$s";
$limitSQL = ""; $limitSQL = "";
$SQLarr = array("search"=>"$s%"); $SQLarr = ["search"=>"$s%"];
if(isset($_GET["limit"]) && $_GET["limit"] !== 0){ if (isset($_GET["limit"]) && $_GET["limit"] !== 0) {
$limitSQL = "LIMIT :limit"; $limitSQL = "LIMIT :limit";
$SQLarr['limit'] = $_GET["limit"]; $SQLarr['limit'] = $_GET["limit"];
$cache_key .= "-" . $_GET["limit"]; $cache_key .= "-" . $_GET["limit"];
} }
$res = $database->cache->get($cache_key); $res = $database->cache->get($cache_key);
if(!$res) { if (!$res) {
$res = $database->get_pairs($database->scoreql_to_sql(" $res = $database->get_pairs(
$database->scoreql_to_sql("
SELECT tag, count SELECT tag, count
FROM tags FROM tags
WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:search) WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:search)
AND count > 0 AND count > 0
ORDER BY count DESC ORDER BY count DESC
$limitSQL"), $SQLarr $limitSQL"),
); $SQLarr
$database->cache->set($cache_key, $res, 600); );
} $database->cache->set($cache_key, $res, 600);
}
$page->set_data(json_encode($res)); $page->set_data(json_encode($res));
} }
$this->theme->build_autocomplete($page); $this->theme->build_autocomplete($page);
} }
} }

View File

@ -1,13 +1,15 @@
<?php <?php
class AutoCompleteTheme extends Themelet { class AutoCompleteTheme extends Themelet
public function build_autocomplete(Page $page) { {
$base_href = get_base_href(); public function build_autocomplete(Page $page)
// TODO: AJAX test and fallback. {
$base_href = get_base_href();
// TODO: AJAX test and fallback.
$page->add_html_header("<script src='$base_href/ext/autocomplete/lib/jquery-ui.min.js' type='text/javascript'></script>"); $page->add_html_header("<script src='$base_href/ext/autocomplete/lib/jquery-ui.min.js' type='text/javascript'></script>");
$page->add_html_header("<script src='$base_href/ext/autocomplete/lib/tag-it.min.js' type='text/javascript'></script>"); $page->add_html_header("<script src='$base_href/ext/autocomplete/lib/tag-it.min.js' type='text/javascript'></script>");
$page->add_html_header('<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/flick/jquery-ui.css">'); $page->add_html_header('<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/flick/jquery-ui.css">');
$page->add_html_header("<link rel='stylesheet' type='text/css' href='$base_href/ext/autocomplete/lib/jquery.tagit.css' />"); $page->add_html_header("<link rel='stylesheet' type='text/css' href='$base_href/ext/autocomplete/lib/jquery.tagit.css' />");
} }
} }

View File

@ -20,10 +20,12 @@
* from Essex" * from Essex"
*/ */
class BanWords extends Extension { class BanWords extends Extension
public function onInitExt(InitExtEvent $event) { {
global $config; public function onInitExt(InitExtEvent $event)
$config->set_default_string('banned_words', " {
global $config;
$config->set_default_string('banned_words', "
a href= a href=
anal anal
blowjob blowjob
@ -51,80 +53,87 @@ very nice site
viagra viagra
xanax xanax
"); ");
} }
public function onCommentPosting(CommentPostingEvent $event) { public function onCommentPosting(CommentPostingEvent $event)
global $user; {
if(!$user->can("bypass_comment_checks")) { global $user;
$this->test_text($event->comment, new CommentPostingException("Comment contains banned terms")); if (!$user->can("bypass_comment_checks")) {
} $this->test_text($event->comment, new CommentPostingException("Comment contains banned terms"));
} }
}
public function onSourceSet(SourceSetEvent $event) { public function onSourceSet(SourceSetEvent $event)
$this->test_text($event->source, new SCoreException("Source contains banned terms")); {
} $this->test_text($event->source, new SCoreException("Source contains banned terms"));
}
public function onTagSet(TagSetEvent $event) { public function onTagSet(TagSetEvent $event)
$this->test_text(Tag::implode($event->tags), new SCoreException("Tags contain banned terms")); {
} $this->test_text(Tag::implode($event->tags), new SCoreException("Tags contain banned terms"));
}
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event)
$sb = new SetupBlock("Banned Phrases"); {
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>"); $sb = new SetupBlock("Banned Phrases");
$sb->add_longtext_option("banned_words"); $sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
$failed = array(); $sb->add_longtext_option("banned_words");
foreach($this->get_words() as $word) { $failed = [];
if($word[0] == '/') { foreach ($this->get_words() as $word) {
if(preg_match($word, "") === false) { if ($word[0] == '/') {
$failed[] = $word; if (preg_match($word, "") === false) {
} $failed[] = $word;
} }
} }
if($failed) { }
$sb->add_label("Failed regexes: ".join(", ", $failed)); if ($failed) {
} $sb->add_label("Failed regexes: ".join(", ", $failed));
$event->panel->add_block($sb); }
} $event->panel->add_block($sb);
}
/** /**
* Throws if the comment contains banned words. * Throws if the comment contains banned words.
*/ */
private function test_text(string $comment, Exception $ex): void { private function test_text(string $comment, Exception $ex): void
$comment = strtolower($comment); {
$comment = strtolower($comment);
foreach($this->get_words() as $word) { foreach ($this->get_words() as $word) {
if($word[0] == '/') { if ($word[0] == '/') {
// lines that start with slash are regex // lines that start with slash are regex
if(preg_match($word, $comment) === 1) { if (preg_match($word, $comment) === 1) {
throw $ex; throw $ex;
} }
} } else {
else { // other words are literal
// other words are literal if (strpos($comment, $word) !== false) {
if(strpos($comment, $word) !== false) { throw $ex;
throw $ex; }
} }
} }
} }
}
private function get_words(): array { private function get_words(): array
global $config; {
$words = array(); global $config;
$words = [];
$banned = $config->get_string("banned_words"); $banned = $config->get_string("banned_words");
foreach(explode("\n", $banned) as $word) { foreach (explode("\n", $banned) as $word) {
$word = trim(strtolower($word)); $word = trim(strtolower($word));
if(strlen($word) == 0) { if (strlen($word) == 0) {
// line is blank // line is blank
continue; continue;
} }
$words[] = $word; $words[] = $word;
} }
return $words; return $words;
} }
public function get_priority(): int {return 30;} public function get_priority(): int
{
return 30;
}
} }

View File

@ -1,33 +1,34 @@
<?php <?php
class BanWordsTest extends ShimmiePHPUnitTestCase { class BanWordsTest extends ShimmiePHPUnitTestCase
public function check_blocked($image_id, $words) { {
global $user; public function check_blocked($image_id, $words)
try { {
send_event(new CommentPostingEvent($image_id, $user, $words)); global $user;
$this->fail("Exception not thrown"); try {
} send_event(new CommentPostingEvent($image_id, $user, $words));
catch(CommentPostingException $e) { $this->fail("Exception not thrown");
$this->assertEquals($e->getMessage(), "Comment contains banned terms"); } catch (CommentPostingException $e) {
} $this->assertEquals($e->getMessage(), "Comment contains banned terms");
} }
}
public function testWordBan() { public function testWordBan()
global $config; {
$config->set_string("banned_words", "viagra\nporn\n\n/http:.*\.cn\//"); global $config;
$config->set_string("banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$this->check_blocked($image_id, "kittens and viagra"); $this->check_blocked($image_id, "kittens and viagra");
$this->check_blocked($image_id, "kittens and ViagrA"); $this->check_blocked($image_id, "kittens and ViagrA");
$this->check_blocked($image_id, "kittens and viagra!"); $this->check_blocked($image_id, "kittens and viagra!");
$this->check_blocked($image_id, "some link to http://something.cn/"); $this->check_blocked($image_id, "some link to http://something.cn/");
$this->get_page('comment/list'); $this->get_page('comment/list');
$this->assert_title('Comments'); $this->assert_title('Comments');
$this->assert_no_text('viagra'); $this->assert_no_text('viagra');
$this->assert_no_text('ViagrA'); $this->assert_no_text('ViagrA');
$this->assert_no_text('http://something.cn/'); $this->assert_no_text('http://something.cn/');
} }
} }

View File

@ -25,135 +25,162 @@
* </ul> * </ul>
*/ */
class BBCode extends FormatterExtension { class BBCode extends FormatterExtension
public function format(string $text): string { {
$text = $this->extract_code($text); public function format(string $text): string
foreach(array( {
"b", "i", "u", "s", "sup", "sub", "h1", "h2", "h3", "h4", $text = $this->extract_code($text);
) as $el) { foreach ([
$text = preg_replace("!\[$el\](.*?)\[/$el\]!s", "<$el>$1</$el>", $text); "b", "i", "u", "s", "sup", "sub", "h1", "h2", "h3", "h4",
} ] as $el) {
$text = preg_replace('!^&gt;&gt;([^\d].+)!', '<blockquote><small>$1</small></blockquote>', $text); $text = preg_replace("!\[$el\](.*?)\[/$el\]!s", "<$el>$1</$el>", $text);
$text = preg_replace('!&gt;&gt;(\d+)(#c?\d+)?!s', '<a class="shm-clink" data-clink-sel="$2" href="'.make_link('post/view/$1$2').'">&gt;&gt;$1$2</a>', $text); }
$text = preg_replace('!\[anchor=(.*?)\](.*?)\[/anchor\]!s', '<span class="anchor">$2 <a class="alink" href="#bb-$1" name="bb-$1" title="link to this anchor"> ¶ </a></span>', $text); // add "bb-" to avoid clashing with eg #top $text = preg_replace('!^&gt;&gt;([^\d].+)!', '<blockquote><small>$1</small></blockquote>', $text);
$text = preg_replace('!\[url=site://(.*?)(#c\d+)?\](.*?)\[/url\]!s', '<a class="shm-clink" data-clink-sel="$2" href="'.make_link('$1$2').'">$3</a>', $text); $text = preg_replace('!&gt;&gt;(\d+)(#c?\d+)?!s', '<a class="shm-clink" data-clink-sel="$2" href="'.make_link('post/view/$1$2').'">&gt;&gt;$1$2</a>', $text);
$text = preg_replace('!\[url\]site://(.*?)(#c\d+)?\[/url\]!s', '<a class="shm-clink" data-clink-sel="$2" href="'.make_link('$1$2').'">$1$2</a>', $text); $text = preg_replace('!\[anchor=(.*?)\](.*?)\[/anchor\]!s', '<span class="anchor">$2 <a class="alink" href="#bb-$1" name="bb-$1" title="link to this anchor"> ¶ </a></span>', $text); // add "bb-" to avoid clashing with eg #top
$text = preg_replace('!\[url=((?:https?|ftp|irc|mailto)://.*?)\](.*?)\[/url\]!s', '<a href="$1">$2</a>', $text); $text = preg_replace('!\[url=site://(.*?)(#c\d+)?\](.*?)\[/url\]!s', '<a class="shm-clink" data-clink-sel="$2" href="'.make_link('$1$2').'">$3</a>', $text);
$text = preg_replace('!\[url\]((?:https?|ftp|irc|mailto)://.*?)\[/url\]!s', '<a href="$1">$1</a>', $text); $text = preg_replace('!\[url\]site://(.*?)(#c\d+)?\[/url\]!s', '<a class="shm-clink" data-clink-sel="$2" href="'.make_link('$1$2').'">$1$2</a>', $text);
$text = preg_replace('!\[email\](.*?)\[/email\]!s', '<a href="mailto:$1">$1</a>', $text); $text = preg_replace('!\[url=((?:https?|ftp|irc|mailto)://.*?)\](.*?)\[/url\]!s', '<a href="$1">$2</a>', $text);
$text = preg_replace('!\[img\](https?:\/\/.*?)\[/img\]!s', '<img src="$1">', $text); $text = preg_replace('!\[url\]((?:https?|ftp|irc|mailto)://.*?)\[/url\]!s', '<a href="$1">$1</a>', $text);
$text = preg_replace('!\[\[([^\|\]]+)\|([^\]]+)\]\]!s', '<a href="'.make_link('wiki/$1').'">$2</a>', $text); $text = preg_replace('!\[email\](.*?)\[/email\]!s', '<a href="mailto:$1">$1</a>', $text);
$text = preg_replace('!\[\[([^\]]+)\]\]!s', '<a href="'.make_link('wiki/$1').'">$1</a>', $text); $text = preg_replace('!\[img\](https?:\/\/.*?)\[/img\]!s', '<img src="$1">', $text);
$text = preg_replace("!\n\s*\n!", "\n\n", $text); $text = preg_replace('!\[\[([^\|\]]+)\|([^\]]+)\]\]!s', '<a href="'.make_link('wiki/$1').'">$2</a>', $text);
$text = str_replace("\n", "\n<br>", $text); $text = preg_replace('!\[\[([^\]]+)\]\]!s', '<a href="'.make_link('wiki/$1').'">$1</a>', $text);
$text = preg_replace("/\[quote\](.*?)\[\/quote\]/s", "<blockquote><small>\\1</small></blockquote>", $text); $text = preg_replace("!\n\s*\n!", "\n\n", $text);
$text = preg_replace("/\[quote=(.*?)\](.*?)\[\/quote\]/s", "<blockquote><em>\\1 said:</em><br><small>\\2</small></blockquote>", $text); $text = str_replace("\n", "\n<br>", $text);
while(preg_match("/\[list\](.*?)\[\/list\]/s", $text)) $text = preg_replace("/\[quote\](.*?)\[\/quote\]/s", "<blockquote><small>\\1</small></blockquote>", $text);
$text = preg_replace("/\[list\](.*?)\[\/list\]/s", "<ul>\\1</ul>", $text); $text = preg_replace("/\[quote=(.*?)\](.*?)\[\/quote\]/s", "<blockquote><em>\\1 said:</em><br><small>\\2</small></blockquote>", $text);
while(preg_match("/\[ul\](.*?)\[\/ul\]/s", $text)) while (preg_match("/\[list\](.*?)\[\/list\]/s", $text)) {
$text = preg_replace("/\[ul\](.*?)\[\/ul\]/s", "<ul>\\1</ul>", $text); $text = preg_replace("/\[list\](.*?)\[\/list\]/s", "<ul>\\1</ul>", $text);
while(preg_match("/\[ol\](.*?)\[\/ol\]/s", $text)) }
$text = preg_replace("/\[ol\](.*?)\[\/ol\]/s", "<ol>\\1</ol>", $text); while (preg_match("/\[ul\](.*?)\[\/ul\]/s", $text)) {
$text = preg_replace("/\[li\](.*?)\[\/li\]/s", "<li>\\1</li>", $text); $text = preg_replace("/\[ul\](.*?)\[\/ul\]/s", "<ul>\\1</ul>", $text);
$text = preg_replace("#\[\*\]#s", "<li>", $text); }
$text = preg_replace("#<br><(li|ul|ol|/ul|/ol)>#s", "<\\1>", $text); while (preg_match("/\[ol\](.*?)\[\/ol\]/s", $text)) {
$text = preg_replace("#\[align=(left|center|right)\](.*?)\[\/align\]#s", "<div style='text-align:\\1;'>\\2</div>", $text); $text = preg_replace("/\[ol\](.*?)\[\/ol\]/s", "<ol>\\1</ol>", $text);
$text = $this->filter_spoiler($text); }
$text = $this->insert_code($text); $text = preg_replace("/\[li\](.*?)\[\/li\]/s", "<li>\\1</li>", $text);
return $text; $text = preg_replace("#\[\*\]#s", "<li>", $text);
} $text = preg_replace("#<br><(li|ul|ol|/ul|/ol)>#s", "<\\1>", $text);
$text = preg_replace("#\[align=(left|center|right)\](.*?)\[\/align\]#s", "<div style='text-align:\\1;'>\\2</div>", $text);
$text = $this->filter_spoiler($text);
$text = $this->insert_code($text);
return $text;
}
public function strip(string $text): string { public function strip(string $text): string
foreach(array( {
"b", "i", "u", "s", "sup", "sub", "h1", "h2", "h3", "h4", foreach ([
"code", "url", "email", "li", "b", "i", "u", "s", "sup", "sub", "h1", "h2", "h3", "h4",
) as $el) { "code", "url", "email", "li",
$text = preg_replace("!\[$el\](.*?)\[/$el\]!s", '$1', $text); ] as $el) {
} $text = preg_replace("!\[$el\](.*?)\[/$el\]!s", '$1', $text);
$text = preg_replace("!\[anchor=(.*?)\](.*?)\[/anchor\]!s", '$2', $text); }
$text = preg_replace("!\[url=(.*?)\](.*?)\[/url\]!s", '$2', $text); $text = preg_replace("!\[anchor=(.*?)\](.*?)\[/anchor\]!s", '$2', $text);
$text = preg_replace("!\[img\](.*?)\[/img\]!s", "", $text); $text = preg_replace("!\[url=(.*?)\](.*?)\[/url\]!s", '$2', $text);
$text = preg_replace("!\[\[([^\|\]]+)\|([^\]]+)\]\]!s", '$2', $text); $text = preg_replace("!\[img\](.*?)\[/img\]!s", "", $text);
$text = preg_replace("!\[\[([^\]]+)\]\]!s", '$1', $text); $text = preg_replace("!\[\[([^\|\]]+)\|([^\]]+)\]\]!s", '$2', $text);
$text = preg_replace("!\[quote\](.*?)\[/quote\]!s", "", $text); $text = preg_replace("!\[\[([^\]]+)\]\]!s", '$1', $text);
$text = preg_replace("!\[quote=(.*?)\](.*?)\[/quote\]!s", "", $text); $text = preg_replace("!\[quote\](.*?)\[/quote\]!s", "", $text);
$text = preg_replace("!\[/?(list|ul|ol)\]!", "", $text); $text = preg_replace("!\[quote=(.*?)\](.*?)\[/quote\]!s", "", $text);
$text = preg_replace("!\[\*\](.*?)!s", '$1', $text); $text = preg_replace("!\[/?(list|ul|ol)\]!", "", $text);
$text = $this->strip_spoiler($text); $text = preg_replace("!\[\*\](.*?)!s", '$1', $text);
return $text; $text = $this->strip_spoiler($text);
} return $text;
}
private function filter_spoiler(string $text): string { private function filter_spoiler(string $text): string
return str_replace( {
array("[spoiler]","[/spoiler]"), return str_replace(
array("<span style=\"background-color:#000; color:#000;\">","</span>"), ["[spoiler]","[/spoiler]"],
$text); ["<span style=\"background-color:#000; color:#000;\">","</span>"],
} $text
);
}
private function strip_spoiler(string $text): string { private function strip_spoiler(string $text): string
$l1 = strlen("[spoiler]"); {
$l2 = strlen("[/spoiler]"); $l1 = strlen("[spoiler]");
while(true) { $l2 = strlen("[/spoiler]");
$start = strpos($text, "[spoiler]"); while (true) {
if($start === false) break; $start = strpos($text, "[spoiler]");
if ($start === false) {
break;
}
$end = strpos($text, "[/spoiler]"); $end = strpos($text, "[/spoiler]");
if($end === false) break; if ($end === false) {
break;
}
if($end < $start) break; if ($end < $start) {
break;
}
$beginning = substr($text, 0, $start); $beginning = substr($text, 0, $start);
$middle = str_rot13(substr($text, $start+$l1, ($end-$start-$l1))); $middle = str_rot13(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2)); $ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$text = $beginning . $middle . $ending; $text = $beginning . $middle . $ending;
} }
return $text; return $text;
} }
private function extract_code(string $text): string { private function extract_code(string $text): string
# at the end of this function, the only code! blocks should be {
# the ones we've added -- others may contain malicious content, # at the end of this function, the only code! blocks should be
# which would only appear after decoding # the ones we've added -- others may contain malicious content,
$text = str_replace("[code!]", "[code]", $text); # which would only appear after decoding
$text = str_replace("[/code!]", "[/code]", $text); $text = str_replace("[code!]", "[code]", $text);
$text = str_replace("[/code!]", "[/code]", $text);
$l1 = strlen("[code]"); $l1 = strlen("[code]");
$l2 = strlen("[/code]"); $l2 = strlen("[/code]");
while(true) { while (true) {
$start = strpos($text, "[code]"); $start = strpos($text, "[code]");
if($start === false) break; if ($start === false) {
break;
}
$end = strpos($text, "[/code]", $start); $end = strpos($text, "[/code]", $start);
if($end === false) break; if ($end === false) {
break;
}
if($end < $start) break; if ($end < $start) {
break;
}
$beginning = substr($text, 0, $start); $beginning = substr($text, 0, $start);
$middle = base64_encode(substr($text, $start+$l1, ($end-$start-$l1))); $middle = base64_encode(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2)); $ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$text = $beginning . "[code!]" . $middle . "[/code!]" . $ending; $text = $beginning . "[code!]" . $middle . "[/code!]" . $ending;
} }
return $text; return $text;
} }
private function insert_code(string $text): string { private function insert_code(string $text): string
$l1 = strlen("[code!]"); {
$l2 = strlen("[/code!]"); $l1 = strlen("[code!]");
while(true) { $l2 = strlen("[/code!]");
$start = strpos($text, "[code!]"); while (true) {
if($start === false) break; $start = strpos($text, "[code!]");
if ($start === false) {
break;
}
$end = strpos($text, "[/code!]"); $end = strpos($text, "[/code!]");
if($end === false) break; if ($end === false) {
break;
}
$beginning = substr($text, 0, $start); $beginning = substr($text, 0, $start);
$middle = base64_decode(substr($text, $start+$l1, ($end-$start-$l1))); $middle = base64_decode(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2)); $ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$text = $beginning . "<pre>" . $middle . "</pre>" . $ending; $text = $beginning . "<pre>" . $middle . "</pre>" . $ending;
} }
return $text; return $text;
} }
} }

View File

@ -1,85 +1,110 @@
<?php <?php
class BBCodeTest extends ShimmiePHPUnitTestCase { class BBCodeTest extends ShimmiePHPUnitTestCase
public function testBasics() { {
$this->assertEquals( public function testBasics()
$this->filter("[b]bold[/b][i]italic[/i]"), {
"<b>bold</b><i>italic</i>"); $this->assertEquals(
} $this->filter("[b]bold[/b][i]italic[/i]"),
"<b>bold</b><i>italic</i>"
);
}
public function testStacking() { public function testStacking()
$this->assertEquals( {
$this->filter("[b]B[/b][i]I[/i][b]B[/b]"), $this->assertEquals(
"<b>B</b><i>I</i><b>B</b>"); $this->filter("[b]B[/b][i]I[/i][b]B[/b]"),
$this->assertEquals( "<b>B</b><i>I</i><b>B</b>"
$this->filter("[b]bold[i]bolditalic[/i]bold[/b]"), );
"<b>bold<i>bolditalic</i>bold</b>"); $this->assertEquals(
} $this->filter("[b]bold[i]bolditalic[/i]bold[/b]"),
"<b>bold<i>bolditalic</i>bold</b>"
);
}
public function testFailure() { public function testFailure()
$this->assertEquals( {
$this->filter("[b]bold[i]italic"), $this->assertEquals(
"[b]bold[i]italic"); $this->filter("[b]bold[i]italic"),
} "[b]bold[i]italic"
);
}
public function testCode() { public function testCode()
$this->assertEquals( {
$this->filter("[code][b]bold[/b][/code]"), $this->assertEquals(
"<pre>[b]bold[/b]</pre>"); $this->filter("[code][b]bold[/b][/code]"),
} "<pre>[b]bold[/b]</pre>"
);
}
public function testNestedList() { public function testNestedList()
$this->assertEquals( {
$this->filter("[list][*]a[list][*]a[*]b[/list][*]b[/list]"), $this->assertEquals(
"<ul><li>a<ul><li>a<li>b</ul><li>b</ul>"); $this->filter("[list][*]a[list][*]a[*]b[/list][*]b[/list]"),
$this->assertEquals( "<ul><li>a<ul><li>a<li>b</ul><li>b</ul>"
$this->filter("[ul][*]a[ol][*]a[*]b[/ol][*]b[/ul]"), );
"<ul><li>a<ol><li>a<li>b</ol><li>b</ul>"); $this->assertEquals(
} $this->filter("[ul][*]a[ol][*]a[*]b[/ol][*]b[/ul]"),
"<ul><li>a<ol><li>a<li>b</ol><li>b</ul>"
);
}
public function testSpoiler() { public function testSpoiler()
$this->assertEquals( {
$this->filter("[spoiler]ShishNet[/spoiler]"), $this->assertEquals(
"<span style=\"background-color:#000; color:#000;\">ShishNet</span>"); $this->filter("[spoiler]ShishNet[/spoiler]"),
$this->assertEquals( "<span style=\"background-color:#000; color:#000;\">ShishNet</span>"
$this->strip("[spoiler]ShishNet[/spoiler]"), );
"FuvfuArg"); $this->assertEquals(
#$this->assertEquals( $this->strip("[spoiler]ShishNet[/spoiler]"),
# $this->filter("[spoiler]ShishNet"), "FuvfuArg"
# "[spoiler]ShishNet"); );
} #$this->assertEquals(
# $this->filter("[spoiler]ShishNet"),
# "[spoiler]ShishNet");
}
public function testURL() { public function testURL()
$this->assertEquals( {
$this->filter("[url]http://shishnet.org[/url]"), $this->assertEquals(
"<a href=\"http://shishnet.org\">http://shishnet.org</a>"); $this->filter("[url]http://shishnet.org[/url]"),
$this->assertEquals( "<a href=\"http://shishnet.org\">http://shishnet.org</a>"
$this->filter("[url=http://shishnet.org]ShishNet[/url]"), );
"<a href=\"http://shishnet.org\">ShishNet</a>"); $this->assertEquals(
$this->assertEquals( $this->filter("[url=http://shishnet.org]ShishNet[/url]"),
$this->filter("[url=javascript:alert(\"owned\")]click to fail[/url]"), "<a href=\"http://shishnet.org\">ShishNet</a>"
"[url=javascript:alert(\"owned\")]click to fail[/url]"); );
} $this->assertEquals(
$this->filter("[url=javascript:alert(\"owned\")]click to fail[/url]"),
"[url=javascript:alert(\"owned\")]click to fail[/url]"
);
}
public function testEmailURL() { public function testEmailURL()
$this->assertEquals( {
$this->filter("[email]spam@shishnet.org[/email]"), $this->assertEquals(
"<a href=\"mailto:spam@shishnet.org\">spam@shishnet.org</a>"); $this->filter("[email]spam@shishnet.org[/email]"),
} "<a href=\"mailto:spam@shishnet.org\">spam@shishnet.org</a>"
);
}
public function testAnchor() { public function testAnchor()
$this->assertEquals( {
$this->filter("[anchor=rules]Rules[/anchor]"), $this->assertEquals(
'<span class="anchor">Rules <a class="alink" href="#bb-rules" name="bb-rules" title="link to this anchor"> ¶ </a></span>'); $this->filter("[anchor=rules]Rules[/anchor]"),
} '<span class="anchor">Rules <a class="alink" href="#bb-rules" name="bb-rules" title="link to this anchor"> ¶ </a></span>'
);
}
private function filter($in) { private function filter($in)
$bb = new BBCode(); {
return $bb->format($in); $bb = new BBCode();
} return $bb->format($in);
}
private function strip($in) { private function strip($in)
$bb = new BBCode(); {
return $bb->strip($in); $bb = new BBCode();
} return $bb->strip($in);
}
} }

View File

@ -7,11 +7,13 @@
* Description: Add HTML to some space (News, Ads, etc) * Description: Add HTML to some space (News, Ads, etc)
*/ */
class Blocks extends Extension { class Blocks extends Extension
public function onInitExt(InitExtEvent $event) { {
global $config, $database; public function onInitExt(InitExtEvent $event)
if($config->get_int("ext_blocks_version") < 1) { {
$database->create_table("blocks", " global $config, $database;
if ($config->get_int("ext_blocks_version") < 1) {
$database->create_table("blocks", "
id SCORE_AIPK, id SCORE_AIPK,
pages VARCHAR(128) NOT NULL, pages VARCHAR(128) NOT NULL,
title VARCHAR(128) NOT NULL, title VARCHAR(128) NOT NULL,
@ -19,73 +21,72 @@ class Blocks extends Extension {
priority INTEGER NOT NULL, priority INTEGER NOT NULL,
content TEXT NOT NULL content TEXT NOT NULL
"); ");
$database->execute("CREATE INDEX blocks_pages_idx ON blocks(pages)", array()); $database->execute("CREATE INDEX blocks_pages_idx ON blocks(pages)", []);
$config->set_int("ext_blocks_version", 1); $config->set_int("ext_blocks_version", 1);
} }
} }
public function onUserBlockBuilding(UserBlockBuildingEvent $event) { public function onUserBlockBuilding(UserBlockBuildingEvent $event)
global $user; {
if($user->can("manage_blocks")) { global $user;
$event->add_link("Blocks Editor", make_link("blocks/list")); if ($user->can("manage_blocks")) {
} $event->add_link("Blocks Editor", make_link("blocks/list"));
} }
}
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
global $database, $page, $user; {
global $database, $page, $user;
$blocks = $database->cache->get("blocks"); $blocks = $database->cache->get("blocks");
if($blocks === false) { if ($blocks === false) {
$blocks = $database->get_all("SELECT * FROM blocks"); $blocks = $database->get_all("SELECT * FROM blocks");
$database->cache->set("blocks", $blocks, 600); $database->cache->set("blocks", $blocks, 600);
} }
foreach($blocks as $block) { foreach ($blocks as $block) {
$path = implode("/", $event->args); $path = implode("/", $event->args);
if(strlen($path) < 4000 && fnmatch($block['pages'], $path)) { if (strlen($path) < 4000 && fnmatch($block['pages'], $path)) {
$b = new Block($block['title'], $block['content'], $block['area'], $block['priority']); $b = new Block($block['title'], $block['content'], $block['area'], $block['priority']);
$b->is_content = false; $b->is_content = false;
$page->add_block($b); $page->add_block($b);
} }
} }
if($event->page_matches("blocks") && $user->can("manage_blocks")) { if ($event->page_matches("blocks") && $user->can("manage_blocks")) {
if($event->get_arg(0) == "add") { if ($event->get_arg(0) == "add") {
if($user->check_auth_token()) { if ($user->check_auth_token()) {
$database->execute(" $database->execute("
INSERT INTO blocks (pages, title, area, priority, content) INSERT INTO blocks (pages, title, area, priority, content)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
", array($_POST['pages'], $_POST['title'], $_POST['area'], (int)$_POST['priority'], $_POST['content'])); ", [$_POST['pages'], $_POST['title'], $_POST['area'], (int)$_POST['priority'], $_POST['content']]);
log_info("blocks", "Added Block #".($database->get_last_insert_id('blocks_id_seq'))." (".$_POST['title'].")"); log_info("blocks", "Added Block #".($database->get_last_insert_id('blocks_id_seq'))." (".$_POST['title'].")");
$database->cache->delete("blocks"); $database->cache->delete("blocks");
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("blocks/list")); $page->set_redirect(make_link("blocks/list"));
} }
} }
if($event->get_arg(0) == "update") { if ($event->get_arg(0) == "update") {
if($user->check_auth_token()) { if ($user->check_auth_token()) {
if(!empty($_POST['delete'])) { if (!empty($_POST['delete'])) {
$database->execute(" $database->execute("
DELETE FROM blocks DELETE FROM blocks
WHERE id=? WHERE id=?
", array($_POST['id'])); ", [$_POST['id']]);
log_info("blocks", "Deleted Block #".$_POST['id']); log_info("blocks", "Deleted Block #".$_POST['id']);
} } else {
else { $database->execute("
$database->execute("
UPDATE blocks SET pages=?, title=?, area=?, priority=?, content=? UPDATE blocks SET pages=?, title=?, area=?, priority=?, content=?
WHERE id=? WHERE id=?
", array($_POST['pages'], $_POST['title'], $_POST['area'], (int)$_POST['priority'], $_POST['content'], $_POST['id'])); ", [$_POST['pages'], $_POST['title'], $_POST['area'], (int)$_POST['priority'], $_POST['content'], $_POST['id']]);
log_info("blocks", "Updated Block #".$_POST['id']." (".$_POST['title'].")"); log_info("blocks", "Updated Block #".$_POST['id']." (".$_POST['title'].")");
} }
$database->cache->delete("blocks"); $database->cache->delete("blocks");
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("blocks/list")); $page->set_redirect(make_link("blocks/list"));
} }
} } elseif ($event->get_arg(0) == "list") {
else if($event->get_arg(0) == "list") { $this->theme->display_blocks($database->get_all("SELECT * FROM blocks ORDER BY area, priority"));
$this->theme->display_blocks($database->get_all("SELECT * FROM blocks ORDER BY area, priority")); }
} }
} }
}
} }

View File

@ -1,10 +1,11 @@
<?php <?php
class BlocksTest extends ShimmiePHPUnitTestCase { class BlocksTest extends ShimmiePHPUnitTestCase
public function testBlocks() { {
$this->log_in_as_admin(); public function testBlocks()
$this->get_page("blocks/list"); {
$this->assert_response(200); $this->log_in_as_admin();
$this->assert_title("Blocks"); $this->get_page("blocks/list");
} $this->assert_response(200);
$this->assert_title("Blocks");
}
} }

View File

@ -1,46 +1,47 @@
<?php <?php
class BlocksTheme extends Themelet { class BlocksTheme extends Themelet
public function display_blocks($blocks) { {
global $page; public function display_blocks($blocks)
{
global $page;
$html = "<table class='form' style='width: 100%;'>"; $html = "<table class='form' style='width: 100%;'>";
foreach($blocks as $block) { foreach ($blocks as $block) {
$html .= make_form(make_link("blocks/update")); $html .= make_form(make_link("blocks/update"));
$html .= "<input type='hidden' name='id' value='".html_escape($block['id'])."'>"; $html .= "<input type='hidden' name='id' value='".html_escape($block['id'])."'>";
$html .= "<tr>"; $html .= "<tr>";
$html .= "<th>Title</th><td><input type='text' name='title' value='".html_escape($block['title'])."'></td>"; $html .= "<th>Title</th><td><input type='text' name='title' value='".html_escape($block['title'])."'></td>";
$html .= "<th>Area</th><td><input type='text' name='area' value='".html_escape($block['area'])."'></td>"; $html .= "<th>Area</th><td><input type='text' name='area' value='".html_escape($block['area'])."'></td>";
$html .= "<th>Priority</th><td><input type='text' name='priority' value='".html_escape($block['priority'])."'></td>"; $html .= "<th>Priority</th><td><input type='text' name='priority' value='".html_escape($block['priority'])."'></td>";
$html .= "<th>Pages</th><td><input type='text' name='pages' value='".html_escape($block['pages'])."'></td>"; $html .= "<th>Pages</th><td><input type='text' name='pages' value='".html_escape($block['pages'])."'></td>";
$html .= "<th>Delete</th><td><input type='checkbox' name='delete'></td>"; $html .= "<th>Delete</th><td><input type='checkbox' name='delete'></td>";
$html .= "<td><input type='submit' value='Save'></td>"; $html .= "<td><input type='submit' value='Save'></td>";
$html .= "</tr>"; $html .= "</tr>";
$html .= "<tr>"; $html .= "<tr>";
$html .= "<td colspan='11'><textarea rows='5' name='content'>".html_escape($block['content'])."</textarea></td>"; $html .= "<td colspan='11'><textarea rows='5' name='content'>".html_escape($block['content'])."</textarea></td>";
$html .= "</tr>\n"; $html .= "</tr>\n";
$html .= "<tr>"; $html .= "<tr>";
$html .= "<td colspan='11'>&nbsp;</td>"; $html .= "<td colspan='11'>&nbsp;</td>";
$html .= "</tr>\n"; $html .= "</tr>\n";
$html .= "</form>\n"; $html .= "</form>\n";
} }
$html .= make_form(make_link("blocks/add")); $html .= make_form(make_link("blocks/add"));
$html .= "<tr>"; $html .= "<tr>";
$html .= "<th>Title</th><td><input type='text' name='title' value=''></td>"; $html .= "<th>Title</th><td><input type='text' name='title' value=''></td>";
$html .= "<th>Area</th><td><select name='area'><option>left<option>main</select></td>"; $html .= "<th>Area</th><td><select name='area'><option>left<option>main</select></td>";
$html .= "<th>Priority</th><td><input type='text' name='priority' value='50'></td>"; $html .= "<th>Priority</th><td><input type='text' name='priority' value='50'></td>";
$html .= "<th>Pages</th><td><input type='text' name='pages' value='post/list*'></td>"; $html .= "<th>Pages</th><td><input type='text' name='pages' value='post/list*'></td>";
$html .= "<td colspan='3'><input type='submit' value='Add'></td>"; $html .= "<td colspan='3'><input type='submit' value='Add'></td>";
$html .= "</tr>"; $html .= "</tr>";
$html .= "<tr>"; $html .= "<tr>";
$html .= "<td colspan='11'><textarea rows='5' name='content'></textarea></td>"; $html .= "<td colspan='11'><textarea rows='5' name='content'></textarea></td>";
$html .= "</tr>\n"; $html .= "</tr>\n";
$html .= "</form>"; $html .= "</form>";
$html .= "</table>"; $html .= "</table>";
$page->set_title("Blocks"); $page->set_title("Blocks");
$page->set_heading("Blocks"); $page->set_heading("Blocks");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
$page->add_block(new Block("Block Editor", $html)); $page->add_block(new Block("Block Editor", $html));
} }
} }

View File

@ -8,125 +8,142 @@
* *
* Development TODO at http://github.com/zshall/shimmie2/issues * Development TODO at http://github.com/zshall/shimmie2/issues
*/ */
class Blotter extends Extension { class Blotter extends Extension
public function onInitExt(InitExtEvent $event) { {
/** public function onInitExt(InitExtEvent $event)
* I love re-using this installer don't I... {
*/ /**
global $config; * I love re-using this installer don't I...
$version = $config->get_int("blotter_version", 0); */
/** global $config;
* If this version is less than "1", it's time to install. $version = $config->get_int("blotter_version", 0);
* /**
* REMINDER: If I change the database tables, I must change up version by 1. * If this version is less than "1", it's time to install.
*/ *
if($version < 1) { * REMINDER: If I change the database tables, I must change up version by 1.
/** */
* Installer if ($version < 1) {
*/ /**
global $database, $config; * Installer
$database->create_table("blotter", " */
global $database, $config;
$database->create_table("blotter", "
id SCORE_AIPK, id SCORE_AIPK,
entry_date SCORE_DATETIME DEFAULT SCORE_NOW, entry_date SCORE_DATETIME DEFAULT SCORE_NOW,
entry_text TEXT NOT NULL, entry_text TEXT NOT NULL,
important SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N important SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N
"); ");
// Insert sample data: // Insert sample data:
$database->execute("INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), ?, ?)", $database->execute(
array("Installed the blotter extension!", "Y")); "INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), ?, ?)",
log_info("blotter", "Installed tables for blotter extension."); ["Installed the blotter extension!", "Y"]
$config->set_int("blotter_version", 1); );
} log_info("blotter", "Installed tables for blotter extension.");
// Set default config: $config->set_int("blotter_version", 1);
$config->set_default_int("blotter_recent", 5); }
$config->set_default_string("blotter_color", "FF0000"); // Set default config:
$config->set_default_string("blotter_position", "subheading"); $config->set_default_int("blotter_recent", 5);
} $config->set_default_string("blotter_color", "FF0000");
$config->set_default_string("blotter_position", "subheading");
}
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event)
$sb = new SetupBlock("Blotter"); {
$sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: "); $sb = new SetupBlock("Blotter");
$sb->add_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) "); $sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: ");
$sb->add_choice_option("blotter_position", array("Top of page" => "subheading", "In navigation bar" => "left"), "<br>Position: "); $sb->add_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) ");
$event->panel->add_block($sb); $sb->add_choice_option("blotter_position", ["Top of page" => "subheading", "In navigation bar" => "left"], "<br>Position: ");
} $event->panel->add_block($sb);
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) { public function onUserBlockBuilding(UserBlockBuildingEvent $event)
global $user; {
if($user->is_admin()) { global $user;
$event->add_link("Blotter Editor", make_link("blotter/editor")); if ($user->is_admin()) {
} $event->add_link("Blotter Editor", make_link("blotter/editor"));
} }
}
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
global $page, $database, $user; {
if($event->page_matches("blotter")) { global $page, $database, $user;
switch($event->get_arg(0)) { if ($event->page_matches("blotter")) {
case "editor": switch ($event->get_arg(0)) {
/** case "editor":
* Displays the blotter editor. /**
*/ * Displays the blotter editor.
if(!$user->is_admin()) { */
$this->theme->display_permission_denied(); if (!$user->is_admin()) {
} else { $this->theme->display_permission_denied();
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC"); } else {
$this->theme->display_editor($entries); $entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC");
} $this->theme->display_editor($entries);
break; }
case "add": break;
/** case "add":
* Adds an entry /**
*/ * Adds an entry
if(!$user->is_admin() || !$user->check_auth_token()) { */
$this->theme->display_permission_denied(); if (!$user->is_admin() || !$user->check_auth_token()) {
} else { $this->theme->display_permission_denied();
$entry_text = $_POST['entry_text']; } else {
if($entry_text == "") { die("No entry message!"); } $entry_text = $_POST['entry_text'];
if(isset($_POST['important'])) { $important = 'Y'; } else { $important = 'N'; } if ($entry_text == "") {
// Now insert into db: die("No entry message!");
$database->execute("INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), ?, ?)", }
array($entry_text, $important)); if (isset($_POST['important'])) {
log_info("blotter", "Added Message: $entry_text"); $important = 'Y';
$page->set_mode("redirect"); } else {
$page->set_redirect(make_link("blotter/editor")); $important = 'N';
} }
break; // Now insert into db:
case "remove": $database->execute(
/** "INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), ?, ?)",
* Removes an entry [$entry_text, $important]
*/ );
if(!$user->is_admin() || !$user->check_auth_token()) { log_info("blotter", "Added Message: $entry_text");
$this->theme->display_permission_denied(); $page->set_mode("redirect");
} else { $page->set_redirect(make_link("blotter/editor"));
$id = int_escape($_POST['id']); }
if(!isset($id)) { die("No ID!"); } break;
$database->Execute("DELETE FROM blotter WHERE id=:id", array("id"=>$id)); case "remove":
log_info("blotter", "Removed Entry #$id"); /**
$page->set_mode("redirect"); * Removes an entry
$page->set_redirect(make_link("blotter/editor")); */
} if (!$user->is_admin() || !$user->check_auth_token()) {
break; $this->theme->display_permission_denied();
case "list": } else {
/** $id = int_escape($_POST['id']);
* Displays all blotter entries if (!isset($id)) {
*/ die("No ID!");
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC"); }
$this->theme->display_blotter_page($entries); $database->Execute("DELETE FROM blotter WHERE id=:id", ["id"=>$id]);
break; log_info("blotter", "Removed Entry #$id");
} $page->set_mode("redirect");
} $page->set_redirect(make_link("blotter/editor"));
/** }
* Finally, display the blotter on whatever page we're viewing. break;
*/ case "list":
$this->display_blotter(); /**
} * Displays all blotter entries
*/
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC");
$this->theme->display_blotter_page($entries);
break;
}
}
/**
* Finally, display the blotter on whatever page we're viewing.
*/
$this->display_blotter();
}
private function display_blotter() { private function display_blotter()
global $database, $config; {
$limit = $config->get_int("blotter_recent", 5); global $database, $config;
$sql = 'SELECT * FROM blotter ORDER BY id DESC LIMIT '.intval($limit); $limit = $config->get_int("blotter_recent", 5);
$entries = $database->get_all($sql); $sql = 'SELECT * FROM blotter ORDER BY id DESC LIMIT '.intval($limit);
$this->theme->display_blotter($entries); $entries = $database->get_all($sql);
} $this->theme->display_blotter($entries);
}
} }

View File

@ -1,34 +1,38 @@
<?php <?php
class BlotterTest extends ShimmiePHPUnitTestCase { class BlotterTest extends ShimmiePHPUnitTestCase
public function testLogin() { {
$this->log_in_as_admin(); public function testLogin()
//$this->assert_text("Blotter Editor"); {
//$this->click("Blotter Editor"); $this->log_in_as_admin();
//$this->log_out(); //$this->assert_text("Blotter Editor");
} //$this->click("Blotter Editor");
//$this->log_out();
}
public function testDenial() { public function testDenial()
$this->get_page("blotter/editor"); {
$this->assert_response(403); $this->get_page("blotter/editor");
$this->get_page("blotter/add"); $this->assert_response(403);
$this->assert_response(403); $this->get_page("blotter/add");
$this->get_page("blotter/remove"); $this->assert_response(403);
$this->assert_response(403); $this->get_page("blotter/remove");
} $this->assert_response(403);
}
public function testAddViewRemove() { public function testAddViewRemove()
$this->log_in_as_admin(); {
$this->log_in_as_admin();
$this->get_page("blotter/editor"); $this->get_page("blotter/editor");
//$this->set_field("entry_text", "blotter testing"); //$this->set_field("entry_text", "blotter testing");
//$this->click("Add"); //$this->click("Add");
//$this->assert_text("blotter testing"); //$this->assert_text("blotter testing");
$this->get_page("blotter"); $this->get_page("blotter");
//$this->assert_text("blotter testing"); //$this->assert_text("blotter testing");
$this->get_page("blotter/editor"); $this->get_page("blotter/editor");
//$this->click("Remove"); //$this->click("Remove");
//$this->assert_no_text("blotter testing"); //$this->assert_no_text("blotter testing");
} }
} }

View File

@ -1,45 +1,50 @@
<?php <?php
class BlotterTheme extends Themelet { class BlotterTheme extends Themelet
public function display_editor($entries) { {
global $page; public function display_editor($entries)
$html = $this->get_html_for_blotter_editor($entries); {
$page->set_title("Blotter Editor"); global $page;
$page->set_heading("Blotter Editor"); $html = $this->get_html_for_blotter_editor($entries);
$page->add_block(new Block("Welcome to the Blotter Editor!", $html, "main", 10)); $page->set_title("Blotter Editor");
$page->add_block(new Block("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0)); $page->set_heading("Blotter Editor");
} $page->add_block(new Block("Welcome to the Blotter Editor!", $html, "main", 10));
$page->add_block(new Block("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0));
}
public function display_blotter_page($entries) { public function display_blotter_page($entries)
global $page; {
$html = $this->get_html_for_blotter_page($entries); global $page;
$page->set_title("Blotter"); $html = $this->get_html_for_blotter_page($entries);
$page->set_heading("Blotter"); $page->set_title("Blotter");
$page->add_block(new Block("Blotter Entries", $html, "main", 10)); $page->set_heading("Blotter");
} $page->add_block(new Block("Blotter Entries", $html, "main", 10));
}
public function display_blotter($entries) { public function display_blotter($entries)
global $page, $config; {
$html = $this->get_html_for_blotter($entries); global $page, $config;
$position = $config->get_string("blotter_position", "subheading"); $html = $this->get_html_for_blotter($entries);
$page->add_block(new Block(null, $html, $position, 20)); $position = $config->get_string("blotter_position", "subheading");
} $page->add_block(new Block(null, $html, $position, 20));
}
private function get_html_for_blotter_editor($entries) { private function get_html_for_blotter_editor($entries)
global $user; {
global $user;
/** /**
* Long function name, but at least I won't confuse it with something else ^_^ * Long function name, but at least I won't confuse it with something else ^_^
*/ */
// Add_new stuff goes here. // Add_new stuff goes here.
$table_header = " $table_header = "
<tr> <tr>
<th>Date</th> <th>Date</th>
<th>Message</th> <th>Message</th>
<th>Important?</th> <th>Important?</th>
<th>Action</th> <th>Action</th>
</tr>"; </tr>";
$add_new = " $add_new = "
<tr class='even'> <tr class='even'>
".make_form(make_link("blotter/add"))." ".make_form(make_link("blotter/add"))."
<td colspan='2'><textarea style='text-align:left;' name='entry_text' rows='2' /></textarea></td> <td colspan='2'><textarea style='text-align:left;' name='entry_text' rows='2' /></textarea></td>
@ -49,21 +54,25 @@ class BlotterTheme extends Themelet {
</tr>"; </tr>";
// Now, time for entries list. // Now, time for entries list.
$table_rows = ""; $table_rows = "";
$num_entries = count($entries); $num_entries = count($entries);
for ($i = 0 ; $i < $num_entries ; $i++) { for ($i = 0 ; $i < $num_entries ; $i++) {
/** /**
* Add table rows * Add table rows
*/ */
$id = $entries[$i]['id']; $id = $entries[$i]['id'];
$entry_date = $entries[$i]['entry_date']; $entry_date = $entries[$i]['entry_date'];
$entry_text = $entries[$i]['entry_text']; $entry_text = $entries[$i]['entry_text'];
if($entries[$i]['important'] == 'Y') { $important = 'Y'; } else { $important = 'N'; } if ($entries[$i]['important'] == 'Y') {
$important = 'Y';
} else {
$important = 'N';
}
// Add the new table row(s) // Add the new table row(s)
$table_rows .= $table_rows .=
"<tr> "<tr>
<td>$entry_date</td> <td>$entry_date</td>
<td>$entry_text</td> <td>$entry_text</td>
<td>$important</td> <td>$important</td>
@ -74,9 +83,9 @@ class BlotterTheme extends Themelet {
</form> </form>
</td> </td>
</tr>"; </tr>";
} }
$html = " $html = "
<table id='blotter_entries' class='zebra'> <table id='blotter_entries' class='zebra'>
<thead>$table_header</thead> <thead>$table_header</thead>
<tbody>$add_new</tbody> <tbody>$add_new</tbody>
@ -87,82 +96,83 @@ class BlotterTheme extends Themelet {
<b>Help:</b><br /> <b>Help:</b><br />
<blockquote>Add entries to the blotter, and they will be displayed.</blockquote>"; <blockquote>Add entries to the blotter, and they will be displayed.</blockquote>";
return $html; return $html;
} }
private function get_html_for_blotter_page($entries) { private function get_html_for_blotter_page($entries)
/** {
* This one displays a list of all blotter entries. /**
*/ * This one displays a list of all blotter entries.
global $config; */
$i_color = $config->get_string("blotter_color", "#FF0000"); global $config;
$html = "<pre>"; $i_color = $config->get_string("blotter_color", "#FF0000");
$html = "<pre>";
$num_entries = count($entries); $num_entries = count($entries);
for ($i = 0 ; $i < $num_entries ; $i++) { for ($i = 0 ; $i < $num_entries ; $i++) {
/** /**
* Blotter entries * Blotter entries
*/ */
// Reset variables: // Reset variables:
$i_open = ""; $i_open = "";
$i_close = ""; $i_close = "";
//$id = $entries[$i]['id']; //$id = $entries[$i]['id'];
$messy_date = $entries[$i]['entry_date']; $messy_date = $entries[$i]['entry_date'];
$clean_date = date("y/m/d", strtotime($messy_date)); $clean_date = date("y/m/d", strtotime($messy_date));
$entry_text = $entries[$i]['entry_text']; $entry_text = $entries[$i]['entry_text'];
if($entries[$i]['important'] == 'Y') { if ($entries[$i]['important'] == 'Y') {
$i_open = "<font color='#{$i_color}'>"; $i_open = "<font color='#{$i_color}'>";
$i_close="</font>"; $i_close="</font>";
} }
$html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}<br /><br />"; $html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}<br /><br />";
} }
$html .= "</pre>"; $html .= "</pre>";
return $html; return $html;
} }
private function get_html_for_blotter($entries) { private function get_html_for_blotter($entries)
global $config; {
$i_color = $config->get_string("blotter_color", "#FF0000"); global $config;
$position = $config->get_string("blotter_position", "subheading"); $i_color = $config->get_string("blotter_color", "#FF0000");
$entries_list = ""; $position = $config->get_string("blotter_position", "subheading");
$num_entries = count($entries); $entries_list = "";
for ($i = 0 ; $i < $num_entries ; $i++) { $num_entries = count($entries);
/** for ($i = 0 ; $i < $num_entries ; $i++) {
* Blotter entries /**
*/ * Blotter entries
// Reset variables: */
$i_open = ""; // Reset variables:
$i_close = ""; $i_open = "";
//$id = $entries[$i]['id']; $i_close = "";
$messy_date = $entries[$i]['entry_date']; //$id = $entries[$i]['id'];
$clean_date = date("m/d/y", strtotime($messy_date)); $messy_date = $entries[$i]['entry_date'];
$entry_text = $entries[$i]['entry_text']; $clean_date = date("m/d/y", strtotime($messy_date));
if($entries[$i]['important'] == 'Y') { $entry_text = $entries[$i]['entry_text'];
$i_open = "<font color='#{$i_color}'>"; if ($entries[$i]['important'] == 'Y') {
$i_close="</font>"; $i_open = "<font color='#{$i_color}'>";
} $i_close="</font>";
$entries_list .= "<li>{$i_open}{$clean_date} - {$entry_text}{$i_close}</li>"; }
} $entries_list .= "<li>{$i_open}{$clean_date} - {$entry_text}{$i_close}</li>";
}
$pos_break = ""; $pos_break = "";
$pos_align = "text-align: right; position: absolute; right: 0px;"; $pos_align = "text-align: right; position: absolute; right: 0px;";
if($position === "left") { if ($position === "left") {
$pos_break = "<br />"; $pos_break = "<br />";
$pos_align = ""; $pos_align = "";
} }
if(count($entries) === 0) { if (count($entries) === 0) {
$out_text = "No blotter entries yet."; $out_text = "No blotter entries yet.";
$in_text = "Empty."; $in_text = "Empty.";
} } else {
else { $clean_date = date("m/d/y", strtotime($entries[0]['entry_date']));
$clean_date = date("m/d/y", strtotime($entries[0]['entry_date'])); $out_text = "Blotter updated: {$clean_date}";
$out_text = "Blotter updated: {$clean_date}"; $in_text = "<ul>$entries_list</ul>";
$in_text = "<ul>$entries_list</ul>"; }
}
$html = " $html = "
<div id='blotter1' class='shm-blotter1'> <div id='blotter1' class='shm-blotter1'>
<span>$out_text</span> <span>$out_text</span>
{$pos_break} {$pos_break}
@ -173,6 +183,6 @@ class BlotterTheme extends Themelet {
</div> </div>
<div id='blotter2' class='shm-blotter2'>$in_text</div> <div id='blotter2' class='shm-blotter2'>$in_text</div>
"; ";
return $html; return $html;
} }
} }

View File

@ -13,31 +13,34 @@
* engine" notification they have * engine" notification they have
*/ */
class BrowserSearch extends Extension { class BrowserSearch extends Extension
public function onInitExt(InitExtEvent $event) { {
global $config; public function onInitExt(InitExtEvent $event)
$config->set_default_string("search_suggestions_results_order", 'a'); {
} global $config;
$config->set_default_string("search_suggestions_results_order", 'a');
}
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
global $config, $database, $page; {
global $config, $database, $page;
// Add in header code to let the browser know that the search plugin exists // Add in header code to let the browser know that the search plugin exists
// We need to build the data for the header // We need to build the data for the header
$search_title = $config->get_string('title'); $search_title = $config->get_string('title');
$search_file_url = make_link('browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml'); $search_file_url = make_link('browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml');
$page->add_html_header("<link rel='search' type='application/opensearchdescription+xml' title='$search_title' href='$search_file_url'>"); $page->add_html_header("<link rel='search' type='application/opensearchdescription+xml' title='$search_title' href='$search_file_url'>");
// The search.xml file that is generated on the fly // The search.xml file that is generated on the fly
if($event->page_matches("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml")) { if ($event->page_matches("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml")) {
// First, we need to build all the variables we'll need // First, we need to build all the variables we'll need
$search_title = $config->get_string('title'); $search_title = $config->get_string('title');
$search_form_url = make_link('post/list/{searchTerms}'); $search_form_url = make_link('post/list/{searchTerms}');
$suggenton_url = make_link('browser_search/')."{searchTerms}"; $suggenton_url = make_link('browser_search/')."{searchTerms}";
$icon_b64 = base64_encode(file_get_contents("ext/handle_static/static/favicon.ico")); $icon_b64 = base64_encode(file_get_contents("ext/handle_static/static/favicon.ico"));
// Now for the XML // Now for the XML
$xml = " $xml = "
<SearchPlugin xmlns='http://www.mozilla.org/2006/browser/search/' xmlns:os='http://a9.com/-/spec/opensearch/1.1/'> <SearchPlugin xmlns='http://www.mozilla.org/2006/browser/search/' xmlns:os='http://a9.com/-/spec/opensearch/1.1/'>
<os:ShortName>$search_title</os:ShortName> <os:ShortName>$search_title</os:ShortName>
<os:InputEncoding>UTF-8</os:InputEncoding> <os:InputEncoding>UTF-8</os:InputEncoding>
@ -50,55 +53,53 @@ class BrowserSearch extends Extension {
</SearchPlugin> </SearchPlugin>
"; ";
// And now to send it to the browser // And now to send it to the browser
$page->set_mode("data"); $page->set_mode("data");
$page->set_type("text/xml"); $page->set_type("text/xml");
$page->set_data($xml); $page->set_data($xml);
} } elseif (
$event->page_matches("browser_search") &&
!$config->get_bool("disable_search_suggestions")
) {
// We have to build some json stuff
$tag_search = $event->get_arg(0);
else if( // Now to get DB results
$event->page_matches("browser_search") && if ($config->get_string("search_suggestions_results_order") == "a") {
!$config->get_bool("disable_search_suggestions") $tags = $database->execute("SELECT tag FROM tags WHERE tag LIKE ? AND count > 0 ORDER BY tag ASC LIMIT 30", [$tag_search."%"]);
) { } else {
// We have to build some json stuff $tags = $database->execute("SELECT tag FROM tags WHERE tag LIKE ? AND count > 0 ORDER BY count DESC LIMIT 30", [$tag_search."%"]);
$tag_search = $event->get_arg(0); }
// Now to get DB results
if($config->get_string("search_suggestions_results_order") == "a") {
$tags = $database->execute("SELECT tag FROM tags WHERE tag LIKE ? AND count > 0 ORDER BY tag ASC LIMIT 30",array($tag_search."%"));
} else {
$tags = $database->execute("SELECT tag FROM tags WHERE tag LIKE ? AND count > 0 ORDER BY count DESC LIMIT 30",array($tag_search."%"));
}
// And to do stuff with it. We want our output to look like: // And to do stuff with it. We want our output to look like:
// ["shimmie",["shimmies","shimmy","shimmie","21 shimmies","hip shimmies","skea shimmies"],[],[]] // ["shimmie",["shimmies","shimmy","shimmie","21 shimmies","hip shimmies","skea shimmies"],[],[]]
$json_tag_list = ""; $json_tag_list = "";
$tags_array = array(); $tags_array = [];
foreach($tags as $tag) { foreach ($tags as $tag) {
array_push($tags_array,$tag['tag']); array_push($tags_array, $tag['tag']);
} }
$json_tag_list .= implode("\",\"", $tags_array); $json_tag_list .= implode("\",\"", $tags_array);
// And now for the final output // And now for the final output
$json_string = "[\"$tag_search\",[\"$json_tag_list\"],[],[]]"; $json_string = "[\"$tag_search\",[\"$json_tag_list\"],[],[]]";
$page->set_mode("data"); $page->set_mode("data");
$page->set_data($json_string); $page->set_data($json_string);
} }
} }
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event)
$sort_by = array(); {
$sort_by['Alphabetical'] = 'a'; $sort_by = [];
$sort_by['Tag Count'] = 't'; $sort_by['Alphabetical'] = 'a';
$sort_by['Tag Count'] = 't';
$sb = new SetupBlock("Browser Search"); $sb = new SetupBlock("Browser Search");
$sb->add_bool_option("disable_search_suggestions", "Disable search suggestions: "); $sb->add_bool_option("disable_search_suggestions", "Disable search suggestions: ");
$sb->add_label("<br>"); $sb->add_label("<br>");
$sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:"); $sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:");
$event->panel->add_block($sb); $event->panel->add_block($sb);
} }
} }

View File

@ -1,8 +1,9 @@
<?php <?php
class BrowserSearchTest extends ShimmiePHPUnitTestCase { class BrowserSearchTest extends ShimmiePHPUnitTestCase
public function testBasic() { {
$this->get_page("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml"); public function testBasic()
$this->get_page("browser_search/test"); {
} $this->get_page("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml");
$this->get_page("browser_search/test");
}
} }

View File

@ -15,60 +15,67 @@
* <p><b>Note:</b> requires the "admin" extension to be enabled * <p><b>Note:</b> requires the "admin" extension to be enabled
*/ */
class BulkAddEvent extends Event { class BulkAddEvent extends Event
public $dir, $results; {
public $dir;
public $results;
public function __construct(string $dir) { public function __construct(string $dir)
$this->dir = $dir; {
$this->results = array(); $this->dir = $dir;
} $this->results = [];
}
} }
class BulkAdd extends Extension { class BulkAdd extends Extension
public function onPageRequest(PageRequestEvent $event) { {
global $page, $user; public function onPageRequest(PageRequestEvent $event)
if($event->page_matches("bulk_add")) { {
if($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) { global $page, $user;
set_time_limit(0); if ($event->page_matches("bulk_add")) {
$bae = new BulkAddEvent($_POST['dir']); if ($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) {
send_event($bae); set_time_limit(0);
if(is_array($bae->results)) { $bae = new BulkAddEvent($_POST['dir']);
foreach($bae->results as $result) { send_event($bae);
$this->theme->add_status("Adding files", $result); if (is_array($bae->results)) {
} foreach ($bae->results as $result) {
} else if(strlen($bae->results) > 0) { $this->theme->add_status("Adding files", $result);
$this->theme->add_status("Adding files", $bae->results); }
} } elseif (strlen($bae->results) > 0) {
$this->theme->display_upload_results($page); $this->theme->add_status("Adding files", $bae->results);
} }
} $this->theme->display_upload_results($page);
} }
}
}
public function onCommand(CommandEvent $event) { public function onCommand(CommandEvent $event)
if($event->cmd == "help") { {
print "\tbulk-add [directory]\n"; if ($event->cmd == "help") {
print "\t\tImport this directory\n\n"; print "\tbulk-add [directory]\n";
} print "\t\tImport this directory\n\n";
if($event->cmd == "bulk-add") { }
if(count($event->args) == 1) { if ($event->cmd == "bulk-add") {
$bae = new BulkAddEvent($event->args[0]); if (count($event->args) == 1) {
send_event($bae); $bae = new BulkAddEvent($event->args[0]);
print(implode("\n", $bae->results)); send_event($bae);
} print(implode("\n", $bae->results));
} }
} }
}
public function onAdminBuilding(AdminBuildingEvent $event) { public function onAdminBuilding(AdminBuildingEvent $event)
$this->theme->display_admin_block(); {
} $this->theme->display_admin_block();
}
public function onBulkAdd(BulkAddEvent $event) { public function onBulkAdd(BulkAddEvent $event)
if(is_dir($event->dir) && is_readable($event->dir)) { {
$event->results = add_dir($event->dir); if (is_dir($event->dir) && is_readable($event->dir)) {
} $event->results = add_dir($event->dir);
else { } else {
$h_dir = html_escape($event->dir); $h_dir = html_escape($event->dir);
$event->results[] = "Error, $h_dir is not a readable directory"; $event->results[] = "Error, $h_dir is not a readable directory";
} }
} }
} }

View File

@ -1,37 +1,42 @@
<?php <?php
class BulkAddTest extends ShimmiePHPUnitTestCase { class BulkAddTest extends ShimmiePHPUnitTestCase
public function testBulkAdd() { {
$this->log_in_as_admin(); public function testBulkAdd()
{
$this->log_in_as_admin();
$this->get_page('admin'); $this->get_page('admin');
$this->assert_title("Admin Tools"); $this->assert_title("Admin Tools");
$bae = new BulkAddEvent('asdf'); $bae = new BulkAddEvent('asdf');
send_event($bae); send_event($bae);
$this->assertContains("Error, asdf is not a readable directory", $this->assertContains(
$bae->results, implode("\n", $bae->results)); "Error, asdf is not a readable directory",
$bae->results,
implode("\n", $bae->results)
);
// FIXME: have BAE return a list of successes as well as errors? // FIXME: have BAE return a list of successes as well as errors?
$this->markTestIncomplete(); $this->markTestIncomplete();
$this->get_page('admin'); $this->get_page('admin');
$this->assert_title("Admin Tools"); $this->assert_title("Admin Tools");
send_event(new BulkAddEvent('tests')); send_event(new BulkAddEvent('tests'));
# FIXME: test that the output here makes sense, no "adding foo.php ... ok" # FIXME: test that the output here makes sense, no "adding foo.php ... ok"
$this->get_page("post/list/hash=17fc89f372ed3636e28bd25cc7f3bac1/1"); $this->get_page("post/list/hash=17fc89f372ed3636e28bd25cc7f3bac1/1");
$this->assert_title(new PatternExpectation("/^Image \d+: data/")); $this->assert_title(new PatternExpectation("/^Image \d+: data/"));
$this->click("Delete"); $this->click("Delete");
$this->get_page("post/list/hash=feb01bab5698a11dd87416724c7a89e3/1"); $this->get_page("post/list/hash=feb01bab5698a11dd87416724c7a89e3/1");
$this->assert_title(new PatternExpectation("/^Image \d+: data/")); $this->assert_title(new PatternExpectation("/^Image \d+: data/"));
$this->click("Delete"); $this->click("Delete");
$this->get_page("post/list/hash=e106ea2983e1b77f11e00c0c54e53805/1"); $this->get_page("post/list/hash=e106ea2983e1b77f11e00c0c54e53805/1");
$this->assert_title(new PatternExpectation("/^Image \d+: data/")); $this->assert_title(new PatternExpectation("/^Image \d+: data/"));
$this->click("Delete"); $this->click("Delete");
$this->log_out(); $this->log_out();
} }
} }

View File

@ -1,30 +1,33 @@
<?php <?php
class BulkAddTheme extends Themelet { class BulkAddTheme extends Themelet
private $messages = array(); {
private $messages = [];
/* /*
* Show a standard page for results to be put into * Show a standard page for results to be put into
*/ */
public function display_upload_results(Page $page) { public function display_upload_results(Page $page)
$page->set_title("Adding folder"); {
$page->set_heading("Adding folder"); $page->set_title("Adding folder");
$page->add_block(new NavBlock()); $page->set_heading("Adding folder");
$html = ""; $page->add_block(new NavBlock());
foreach($this->messages as $block) { $html = "";
$html .= "<br/>" . $block->body; foreach ($this->messages as $block) {
} $html .= "<br/>" . $block->body;
$page->add_block(new Block("Results", $html)); }
} $page->add_block(new Block("Results", $html));
}
/* /*
* Add a section to the admin page. This should contain a form which * Add a section to the admin page. This should contain a form which
* links to bulk_add with POST[dir] set to the name of a server-side * links to bulk_add with POST[dir] set to the name of a server-side
* directory full of images * directory full of images
*/ */
public function display_admin_block() { public function display_admin_block()
global $page; {
$html = " global $page;
$html = "
Add a folder full of images; any subfolders will have their names Add a folder full of images; any subfolders will have their names
used as tags for the images within. used as tags for the images within.
<br>Note: this is the folder as seen by the server -- you need to <br>Note: this is the folder as seen by the server -- you need to
@ -37,10 +40,11 @@ class BulkAddTheme extends Themelet {
</table> </table>
</form> </form>
"; ";
$page->add_block(new Block("Bulk Add", $html)); $page->add_block(new Block("Bulk Add", $html));
} }
public function add_status($title, $body) { public function add_status($title, $body)
$this->messages[] = new Block($title, $body); {
} $this->messages[] = new Block($title, $body);
}
} }

View File

@ -15,125 +15,129 @@
* normally static (e.g. SWF) will be displayed at the board's max thumbnail size<br><br> * normally static (e.g. SWF) will be displayed at the board's max thumbnail size<br><br>
* Useful for importing tagged images without having to do database manipulation.<br> * Useful for importing tagged images without having to do database manipulation.<br>
* <p><b>Note:</b> requires "Admin Controls" and optionally "Image Ratings" to be enabled<br><br> * <p><b>Note:</b> requires "Admin Controls" and optionally "Image Ratings" to be enabled<br><br>
* *
*/ */
class BulkAddCSV extends Extension { class BulkAddCSV extends Extension
public function onPageRequest(PageRequestEvent $event) { {
global $page, $user; public function onPageRequest(PageRequestEvent $event)
if($event->page_matches("bulk_add_csv")) { {
if($user->is_admin() && $user->check_auth_token() && isset($_POST['csv'])) { global $page, $user;
set_time_limit(0); if ($event->page_matches("bulk_add_csv")) {
$this->add_csv($_POST['csv']); if ($user->is_admin() && $user->check_auth_token() && isset($_POST['csv'])) {
$this->theme->display_upload_results($page); set_time_limit(0);
} $this->add_csv($_POST['csv']);
} $this->theme->display_upload_results($page);
} }
}
}
public function onCommand(CommandEvent $event) { public function onCommand(CommandEvent $event)
if($event->cmd == "help") { {
print " bulk-add-csv [/path/to.csv]\n"; if ($event->cmd == "help") {
print " Import this .csv file (refer to documentation)\n\n"; print " bulk-add-csv [/path/to.csv]\n";
} print " Import this .csv file (refer to documentation)\n\n";
if($event->cmd == "bulk-add-csv") { }
global $user; if ($event->cmd == "bulk-add-csv") {
global $user;
//Nag until CLI is admin by default
if (!$user->is_admin()) { //Nag until CLI is admin by default
print "Not running as an admin, which can cause problems.\n"; if (!$user->is_admin()) {
print "Please add the parameter: -u admin_username"; print "Not running as an admin, which can cause problems.\n";
} elseif(count($event->args) == 1) { print "Please add the parameter: -u admin_username";
$this->add_csv($event->args[0]); } elseif (count($event->args) == 1) {
} $this->add_csv($event->args[0]);
} }
} }
}
public function onAdminBuilding(AdminBuildingEvent $event) { public function onAdminBuilding(AdminBuildingEvent $event)
$this->theme->display_admin_block(); {
} $this->theme->display_admin_block();
}
/** /**
* Generate the necessary DataUploadEvent for a given image and tags. * Generate the necessary DataUploadEvent for a given image and tags.
*/ */
private function add_image(string $tmpname, string $filename, string $tags, string $source, string $rating, string $thumbfile) { private function add_image(string $tmpname, string $filename, string $tags, string $source, string $rating, string $thumbfile)
assert(file_exists($tmpname)); {
assert(file_exists($tmpname));
$pathinfo = pathinfo($filename); $pathinfo = pathinfo($filename);
if(!array_key_exists('extension', $pathinfo)) { if (!array_key_exists('extension', $pathinfo)) {
throw new UploadException("File has no extension"); throw new UploadException("File has no extension");
} }
$metadata = array(); $metadata = [];
$metadata['filename'] = $pathinfo['basename']; $metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension']; $metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = Tag::explode($tags); $metadata['tags'] = Tag::explode($tags);
$metadata['source'] = $source; $metadata['source'] = $source;
$event = new DataUploadEvent($tmpname, $metadata); $event = new DataUploadEvent($tmpname, $metadata);
send_event($event); send_event($event);
if($event->image_id == -1) { if ($event->image_id == -1) {
throw new UploadException("File type not recognised"); throw new UploadException("File type not recognised");
} else { } else {
if(class_exists("RatingSetEvent") && in_array($rating, array("s", "q", "e"))) { if (class_exists("RatingSetEvent") && in_array($rating, ["s", "q", "e"])) {
$ratingevent = new RatingSetEvent(Image::by_id($event->image_id), $rating); $ratingevent = new RatingSetEvent(Image::by_id($event->image_id), $rating);
send_event($ratingevent); send_event($ratingevent);
} }
if (file_exists($thumbfile)) { if (file_exists($thumbfile)) {
copy($thumbfile, warehouse_path("thumbs", $event->hash)); copy($thumbfile, warehouse_path("thumbs", $event->hash));
} }
} }
} }
private function add_csv(string $csvfile) { private function add_csv(string $csvfile)
if(!file_exists($csvfile)) { {
$this->theme->add_status("Error", "$csvfile not found"); if (!file_exists($csvfile)) {
return; $this->theme->add_status("Error", "$csvfile not found");
} return;
if (!is_file($csvfile) || strtolower(substr($csvfile, -4)) != ".csv") { }
$this->theme->add_status("Error", "$csvfile doesn't appear to be a csv file"); if (!is_file($csvfile) || strtolower(substr($csvfile, -4)) != ".csv") {
return; $this->theme->add_status("Error", "$csvfile doesn't appear to be a csv file");
} return;
}
$linenum = 1;
$list = ""; $linenum = 1;
$csvhandle = fopen($csvfile, "r"); $list = "";
$csvhandle = fopen($csvfile, "r");
while (($csvdata = fgetcsv($csvhandle, 0, ",")) !== FALSE) {
if(count($csvdata) != 5) { while (($csvdata = fgetcsv($csvhandle, 0, ",")) !== false) {
if(strlen($list) > 0) { if (count($csvdata) != 5) {
$this->theme->add_status("Error", "<b>Encountered malformed data. Line $linenum $csvfile</b><br>".$list); if (strlen($list) > 0) {
fclose($csvhandle); $this->theme->add_status("Error", "<b>Encountered malformed data. Line $linenum $csvfile</b><br>".$list);
return; fclose($csvhandle);
} else { return;
$this->theme->add_status("Error", "<b>Encountered malformed data. Line $linenum $csvfile</b><br>Check <a href=\"" . make_link("ext_doc/bulk_add_csv") . "\">here</a> for the expected format"); } else {
fclose($csvhandle); $this->theme->add_status("Error", "<b>Encountered malformed data. Line $linenum $csvfile</b><br>Check <a href=\"" . make_link("ext_doc/bulk_add_csv") . "\">here</a> for the expected format");
return; fclose($csvhandle);
} return;
} }
$fullpath = $csvdata[0]; }
$tags = trim($csvdata[1]); $fullpath = $csvdata[0];
$source = $csvdata[2]; $tags = trim($csvdata[1]);
$rating = $csvdata[3]; $source = $csvdata[2];
$thumbfile = $csvdata[4]; $rating = $csvdata[3];
$pathinfo = pathinfo($fullpath); $thumbfile = $csvdata[4];
$shortpath = $pathinfo["basename"]; $pathinfo = pathinfo($fullpath);
$list .= "<br>".html_escape("$shortpath (".str_replace(" ", ", ", $tags).")... "); $shortpath = $pathinfo["basename"];
if (file_exists($csvdata[0]) && is_file($csvdata[0])) { $list .= "<br>".html_escape("$shortpath (".str_replace(" ", ", ", $tags).")... ");
try{ if (file_exists($csvdata[0]) && is_file($csvdata[0])) {
$this->add_image($fullpath, $pathinfo["basename"], $tags, $source, $rating, $thumbfile); try {
$list .= "ok\n"; $this->add_image($fullpath, $pathinfo["basename"], $tags, $source, $rating, $thumbfile);
} $list .= "ok\n";
catch(Exception $ex) { } catch (Exception $ex) {
$list .= "failed:<br>". $ex->getMessage(); $list .= "failed:<br>". $ex->getMessage();
} }
} else { } else {
$list .= "failed:<br> File doesn't exist ".html_escape($csvdata[0]); $list .= "failed:<br> File doesn't exist ".html_escape($csvdata[0]);
} }
$linenum += 1; $linenum += 1;
} }
if(strlen($list) > 0) { if (strlen($list) > 0) {
$this->theme->add_status("Adding $csvfile", $list); $this->theme->add_status("Adding $csvfile", $list);
} }
fclose($csvhandle); fclose($csvhandle);
} }
} }

View File

@ -1,28 +1,31 @@
<?php <?php
class BulkAddCSVTheme extends Themelet { class BulkAddCSVTheme extends Themelet
private $messages = array(); {
private $messages = [];
/* /*
* Show a standard page for results to be put into * Show a standard page for results to be put into
*/ */
public function display_upload_results(Page $page) { public function display_upload_results(Page $page)
$page->set_title("Adding images from csv"); {
$page->set_heading("Adding images from csv"); $page->set_title("Adding images from csv");
$page->add_block(new NavBlock()); $page->set_heading("Adding images from csv");
foreach($this->messages as $block) { $page->add_block(new NavBlock());
$page->add_block($block); foreach ($this->messages as $block) {
} $page->add_block($block);
} }
}
/* /*
* Add a section to the admin page. This should contain a form which * Add a section to the admin page. This should contain a form which
* links to bulk_add_csv with POST[csv] set to the name of a server-side * links to bulk_add_csv with POST[csv] set to the name of a server-side
* csv file * csv file
*/ */
public function display_admin_block() { public function display_admin_block()
global $page; {
$html = " global $page;
$html = "
Add images from a csv. Images will be tagged and have their Add images from a csv. Images will be tagged and have their
source and rating set (if \"Image Ratings\" is enabled) source and rating set (if \"Image Ratings\" is enabled)
<br>Specify the absolute or relative path to a local .csv file. Check <a href=\"" . make_link("ext_doc/bulk_add_csv") . "\">here</a> for the expected format. <br>Specify the absolute or relative path to a local .csv file. Check <a href=\"" . make_link("ext_doc/bulk_add_csv") . "\">here</a> for the expected format.
@ -34,11 +37,11 @@ class BulkAddCSVTheme extends Themelet {
</table> </table>
</form> </form>
"; ";
$page->add_block(new Block("Bulk Add CSV", $html)); $page->add_block(new Block("Bulk Add CSV", $html));
} }
public function add_status($title, $body) { public function add_status($title, $body)
$this->messages[] = new Block($title, $body); {
} $this->messages[] = new Block($title, $body);
}
} }

View File

@ -6,22 +6,28 @@
* License: GPLv2 * License: GPLv2
* Description: Allows admin to delete many images at once through Board Admin. * Description: Allows admin to delete many images at once through Board Admin.
* Documentation: * Documentation:
* *
*/ */
//todo: removal by tag returns 1 less image in test for some reason, actually a combined search doesn't seem to work for shit either //todo: removal by tag returns 1 less image in test for some reason, actually a combined search doesn't seem to work for shit either
class BulkRemove extends Extension { class BulkRemove extends Extension
public function onPageRequest(PageRequestEvent $event) { {
global $user; public function onPageRequest(PageRequestEvent $event)
if($event->page_matches("bulk_remove") && $user->is_admin() && $user->check_auth_token()) { {
if ($event->get_arg(0) == "confirm") $this->do_bulk_remove(); global $user;
else $this->show_confirm(); if ($event->page_matches("bulk_remove") && $user->is_admin() && $user->check_auth_token()) {
} if ($event->get_arg(0) == "confirm") {
} $this->do_bulk_remove();
} else {
$this->show_confirm();
}
}
}
public function onAdminBuilding(AdminBuildingEvent $event) { public function onAdminBuilding(AdminBuildingEvent $event)
global $page; {
$html = "<b>Be extremely careful when using this!</b><br> global $page;
$html = "<b>Be extremely careful when using this!</b><br>
Once an image is removed there is no way to recover it so it is recommended that Once an image is removed there is no way to recover it so it is recommended that
you first take when removing a large amount of images.<br> you first take when removing a large amount of images.<br>
<b>Note:</b> Entering both an ID range and tags will only remove images between the given ID's that have the given tags. <b>Note:</b> Entering both an ID range and tags will only remove images between the given ID's that have the given tags.
@ -40,94 +46,95 @@ class BulkRemove extends Extension {
</table> </table>
</form> </form>
"; ";
$page->add_block(new Block("Bulk Remove", $html)); $page->add_block(new Block("Bulk Remove", $html));
} }
// returns a list of images to be removed // returns a list of images to be removed
private function determine_images() private function determine_images()
{ {
// set vars // set vars
$images_for_removal = array(); $images_for_removal = [];
$error = ""; $error = "";
$min_id = $_POST['remove_id_min']; $min_id = $_POST['remove_id_min'];
$max_id = $_POST['remove_id_max']; $max_id = $_POST['remove_id_max'];
$tags = $_POST['remove_tags']; $tags = $_POST['remove_tags'];
// if using id range to remove (comined removal with tags) // if using id range to remove (comined removal with tags)
if ($min_id != "" && $max_id != "") if ($min_id != "" && $max_id != "") {
{ // error if values are not correctly entered
// error if values are not correctly entered if (!is_numeric($min_id) || !is_numeric($max_id) ||
if (!is_numeric($min_id) || !is_numeric($max_id) || intval($max_id) < intval($min_id)) {
intval($max_id) < intval($min_id)) $error = "Values not correctly entered for removal between id.";
$error = "Values not correctly entered for removal between id."; } else { // if min & max id are valid
else { // if min & max id are valid
// Grab the list of images & place it in the removing array // Grab the list of images & place it in the removing array
foreach (Image::find_images(intval($min_id), intval($max_id)) as $image) foreach (Image::find_images(intval($min_id), intval($max_id)) as $image) {
array_push($images_for_removal, $image); array_push($images_for_removal, $image);
} }
} }
}
// refine previous results or create results from tags // refine previous results or create results from tags
if ($tags != "") if ($tags != "") {
{ $tags_arr = explode(" ", $_POST['remove_tags']);
$tags_arr = explode(" ", $_POST['remove_tags']);
// Search all images with the specified tags & add to list // Search all images with the specified tags & add to list
foreach (Image::find_images(1, 2147483647, $tags_arr) as $image) foreach (Image::find_images(1, 2147483647, $tags_arr) as $image) {
array_push($images_for_removal, $image); array_push($images_for_removal, $image);
} }
// if no images were found with the given info
if (count($images_for_removal) == 0)
$error = "No images selected for removal";
//var_dump($tags_arr);
return array(
"error" => $error,
"images_for_removal" => $images_for_removal);
} }
// if no images were found with the given info
if (count($images_for_removal) == 0) {
$error = "No images selected for removal";
}
//var_dump($tags_arr);
return [
"error" => $error,
"images_for_removal" => $images_for_removal];
}
// displays confirmation to admin before removal // displays confirmation to admin before removal
private function show_confirm() private function show_confirm()
{ {
global $page; global $page;
// set vars // set vars
$determined_imgs = $this->determine_images(); $determined_imgs = $this->determine_images();
$error = $determined_imgs["error"]; $error = $determined_imgs["error"];
$images_for_removal = $determined_imgs["images_for_removal"]; $images_for_removal = $determined_imgs["images_for_removal"];
// if there was an error in determine_images() // if there was an error in determine_images()
if ($error != "") { if ($error != "") {
$page->add_block(new Block("Cannot remove images", $error)); $page->add_block(new Block("Cannot remove images", $error));
return; return;
} }
// generates the image array & places it in $_POST["bulk_remove_images"] // generates the image array & places it in $_POST["bulk_remove_images"]
$_POST["bulk_remove_images"] = $images_for_removal; $_POST["bulk_remove_images"] = $images_for_removal;
// Display confirmation message // Display confirmation message
$html = make_form(make_link("bulk_remove")). $html = make_form(make_link("bulk_remove")).
"Are you sure you want to PERMANENTLY remove ". "Are you sure you want to PERMANENTLY remove ".
count($images_for_removal) ." images?<br></form>"; count($images_for_removal) ." images?<br></form>";
$page->add_block(new Block("Confirm Removal", $html)); $page->add_block(new Block("Confirm Removal", $html));
} }
private function do_bulk_remove() private function do_bulk_remove()
{ {
global $page; global $page;
// display error if user didn't go through admin board // display error if user didn't go through admin board
if (!isset($_POST["bulk_remove_images"])) { if (!isset($_POST["bulk_remove_images"])) {
$page->add_block(new Block("Bulk Remove Error", $page->add_block(new Block(
"Please use Board Admin to use bulk remove.")); "Bulk Remove Error",
} "Please use Board Admin to use bulk remove."
));
//
$image_arr = $_POST["bulk_remove_images"];
} }
//
$image_arr = $_POST["bulk_remove_images"];
}
} }

View File

@ -8,185 +8,211 @@ include '../php/functions.php';
include '../php/yshout.class.php'; include '../php/yshout.class.php';
include '../php/ajaxcall.class.php'; include '../php/ajaxcall.class.php';
if (isset($_POST['mode'])) if (isset($_POST['mode'])) {
switch($_POST['mode']) { switch ($_POST['mode']) {
case 'login': case 'login':
doLogin(); doLogin();
break; break;
case 'logout': case 'logout':
doLogout(); doLogout();
break; break;
case 'unban': case 'unban':
doUnban(); doUnban();
break; break;
case 'unbanall': case 'unbanall':
doUnbanAll(); doUnbanAll();
break; break;
case 'setpreference': case 'setpreference':
doSetPreference(); doSetPreference();
break; break;
case 'resetpreferences': case 'resetpreferences':
doResetPreferences(); doResetPreferences();
break; break;
} }
function doLogin() {
global $kioskMode;
if ($kioskMode) {
logout();
$result = array(
'error' => false,
'html' => cp()
);
echo json_encode($result);
return;
}
login(md5($_POST['password']));
$result = array();
if (loggedIn()) {
$result['error'] = false;
$result['html'] = cp();
} else
$result['error'] = 'invalid';
echo json_encode($result);
} }
function doLogout() { function doLogin()
logout(); {
global $kioskMode;
if ($kioskMode) {
logout();
$result = [
'error' => false,
'html' => cp()
];
echo json_encode($result);
return;
}
login(md5($_POST['password']));
$result = [];
if (loggedIn()) {
$result['error'] = false;
$result['html'] = cp();
} else {
$result['error'] = 'invalid';
}
$result = array( echo json_encode($result);
'error' => false
);
echo json_encode($result);
} }
function doUnban() { function doLogout()
global $kioskMode; {
logout();
if ($kioskMode) {
$result = array(
'error' => false
);
echo json_encode($result);
return;
}
if (!loggedIn()) return;
$ys = ys(); $result = [
$result = array(); 'error' => false
];
$ip = $_POST['ip']; echo json_encode($result);
if ($ys->banned($ip)) {
$ys->unban($ip);
$result['error'] = false;
} else
$result['error'] = 'notbanned';
echo json_encode($result);
} }
function doUnbanAll() { function doUnban()
global $kioskMode; {
global $kioskMode;
if ($kioskMode) {
$result = array( if ($kioskMode) {
'error' => false $result = [
); 'error' => false
];
echo json_encode($result);
return; echo json_encode($result);
} return;
}
if (!loggedIn()) return;
if (!loggedIn()) {
return;
}
$ys = ys(); $ys = ys();
$ys->unbanAll(); $result = [];
$result = array( $ip = $_POST['ip'];
'error' => false
);
echo json_encode($result); if ($ys->banned($ip)) {
$ys->unban($ip);
$result['error'] = false;
} else {
$result['error'] = 'notbanned';
}
echo json_encode($result);
}
function doUnbanAll()
{
global $kioskMode;
if ($kioskMode) {
$result = [
'error' => false
];
echo json_encode($result);
return;
}
if (!loggedIn()) {
return;
}
$ys = ys();
$ys->unbanAll();
$result = [
'error' => false
];
echo json_encode($result);
} }
function doSetPreference() { function doSetPreference()
global $prefs, $kioskMode; {
global $prefs, $kioskMode;
if ($kioskMode) {
$result = array( if ($kioskMode) {
'error' => false $result = [
); 'error' => false
];
echo json_encode($result);
return; echo json_encode($result);
} return;
}
if (!loggedIn()) return;
if (!loggedIn()) {
return;
}
$pref = $_POST['preference']; $pref = $_POST['preference'];
$value = magic($_POST['value']); $value = magic($_POST['value']);
if ($value === 'true') $value = true; if ($value === 'true') {
if ($value === 'false') $value = false; $value = true;
}
if ($value === 'false') {
$value = false;
}
$prefs[$pref] = $value; $prefs[$pref] = $value;
savePrefs($prefs); savePrefs($prefs);
if ($pref == 'password') login(md5($value)); if ($pref == 'password') {
login(md5($value));
}
$result = array( $result = [
'error' => false 'error' => false
); ];
echo json_encode($result); echo json_encode($result);
} }
function doResetPreferences() { function doResetPreferences()
global $prefs, $kioskMode; {
global $prefs, $kioskMode;
if ($kioskMode) {
$result = array( if ($kioskMode) {
'error' => false $result = [
); 'error' => false
];
echo json_encode($result);
return; echo json_encode($result);
} return;
}
if (!loggedIn()) return;
if (!loggedIn()) {
return;
}
resetPrefs(); resetPrefs();
login(md5($prefs['password'])); login(md5($prefs['password']));
// $prefs['password'] = 'lol no'; // $prefs['password'] = 'lol no';
$result = array( $result = [
'error' => false, 'error' => false,
'prefs' => $prefs 'prefs' => $prefs
); ];
echo json_encode($result); echo json_encode($result);
} }
/* CP Display */ /* CP Display */
function cp() { function cp()
global $kioskMode; {
global $kioskMode;
if (!loggedIn() && !$kioskMode) return 'You\'re not logged in!';
if (!loggedIn() && !$kioskMode) {
return 'You\'re not logged in!';
}
return ' return '
<div class="section" id="preferences"> <div class="section" id="preferences">
<span style="display: none;" id="cp-loaded">true</span> <span style="display: none;" id="cp-loaded">true</span>
@ -236,38 +262,41 @@ function cp() {
</div>'; </div>';
} }
function bansList() { function bansList()
global $kioskMode; {
global $kioskMode;
$ys = ys();
$bans = $ys->bans(); $ys = ys();
$bans = $ys->bans();
$html = '<ul id="bans-list">'; $html = '<ul id="bans-list">';
$hasBans = false; $hasBans = false;
foreach($bans as $ban) { foreach ($bans as $ban) {
$hasBans = true; $hasBans = true;
$html .= ' $html .= '
<li> <li>
<span class="nickname">' . $ban['nickname']. '</span> <span class="nickname">' . $ban['nickname']. '</span>
(<span class="ip">' . ($kioskMode ? '[No IP in Kiosk Mode]' : $ban['ip']) . '</span>) (<span class="ip">' . ($kioskMode ? '[No IP in Kiosk Mode]' : $ban['ip']) . '</span>)
<a title="Unban" class="unban-link" href="#" rel="' . $ban['timestamp'] . '">Unban</a> <a title="Unban" class="unban-link" href="#" rel="' . $ban['timestamp'] . '">Unban</a>
</li> </li>
'; ';
} }
if (!$hasBans) if (!$hasBans) {
$html = '<p id="no-bans">No one is banned.</p>'; $html = '<p id="no-bans">No one is banned.</p>';
else } else {
$html .= '</ul>'; $html .= '</ul>';
}
return $html; return $html;
} }
function preferencesForm() { function preferencesForm()
global $prefs, $kioskMode; {
global $prefs, $kioskMode;
return ' return '
<form id="preferences-form"> <form id="preferences-form">
<div id="cp-pane-administration" class="cp-pane"> <div id="cp-pane-administration" class="cp-pane">
<fieldset id="prefs-cat-cp"> <fieldset id="prefs-cat-cp">
@ -434,10 +463,11 @@ function preferencesForm() {
'; ';
} }
function about() { function about()
global $prefs; {
global $prefs;
$html = ' $html = '
<div id="cp-pane-about" class="cp-pane"> <div id="cp-pane-about" class="cp-pane">
<h2>About YShout</h2> <h2>About YShout</h2>
<p>YShout was created and developed by Yuri Vishnevsky. Version 5 is the first one with an about page, so you\'ll have to excuse the lack of appropriate information &mdash; I\'m not quite sure what it is that goes on "About" pages anyway.</p> <p>YShout was created and developed by Yuri Vishnevsky. Version 5 is the first one with an about page, so you\'ll have to excuse the lack of appropriate information &mdash; I\'m not quite sure what it is that goes on "About" pages anyway.</p>
@ -451,7 +481,6 @@ function about() {
</div> </div>
'; ';
return $html; return $html;
} }

View File

@ -1,5 +1,5 @@
<?php <?php
include 'ajax.php'; include 'ajax.php';
?> ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
@ -35,8 +35,10 @@
</form> </form>
</div> </div>
<?php <?php
if (loggedIn()) echo cp(); if (loggedIn()) {
?> echo cp();
}
?>
</div> </div>
</div> </div>
</body> </body>

View File

@ -1,92 +1,90 @@
<?php <?php
error_reporting(E_ALL); error_reporting(E_ALL);
include '../php/filestorage.class.php'; include '../php/filestorage.class.php';
include '../preferences.php'; include '../preferences.php';
include '../php/functions.php'; include '../php/functions.php';
include '../php/yshout.class.php'; include '../php/yshout.class.php';
$html = '<div id="history-posts">'; $html = '<div id="history-posts">';
$admin = loggedIn(); $admin = loggedIn();
$log = 1; $log = 1;
if (isset($_GET['log'])) if (isset($_GET['log'])) {
{ $log = $_GET['log'];
$log = $_GET['log']; }
}
if (isset($_POST['log'])) {
if (isset($_POST['log'])) $log = $_POST['log'];
{ }
$log = $_POST['log'];
}
if (filter_var($log, FILTER_VALIDATE_INT) === false) if (filter_var($log, FILTER_VALIDATE_INT) === false) {
{ $log = 1;
$log = 1; }
}
$ys = ys($log);
$ys = ys($log); $posts = $ys->posts();
$posts = $ys->posts();
if (sizeof($posts) === 0) if (sizeof($posts) === 0) {
$html .= ' $html .= '
<div id="ys-post-1" class="ys-post ys-first ys-admin-post"> <div id="ys-post-1" class="ys-post ys-first ys-admin-post">
<span class="ys-post-timestamp">13:37</span> <span class="ys-post-timestamp">13:37</span>
<span class="ys-post-nickname">Yurivish:<span> <span class="ys-post-nickname">Yurivish:<span>
<span class="ys-post-message">Hey, there aren\'t any posts in this log.</span> <span class="ys-post-message">Hey, there aren\'t any posts in this log.</span>
</div> </div>
'; ';
}
$id = 0; $id = 0;
foreach($posts as $post) { foreach ($posts as $post) {
$id++; $id++;
$banned = $ys->banned($post['adminInfo']['ip']); $banned = $ys->banned($post['adminInfo']['ip']);
$html .= '<div ' . ($admin ? 'rel="' . $post['adminInfo']['ip'] . '" ' : '') . 'id="ys-post-' . $id . '" class="ys-post' . ($post['admin'] ? ' ys-admin-post' : '') . ($banned ? ' ys-banned-post' : '') . '">' . "\n"; $html .= '<div ' . ($admin ? 'rel="' . $post['adminInfo']['ip'] . '" ' : '') . 'id="ys-post-' . $id . '" class="ys-post' . ($post['admin'] ? ' ys-admin-post' : '') . ($banned ? ' ys-banned-post' : '') . '">' . "\n";
$ts = ''; $ts = '';
switch($prefs['timestamp']) { switch ($prefs['timestamp']) {
case 12: case 12:
$ts = date('h:i', $post['timestamp']); $ts = date('h:i', $post['timestamp']);
break; break;
case 24: case 24:
$ts = date('H:i', $post['timestamp']); $ts = date('H:i', $post['timestamp']);
break; break;
case 0: case 0:
$ts = ''; $ts = '';
break; break;
} }
$html .= ' <span class="ys-post-timestamp">' . $ts . '</span> ' . "\n"; $html .= ' <span class="ys-post-timestamp">' . $ts . '</span> ' . "\n";
$html .= ' <span class="ys-post-nickname">' . $post['nickname'] . '</span>' . $prefs['nicknameSeparator'] . ' ' . "\n"; $html .= ' <span class="ys-post-nickname">' . $post['nickname'] . '</span>' . $prefs['nicknameSeparator'] . ' ' . "\n";
$html .= ' <span class="ys-post-message">' . $post['message'] . '</span>' . "\n"; $html .= ' <span class="ys-post-message">' . $post['message'] . '</span>' . "\n";
$html .= ' <span class="ys-post-info' . ($prefs['info'] == 'overlay' ? ' ys-info-overlay' : ' ys-info-inline') . '">' . ($admin ? '<em>IP:</em> ' . $post['adminInfo']['ip'] . ', ' : '') . '<em>Posted:</em> ' . date('l M. j, Y \a\t ' . ($prefs['timestamp'] > 12 ? 'G:i' : 'g:i')) .'.</span>' . "\n"; $html .= ' <span class="ys-post-info' . ($prefs['info'] == 'overlay' ? ' ys-info-overlay' : ' ys-info-inline') . '">' . ($admin ? '<em>IP:</em> ' . $post['adminInfo']['ip'] . ', ' : '') . '<em>Posted:</em> ' . date('l M. j, Y \a\t ' . ($prefs['timestamp'] > 12 ? 'G:i' : 'g:i')) .'.</span>' . "\n";
$html .= ' <span class="ys-post-actions">' . "\n"; $html .= ' <span class="ys-post-actions">' . "\n";
$html .= ' <a title="Show post information" class="ys-info-link" href="#">Info</a>' . ($admin ? ' | <a title="Delete post" class="ys-delete-link" href="#">Delete</a> | ' . ($banned ? '<a title="Unban ' . $post['nickname'] . '" class="ys-ban-link" href="#">Unban</a>' : '<a title="Ban ' . $post['nickname'] . '" class="ys-ban-link" href="#">Ban</a>') : '') . "\n"; $html .= ' <a title="Show post information" class="ys-info-link" href="#">Info</a>' . ($admin ? ' | <a title="Delete post" class="ys-delete-link" href="#">Delete</a> | ' . ($banned ? '<a title="Unban ' . $post['nickname'] . '" class="ys-ban-link" href="#">Unban</a>' : '<a title="Ban ' . $post['nickname'] . '" class="ys-ban-link" href="#">Ban</a>') : '') . "\n";
$html .= ' </span>' . "\n"; $html .= ' </span>' . "\n";
if ($admin) { if ($admin) {
$html .= '<div class="ys-history" style="display: none;">'; $html .= '<div class="ys-history" style="display: none;">';
$html .= ' <span class="ys-h-ip">' . $post['adminInfo']['ip'] . '</span>'; $html .= ' <span class="ys-h-ip">' . $post['adminInfo']['ip'] . '</span>';
$html .= ' <span class="ys-h-nickname">' . $post['nickname'] . '</span>'; $html .= ' <span class="ys-h-nickname">' . $post['nickname'] . '</span>';
$html .= ' <span class="ys-h-uid">' . $post['uid'] . '</span>'; $html .= ' <span class="ys-h-uid">' . $post['uid'] . '</span>';
$html .= '</div>'; $html .= '</div>';
} }
$html .= '</div>' . "\n"; $html .= '</div>' . "\n";
} }
$html .= '</div>' . "\n"; $html .= '</div>' . "\n";
if (isset($_POST['p'])) { if (isset($_POST['p'])) {
echo $html; echo $html;
exit; exit;
} }
?> ?>
@ -115,16 +113,17 @@ if (isset($_POST['p'])) {
<div id="top"> <div id="top">
<h1>YShout.History</h1> <h1>YShout.History</h1>
<div id="controls"> <div id="controls">
<?php if($admin) : ?> <?php if ($admin) : ?>
<a id="clear-log" href="#">Clear this log</a>, or <a id="clear-log" href="#">Clear this log</a>, or
<a id="clear-logs" href="#">Clear all logs</a>. <a id="clear-logs" href="#">Clear all logs</a>.
<?php endif; ?> <?php endif; ?>
<select id="log"> <select id="log">
<?php <?php
for ($i = 1; $i <= $prefs['logs']; $i++) for ($i = 1; $i <= $prefs['logs']; $i++) {
echo '<option' . ($log == $i ? ' selected' : '') . ' rel="' . $i . '">Log ' . $i . '</option>' . "\n"; echo '<option' . ($log == $i ? ' selected' : '') . ' rel="' . $i . '">Log ' . $i . '</option>' . "\n";
?> }
?>
</select> </select>
</div> </div>
</div> </div>

View File

@ -1,8 +1,7 @@
<?php <?php
$null = null; $null = null;
include 'php/filestorage.class.php'; include 'php/filestorage.class.php';
include 'preferences.php'; include 'preferences.php';
include 'php/functions.php'; include 'php/functions.php';
include 'php/yshout.class.php'; include 'php/yshout.class.php';
include 'php/ajaxcall.class.php'; include 'php/ajaxcall.class.php';

View File

@ -8,14 +8,16 @@
* Documentation: * Documentation:
* This chatbox uses YShout 5 as core. * This chatbox uses YShout 5 as core.
*/ */
class Chatbox extends Extension { class Chatbox extends Extension
public function onPageRequest(PageRequestEvent $event) { {
global $page, $user; public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
// Adds header to enable chatbox // Adds header to enable chatbox
$root = get_base_href(); $root = get_base_href();
$yPath = make_http( $root . "/ext/chatbox/"); $yPath = make_http($root . "/ext/chatbox/");
$page->add_html_header(" $page->add_html_header("
<script src=\"http://code.jquery.com/jquery-migrate-1.2.1.js\" type=\"text/javascript\"></script> <script src=\"http://code.jquery.com/jquery-migrate-1.2.1.js\" type=\"text/javascript\"></script>
<script src=\"$root/ext/chatbox/js/yshout.js\" type=\"text/javascript\"></script> <script src=\"$root/ext/chatbox/js/yshout.js\" type=\"text/javascript\"></script>
@ -27,10 +29,10 @@ class Chatbox extends Extension {
</script> </script>
", 500); ", 500);
// loads the chatbox at the set location // loads the chatbox at the set location
$html = "<div id=\"yshout\"></div>"; $html = "<div id=\"yshout\"></div>";
$chatblock = new Block("Chatbox", $html, "main", 97); $chatblock = new Block("Chatbox", $html, "main", 97);
$chatblock->is_content = false; $chatblock->is_content = false;
$page->add_block($chatblock); $page->add_block($chatblock);
} }
} }

View File

@ -1,284 +1,314 @@
<?php <?php
class AjaxCall { class AjaxCall
{
public $reqType;
public $updates;
public $reqType; public function AjaxCall($log = null)
public $updates; {
header('Content-type: application/json');
session_start();
if (isset($log)) {
$_SESSION['yLog'] = $log;
}
$this->reqType = $_POST['reqType'];
}
function AjaxCall($log = null) { public function process()
header('Content-type: application/json'); {
session_start(); switch ($this->reqType) {
case 'init':
if (isset($log)) $_SESSION['yLog'] = $log;
$this->reqType = $_POST['reqType'];
}
function process() { $this->initSession();
switch($this->reqType) { $this->sendFirstUpdates();
case 'init': break;
$this->initSession(); case 'post':
$this->sendFirstUpdates(); $nickname = $_POST['nickname'];
break; $message = $_POST['message'];
cookie('yNickname', $nickname);
$ys = ys($_SESSION['yLog']);
if ($ys->banned(ip())) {
$this->sendBanned();
break;
}
if ($post = $ys->post($nickname, $message)) {
// To use $post somewheres later
$this->sendUpdates();
}
break;
case 'post': case 'refresh':
$nickname = $_POST['nickname']; $ys = ys($_SESSION['yLog']);
$message = $_POST['message']; if ($ys->banned(ip())) {
cookie('yNickname', $nickname); $this->sendBanned();
$ys = ys($_SESSION['yLog']); break;
}
if ($ys->banned(ip())) { $this->sendBanned(); break; }
if ($post = $ys->post($nickname, $message)) { $this->sendUpdates();
// To use $post somewheres later break;
$this->sendUpdates();
}
break;
case 'refresh': case 'reload':
$ys = ys($_SESSION['yLog']); $this->reload();
if ($ys->banned(ip())) { $this->sendBanned(); break; } break;
$this->sendUpdates(); case 'ban':
break; $this->doBan();
break;
case 'reload': case 'unban':
$this->reload(); $this->doUnban();
break; break;
case 'ban': case 'delete':
$this->doBan(); $this->doDelete();
break; break;
case 'unban': case 'banself':
$this->doUnban(); $this->banSelf();
break; break;
case 'delete':
$this->doDelete();
break;
case 'banself': case 'unbanself':
$this->banSelf(); $this->unbanSelf();
break; break;
case 'unbanself': case 'clearlog':
$this->unbanSelf(); $this->clearLog();
break; break;
case 'clearlogs':
$this->clearLogs();
break;
}
}
case 'clearlog': public function doBan()
$this->clearLog(); {
break; $ip = $_POST['ip'];
$nickname = $_POST['nickname'];
case 'clearlogs': $send = [];
$this->clearLogs(); $ys = ys($_SESSION['yLog']);
break;
}
}
function doBan() { switch (true) {
$ip = $_POST['ip']; case !loggedIn():
$nickname = $_POST['nickname']; $send['error'] = 'admin';
$send = array(); break;
$ys = ys($_SESSION['yLog']); case $ys->banned($ip):
$send['error'] = 'already';
break;
default:
$ys->ban($ip, $nickname);
if ($ip == ip()) {
$send['bannedSelf'] = true;
}
$send['error'] = false;
}
switch(true) { echo json_encode($send);
case !loggedIn(): }
$send['error'] = 'admin';
break;
case $ys->banned($ip):
$send['error'] = 'already';
break;
default:
$ys->ban($ip, $nickname);
if ($ip == ip())
$send['bannedSelf'] = true;
$send['error'] = false;
}
echo json_encode($send); public function doUnban()
} {
$ip = $_POST['ip'];
$send = [];
$ys = ys($_SESSION['yLog']);
function doUnban() { switch (true) {
$ip = $_POST['ip']; case !loggedIn():
$send = array(); $send['error'] = 'admin';
$ys = ys($_SESSION['yLog']); break;
case !$ys->banned($ip):
$send['error'] = 'already';
break;
default:
$ys->unban($ip);
$send['error'] = false;
}
switch(true) { echo json_encode($send);
case !loggedIn(): }
$send['error'] = 'admin';
break;
case !$ys->banned($ip):
$send['error'] = 'already';
break;
default:
$ys->unban($ip);
$send['error'] = false;
}
echo json_encode($send); public function doDelete()
} {
$uid = $_POST['uid'];
$send = [];
$ys = ys($_SESSION['yLog']);
function doDelete() { switch (true) {
$uid = $_POST['uid']; case !loggedIn():
$send = array(); $send['error'] = 'admin';
$ys = ys($_SESSION['yLog']); break;
default:
$ys->delete($uid);
$send['error'] = false;
}
switch(true) { echo json_encode($send);
case !loggedIn(): }
$send['error'] = 'admin';
break;
default:
$ys->delete($uid);
$send['error'] = false;
}
echo json_encode($send); public function banSelf()
} {
$ys = ys($_SESSION['yLog']);
$nickname = $_POST['nickname'];
$ys->ban(ip(), $nickname);
function banSelf() { $send = [];
$ys = ys($_SESSION['yLog']); $send['error'] = false;
$nickname = $_POST['nickname'];
$ys->ban(ip(), $nickname); echo json_encode($send);
}
$send = array(); public function unbanSelf()
$send['error'] = false; {
if (loggedIn()) {
echo json_encode($send); $ys = ys($_SESSION['yLog']);
} $ys->unban(ip());
$send = [];
$send['error'] = false;
} else {
$send = [];
$send['error'] = 'admin';
}
echo json_encode($send);
}
public function reload()
{
global $prefs;
$ys = ys($_SESSION['yLog']);
function unbanSelf() { $posts = $ys->latestPosts($prefs['truncate']);
if (loggedIn()) { $this->setSessTimestamp($posts);
$ys = ys($_SESSION['yLog']); $this->updates['posts'] = $posts;
$ys->unban(ip()); echo json_encode($this->updates);
}
$send = array();
$send['error'] = false;
} else {
$send = array();
$send['error'] = 'admin';
}
echo json_encode($send);
}
function reload() {
global $prefs;
$ys = ys($_SESSION['yLog']);
$posts = $ys->latestPosts($prefs['truncate']); public function initSession()
$this->setSessTimestamp($posts); {
$this->updates['posts'] = $posts; $_SESSION['yLatestTimestamp'] = 0;
echo json_encode($this->updates); $_SESSION['yYPath'] = $_POST['yPath'];
} $_SESSION['yLog'] = $_POST['log'];
$loginHash = cookieGet('yLoginHash') ;
if (isset($loginHash) && $loginHash != '') {
login($loginHash);
}
}
function initSession() { public function sendBanned()
$_SESSION['yLatestTimestamp'] = 0; {
$_SESSION['yYPath'] = $_POST['yPath']; $this->updates = [
$_SESSION['yLog'] = $_POST['log']; 'banned' => true
$loginHash = cookieGet('yLoginHash') ; ];
if (isset($loginHash) && $loginHash != '') {
login($loginHash);
}
}
function sendBanned() { echo json_encode($this->updates);
$this->updates = array( }
'banned' => true
); public function sendUpdates()
{
global $prefs;
$ys = ys($_SESSION['yLog']);
if (!$ys->hasPostsAfter($_SESSION['yLatestTimestamp'])) {
return;
}
echo json_encode($this->updates); $posts = $ys->postsAfter($_SESSION['yLatestTimestamp']);
} $this->setSessTimestamp($posts);
function sendUpdates() {
global $prefs;
$ys = ys($_SESSION['yLog']);
if (!$ys->hasPostsAfter($_SESSION['yLatestTimestamp'])) return;
$posts = $ys->postsAfter($_SESSION['yLatestTimestamp']); $this->updates['posts'] = $posts;
$this->setSessTimestamp($posts);
$this->updates['posts'] = $posts; echo json_encode($this->updates);
}
echo json_encode($this->updates); public function setSessTimestamp(&$posts)
} {
if (!$posts) {
return;
}
function setSessTimestamp(&$posts) { $latest = array_slice($posts, -1, 1);
if (!$posts) return; $_SESSION['yLatestTimestamp'] = $latest[0]['timestamp'];
}
$latest = array_slice( $posts, -1, 1); public function sendFirstUpdates()
$_SESSION['yLatestTimestamp'] = $latest[0]['timestamp']; {
} global $prefs, $overrideNickname;
function sendFirstUpdates() { $this->updates = [];
global $prefs, $overrideNickname;
$this->updates = array(); $ys = ys($_SESSION['yLog']);
$ys = ys($_SESSION['yLog']); $posts = $ys->latestPosts($prefs['truncate']);
$this->setSessTimestamp($posts);
$posts = $ys->latestPosts($prefs['truncate']); $this->updates['posts'] = $posts;
$this->setSessTimestamp($posts); $this->updates['prefs'] = $this->cleanPrefs($prefs);
$this->updates['posts'] = $posts; if ($nickname = cookieGet('yNickname')) {
$this->updates['prefs'] = $this->cleanPrefs($prefs); $this->updates['nickname'] = $nickname;
}
if ($overrideNickname) {
$this->updates['nickname'] = $overrideNickname;
}
if ($ys->banned(ip())) {
$this->updates['banned'] = true;
}
if ($nickname = cookieGet('yNickname')) echo json_encode($this->updates);
$this->updates['nickname'] = $nickname; }
if ($overrideNickname) public function cleanPrefs($prefs)
$this->updates['nickname'] = $overrideNickname; {
unset($prefs['password']);
if ($ys->banned(ip())) return $prefs;
$this->updates['banned'] = true; }
public function clearLog()
{
//$log = $_POST['log'];
$send = [];
$ys = ys($_SESSION['yLog']);
echo json_encode($this->updates); switch (true) {
} case !loggedIn():
$send['error'] = 'admin';
function cleanPrefs($prefs) { break;
unset($prefs['password']); default:
return $prefs; $ys->clear();
} $send['error'] = false;
}
function clearLog() {
//$log = $_POST['log'];
$send = array();
$ys = ys($_SESSION['yLog']);
switch(true) { echo json_encode($send);
case !loggedIn(): }
$send['error'] = 'admin';
break; public function clearLogs()
default: {
$ys->clear(); global $prefs;
$send['error'] = false;
} //$log = $_POST['log'];
$send = [];
echo json_encode($send); //$ys = ys($_SESSION['yLog']);
}
function clearLogs() {
global $prefs;
//$log = $_POST['log'];
$send = array();
//$ys = ys($_SESSION['yLog']);
switch(true) {
case !loggedIn():
$send['error'] = 'admin';
break;
default:
for ($i = 1; $i <= $prefs['logs']; $i++) {
$ys = ys($i);
$ys->clear();
}
$send['error'] = false;
}
echo json_encode($send);
}
}
switch (true) {
case !loggedIn():
$send['error'] = 'admin';
break;
default:
for ($i = 1; $i <= $prefs['logs']; $i++) {
$ys = ys($i);
$ys->clear();
}
$send['error'] = false;
}
echo json_encode($send);
}
}

View File

@ -1,84 +1,106 @@
<?php <?php
class FileStorage { class FileStorage
{
public $shoutLog;
public $path;
public $handle;
public $shoutLog, $path, $handle; public function FileStorage($path, $shoutLog = false)
{
$this->shoutLog = $shoutLog;
$folder = 'logs';
if (!is_dir($folder)) {
$folder = '../' . $folder;
}
if (!is_dir($folder)) {
$folder = '../' . $folder;
}
$this->path = $folder . '/' . $path . '.txt';
}
public function open($lock = false)
{
$this->handle = fopen($this->path, 'a+');
function FileStorage($path, $shoutLog = false) { if ($lock) {
$this->shoutLog = $shoutLog; $this->lock();
$folder = 'logs'; return $this->load();
if (!is_dir($folder)) $folder = '../' . $folder; }
if (!is_dir($folder)) $folder = '../' . $folder; }
$this->path = $folder . '/' . $path . '.txt';
}
function open($lock = false) {
$this->handle = fopen($this->path, 'a+');
if ($lock) { public function close(&$array)
$this->lock(); {
return $this->load(); if (isset($array)) {
} $this->save($array);
} }
$this->unlock();
fclose($this->handle);
unset($this->handle);
}
function close(&$array) { public function load()
if (isset($array)) {
$this->save($array); if (($contents = $this->read($this->path)) == null) {
return $this->resetArray();
$this->unlock(); }
fclose($this->handle);
unset($this->handle);
}
function load() { return unserialize($contents);
if (($contents = $this->read($this->path)) == null) }
return $this->resetArray();
return unserialize($contents); public function save(&$array, $unlock = true)
} {
$contents = serialize($array);
$this->write($contents);
if ($unlock) {
$this->unlock();
}
}
function save(&$array, $unlock = true) { public function unlock()
$contents = serialize($array); {
$this->write($contents); if (isset($this->handle)) {
if ($unlock) $this->unlock(); flock($this->handle, LOCK_UN);
} }
}
public function lock()
{
if (isset($this->handle)) {
flock($this->handle, LOCK_EX);
}
}
function unlock() { public function read()
if (isset($this->handle)) {
flock($this->handle, LOCK_UN); fseek($this->handle, 0);
} //return stream_get_contents($this->handle);
return file_get_contents($this->path);
function lock() { }
if (isset($this->handle))
flock($this->handle, LOCK_EX);
}
function read() { public function write($contents)
fseek($this->handle, 0); {
//return stream_get_contents($this->handle); ftruncate($this->handle, 0);
return file_get_contents($this->path); fwrite($this->handle, $contents);
} }
function write($contents) { public function resetArray()
ftruncate($this->handle, 0); {
fwrite($this->handle, $contents); if ($this->shoutLog) {
} $default = [
'info' => [
'latestTimestamp' => -1
],
'posts' => []
];
} else {
$default = [];
}
function resetArray() { $this->save($default, false);
if ($this->shoutLog) return $default;
$default = array( }
'info' => array(
'latestTimestamp' => -1
),
'posts' => array()
);
else
$default = array();
$this->save($default, false);
return $default;
}
} }

View File

@ -1,141 +1,174 @@
<?php <?php
function cookie($name, $data) { function cookie($name, $data)
return setcookie($name, $data, time() + 60 * 60 * 24 * 30, '/'); {
} return setcookie($name, $data, time() + 60 * 60 * 24 * 30, '/');
}
function cookieGet($name, $default = null) { function cookieGet($name, $default = null)
if (isset($_COOKIE[$name])) {
return $_COOKIE[$name]; if (isset($_COOKIE[$name])) {
else return $_COOKIE[$name];
return $default; } else {
} return $default;
}
}
function cookieClear($name) { function cookieClear($name)
setcookie ($name, false, time() - 42); {
} setcookie($name, false, time() - 42);
}
function getVar($name) { function getVar($name)
if (isset($_POST[$name])) { return $_POST[$name]; } {
if (isset($_GET[$name])) { return $_GET[$name]; } if (isset($_POST[$name])) {
return null; return $_POST[$name];
} }
if (isset($_GET[$name])) {
function clean($s) { return $_GET[$name];
$s = magic($s); }
$s = htmlspecialchars($s); return null;
return $s; }
}
function clean($s)
{
$s = magic($s);
$s = htmlspecialchars($s);
return $s;
}
function magic($s) { function magic($s)
if (get_magic_quotes_gpc()) { $s = stripslashes($s); } {
return $s; if (get_magic_quotes_gpc()) {
} $s = stripslashes($s);
}
function ip() { return $s;
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) }
return $_SERVER['HTTP_X_FORWARDED_FOR'];
else function ip()
return $_SERVER['REMOTE_ADDR']; {
} if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
function ipValid($ip) { function ipValid($ip)
if ($ip == long2ip(ip2long($ip))) {
return true; if ($ip == long2ip(ip2long($ip))) {
return false; return true;
} }
return false;
}
function validIP($ip) { function validIP($ip)
if ($ip == long2ip(ip2long($ip))) {
return true; if ($ip == long2ip(ip2long($ip))) {
return false; return true;
} }
return false;
}
function ts() { function ts()
// return microtime(true); {
list($usec, $sec) = explode(" ", microtime()); // return microtime(true);
return ((float)$usec + (float)$sec); list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
} function len($string)
{
$i = 0;
$count = 0;
$len = strlen($string);
function len($string) { while ($i < $len) {
$i = 0; $count = 0; $chr = ord($string[$i]);
$len = strlen($string); $count++;
$i++;
while ($i < $len) { if ($i >= $len) {
$chr = ord($string[$i]); break;
$count++; }
$i++; if ($chr & 0x80) {
$chr <<= 1;
while ($chr & 0x80) {
$i++;
$chr <<= 1;
}
}
}
if ($i >= $len) break; return $count;
if ($chr & 0x80) { }
$chr <<= 1;
while ($chr & 0x80) {
$i++;
$chr <<= 1;
}
}
}
return $count; function error($err)
} {
echo 'Error: ' . $err;
exit;
}
function error($err) { function ys($log = 1)
echo 'Error: ' . $err; {
exit; global $yShout, $prefs;
} if ($yShout) {
return $yShout;
}
function ys($log = 1) { if (filter_var($log, FILTER_VALIDATE_INT, ["options" => ["min_range" => 0, "max_range" => $prefs['logs']]]) === false) {
global $yShout, $prefs; $log = 1;
if ($yShout) return $yShout; }
$log = 'log.' . $log;
return new YShout($log, loggedIn());
}
if (filter_var($log, FILTER_VALIDATE_INT, array("options" => array("min_range" => 0, "max_range" => $prefs['logs']))) === false) function dstart()
{ {
$log = 1; global $ts;
}
$log = 'log.' . $log;
return new YShout($log, loggedIn());
}
function dstart() { $ts = ts();
global $ts; }
$ts = ts(); function dstop()
} {
global $ts;
echo 'Time elapsed: ' . ((ts() - $ts) * 100000);
exit;
}
function dstop() { function login($hash)
global $ts; {
echo 'Time elapsed: ' . ((ts() - $ts) * 100000); // echo 'login: ' . $hash . "\n";
exit;
} $_SESSION['yLoginHash'] = $hash;
cookie('yLoginHash', $hash);
// return loggedIn();
}
function login($hash) { function logout()
// echo 'login: ' . $hash . "\n"; {
$_SESSION['yLoginHash'] = '';
$_SESSION['yLoginHash'] = $hash; cookie('yLoginHash', '');
cookie('yLoginHash', $hash); // cookieClear('yLoginHash');
// return loggedIn(); }
}
function logout() { function loggedIn()
$_SESSION['yLoginHash'] = ''; {
cookie('yLoginHash', ''); global $prefs;
// cookieClear('yLoginHash');
}
function loggedIn() { $loginHash = cookieGet('yLoginHash', false);
global $prefs; // echo 'loggedin: ' . $loginHash . "\n";
// echo 'pw: ' . $prefs['password'] . "\n";
if (isset($loginHash)) {
return $loginHash == md5($prefs['password']);
}
$loginHash = cookieGet('yLoginHash', false); if (isset($_SESSION['yLoginHash'])) {
// echo 'loggedin: ' . $loginHash . "\n"; return $_SESSION['yLoginHash'] == md5($prefs['password']);
// echo 'pw: ' . $prefs['password'] . "\n"; }
if (isset($loginHash)) return $loginHash == md5($prefs['password']);
if (isset($_SESSION['yLoginHash']))
return $_SESSION['yLoginHash'] == md5($prefs['password']);
return false;
}
return false;
}

View File

@ -1,251 +1,292 @@
<?php <?php
class YShout { class YShout
{
public function YShout($path, $admin = false)
{
global $storage;
// Redo to check for folders or just not break, because nonexistent files should be allowed.
// if (!file_exists($path)) error('That file does not exist.');
function YShout($path, $admin = false) { $this->storage = new $storage($path, true);
global $storage; $this->admin = $admin;
// Redo to check for folders or just not break, because nonexistent files should be allowed. }
// if (!file_exists($path)) error('That file does not exist.');
$this->storage = new $storage($path, true); public function posts()
$this->admin = $admin; {
} global $null;
$this->storage->open();
$s = $this->storage->load();
$this->storage->close($null);
function posts() { if ($s) {
global $null; return $s['posts'];
$this->storage->open(); }
$s = $this->storage->load(); }
$this->storage->close($null);
if ($s) public function info()
return $s['posts']; {
} global $null;
$s = $this->storage->open(true);
function info() { $this->storage->close($null);
global $null;
$s = $this->storage->open(true);
$this->storage->close($null); if ($s) {
return $s['info'];
}
}
if ($s) public function postsAfter($ts)
return $s['info']; {
} $allPosts = $this->posts();
function postsAfter($ts) { $posts = [];
$allPosts = $this->posts();
$posts = array(); /* for ($i = sizeof($allPosts) - 1; $i > -1; $i--) {
$post = $allPosts[$i];
/* for ($i = sizeof($allPosts) - 1; $i > -1; $i--) { if ($post['timestamp'] > $ts)
$post = $allPosts[$i]; $posts[] = $post;
} */
if ($post['timestamp'] > $ts) foreach ($allPosts as $post) {
$posts[] = $post; if ($post['timestamp'] > $ts) {
} */ $posts[] = $post;
}
}
foreach($allPosts as $post) { $this->postProcess($posts);
if ($post['timestamp'] > $ts) return $posts;
$posts[] = $post; }
}
$this->postProcess($posts); public function latestPosts($num)
return $posts; {
} $allPosts = $this->posts();
$posts = array_slice($allPosts, -$num, $num);
function latestPosts($num) { $this->postProcess($posts);
$allPosts = $this->posts(); return array_values($posts);
$posts = array_slice($allPosts, -$num, $num); }
$this->postProcess($posts); public function hasPostsAfter($ts)
return array_values($posts); {
} $info = $this->info();
$timestamp = $info['latestTimestamp'];
return $timestamp > $ts;
}
function hasPostsAfter($ts) { public function post($nickname, $message)
$info = $this->info(); {
$timestamp = $info['latestTimestamp']; global $prefs;
return $timestamp > $ts;
}
function post($nickname, $message) { if ($this->banned(ip()) /* && !$this->admin*/) {
global $prefs; return false;
}
if ($this->banned(ip()) /* && !$this->admin*/) return false; if (!$this->validate($message, $prefs['messageLength'])) {
return false;
}
if (!$this->validate($nickname, $prefs['nicknameLength'])) {
return false;
}
if (!$this->validate($message, $prefs['messageLength'])) return false; $message = trim(clean($message));
if (!$this->validate($nickname, $prefs['nicknameLength'])) return false; $nickname = trim(clean($nickname));
if ($message == '') {
return false;
}
if ($nickname == '') {
return false;
}
$timestamp = ts();
$message = $this->censor($message);
$nickname = $this->censor($nickname);
$post = [
'nickname' => $nickname,
'message' => $message,
'timestamp' => $timestamp,
'admin' => $this->admin,
'uid' => md5($timestamp . ' ' . $nickname),
'adminInfo' => [
'ip' => ip()
]
];
$message = trim(clean($message)); $s = $this->storage->open(true);
$nickname = trim(clean($nickname));
if ($message == '') return false;
if ($nickname == '') return false;
$timestamp = ts();
$message = $this->censor($message);
$nickname = $this->censor($nickname);
$post = array(
'nickname' => $nickname,
'message' => $message,
'timestamp' => $timestamp,
'admin' => $this->admin,
'uid' => md5($timestamp . ' ' . $nickname),
'adminInfo' => array(
'ip' => ip()
)
);
$s = $this->storage->open(true); $s['posts'][] = $post;
$s['posts'][] = $post; if (sizeof($s['posts']) > $prefs['history']) {
$this->truncate($s['posts']);
}
if (sizeof($s['posts']) > $prefs['history']) $s['info']['latestTimestamp'] = $post['timestamp'];
$this->truncate($s['posts']);
$s['info']['latestTimestamp'] = $post['timestamp']; $this->storage->close($s);
$this->postProcess($post);
return $post;
}
$this->storage->close($s); public function truncate(&$array)
$this->postProcess($post); {
return $post; global $prefs;
}
function truncate(&$array) { $array = array_slice($array, -$prefs['history']);
global $prefs; $array = array_values($array);
}
$array = array_slice($array, -$prefs['history']); public function clear()
$array = array_values($array); {
} global $null;
function clear() { $this->storage->open(true);
global $null; $this->storage->resetArray();
// ? Scared to touch it... Misspelled though. Update: Touched! Used to be $nulls...
$this->storage->close($null);
}
$this->storage->open(true); public function bans()
$this->storage->resetArray(); {
// ? Scared to touch it... Misspelled though. Update: Touched! Used to be $nulls... global $storage, $null;
$this->storage->close($null);
}
function bans() { $s = new $storage('yshout.bans');
global $storage, $null; $s->open();
$bans = $s->load();
$s->close($null);
$s = new $storage('yshout.bans'); return $bans;
$s->open(); }
$bans = $s->load();
$s->close($null);
return $bans; public function ban($ip, $nickname = '', $info = '')
} {
global $storage;
function ban($ip, $nickname = '', $info = '') { $s = new $storage('yshout.bans');
global $storage; $bans = $s->open(true);
$s = new $storage('yshout.bans'); $bans[] = [
$bans = $s->open(true); 'ip' => $ip,
'nickname' => $nickname,
'info' => $info,
'timestamp' => ts()
];
$bans[] = array( $s->close($bans);
'ip' => $ip, }
'nickname' => $nickname,
'info' => $info,
'timestamp' => ts()
);
$s->close($bans); public function banned($ip)
} {
global $storage, $null;
function banned($ip) { $s = new $storage('yshout.bans');
global $storage, $null; $bans = $s->open(true);
$s->close($null);
$s = new $storage('yshout.bans'); foreach ($bans as $ban) {
$bans = $s->open(true); if ($ban['ip'] == $ip) {
$s->close($null); return true;
}
}
foreach($bans as $ban) { return false;
if ($ban['ip'] == $ip) }
return true;
}
return false; public function unban($ip)
} {
global $storage;
function unban($ip) { $s = new $storage('yshout.bans');
global $storage; $bans = $s->open(true);
$s = new $storage('yshout.bans'); foreach ($bans as $key=>$value) {
$bans = $s->open(true); if ($value['ip'] == $ip) {
unset($bans[$key]);
}
}
foreach($bans as $key=>$value) $bans = array_values($bans);
if ($value['ip'] == $ip) { $s->close($bans);
unset($bans[$key]); }
}
$bans = array_values($bans); public function unbanAll()
$s->close($bans); {
global $storage, $null;
} $s = new $storage('yshout.bans');
$s->open(true);
$s->resetArray();
$s->close($null);
}
function unbanAll() { public function delete($uid)
global $storage, $null; {
global $prefs, $storage;
$s = new $storage('yshout.bans');
$s->open(true); $s = $this->storage->open(true);
$s->resetArray();
$s->close($null);
}
function delete($uid) { $posts = $s['posts'];
global $prefs, $storage;
foreach ($posts as $key=>$value) {
if (!isset($value['uid'])) {
unset($posts['key']);
} elseif ($value['uid'] == $uid) {
unset($posts[$key]);
}
}
$s['posts'] = array_values($posts);
$this->storage->close($s);
return true;
$s = $this->storage->open(true); }
public function validate($str, $maxLen)
{
return len($str) <= $maxLen;
}
public function censor($str)
{
global $prefs;
$cWords = explode(' ', $prefs['censorWords']);
$words = explode(' ', $str);
$endings = '|ed|es|ing|s|er|ers';
$arrEndings = explode('|', $endings);
foreach ($cWords as $cWord) {
foreach ($words as $i=>$word) {
$pattern = '/^(' . $cWord . ')+(' . $endings . ')\W*$/i';
$words[$i] = preg_replace($pattern, str_repeat('*', strlen($word)), $word);
}
}
return implode(' ', $words);
}
$posts = $s['posts']; public function postProcess(&$post)
{
foreach($posts as $key=>$value) { if (isset($post['message'])) {
if (!isset($value['uid'])) if ($this->banned($post['adminInfo']['ip'])) {
unset($posts['key']); $post['banned'] = true;
else }
if($value['uid'] == $uid) if (!$this->admin) {
unset($posts[$key]); unset($post['adminInfo']);
} }
} else {
$s['posts'] = array_values($posts); foreach ($post as $key=>$value) {
$this->storage->close($s); if ($this->banned($value['adminInfo']['ip'])) {
$post[$key]['banned'] = true;
return true; }
} if (!$this->admin) {
unset($post[$key]['adminInfo']);
function validate($str, $maxLen) { }
return len($str) <= $maxLen; }
} }
}
function censor($str) {
global $prefs;
$cWords = explode(' ', $prefs['censorWords']);
$words = explode(' ', $str);
$endings = '|ed|es|ing|s|er|ers';
$arrEndings = explode('|', $endings);
foreach ($cWords as $cWord) foreach ($words as $i=>$word) {
$pattern = '/^(' . $cWord . ')+(' . $endings . ')\W*$/i';
$words[$i] = preg_replace($pattern, str_repeat('*', strlen($word)), $word);
}
return implode(' ', $words);
}
function postProcess(&$post) {
if (isset($post['message'])) {
if ($this->banned($post['adminInfo']['ip'])) $post['banned'] = true;
if (!$this->admin) unset($post['adminInfo']);
} else {
foreach($post as $key=>$value) {
if ($this->banned($value['adminInfo']['ip'])) $post[$key]['banned'] = true;
if (!$this->admin) unset($post[$key]['adminInfo']);
}
}
}
} }

View File

@ -1,74 +1,75 @@
<?php <?php
// If you want to change the nickname, the line below is the one to modify. // If you want to change the nickname, the line below is the one to modify.
// Simply set $overrideNickname to whatever variable you want to appear as the nickname, // Simply set $overrideNickname to whatever variable you want to appear as the nickname,
// or leave it null to use the set nicknames. // or leave it null to use the set nicknames.
$overrideNickname = null; $overrideNickname = null;
$storage = 'FileStorage'; $storage = 'FileStorage';
function loadPrefs() { function loadPrefs()
global $prefs, $storage, $null; {
$s = new $storage('yshout.prefs'); global $prefs, $storage, $null;
$s->open(); $s = new $storage('yshout.prefs');
$prefs = $s->load(); $s->open();
$s->close($null); $prefs = $s->load();
} $s->close($null);
}
function savePrefs($newPrefs) { function savePrefs($newPrefs)
global $prefs, $storage; {
global $prefs, $storage;
$s = new $storage('yshout.prefs'); $s = new $storage('yshout.prefs');
$s->open(true); $s->open(true);
$s->close($newPrefs); $s->close($newPrefs);
$prefs = $newPrefs; $prefs = $newPrefs;
} }
function resetPrefs() { function resetPrefs()
$defaultPrefs = array( {
'password' => 'fortytwo', // The password for the CP $defaultPrefs = [
'password' => 'fortytwo', // The password for the CP
'refresh' => 6000, // Refresh rate 'refresh' => 6000, // Refresh rate
'logs' => 5, // Amount of different log files to allow 'logs' => 5, // Amount of different log files to allow
'history' => 200, // Shouts to keep in history 'history' => 200, // Shouts to keep in history
'inverse' => false, // Inverse shoutbox / form on top 'inverse' => false, // Inverse shoutbox / form on top
'truncate' => 15, // Truncate messages client-side 'truncate' => 15, // Truncate messages client-side
'doTruncate' => true, // Truncate messages? 'doTruncate' => true, // Truncate messages?
'timestamp' => 12, // Timestamp format 12- or 24-hour 'timestamp' => 12, // Timestamp format 12- or 24-hour
'defaultNickname' => 'Nickname', 'defaultNickname' => 'Nickname',
'defaultMessage' => 'Message Text', 'defaultMessage' => 'Message Text',
'defaultSubmit' => 'Shout!', 'defaultSubmit' => 'Shout!',
'showSubmit' => true, 'showSubmit' => true,
'nicknameLength' => 25, 'nicknameLength' => 25,
'messageLength' => 175, 'messageLength' => 175,
'nicknameSeparator' => ':', 'nicknameSeparator' => ':',
'flood' => true, 'flood' => true,
'floodTimeout' => 5000, 'floodTimeout' => 5000,
'floodMessages' => 4, 'floodMessages' => 4,
'floodDisable' => 8000, 'floodDisable' => 8000,
'floodDelete' => false, 'floodDelete' => false,
'autobanFlood' => 0, // Autoban people for flooding after X messages 'autobanFlood' => 0, // Autoban people for flooding after X messages
'censorWords' => 'fuck shit bitch ass', 'censorWords' => 'fuck shit bitch ass',
'postFormLink' => 'history', 'postFormLink' => 'history',
'info' => 'inline'
);
savePrefs($defaultPrefs);
}
resetPrefs();
//loadPrefs();
'info' => 'inline'
];
savePrefs($defaultPrefs);
}
resetPrefs();
//loadPrefs();

View File

@ -5,34 +5,35 @@ ob_start();
set_error_handler('errorOccurred'); set_error_handler('errorOccurred');
include 'include.php'; include 'include.php';
if (isset($_POST['reqFor'])) if (isset($_POST['reqFor'])) {
switch($_POST['reqFor']) { switch ($_POST['reqFor']) {
case 'shout': case 'shout':
$ajax = new AjaxCall(); $ajax = new AjaxCall();
$ajax->process(); $ajax->process();
break; break;
case 'history': case 'history':
// echo $_POST['log']; // echo $_POST['log'];
$ajax = new AjaxCall($_POST['log']); $ajax = new AjaxCall($_POST['log']);
$ajax->process(); $ajax->process();
break; break;
default: default:
exit; exit;
} else { }
include 'example.html'; } else {
} include 'example.html';
function errorOccurred($num, $str, $file, $line) {
$err = array (
'yError' => "$str. \n File: $file \n Line: $line"
);
echo json_encode($err);
exit;
} }
function errorOccurred($num, $str, $file, $line)
{
$err = [
'yError' => "$str. \n File: $file \n Line: $line"
];
echo json_encode($err);
exit;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,110 +1,111 @@
<?php <?php
class CommentListTest extends ShimmiePHPUnitTestCase { class CommentListTest extends ShimmiePHPUnitTestCase
public function setUp() { {
global $config; public function setUp()
parent::setUp(); {
$config->set_int("comment_limit", 100); global $config;
$this->log_out(); parent::setUp();
} $config->set_int("comment_limit", 100);
$this->log_out();
}
public function tearDown() { public function tearDown()
global $config; {
$config->set_int("comment_limit", 10); global $config;
parent::tearDown(); $config->set_int("comment_limit", 10);
} parent::tearDown();
}
public function testCommentsPage() { public function testCommentsPage()
global $user; {
global $user;
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
# a good comment # a good comment
send_event(new CommentPostingEvent($image_id, $user, "Test Comment ASDFASDF")); send_event(new CommentPostingEvent($image_id, $user, "Test Comment ASDFASDF"));
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_text("ASDFASDF"); $this->assert_text("ASDFASDF");
# dupe # dupe
try { try {
send_event(new CommentPostingEvent($image_id, $user, "Test Comment ASDFASDF")); send_event(new CommentPostingEvent($image_id, $user, "Test Comment ASDFASDF"));
} } catch (CommentPostingException $e) {
catch(CommentPostingException $e) { $this->assertContains("try and be more original", $e->getMessage());
$this->assertContains("try and be more original", $e->getMessage()); }
}
# empty comment # empty comment
try { try {
send_event(new CommentPostingEvent($image_id, $user, "")); send_event(new CommentPostingEvent($image_id, $user, ""));
} } catch (CommentPostingException $e) {
catch(CommentPostingException $e) { $this->assertContains("Comments need text", $e->getMessage());
$this->assertContains("Comments need text", $e->getMessage()); }
}
# whitespace is still empty... # whitespace is still empty...
try { try {
send_event(new CommentPostingEvent($image_id, $user, " \t\r\n")); send_event(new CommentPostingEvent($image_id, $user, " \t\r\n"));
} } catch (CommentPostingException $e) {
catch(CommentPostingException $e) { $this->assertContains("Comments need text", $e->getMessage());
$this->assertContains("Comments need text", $e->getMessage()); }
}
# repetitive (aka. gzip gives >= 10x improvement) # repetitive (aka. gzip gives >= 10x improvement)
try { try {
send_event(new CommentPostingEvent($image_id, $user, str_repeat("U", 5000))); send_event(new CommentPostingEvent($image_id, $user, str_repeat("U", 5000)));
} } catch (CommentPostingException $e) {
catch(CommentPostingException $e) { $this->assertContains("Comment too repetitive", $e->getMessage());
$this->assertContains("Comment too repetitive", $e->getMessage()); }
}
# test UTF8 # test UTF8
send_event(new CommentPostingEvent($image_id, $user, "Test Comment むちむち")); send_event(new CommentPostingEvent($image_id, $user, "Test Comment むちむち"));
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_text("むちむち"); $this->assert_text("むちむち");
# test that search by comment metadata works # test that search by comment metadata works
// $this->get_page("post/list/commented_by=test/1"); // $this->get_page("post/list/commented_by=test/1");
// $this->assert_title("Image $image_id: pbx"); // $this->assert_title("Image $image_id: pbx");
// $this->get_page("post/list/comments=2/1"); // $this->get_page("post/list/comments=2/1");
// $this->assert_title("Image $image_id: pbx"); // $this->assert_title("Image $image_id: pbx");
$this->log_out(); $this->log_out();
$this->get_page('comment/list'); $this->get_page('comment/list');
$this->assert_title('Comments'); $this->assert_title('Comments');
$this->assert_text('ASDFASDF'); $this->assert_text('ASDFASDF');
$this->get_page('comment/list/2'); $this->get_page('comment/list/2');
$this->assert_title('Comments'); $this->assert_title('Comments');
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->delete_image($image_id); $this->delete_image($image_id);
$this->log_out(); $this->log_out();
$this->get_page('comment/list'); $this->get_page('comment/list');
$this->assert_title('Comments'); $this->assert_title('Comments');
$this->assert_no_text('ASDFASDF'); $this->assert_no_text('ASDFASDF');
} }
public function testSingleDel() { public function testSingleDel()
$this->markTestIncomplete(); {
$this->markTestIncomplete();
$this->log_in_as_admin(); $this->log_in_as_admin();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
# make a comment # make a comment
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->set_field('comment', "Test Comment ASDFASDF"); $this->set_field('comment', "Test Comment ASDFASDF");
$this->click("Post Comment"); $this->click("Post Comment");
$this->assert_title("Image $image_id: pbx"); $this->assert_title("Image $image_id: pbx");
$this->assert_text("ASDFASDF"); $this->assert_text("ASDFASDF");
# delete it # delete it
$this->click("Del"); $this->click("Del");
$this->assert_title("Image $image_id: pbx"); $this->assert_title("Image $image_id: pbx");
$this->assert_no_text("ASDFASDF"); $this->assert_no_text("ASDFASDF");
# tidy up # tidy up
$this->delete_image($image_id); $this->delete_image($image_id);
$this->log_out(); $this->log_out();
} }
} }

View File

@ -1,109 +1,111 @@
<?php <?php
class CommentListTheme extends Themelet { class CommentListTheme extends Themelet
private $comments_shown = 0; {
private $show_anon_id = false; private $comments_shown = 0;
private $anon_id = 1; private $show_anon_id = false;
private $anon_cid = 0; private $anon_id = 1;
private $anon_map = array(); private $anon_cid = 0;
private $ct = null; private $anon_map = [];
private $ct = null;
private function get_anon_colour($ip) { private function get_anon_colour($ip)
if(is_null($this->ct)) { {
$this->ct = hsl_rainbow(); if (is_null($this->ct)) {
} $this->ct = hsl_rainbow();
if(!array_key_exists($ip, $this->anon_map)) { }
$this->anon_map[$ip] = $this->ct[$this->anon_cid++ % count($this->ct)]; if (!array_key_exists($ip, $this->anon_map)) {
} $this->anon_map[$ip] = $this->ct[$this->anon_cid++ % count($this->ct)];
return $this->anon_map[$ip]; }
} return $this->anon_map[$ip];
}
/** /**
* Display a page with a list of images, and for each image, the image's comments. * Display a page with a list of images, and for each image, the image's comments.
*/ */
public function display_comment_list(array $images, int $page_number, int $total_pages, bool $can_post) { public function display_comment_list(array $images, int $page_number, int $total_pages, bool $can_post)
global $config, $page, $user; {
global $config, $page, $user;
// aaaaaaargh php // aaaaaaargh php
assert(is_array($images)); assert(is_array($images));
assert(is_numeric($page_number)); assert(is_numeric($page_number));
assert(is_numeric($total_pages)); assert(is_numeric($total_pages));
assert(is_bool($can_post)); assert(is_bool($can_post));
// parts for the whole page // parts for the whole page
$prev = $page_number - 1; $prev = $page_number - 1;
$next = $page_number + 1; $next = $page_number + 1;
$h_prev = ($page_number <= 1) ? "Prev" : $h_prev = ($page_number <= 1) ? "Prev" :
'<a href="'.make_link('comment/list/'.$prev).'">Prev</a>'; '<a href="'.make_link('comment/list/'.$prev).'">Prev</a>';
$h_index = "<a href='".make_link("post/list")."'>Index</a>"; $h_index = "<a href='".make_link("post/list")."'>Index</a>";
$h_next = ($page_number >= $total_pages) ? "Next" : $h_next = ($page_number >= $total_pages) ? "Next" :
'<a href="'.make_link('comment/list/'.$next).'">Next</a>'; '<a href="'.make_link('comment/list/'.$next).'">Next</a>';
$nav = $h_prev.' | '.$h_index.' | '.$h_next; $nav = $h_prev.' | '.$h_index.' | '.$h_next;
$page->set_title("Comments"); $page->set_title("Comments");
$page->set_heading("Comments"); $page->set_heading("Comments");
$page->add_block(new Block("Navigation", $nav, "left")); $page->add_block(new Block("Navigation", $nav, "left"));
$this->display_paginator($page, "comment/list", null, $page_number, $total_pages); $this->display_paginator($page, "comment/list", null, $page_number, $total_pages);
// parts for each image // parts for each image
$position = 10; $position = 10;
$comment_limit = $config->get_int("comment_list_count", 10); $comment_limit = $config->get_int("comment_list_count", 10);
$comment_captcha = $config->get_bool('comment_captcha'); $comment_captcha = $config->get_bool('comment_captcha');
foreach($images as $pair) { foreach ($images as $pair) {
$image = $pair[0]; $image = $pair[0];
$comments = $pair[1]; $comments = $pair[1];
$thumb_html = $this->build_thumb_html($image); $thumb_html = $this->build_thumb_html($image);
$comment_html = ""; $comment_html = "";
$comment_count = count($comments); $comment_count = count($comments);
if($comment_limit > 0 && $comment_count > $comment_limit) { if ($comment_limit > 0 && $comment_count > $comment_limit) {
$comment_html .= "<p>showing $comment_limit of $comment_count comments</p>"; $comment_html .= "<p>showing $comment_limit of $comment_count comments</p>";
$comments = array_slice($comments, -$comment_limit); $comments = array_slice($comments, -$comment_limit);
$this->show_anon_id = false; $this->show_anon_id = false;
} } else {
else { $this->show_anon_id = true;
$this->show_anon_id = true; }
} $this->anon_id = 1;
$this->anon_id = 1; foreach ($comments as $comment) {
foreach($comments as $comment) { $comment_html .= $this->comment_to_html($comment);
$comment_html .= $this->comment_to_html($comment); }
} if (!$user->is_anonymous()) {
if(!$user->is_anonymous()) { if ($can_post) {
if($can_post) { $comment_html .= $this->build_postbox($image->id);
$comment_html .= $this->build_postbox($image->id); }
} } else {
} else { if ($can_post) {
if ($can_post) { if (!$comment_captcha) {
if(!$comment_captcha) { $comment_html .= $this->build_postbox($image->id);
$comment_html .= $this->build_postbox($image->id); } else {
} $link = make_link("post/view/".$image->id);
else { $comment_html .= "<a href='$link'>Add Comment</a>";
$link = make_link("post/view/".$image->id); }
$comment_html .= "<a href='$link'>Add Comment</a>"; }
} }
}
}
$html = ' $html = '
<table class="comment_list_table"><tr> <table class="comment_list_table"><tr>
<td width="220">'.$thumb_html.'</td> <td width="220">'.$thumb_html.'</td>
<td>'.$comment_html.'</td> <td>'.$comment_html.'</td>
</tr></table> </tr></table>
'; ';
$page->add_block(new Block( $image->id.': '.$image->get_tag_list(), $html, "main", $position++, "comment-list-list")); $page->add_block(new Block($image->id.': '.$image->get_tag_list(), $html, "main", $position++, "comment-list-list"));
} }
} }
public function display_admin_block() { public function display_admin_block()
global $page; {
global $page;
$html = ' $html = '
Delete comments by IP. Delete comments by IP.
<br><br>'.make_form(make_link("comment/bulk_delete"), 'POST')." <br><br>'.make_form(make_link("comment/bulk_delete"), 'POST')."
@ -113,161 +115,163 @@ class CommentListTheme extends Themelet {
</table> </table>
</form> </form>
"; ";
$page->add_block(new Block("Mass Comment Delete", $html)); $page->add_block(new Block("Mass Comment Delete", $html));
} }
/** /**
* Add some comments to the page, probably in a sidebar. * Add some comments to the page, probably in a sidebar.
* *
* #param Comment[] $comments An array of Comment objects to be shown * #param Comment[] $comments An array of Comment objects to be shown
*/ */
public function display_recent_comments(array $comments) { public function display_recent_comments(array $comments)
global $page; {
$this->show_anon_id = false; global $page;
$html = ""; $this->show_anon_id = false;
foreach($comments as $comment) { $html = "";
$html .= $this->comment_to_html($comment, true); foreach ($comments as $comment) {
} $html .= $this->comment_to_html($comment, true);
$html .= "<a class='more' href='".make_link("comment/list")."'>Full List</a>"; }
$page->add_block(new Block("Comments", $html, "left", 50, "comment-list-recent")); $html .= "<a class='more' href='".make_link("comment/list")."'>Full List</a>";
} $page->add_block(new Block("Comments", $html, "left", 50, "comment-list-recent"));
}
/** /**
* Show comments for an image. * Show comments for an image.
* *
* #param Comment[] $comments * #param Comment[] $comments
*/ */
public function display_image_comments(Image $image, array $comments, bool $postbox) { public function display_image_comments(Image $image, array $comments, bool $postbox)
global $page; {
$this->show_anon_id = true; global $page;
$html = ""; $this->show_anon_id = true;
foreach($comments as $comment) { $html = "";
$html .= $this->comment_to_html($comment); foreach ($comments as $comment) {
} $html .= $this->comment_to_html($comment);
if($postbox) { }
$html .= $this->build_postbox($image->id); if ($postbox) {
} $html .= $this->build_postbox($image->id);
$page->add_block(new Block("Comments", $html, "main", 30, "comment-list-image")); }
} $page->add_block(new Block("Comments", $html, "main", 30, "comment-list-image"));
}
/** /**
* Show comments made by a user. * Show comments made by a user.
* *
* #param Comment[] $comments * #param Comment[] $comments
*/ */
public function display_recent_user_comments(array $comments, User $user) { public function display_recent_user_comments(array $comments, User $user)
global $page; {
$html = ""; global $page;
foreach($comments as $comment) { $html = "";
$html .= $this->comment_to_html($comment, true); foreach ($comments as $comment) {
} $html .= $this->comment_to_html($comment, true);
if(empty($html)) { }
$html = '<p>No comments by this user.</p>'; if (empty($html)) {
} $html = '<p>No comments by this user.</p>';
else { } else {
$html .= "<p><a href='".make_link("comment/beta-search/{$user->name}/1")."'>More</a></p>"; $html .= "<p><a href='".make_link("comment/beta-search/{$user->name}/1")."'>More</a></p>";
} }
$page->add_block(new Block("Comments", $html, "left", 70, "comment-list-user")); $page->add_block(new Block("Comments", $html, "left", 70, "comment-list-user"));
} }
public function display_all_user_comments(array $comments, int $page_number, int $total_pages, User $user) { public function display_all_user_comments(array $comments, int $page_number, int $total_pages, User $user)
global $page; {
global $page;
assert(is_numeric($page_number));
assert(is_numeric($total_pages)); assert(is_numeric($page_number));
assert(is_numeric($total_pages));
$html = "";
foreach($comments as $comment) { $html = "";
$html .= $this->comment_to_html($comment, true); foreach ($comments as $comment) {
} $html .= $this->comment_to_html($comment, true);
if(empty($html)) { }
$html = '<p>No comments by this user.</p>'; if (empty($html)) {
} $html = '<p>No comments by this user.</p>';
$page->add_block(new Block("Comments", $html, "main", 70, "comment-list-user")); }
$page->add_block(new Block("Comments", $html, "main", 70, "comment-list-user"));
$prev = $page_number - 1; $prev = $page_number - 1;
$next = $page_number + 1; $next = $page_number + 1;
//$search_terms = array('I','have','no','idea','what','this','does!'); //$search_terms = array('I','have','no','idea','what','this','does!');
//$u_tags = url_escape(Tag::implode($search_terms)); //$u_tags = url_escape(Tag::implode($search_terms));
//$query = empty($u_tags) ? "" : '/'.$u_tags; //$query = empty($u_tags) ? "" : '/'.$u_tags;
$h_prev = ($page_number <= 1) ? "Prev" : "<a href='$prev'>Prev</a>"; $h_prev = ($page_number <= 1) ? "Prev" : "<a href='$prev'>Prev</a>";
$h_index = "<a href='".make_link("post/list")."'>Index</a>"; $h_index = "<a href='".make_link("post/list")."'>Index</a>";
$h_next = ($page_number >= $total_pages) ? "Next" : "<a href='$next'>Next</a>"; $h_next = ($page_number >= $total_pages) ? "Next" : "<a href='$next'>Next</a>";
$page->set_title(html_escape($user->name)."'s comments"); $page->set_title(html_escape($user->name)."'s comments");
$page->add_block(new Block("Navigation", $h_prev.' | '.$h_index.' | '.$h_next, "left", 0)); $page->add_block(new Block("Navigation", $h_prev.' | '.$h_index.' | '.$h_next, "left", 0));
$this->display_paginator($page, "comment/beta-search/{$user->name}", null, $page_number, $total_pages); $this->display_paginator($page, "comment/beta-search/{$user->name}", null, $page_number, $total_pages);
} }
protected function comment_to_html(Comment $comment, bool $trim=false): string { protected function comment_to_html(Comment $comment, bool $trim=false): string
global $config, $user; {
global $config, $user;
$tfe = new TextFormattingEvent($comment->comment); $tfe = new TextFormattingEvent($comment->comment);
send_event($tfe); send_event($tfe);
$i_uid = int_escape($comment->owner_id); $i_uid = int_escape($comment->owner_id);
$h_name = html_escape($comment->owner_name); $h_name = html_escape($comment->owner_name);
$h_timestamp = autodate($comment->posted); $h_timestamp = autodate($comment->posted);
$h_comment = ($trim ? truncate($tfe->stripped, 50) : $tfe->formatted); $h_comment = ($trim ? truncate($tfe->stripped, 50) : $tfe->formatted);
$i_comment_id = int_escape($comment->comment_id); $i_comment_id = int_escape($comment->comment_id);
$i_image_id = int_escape($comment->image_id); $i_image_id = int_escape($comment->image_id);
if($i_uid == $config->get_int("anon_id")) { if ($i_uid == $config->get_int("anon_id")) {
$anoncode = ""; $anoncode = "";
$anoncode2 = ""; $anoncode2 = "";
if($this->show_anon_id) { if ($this->show_anon_id) {
$anoncode = '<sup>'.$this->anon_id.'</sup>'; $anoncode = '<sup>'.$this->anon_id.'</sup>';
if(!array_key_exists($comment->poster_ip, $this->anon_map)) { if (!array_key_exists($comment->poster_ip, $this->anon_map)) {
$this->anon_map[$comment->poster_ip] = $this->anon_id; $this->anon_map[$comment->poster_ip] = $this->anon_id;
} }
#if($user->can("view_ip")) { #if($user->can("view_ip")) {
#$style = " style='color: ".$this->get_anon_colour($comment->poster_ip).";'"; #$style = " style='color: ".$this->get_anon_colour($comment->poster_ip).";'";
if($user->can("view_ip") || $config->get_bool("comment_samefags_public", false)) { if ($user->can("view_ip") || $config->get_bool("comment_samefags_public", false)) {
if($this->anon_map[$comment->poster_ip] != $this->anon_id) { if ($this->anon_map[$comment->poster_ip] != $this->anon_id) {
$anoncode2 = '<sup>('.$this->anon_map[$comment->poster_ip].')</sup>'; $anoncode2 = '<sup>('.$this->anon_map[$comment->poster_ip].')</sup>';
} }
} }
} }
$h_userlink = "<span class='username'>" . $h_name . $anoncode . $anoncode2 . "</span>"; $h_userlink = "<span class='username'>" . $h_name . $anoncode . $anoncode2 . "</span>";
$this->anon_id++; $this->anon_id++;
} } else {
else { $h_userlink = '<a class="username" href="'.make_link('user/'.$h_name).'">'.$h_name.'</a>';
$h_userlink = '<a class="username" href="'.make_link('user/'.$h_name).'">'.$h_name.'</a>'; }
}
$hb = ($comment->owner_class == "hellbanned" ? "hb" : ""); $hb = ($comment->owner_class == "hellbanned" ? "hb" : "");
if($trim) { if ($trim) {
$html = " $html = "
<div class=\"comment $hb\"> <div class=\"comment $hb\">
$h_userlink: $h_comment $h_userlink: $h_comment
<a href=\"".make_link("post/view/$i_image_id#c$i_comment_id")."\">&gt;&gt;&gt;</a> <a href=\"".make_link("post/view/$i_image_id#c$i_comment_id")."\">&gt;&gt;&gt;</a>
</div> </div>
"; ";
} } else {
else { $h_avatar = "";
$h_avatar = ""; if (!empty($comment->owner_email)) {
if(!empty($comment->owner_email)) { $hash = md5(strtolower($comment->owner_email));
$hash = md5(strtolower($comment->owner_email)); $cb = date("Y-m-d");
$cb = date("Y-m-d"); $h_avatar = "<img src=\"//www.gravatar.com/avatar/$hash.jpg?cacheBreak=$cb\"><br>";
$h_avatar = "<img src=\"//www.gravatar.com/avatar/$hash.jpg?cacheBreak=$cb\"><br>"; }
} $h_reply = " - <a href='javascript: replyTo($i_image_id, $i_comment_id, \"$h_name\")'>Reply</a>";
$h_reply = " - <a href='javascript: replyTo($i_image_id, $i_comment_id, \"$h_name\")'>Reply</a>"; $h_ip = $user->can("view_ip") ? "<br>".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : "";
$h_ip = $user->can("view_ip") ? "<br>".show_ip($comment->poster_ip, "Comment posted {$comment->posted}") : ""; $h_del = "";
$h_del = ""; if ($user->can("delete_comment")) {
if ($user->can("delete_comment")) { $comment_preview = substr(html_unescape($tfe->stripped), 0, 50);
$comment_preview = substr(html_unescape($tfe->stripped), 0, 50); $j_delete_confirm_message = json_encode("Delete comment by {$comment->owner_name}:\n$comment_preview");
$j_delete_confirm_message = json_encode("Delete comment by {$comment->owner_name}:\n$comment_preview"); $h_delete_script = html_escape("return confirm($j_delete_confirm_message);");
$h_delete_script = html_escape("return confirm($j_delete_confirm_message);"); $h_delete_link = make_link("comment/delete/$i_comment_id/$i_image_id");
$h_delete_link = make_link("comment/delete/$i_comment_id/$i_image_id"); $h_del = " - <a onclick='$h_delete_script' href='$h_delete_link'>Del</a>";
$h_del = " - <a onclick='$h_delete_script' href='$h_delete_link'>Del</a>"; }
} $html = "
$html = "
<div class=\"comment $hb\" id=\"c$i_comment_id\"> <div class=\"comment $hb\" id=\"c$i_comment_id\">
<div class=\"info\"> <div class=\"info\">
$h_avatar $h_avatar
@ -276,18 +280,19 @@ class CommentListTheme extends Themelet {
$h_userlink: $h_comment $h_userlink: $h_comment
</div> </div>
"; ";
} }
return $html; return $html;
} }
protected function build_postbox(int $image_id): string { protected function build_postbox(int $image_id): string
global $config; {
global $config;
$i_image_id = int_escape($image_id); $i_image_id = int_escape($image_id);
$hash = CommentList::get_hash(); $hash = CommentList::get_hash();
$h_captcha = $config->get_bool("comment_captcha") ? captcha_get_html() : ""; $h_captcha = $config->get_bool("comment_captcha") ? captcha_get_html() : "";
return ' return '
<div class="comment comment_add"> <div class="comment comment_add">
'.make_form(make_link("comment/add")).' '.make_form(make_link("comment/add")).'
<input type="hidden" name="image_id" value="'.$i_image_id.'" /> <input type="hidden" name="image_id" value="'.$i_image_id.'" />
@ -298,6 +303,5 @@ class CommentListTheme extends Themelet {
</form> </form>
</div> </div>
'; ';
} }
} }

View File

@ -7,76 +7,77 @@
* Description: Uploads images automatically using Cron Jobs * Description: Uploads images automatically using Cron Jobs
* Documentation: Installation guide: activate this extension and navigate to www.yoursite.com/cron_upload * Documentation: Installation guide: activate this extension and navigate to www.yoursite.com/cron_upload
*/ */
class CronUploader extends Extension { class CronUploader extends Extension
// TODO: Checkbox option to only allow localhost + a list of additional IP adresses that can be set in /cron_upload {
// TODO: Change logging to MySQL + display log at /cron_upload // TODO: Checkbox option to only allow localhost + a list of additional IP adresses that can be set in /cron_upload
// TODO: Move stuff to theme.php // TODO: Change logging to MySQL + display log at /cron_upload
// TODO: Move stuff to theme.php
/**
* Lists all log events this session /**
* @var string * Lists all log events this session
*/ * @var string
private $upload_info = ""; */
private $upload_info = "";
/**
* Lists all files & info required to upload. /**
* @var array * Lists all files & info required to upload.
*/ * @var array
private $image_queue = array(); */
private $image_queue = [];
/**
* Cron Uploader root directory /**
* @var string * Cron Uploader root directory
*/ * @var string
private $root_dir = ""; */
private $root_dir = "";
/**
* Key used to identify uploader /**
* @var string * Key used to identify uploader
*/ * @var string
private $upload_key = ""; */
private $upload_key = "";
/**
* Checks if the cron upload page has been accessed /**
* and initializes the upload. * Checks if the cron upload page has been accessed
*/ * and initializes the upload.
public function onPageRequest(PageRequestEvent $event) { */
global $config, $user; public function onPageRequest(PageRequestEvent $event)
{
if ($event->page_matches ( "cron_upload" )) { global $config, $user;
$this->upload_key = $config->get_string ( "cron_uploader_key", "" );
if ($event->page_matches("cron_upload")) {
// If the key is in the url, upload $this->upload_key = $config->get_string("cron_uploader_key", "");
if ($this->upload_key != "" && $event->get_arg ( 0 ) == $this->upload_key) {
// log in as admin // If the key is in the url, upload
$this->process_upload(); // Start upload if ($this->upload_key != "" && $event->get_arg(0) == $this->upload_key) {
} // log in as admin
else if ($user->is_admin()) { $this->process_upload(); // Start upload
$this->set_dir(); } elseif ($user->is_admin()) {
$this->display_documentation(); $this->set_dir();
} $this->display_documentation();
}
} }
} }
private function display_documentation() { private function display_documentation()
global $page; {
$this->set_dir(); // Determines path to cron_uploader_dir global $page;
$this->set_dir(); // Determines path to cron_uploader_dir
$queue_dir = $this->root_dir . "/queue";
$uploaded_dir = $this->root_dir . "/uploaded"; $queue_dir = $this->root_dir . "/queue";
$failed_dir = $this->root_dir . "/failed_to_upload"; $uploaded_dir = $this->root_dir . "/uploaded";
$failed_dir = $this->root_dir . "/failed_to_upload";
$queue_dirinfo = $this->scan_dir($queue_dir);
$uploaded_dirinfo = $this->scan_dir($uploaded_dir); $queue_dirinfo = $this->scan_dir($queue_dir);
$failed_dirinfo = $this->scan_dir($failed_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"; $cron_url = make_http(make_link("/cron_upload/" . $this->upload_key));
$log_path = $this->root_dir . "/uploads.log"; $cron_cmd = "curl --silent $cron_url";
$log_path = $this->root_dir . "/uploads.log";
$info_html = "<b>Information</b>
$info_html = "<b>Information</b>
<br> <br>
<table style='width:470px;'> <table style='width:470px;'>
<tr> <tr>
@ -104,8 +105,8 @@ class CronUploader extends Extension {
<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br> <br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br>
Create a cron job with the command above.<br/> Create a cron job with the command above.<br/>
Read the documentation if you're not sure what to do.<br>"; Read the documentation if you're not sure what to do.<br>";
$install_html = " $install_html = "
This cron uploader is fairly easy to use but has to be configured first. This cron uploader is fairly easy to use but has to be configured first.
<br />1. Install & activate this plugin. <br />1. Install & activate this plugin.
<br /> <br />
@ -129,289 +130,308 @@ class CronUploader extends Extension {
<br />So when you want to manually upload an image, all you have to do is open the link once. <br />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 />This link can be found under 'Cron Command' in the board config, just remove the 'wget ' part and only the url remains.
<br />(<b>$cron_url</b>)"; <br />(<b>$cron_url</b>)";
$block = new Block("Cron Uploader", $info_html, "main", 10); $block = new Block("Cron Uploader", $info_html, "main", 10);
$block_install = new Block("Installation Guide", $install_html, "main", 20); $block_install = new Block("Installation Guide", $install_html, "main", 20);
$page->add_block($block); $page->add_block($block);
$page->add_block($block_install); $page->add_block($block_install);
} }
public function onInitExt(InitExtEvent $event) { public function onInitExt(InitExtEvent $event)
global $config; {
// Set default values global $config;
if ($config->get_string("cron_uploader_key", "")) { // Set default values
$this->upload_key = $this->generate_key (); if ($config->get_string("cron_uploader_key", "")) {
$this->upload_key = $this->generate_key();
$config->set_default_int ( 'cron_uploader_count', 1 );
$config->set_default_string ( 'cron_uploader_key', $this->upload_key ); $config->set_default_int('cron_uploader_count', 1);
$this->set_dir(); $config->set_default_string('cron_uploader_key', $this->upload_key);
} $this->set_dir();
} }
}
public function onSetupBuilding(SetupBuildingEvent $event) {
$this->set_dir(); public function onSetupBuilding(SetupBuildingEvent $event)
{
$cron_url = make_http(make_link("/cron_upload/" . $this->upload_key)); $this->set_dir();
$cron_cmd = "curl --silent $cron_url";
$documentation_link = make_http(make_link("cron_upload")); $cron_url = make_http(make_link("/cron_upload/" . $this->upload_key));
$cron_cmd = "curl --silent $cron_url";
$sb = new SetupBlock ( "Cron Uploader" ); $documentation_link = make_http(make_link("cron_upload"));
$sb->add_label ( "<b>Settings</b><br>" );
$sb->add_int_option ( "cron_uploader_count", "How many to upload each time" ); $sb = new SetupBlock("Cron Uploader");
$sb->add_text_option ( "cron_uploader_dir", "<br>Set Cron Uploader root directory<br>"); $sb->add_label("<b>Settings</b><br>");
$sb->add_int_option("cron_uploader_count", "How many to upload each time");
$sb->add_label ("<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br> $sb->add_text_option("cron_uploader_dir", "<br>Set Cron Uploader root directory<br>");
$sb->add_label("<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br>
Create a cron job with the command above.<br/> Create a cron job with the command above.<br/>
<a href='$documentation_link'>Read the documentation</a> if you're not sure what to do."); <a href='$documentation_link'>Read the documentation</a> if you're not sure what to do.");
$event->panel->add_block ( $sb ); $event->panel->add_block($sb);
} }
/* /*
* Generates a unique key for the website to prevent unauthorized access. * Generates a unique key for the website to prevent unauthorized access.
*/ */
private function generate_key() { private function generate_key()
$length = 20; {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $length = 20;
$randomString = ''; $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for($i = 0; $i < $length; $i ++) {
$randomString .= $characters [rand ( 0, strlen ( $characters ) - 1 )]; for ($i = 0; $i < $length; $i ++) {
} $randomString .= $characters [rand(0, strlen($characters) - 1)];
}
return $randomString;
} return $randomString;
}
/*
* Set the directory for the image queue. If no directory was given, set it to the default directory. /*
*/ * Set the directory for the image queue. If no directory was given, set it to the default directory.
private function set_dir() { */
global $config; private function set_dir()
// Determine directory (none = default) {
global $config;
$dir = $config->get_string("cron_uploader_dir", ""); // Determine directory (none = default)
// Sets new default dir if not in config yet/anymore $dir = $config->get_string("cron_uploader_dir", "");
if ($dir == "") {
$dir = data_path("cron_uploader"); // Sets new default dir if not in config yet/anymore
$config->set_string ('cron_uploader_dir', $dir); if ($dir == "") {
} $dir = data_path("cron_uploader");
$config->set_string('cron_uploader_dir', $dir);
// Make the directory if it doesn't exist yet }
if (!is_dir($dir . "/queue/"))
mkdir ( $dir . "/queue/", 0775, true ); // Make the directory if it doesn't exist yet
if (!is_dir($dir . "/uploaded/")) if (!is_dir($dir . "/queue/")) {
mkdir ( $dir . "/uploaded/", 0775, true ); mkdir($dir . "/queue/", 0775, true);
if (!is_dir($dir . "/failed_to_upload/")) }
mkdir ( $dir . "/failed_to_upload/", 0775, true ); if (!is_dir($dir . "/uploaded/")) {
mkdir($dir . "/uploaded/", 0775, true);
$this->root_dir = $dir; }
return $dir; if (!is_dir($dir . "/failed_to_upload/")) {
} mkdir($dir . "/failed_to_upload/", 0775, true);
}
/**
* Returns amount of files & total size of dir. $this->root_dir = $dir;
*/ return $dir;
function scan_dir(string $path): array{ }
$ite=new RecursiveDirectoryIterator($path);
/**
$bytestotal=0; * Returns amount of files & total size of dir.
$nbfiles=0; */
foreach (new RecursiveIteratorIterator($ite) as $filename=>$cur) { public function scan_dir(string $path): array
$filesize = $cur->getSize(); {
$bytestotal += $filesize; $ite=new RecursiveDirectoryIterator($path);
$nbfiles++;
} $bytestotal=0;
$nbfiles=0;
$size_mb = $bytestotal / 1048576; // to mb foreach (new RecursiveIteratorIterator($ite) as $filename=>$cur) {
$size_mb = number_format($size_mb, 2, '.', ''); $filesize = $cur->getSize();
return array('total_files'=>$nbfiles,'total_mb'=>$size_mb); $bytestotal += $filesize;
} $nbfiles++;
}
/**
* Uploads the image & handles everything $size_mb = $bytestotal / 1048576; // to mb
*/ $size_mb = number_format($size_mb, 2, '.', '');
public function process_upload(int $upload_count = 0): bool { return ['total_files'=>$nbfiles,'total_mb'=>$size_mb];
global $config; }
set_time_limit(0);
$this->set_dir(); /**
$this->generate_image_queue(); * Uploads the image & handles everything
*/
// Gets amount of imgs to upload public function process_upload(int $upload_count = 0): bool
if ($upload_count == 0) $upload_count = $config->get_int ("cron_uploader_count", 1); {
global $config;
// Throw exception if there's nothing in the queue set_time_limit(0);
if (count($this->image_queue) == 0) { $this->set_dir();
$this->add_upload_info("Your queue is empty so nothing could be uploaded."); $this->generate_image_queue();
$this->handle_log();
return false; // Gets amount of imgs to upload
} if ($upload_count == 0) {
$upload_count = $config->get_int("cron_uploader_count", 1);
// Randomize Images }
shuffle($this->image_queue);
// Throw exception if there's nothing in the queue
if (count($this->image_queue) == 0) {
$this->add_upload_info("Your queue is empty so nothing could be uploaded.");
$this->handle_log();
return false;
}
// Randomize Images
shuffle($this->image_queue);
// Upload the file(s) // Upload the file(s)
for ($i = 0; $i < $upload_count; $i++) { for ($i = 0; $i < $upload_count; $i++) {
$img = $this->image_queue[$i]; $img = $this->image_queue[$i];
try { try {
$this->add_image($img[0], $img[1], $img[2]); $this->add_image($img[0], $img[1], $img[2]);
$this->move_uploaded($img[0], $img[1], false); $this->move_uploaded($img[0], $img[1], false);
} catch (Exception $e) {
} $this->move_uploaded($img[0], $img[1], true);
catch (Exception $e) { }
$this->move_uploaded($img[0], $img[1], true);
} // Remove img from queue array
unset($this->image_queue[$i]);
// Remove img from queue array }
unset($this->image_queue[$i]);
} // Display & save upload log
$this->handle_log();
// Display & save upload log
$this->handle_log(); return true;
}
return true;
} private function move_uploaded($path, $filename, $corrupt = false)
{
private function move_uploaded($path, $filename, $corrupt = false) { global $config;
global $config;
// Create
// Create $newDir = $this->root_dir;
$newDir = $this->root_dir;
// Determine which dir to move to
// Determine which dir to move to if ($corrupt) {
if ($corrupt) { // Move to corrupt dir
// Move to corrupt dir $newDir .= "/failed_to_upload/";
$newDir .= "/failed_to_upload/"; $info = "ERROR: Image was not uploaded.";
$info = "ERROR: Image was not uploaded."; } else {
} $newDir .= "/uploaded/";
else { $info = "Image successfully uploaded. ";
$newDir .= "/uploaded/"; }
$info = "Image successfully uploaded. ";
} // move file to correct dir
rename($path, $newDir.$filename);
// move file to correct dir
rename($path, $newDir.$filename); $this->add_upload_info($info . "Image \"$filename\" moved from queue to \"$newDir\".");
}
$this->add_upload_info($info . "Image \"$filename\" moved from queue to \"$newDir\".");
}
/** /**
* Generate the necessary DataUploadEvent for a given image and tags. * Generate the necessary DataUploadEvent for a given image and tags.
*/ */
private function add_image(string $tmpname, string $filename, string $tags) { private function add_image(string $tmpname, string $filename, string $tags)
assert ( file_exists ( $tmpname ) ); {
assert(file_exists($tmpname));
$pathinfo = pathinfo ( $filename );
if (! array_key_exists ( 'extension', $pathinfo )) { $pathinfo = pathinfo($filename);
throw new UploadException ( "File has no extension" ); if (! array_key_exists('extension', $pathinfo)) {
} throw new UploadException("File has no extension");
$metadata = array(); }
$metadata ['filename'] = $pathinfo ['basename']; $metadata = [];
$metadata ['extension'] = $pathinfo ['extension']; $metadata ['filename'] = $pathinfo ['basename'];
$metadata ['tags'] = array(); // = $tags; doesn't work when not logged in here $metadata ['extension'] = $pathinfo ['extension'];
$metadata ['source'] = null; $metadata ['tags'] = []; // = $tags; doesn't work when not logged in here
$event = new DataUploadEvent ( $tmpname, $metadata ); $metadata ['source'] = null;
send_event ( $event ); $event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
// Generate info message
$infomsg = ""; // Will contain info message // Generate info message
if ($event->image_id == -1) $infomsg = ""; // Will contain info message
$infomsg = "File type not recognised. Filename: {$filename}"; if ($event->image_id == -1) {
else $infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename} - Tags: {$tags}"; $infomsg = "File type not recognised. Filename: {$filename}";
$msgNumber = $this->add_upload_info($infomsg); } else {
$infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename} - Tags: {$tags}";
// Set tags }
$img = Image::by_id($event->image_id); $msgNumber = $this->add_upload_info($infomsg);
$img->set_tags(Tag::explode($tags));
} // Set tags
$img = Image::by_id($event->image_id);
private function generate_image_queue($base = "", $subdir = "") { $img->set_tags(Tag::explode($tags));
if ($base == "") }
$base = $this->root_dir . "/queue";
private function generate_image_queue($base = "", $subdir = "")
if (! is_dir ( $base )) { {
$this->add_upload_info("Image Queue Directory could not be found at \"$base\"."); if ($base == "") {
return array(); $base = $this->root_dir . "/queue";
} }
foreach ( glob ( "$base/$subdir/*" ) as $fullpath ) { if (! is_dir($base)) {
$fullpath = str_replace ( "//", "/", $fullpath ); $this->add_upload_info("Image Queue Directory could not be found at \"$base\".");
//$shortpath = str_replace ( $base, "", $fullpath ); return [];
}
if (is_link ( $fullpath )) {
// ignore foreach (glob("$base/$subdir/*") as $fullpath) {
} else if (is_dir ( $fullpath )) { $fullpath = str_replace("//", "/", $fullpath);
$this->generate_image_queue ( $base, str_replace ( $base, "", $fullpath ) ); //$shortpath = str_replace ( $base, "", $fullpath );
} else {
$pathinfo = pathinfo ( $fullpath ); if (is_link($fullpath)) {
$matches = array (); // ignore
} elseif (is_dir($fullpath)) {
if (preg_match ( "/\d+ - (.*)\.([a-zA-Z]+)/", $pathinfo ["basename"], $matches )) { $this->generate_image_queue($base, str_replace($base, "", $fullpath));
$tags = $matches [1]; } else {
} else { $pathinfo = pathinfo($fullpath);
$tags = $subdir; $matches = [];
$tags = str_replace ( "/", " ", $tags );
$tags = str_replace ( "__", " ", $tags ); if (preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", $pathinfo ["basename"], $matches)) {
if ($tags == "") $tags = " "; $tags = $matches [1];
$tags = trim ( $tags ); } else {
} $tags = $subdir;
$tags = str_replace("/", " ", $tags);
$img = array ( $tags = str_replace("__", " ", $tags);
0 => $fullpath, if ($tags == "") {
1 => $pathinfo ["basename"], $tags = " ";
2 => $tags }
); $tags = trim($tags);
array_push ($this->image_queue, $img ); }
}
} $img = [
} 0 => $fullpath,
1 => $pathinfo ["basename"],
/** 2 => $tags
* Adds a message to the info being published at the end ];
*/ array_push($this->image_queue, $img);
private function add_upload_info(string $text, int $addon = 0): int { }
$info = $this->upload_info; }
$time = "[" .date('Y-m-d H:i:s'). "]"; }
// If addon function is not used /**
if ($addon == 0) { * Adds a message to the info being published at the end
$this->upload_info .= "$time $text\r\n"; */
private function add_upload_info(string $text, int $addon = 0): int
// Returns the number of the current line {
$currentLine = substr_count($this->upload_info, "\n") -1; $info = $this->upload_info;
return $currentLine; $time = "[" .date('Y-m-d H:i:s'). "]";
}
// If addon function is not used
// else if addon function is used, select the line & modify it if ($addon == 0) {
$lines = substr($info, "\n"); // Seperate the string to array in lines $this->upload_info .= "$time $text\r\n";
$lines[$addon] = "$lines[$addon] $text"; // Add the content to the line
$this->upload_info = implode("\n", $lines); // Put string back together & update // Returns the number of the current line
$currentLine = substr_count($this->upload_info, "\n") -1;
return $addon; // Return line number return $currentLine;
} }
/** // else if addon function is used, select the line & modify it
* This is run at the end to display & save the log. $lines = substr($info, "\n"); // Seperate the string to array in lines
*/ $lines[$addon] = "$lines[$addon] $text"; // Add the content to the line
private function handle_log() { $this->upload_info = implode("\n", $lines); // Put string back together & update
global $page;
return $addon; // Return line number
// Display message }
$page->set_mode("data");
$page->set_type("text/plain"); /**
$page->set_data($this->upload_info); * This is run at the end to display & save the log.
*/
// Save log private function handle_log()
$log_path = $this->root_dir . "/uploads.log"; {
global $page;
if (file_exists($log_path))
$prev_content = file_get_contents($log_path); // Display message
else $prev_content = ""; $page->set_mode("data");
$page->set_type("text/plain");
$content = $prev_content ."\r\n".$this->upload_info; $page->set_data($this->upload_info);
file_put_contents ($log_path, $content);
} // 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);
}
} }

View File

@ -8,65 +8,75 @@
* Documentation: * Documentation:
* When you go to board config you can find a block named Custom HTML Headers. * When you go to board config you can find a block named Custom HTML Headers.
* In that block you can simply place any thing you can place within &lt;head&gt;&lt;/head&gt; * In that block you can simply place any thing you can place within &lt;head&gt;&lt;/head&gt;
* *
* This can be useful if you want to add website tracking code or other javascript. * This can be useful if you want to add website tracking code or other javascript.
* NOTE: Only use if you know what you're doing. * NOTE: Only use if you know what you're doing.
* *
* You can also add your website name as prefix or suffix to the title of each page on your website. * You can also add your website name as prefix or suffix to the title of each page on your website.
*/ */
class custom_html_headers extends Extension { class custom_html_headers extends Extension
{
# Adds setup block for custom <head> content # Adds setup block for custom <head> content
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event)
$sb = new SetupBlock("Custom HTML Headers"); {
$sb = new SetupBlock("Custom HTML Headers");
// custom headers // custom headers
$sb->add_longtext_option("custom_html_headers", $sb->add_longtext_option(
"HTML Code to place within &lt;head&gt;&lt;/head&gt; on all pages<br>"); "custom_html_headers",
"HTML Code to place within &lt;head&gt;&lt;/head&gt; on all pages<br>"
);
// modified title // modified title
$sb->add_choice_option("sitename_in_title", array( $sb->add_choice_option("sitename_in_title", [
"none" => 0, "none" => 0,
"as prefix" => 1, "as prefix" => 1,
"as suffix" => 2 "as suffix" => 2
), "<br>Add website name in title"); ], "<br>Add website name in title");
$event->panel->add_block($sb); $event->panel->add_block($sb);
} }
public function onInitExt(InitExtEvent $event) { public function onInitExt(InitExtEvent $event)
global $config; {
$config->set_default_int("sitename_in_title", 0); global $config;
} $config->set_default_int("sitename_in_title", 0);
}
# Load Analytics tracking code on page request # Load Analytics tracking code on page request
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
$this->handle_custom_html_headers(); {
$this->handle_modified_page_title(); $this->handle_custom_html_headers();
} $this->handle_modified_page_title();
}
private function handle_custom_html_headers() { private function handle_custom_html_headers()
global $config, $page; {
global $config, $page;
$header = $config->get_string('custom_html_headers',''); $header = $config->get_string('custom_html_headers', '');
if ($header!='') $page->add_html_header($header); if ($header!='') {
$page->add_html_header($header);
} }
}
private function handle_modified_page_title() { private function handle_modified_page_title()
global $config, $page; {
global $config, $page;
// get config values // get config values
$site_title = $config->get_string("title"); $site_title = $config->get_string("title");
$sitename_in_title = $config->get_int("sitename_in_title"); $sitename_in_title = $config->get_int("sitename_in_title");
// if feature is enabled & sitename isn't already in title // if feature is enabled & sitename isn't already in title
// (can occur on index & other pages) // (can occur on index & other pages)
if ($sitename_in_title != 0 && !strstr($page->title, $site_title)) if ($sitename_in_title != 0 && !strstr($page->title, $site_title)) {
{ if ($sitename_in_title == 1) {
if ($sitename_in_title == 1) $page->title = "$site_title - $page->title";
$page->title = "$site_title - $page->title"; // as prefix } // as prefix
else if ($sitename_in_title == 2) elseif ($sitename_in_title == 2) {
$page->title = "$page->title - $site_title"; // as suffix $page->title = "$page->title - $site_title";
} } // as suffix
} }
}
} }

View File

@ -47,222 +47,222 @@ Completely compatibility will probably involve a rewrite with a different URL
*/ */
class DanbooruApi extends Extension { class DanbooruApi extends Extension
public function onPageRequest(PageRequestEvent $event) { {
if($event->page_matches("api") && ($event->get_arg(0) == 'danbooru')) { public function onPageRequest(PageRequestEvent $event)
$this->api_danbooru($event); {
} if ($event->page_matches("api") && ($event->get_arg(0) == 'danbooru')) {
} $this->api_danbooru($event);
}
}
// Danbooru API // Danbooru API
private function api_danbooru(PageRequestEvent $event) { private function api_danbooru(PageRequestEvent $event)
global $page; {
$page->set_mode("data"); global $page;
$page->set_mode("data");
if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) { if (($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) {
// No XML data is returned from this function // No XML data is returned from this function
$page->set_type("text/plain"); $page->set_type("text/plain");
$this->api_add_post(); $this->api_add_post();
} } elseif (($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) {
$page->set_type("application/xml");
$page->set_data($this->api_find_posts());
} elseif ($event->get_arg(1) == 'find_tags') {
$page->set_type("application/xml");
$page->set_data($this->api_find_tags());
}
elseif(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) { // Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
$page->set_type("application/xml"); // Shimmie view page
$page->set_data($this->api_find_posts()); // Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123
} // This redirects that to http://shimmie/post/view/123
elseif (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) {
$fixedlocation = make_link("post/view/" . $event->get_arg(3));
$page->set_mode("redirect");
$page->set_redirect($fixedlocation);
}
}
elseif($event->get_arg(1) == 'find_tags') { /**
$page->set_type("application/xml"); * Turns out I use this a couple times so let's make it a utility function
$page->set_data($this->api_find_tags()); * Authenticates a user based on the contents of the login and password parameters
} * or makes them anonymous. Does not set any cookies or anything permanent.
*/
private function authenticate_user()
{
global $config, $user;
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper if (isset($_REQUEST['login']) && isset($_REQUEST['password'])) {
// Shimmie view page // Get this user from the db, if it fails the user becomes anonymous
// Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123 // Code borrowed from /ext/user
// This redirects that to http://shimmie/post/view/123 $name = $_REQUEST['login'];
elseif(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) { $pass = $_REQUEST['password'];
$fixedlocation = make_link("post/view/" . $event->get_arg(3)); $duser = User::by_name_and_pass($name, $pass);
$page->set_mode("redirect"); if (!is_null($duser)) {
$page->set_redirect($fixedlocation); $user = $duser;
} } else {
} $user = User::by_id($config->get_int("anon_id", 0));
}
}
}
/** /**
* Turns out I use this a couple times so let's make it a utility function
* Authenticates a user based on the contents of the login and password parameters
* or makes them anonymous. Does not set any cookies or anything permanent.
*/
private function authenticate_user() {
global $config, $user;
if(isset($_REQUEST['login']) && isset($_REQUEST['password'])) {
// Get this user from the db, if it fails the user becomes anonymous
// Code borrowed from /ext/user
$name = $_REQUEST['login'];
$pass = $_REQUEST['password'];
$duser = User::by_name_and_pass($name, $pass);
if(!is_null($duser)) {
$user = $duser;
}
else {
$user = User::by_id($config->get_int("anon_id", 0));
}
}
}
/**
* find_tags() * find_tags()
* Find all tags that match the search criteria. * Find all tags that match the search criteria.
* *
* Parameters * Parameters
* - id: A comma delimited list of tag id numbers. * - id: A comma delimited list of tag id numbers.
* - name: A comma delimited list of tag names. * - name: A comma delimited list of tag names.
* - tags: any typical tag query. See Tag#parse_query for details. * - tags: any typical tag query. See Tag#parse_query for details.
* - after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh * - after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh
* */
* #return string private function api_find_tags(): string
*/ {
private function api_find_tags() { global $database;
global $database; $results = [];
$results = array(); if (isset($_GET['id'])) {
if(isset($_GET['id'])) { $idlist = explode(",", $_GET['id']);
$idlist = explode(",", $_GET['id']); foreach ($idlist as $id) {
foreach ($idlist as $id) { $sqlresult = $database->get_all(
$sqlresult = $database->get_all( "SELECT id,tag,count FROM tags WHERE id = ?",
"SELECT id,tag,count FROM tags WHERE id = ?", [$id]
array($id)); );
foreach ($sqlresult as $row) { foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']); $results[] = [$row['count'], $row['tag'], $row['id']];
} }
} }
} } elseif (isset($_GET['name'])) {
elseif(isset($_GET['name'])) { $namelist = explode(",", $_GET['name']);
$namelist = explode(",", $_GET['name']); foreach ($namelist as $name) {
foreach ($namelist as $name) { $sqlresult = $database->get_all(
$sqlresult = $database->get_all( "SELECT id,tag,count FROM tags WHERE tag = ?",
"SELECT id,tag,count FROM tags WHERE tag = ?", [$name]
array($name)); );
foreach ($sqlresult as $row) { foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']); $results[] = [$row['count'], $row['tag'], $row['id']];
} }
} }
} }
// Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags // Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags
elseif(false && isset($_GET['tags'])) { elseif (false && isset($_GET['tags'])) {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0; $start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$tags = Tag::explode($_GET['tags']); $tags = Tag::explode($_GET['tags']);
} } else {
else { $start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0; $sqlresult = $database->get_all(
$sqlresult = $database->get_all( "SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC",
"SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC", [$start]
array($start)); );
foreach ($sqlresult as $row) { foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']); $results[] = [$row['count'], $row['tag'], $row['id']];
} }
} }
// Tag results collected, build XML output // Tag results collected, build XML output
$xml = "<tags>\n"; $xml = "<tags>\n";
foreach ($results as $tag) { foreach ($results as $tag) {
$xml .= xml_tag("tag", array( $xml .= xml_tag("tag", [
"type" => "0", "type" => "0",
"counts" => $tag[0], "counts" => $tag[0],
"name" => $tag[1], "name" => $tag[1],
"id" => $tag[2], "id" => $tag[2],
)); ]);
} }
$xml .= "</tags>"; $xml .= "</tags>";
return $xml; return $xml;
} }
/** /**
* find_posts() * find_posts()
* Find all posts that match the search criteria. Posts will be ordered by id descending. * Find all posts that match the search criteria. Posts will be ordered by id descending.
* *
* Parameters: * Parameters:
* - md5: md5 hash to search for (comma delimited) * - md5: md5 hash to search for (comma delimited)
* - id: id to search for (comma delimited) * - id: id to search for (comma delimited)
* - tags: what tags to search for * - tags: what tags to search for
* - limit: limit * - limit: limit
* - page: page number * - page: page number
* - after_id: limit results to posts added after this id * - after_id: limit results to posts added after this id
* *
* #return string * #return string
*/ */
private function api_find_posts() { private function api_find_posts()
$results = array(); {
$results = [];
$this->authenticate_user(); $this->authenticate_user();
$start = 0; $start = 0;
if(isset($_GET['md5'])) { if (isset($_GET['md5'])) {
$md5list = explode(",", $_GET['md5']); $md5list = explode(",", $_GET['md5']);
foreach ($md5list as $md5) { foreach ($md5list as $md5) {
$results[] = Image::by_hash($md5); $results[] = Image::by_hash($md5);
} }
$count = count($results); $count = count($results);
} } elseif (isset($_GET['id'])) {
elseif(isset($_GET['id'])) { $idlist = explode(",", $_GET['id']);
$idlist = explode(",", $_GET['id']); foreach ($idlist as $id) {
foreach ($idlist as $id) { $results[] = Image::by_id($id);
$results[] = Image::by_id($id); }
} $count = count($results);
$count = count($results); } else {
} $limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
else {
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
// Calculate start offset. // Calculate start offset.
if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1 if (isset($_GET['page'])) { // Danbooru API uses 'page' >= 1
$start = (int_escape($_GET['page']) - 1) * $limit; $start = (int_escape($_GET['page']) - 1) * $limit;
else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0 } elseif (isset($_GET['pid'])) { // Gelbooru API uses 'pid' >= 0
$start = int_escape($_GET['pid']) * $limit; $start = int_escape($_GET['pid']) * $limit;
else } else {
$start = 0; $start = 0;
}
$tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array(); $tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : [];
$count = Image::count_images($tags); $count = Image::count_images($tags);
$results = Image::find_images(max($start, 0), min($limit, 100), $tags); $results = Image::find_images(max($start, 0), min($limit, 100), $tags);
} }
// Now we have the array $results filled with Image objects // Now we have the array $results filled with Image objects
// Let's display them // Let's display them
$xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n"; $xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n";
foreach ($results as $img) { foreach ($results as $img) {
// Sanity check to see if $img is really an image object // Sanity check to see if $img is really an image object
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this // If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
if (!is_object($img)) if (!is_object($img)) {
continue; continue;
$taglist = $img->get_tag_list(); }
$owner = $img->get_owner(); $taglist = $img->get_tag_list();
$previewsize = get_thumbnail_size($img->width, $img->height); $owner = $img->get_owner();
$xml .= xml_tag("post", array( $previewsize = get_thumbnail_size($img->width, $img->height);
"id" => $img->id, $xml .= xml_tag("post", [
"md5" => $img->hash, "id" => $img->id,
"file_name" => $img->filename, "md5" => $img->hash,
"file_url" => $img->get_image_link(), "file_name" => $img->filename,
"height" => $img->height, "file_url" => $img->get_image_link(),
"width" => $img->width, "height" => $img->height,
"preview_url" => $img->get_thumb_link(), "width" => $img->width,
"preview_height" => $previewsize[1], "preview_url" => $img->get_thumb_link(),
"preview_width" => $previewsize[0], "preview_height" => $previewsize[1],
"rating" => "u", "preview_width" => $previewsize[0],
"date" => $img->posted, "rating" => "u",
"is_warehoused" => false, "date" => $img->posted,
"tags" => $taglist, "is_warehoused" => false,
"source" => $img->source, "tags" => $taglist,
"score" => 0, "source" => $img->source,
"author" => $owner->name "score" => 0,
)); "author" => $owner->name
} ]);
$xml .= "</posts>"; }
return $xml; $xml .= "</posts>";
} return $xml;
}
/** /**
* add_post() * add_post()
* Adds a post to the database. * Adds a post to the database.
* *
* Parameters: * Parameters:
* - login: login * - login: login
* - password: password * - password: password
@ -272,124 +272,127 @@ class DanbooruApi extends Extension {
* - tags: list of tags as a string, delimited by whitespace * - tags: list of tags as a string, delimited by whitespace
* - md5: MD5 hash of upload in hexadecimal format * - md5: MD5 hash of upload in hexadecimal format
* - rating: rating of the post. can be explicit, questionable, or safe. **IGNORED** * - rating: rating of the post. can be explicit, questionable, or safe. **IGNORED**
* *
* Notes: * Notes:
* - The only necessary parameter is tags and either file or source. * - The only necessary parameter is tags and either file or source.
* - If you want to sign your post, you need a way to authenticate your account, either by supplying login and password, or by supplying a cookie. * - If you want to sign your post, you need a way to authenticate your account, either by supplying login and password, or by supplying a cookie.
* - If an account is not supplied or if it doesn‘t authenticate, he post will be added anonymously. * - If an account is not supplied or if it doesn‘t authenticate, he post will be added anonymously.
* - If the md5 parameter is supplied and does not match the hash of what‘s on the server, the post is rejected. * - If the md5 parameter is supplied and does not match the hash of what‘s on the server, the post is rejected.
* *
* Response * Response
* The response depends on the method used: * The response depends on the method used:
* Post: * Post:
* - X-Danbooru-Location set to the URL for newly uploaded post. * - X-Danbooru-Location set to the URL for newly uploaded post.
* Get: * Get:
* - Redirected to the newly uploaded post. * - Redirected to the newly uploaded post.
*/ */
private function api_add_post() { private function api_add_post()
global $user, $config, $page; {
$danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path global $user, $config, $page;
$danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path
// Check first if a login was supplied, if it wasn't check if the user is logged in via cookie // Check first if a login was supplied, if it wasn't check if the user is logged in via cookie
// If all that fails, it's an anonymous upload // If all that fails, it's an anonymous upload
$this->authenticate_user(); $this->authenticate_user();
// Now we check if a file was uploaded or a url was provided to transload // Now we check if a file was uploaded or a url was provided to transload
// Much of this code is borrowed from /ext/upload // Much of this code is borrowed from /ext/upload
if (!$user->can("create_image")) { if (!$user->can("create_image")) {
$page->set_code(409); $page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: authentication error"); $page->add_http_header("X-Danbooru-Errors: authentication error");
return; return;
} }
if (isset($_FILES['file'])) { // A file was POST'd in if (isset($_FILES['file'])) { // A file was POST'd in
$file = $_FILES['file']['tmp_name']; $file = $_FILES['file']['tmp_name'];
$filename = $_FILES['file']['name']; $filename = $_FILES['file']['name'];
// If both a file is posted and a source provided, I'm assuming source is the source of the file // If both a file is posted and a source provided, I'm assuming source is the source of the file
if (isset($_REQUEST['source']) && !empty($_REQUEST['source'])) { if (isset($_REQUEST['source']) && !empty($_REQUEST['source'])) {
$source = $_REQUEST['source']; $source = $_REQUEST['source'];
} else { } else {
$source = null; $source = null;
} }
} elseif (isset($_FILES['post'])) { } elseif (isset($_FILES['post'])) {
$file = $_FILES['post']['tmp_name']['file']; $file = $_FILES['post']['tmp_name']['file'];
$filename = $_FILES['post']['name']['file']; $filename = $_FILES['post']['name']['file'];
if (isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source'])) { if (isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source'])) {
$source = $_REQUEST['post']['source']; $source = $_REQUEST['post']['source'];
} else { } else {
$source = null; $source = null;
} }
} elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided } elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided
$source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source']; $source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
$file = tempnam("/tmp", "shimmie_transload"); $file = tempnam("/tmp", "shimmie_transload");
$ok = transload($source, $file); $ok = transload($source, $file);
if (!$ok) { if (!$ok) {
$page->set_code(409); $page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: fopen read error"); $page->add_http_header("X-Danbooru-Errors: fopen read error");
return; return;
} }
$filename = basename($source); $filename = basename($source);
} else { // Nothing was specified at all } else { // Nothing was specified at all
$page->set_code(409); $page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: no input files"); $page->add_http_header("X-Danbooru-Errors: no input files");
return; return;
} }
// Get tags out of url // Get tags out of url
$posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']); $posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']);
// Was an md5 supplied? Does it match the file hash? // Was an md5 supplied? Does it match the file hash?
$hash = md5_file($file); $hash = md5_file($file);
if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) { if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) {
$page->set_code(409); $page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: md5 mismatch"); $page->add_http_header("X-Danbooru-Errors: md5 mismatch");
return; return;
} }
// Upload size checking is now performed in the upload extension // Upload size checking is now performed in the upload extension
// It is also currently broken due to some confusion over file variable ($tmp_filename?) // It is also currently broken due to some confusion over file variable ($tmp_filename?)
// Does it exist already? // Does it exist already?
$existing = Image::by_hash($hash); $existing = Image::by_hash($hash);
if (!is_null($existing)) { if (!is_null($existing)) {
$page->set_code(409); $page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: duplicate"); $page->add_http_header("X-Danbooru-Errors: duplicate");
$existinglink = make_link("post/view/" . $existing->id); $existinglink = make_link("post/view/" . $existing->id);
if ($danboorup_kludge) $existinglink = make_http($existinglink); if ($danboorup_kludge) {
$page->add_http_header("X-Danbooru-Location: $existinglink"); $existinglink = make_http($existinglink);
return; }
} $page->add_http_header("X-Danbooru-Location: $existinglink");
return;
}
// Fire off an event which should process the new file and add it to the db // Fire off an event which should process the new file and add it to the db
$fileinfo = pathinfo($filename); $fileinfo = pathinfo($filename);
$metadata = array(); $metadata = [];
$metadata['filename'] = $fileinfo['basename']; $metadata['filename'] = $fileinfo['basename'];
$metadata['extension'] = $fileinfo['extension']; $metadata['extension'] = $fileinfo['extension'];
$metadata['tags'] = $posttags; $metadata['tags'] = $posttags;
$metadata['source'] = $source; $metadata['source'] = $source;
//log_debug("danbooru_api","========== NEW($filename) ========="); //log_debug("danbooru_api","========== NEW($filename) =========");
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")..."); //log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
try { try {
$nevent = new DataUploadEvent($file, $metadata); $nevent = new DataUploadEvent($file, $metadata);
//log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")"); //log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
send_event($nevent); send_event($nevent);
// If it went ok, grab the id for the newly uploaded image and pass it in the header // If it went ok, grab the id for the newly uploaded image and pass it in the header
$newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error? $newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error?
$newid = make_link("post/view/" . $newimg->id); $newid = make_link("post/view/" . $newimg->id);
if ($danboorup_kludge) $newid = make_http($newid); if ($danboorup_kludge) {
$newid = make_http($newid);
}
// Did we POST or GET this call? // Did we POST or GET this call?
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$page->add_http_header("X-Danbooru-Location: $newid"); $page->add_http_header("X-Danbooru-Location: $newid");
} else { } else {
$page->add_http_header("Location: $newid"); $page->add_http_header("Location: $newid");
} }
} catch (UploadException $ex) { } catch (UploadException $ex) {
// Did something screw up? // Did something screw up?
$page->set_code(409); $page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage()); $page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage());
} }
} }
} }

View File

@ -1,23 +1,25 @@
<?php <?php
class DanbooruApiTest extends ShimmiePHPUnitTestCase { class DanbooruApiTest extends ShimmiePHPUnitTestCase
public function testSearch() { {
$this->log_in_as_admin(); public function testSearch()
{
$this->log_in_as_admin();
$image_id = $this->post_image("tests/bedroom_workshop.jpg", "data"); $image_id = $this->post_image("tests/bedroom_workshop.jpg", "data");
$this->get_page("api/danbooru/find_posts"); $this->get_page("api/danbooru/find_posts");
$this->get_page("api/danbooru/find_posts?id=$image_id"); $this->get_page("api/danbooru/find_posts?id=$image_id");
$this->get_page("api/danbooru/find_posts?md5=17fc89f372ed3636e28bd25cc7f3bac1"); $this->get_page("api/danbooru/find_posts?md5=17fc89f372ed3636e28bd25cc7f3bac1");
$this->get_page("api/danbooru/find_tags"); $this->get_page("api/danbooru/find_tags");
$this->get_page("api/danbooru/find_tags?id=1"); $this->get_page("api/danbooru/find_tags?id=1");
$this->get_page("api/danbooru/find_tags?name=data"); $this->get_page("api/danbooru/find_tags?name=data");
$this->get_page("api/danbooru/post/show/$image_id"); $this->get_page("api/danbooru/post/show/$image_id");
//$this->assert_response(302); // FIXME //$this->assert_response(302); // FIXME
$this->get_page("post/list/md5:17fc89f372ed3636e28bd25cc7f3bac1/1"); $this->get_page("post/list/md5:17fc89f372ed3636e28bd25cc7f3bac1/1");
//$this->assert_title(new PatternExpectation("/^Image \d+: data/")); //$this->assert_title(new PatternExpectation("/^Image \d+: data/"));
//$this->click("Delete"); //$this->click("Delete");
} }
} }

View File

@ -12,35 +12,45 @@
* message specified in the box. * message specified in the box.
*/ */
class Downtime extends Extension { class Downtime extends Extension
public function get_priority(): int {return 10;} {
public function get_priority(): int
{
return 10;
}
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event)
$sb = new SetupBlock("Downtime"); {
$sb->add_bool_option("downtime", "Disable non-admin access: "); $sb = new SetupBlock("Downtime");
$sb->add_longtext_option("downtime_message", "<br>"); $sb->add_bool_option("downtime", "Disable non-admin access: ");
$event->panel->add_block($sb); $sb->add_longtext_option("downtime_message", "<br>");
} $event->panel->add_block($sb);
}
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
global $config, $page, $user; {
global $config, $page, $user;
if($config->get_bool("downtime")) { if ($config->get_bool("downtime")) {
if(!$user->can("ignore_downtime") && !$this->is_safe_page($event)) { if (!$user->can("ignore_downtime") && !$this->is_safe_page($event)) {
$msg = $config->get_string("downtime_message"); $msg = $config->get_string("downtime_message");
$this->theme->display_message($msg); $this->theme->display_message($msg);
if(!defined("UNITTEST")) { // hax D: if (!defined("UNITTEST")) { // hax D:
header("HTTP/1.0 {$page->code} Downtime"); header("HTTP/1.0 {$page->code} Downtime");
print($page->data); print($page->data);
exit; exit;
} }
} }
$this->theme->display_notification($page); $this->theme->display_notification($page);
} }
} }
private function is_safe_page(PageRequestEvent $event) { private function is_safe_page(PageRequestEvent $event)
if($event->page_matches("user_admin/login")) return true; {
else return false; if ($event->page_matches("user_admin/login")) {
} return true;
} else {
return false;
}
}
} }

View File

@ -1,39 +1,42 @@
<?php <?php
class DowntimeTest extends ShimmiePHPUnitTestCase { class DowntimeTest extends ShimmiePHPUnitTestCase
public function tearDown() { {
global $config; public function tearDown()
$config->set_bool("downtime", false); {
} global $config;
$config->set_bool("downtime", false);
}
public function testDowntime() { public function testDowntime()
global $config; {
global $config;
$config->set_string("downtime_message", "brb, unit testing"); $config->set_string("downtime_message", "brb, unit testing");
// downtime on // downtime on
$config->set_bool("downtime", true); $config->set_bool("downtime", true);
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("post/list"); $this->get_page("post/list");
$this->assert_text("DOWNTIME MODE IS ON!"); $this->assert_text("DOWNTIME MODE IS ON!");
$this->assert_response(200); $this->assert_response(200);
$this->log_in_as_user(); $this->log_in_as_user();
$this->get_page("post/list"); $this->get_page("post/list");
$this->assert_content("brb, unit testing"); $this->assert_content("brb, unit testing");
$this->assert_response(503); $this->assert_response(503);
// downtime off // downtime off
$config->set_bool("downtime", false); $config->set_bool("downtime", false);
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("post/list"); $this->get_page("post/list");
$this->assert_no_text("DOWNTIME MODE IS ON!"); $this->assert_no_text("DOWNTIME MODE IS ON!");
$this->assert_response(200); $this->assert_response(200);
$this->log_in_as_user(); $this->log_in_as_user();
$this->get_page("post/list"); $this->get_page("post/list");
$this->assert_no_content("brb, unit testing"); $this->assert_no_content("brb, unit testing");
$this->assert_response(200); $this->assert_response(200);
} }
} }

View File

@ -1,27 +1,35 @@
<?php <?php
class DowntimeTheme extends Themelet { class DowntimeTheme extends Themelet
/** {
* Show the admin that downtime mode is enabled /**
*/ * Show the admin that downtime mode is enabled
public function display_notification(Page $page) { */
$page->add_block(new Block("Downtime", public function display_notification(Page $page)
"<span style='font-size: 1.5em'><b><center>DOWNTIME MODE IS ON!</center></b></span>", "left", 0)); {
} $page->add_block(new Block(
"Downtime",
"<span style='font-size: 1.5em'><b><center>DOWNTIME MODE IS ON!</center></b></span>",
"left",
0
));
}
/** /**
* Display $message and exit * Display $message and exit
*/ */
public function display_message(string $message) { public function display_message(string $message)
global $config, $user, $page; {
$theme_name = $config->get_string('theme'); global $config, $user, $page;
$data_href = get_base_href(); $theme_name = $config->get_string('theme');
$login_link = make_link("user_admin/login"); $data_href = get_base_href();
$auth = $user->get_auth_html(); $login_link = make_link("user_admin/login");
$auth = $user->get_auth_html();
$page->set_mode('data'); $page->set_mode('data');
$page->set_code(503); $page->set_code(503);
$page->set_data(<<<EOD $page->set_data(
<<<EOD
<html> <html>
<head> <head>
<title>Downtime</title> <title>Downtime</title>
@ -59,5 +67,5 @@ class DowntimeTheme extends Themelet {
</html> </html>
EOD EOD
); );
} }
} }

View File

@ -16,26 +16,30 @@
/** /**
* Class Emoticons * Class Emoticons
*/ */
class Emoticons extends FormatterExtension { class Emoticons extends FormatterExtension
public function format(string $text): string { {
$data_href = get_base_href(); public function format(string $text): string
$text = preg_replace("/:([a-z]*?):/s", "<img src='$data_href/ext/emoticons/default/\\1.gif'>", $text); {
return $text; $data_href = get_base_href();
} $text = preg_replace("/:([a-z]*?):/s", "<img src='$data_href/ext/emoticons/default/\\1.gif'>", $text);
return $text;
}
public function strip(string $text): string { public function strip(string $text): string
return $text; {
} return $text;
}
} }
/** /**
* Class EmoticonList * Class EmoticonList
*/ */
class EmoticonList extends Extension { class EmoticonList extends Extension
public function onPageRequest(PageRequestEvent $event) { {
if($event->page_matches("emote/list")) { public function onPageRequest(PageRequestEvent $event)
$this->theme->display_emotes(glob("ext/emoticons/default/*")); {
} if ($event->page_matches("emote/list")) {
} $this->theme->display_emotes(glob("ext/emoticons/default/*"));
}
}
} }

View File

@ -1,19 +1,20 @@
<?php <?php
class EmoticonTest extends ShimmiePHPUnitTestCase { class EmoticonTest extends ShimmiePHPUnitTestCase
public function testEmoticons() { {
global $user; public function testEmoticons()
{
global $user;
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
send_event(new CommentPostingEvent($image_id, $user, ":cool: :beans:")); send_event(new CommentPostingEvent($image_id, $user, ":cool: :beans:"));
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_no_text(":cool:"); # FIXME: test for working image link $this->assert_no_text(":cool:"); # FIXME: test for working image link
//$this->assert_text(":beans:"); # FIXME: this should be left as-is //$this->assert_text(":beans:"); # FIXME: this should be left as-is
$this->get_page("emote/list"); $this->get_page("emote/list");
//$this->assert_text(":arrow:"); //$this->assert_text(":arrow:");
} }
} }

View File

@ -1,21 +1,24 @@
<?php <?php
class EmoticonListTheme extends Themelet { class EmoticonListTheme extends Themelet
public function display_emotes(array $list) { {
global $page; public function display_emotes(array $list)
$data_href = get_base_href(); {
$html = "<html><head><title>Emoticon list</title></head><body>"; global $page;
$html .= "<table><tr>"; $data_href = get_base_href();
$n = 1; $html = "<html><head><title>Emoticon list</title></head><body>";
foreach($list as $item) { $html .= "<table><tr>";
$pathinfo = pathinfo($item); $n = 1;
$name = $pathinfo["filename"]; foreach ($list as $item) {
$html .= "<td><img src='$data_href/$item'> :$name:</td>"; $pathinfo = pathinfo($item);
if($n++ % 3 == 0) $html .= "</tr><tr>"; $name = $pathinfo["filename"];
} $html .= "<td><img src='$data_href/$item'> :$name:</td>";
$html .= "</tr></table>"; if ($n++ % 3 == 0) {
$html .= "</body></html>"; $html .= "</tr><tr>";
$page->set_mode("data"); }
$page->set_data($html); }
} $html .= "</tr></table>";
$html .= "</body></html>";
$page->set_mode("data");
$page->set_data($html);
}
} }

View File

@ -12,74 +12,76 @@
* versions of PHP I should test with, etc. * versions of PHP I should test with, etc.
*/ */
class ET extends Extension { class ET extends Extension
public function onPageRequest(PageRequestEvent $event) { {
global $user; public function onPageRequest(PageRequestEvent $event)
if($event->page_matches("system_info")) { {
if($user->can("view_sysinfo")) { global $user;
$this->theme->display_info_page($this->get_info()); if ($event->page_matches("system_info")) {
} if ($user->can("view_sysinfo")) {
} $this->theme->display_info_page($this->get_info());
} }
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) { public function onUserBlockBuilding(UserBlockBuildingEvent $event)
global $user; {
if($user->can("view_sysinfo")) { global $user;
$event->add_link("System Info", make_link("system_info")); if ($user->can("view_sysinfo")) {
} $event->add_link("System Info", make_link("system_info"));
} }
}
/** /**
* Collect the information and return it in a keyed array. * Collect the information and return it in a keyed array.
*/ */
private function get_info() { private function get_info()
global $config, $database; {
global $config, $database;
$info = array(); $info = [];
$info['site_title'] = $config->get_string("title"); $info['site_title'] = $config->get_string("title");
$info['site_theme'] = $config->get_string("theme"); $info['site_theme'] = $config->get_string("theme");
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href(); $info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
$info['sys_shimmie'] = VERSION; $info['sys_shimmie'] = VERSION;
$info['sys_schema'] = $config->get_string("db_version"); $info['sys_schema'] = $config->get_string("db_version");
$info['sys_php'] = phpversion(); $info['sys_php'] = phpversion();
$info['sys_db'] = $database->get_driver_name(); $info['sys_db'] = $database->get_driver_name();
$info['sys_os'] = php_uname(); $info['sys_os'] = php_uname();
$info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " . $info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " .
to_shorthand_int(disk_total_space("./")); to_shorthand_int(disk_total_space("./"));
$info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown'; $info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown';
$info['thumb_engine'] = $config->get_string("thumb_engine"); $info['thumb_engine'] = $config->get_string("thumb_engine");
$info['thumb_quality'] = $config->get_int('thumb_quality'); $info['thumb_quality'] = $config->get_int('thumb_quality');
$info['thumb_width'] = $config->get_int('thumb_width'); $info['thumb_width'] = $config->get_int('thumb_width');
$info['thumb_height'] = $config->get_int('thumb_height'); $info['thumb_height'] = $config->get_int('thumb_height');
$info['thumb_mem'] = $config->get_int("thumb_mem_limit"); $info['thumb_mem'] = $config->get_int("thumb_mem_limit");
$info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images"); $info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images");
$info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments"); $info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments");
$info['stat_users'] = $database->get_one("SELECT COUNT(*) FROM users"); $info['stat_users'] = $database->get_one("SELECT COUNT(*) FROM users");
$info['stat_tags'] = $database->get_one("SELECT COUNT(*) FROM tags"); $info['stat_tags'] = $database->get_one("SELECT COUNT(*) FROM tags");
$info['stat_image_tags'] = $database->get_one("SELECT COUNT(*) FROM image_tags"); $info['stat_image_tags'] = $database->get_one("SELECT COUNT(*) FROM image_tags");
$els = array(); $els = [];
foreach(get_declared_classes() as $class) { foreach (get_declared_classes() as $class) {
$rclass = new ReflectionClass($class); $rclass = new ReflectionClass($class);
if($rclass->isAbstract()) { if ($rclass->isAbstract()) {
// don't do anything // don't do anything
} } elseif (is_subclass_of($class, "Extension")) {
elseif(is_subclass_of($class, "Extension")) { $els[] = $class;
$els[] = $class; }
} }
} $info['sys_extensions'] = join(', ', $els);
$info['sys_extensions'] = join(', ', $els);
//$cfs = array(); //$cfs = array();
//foreach($database->get_all("SELECT name, value FROM config") as $pair) { //foreach($database->get_all("SELECT name, value FROM config") as $pair) {
// $cfs[] = $pair['name']."=".$pair['value']; // $cfs[] = $pair['name']."=".$pair['value'];
//} //}
//$info[''] = "Config: ".join(", ", $cfs); //$info[''] = "Config: ".join(", ", $cfs);
return $info; return $info;
} }
} }

View File

@ -1,8 +1,10 @@
<?php <?php
class ETTest extends ShimmiePHPUnitTestCase { class ETTest extends ShimmiePHPUnitTestCase
public function testET() { {
$this->log_in_as_admin(); public function testET()
$this->get_page("system_info"); {
$this->assert_title("System Info"); $this->log_in_as_admin();
} $this->get_page("system_info");
$this->assert_title("System Info");
}
} }

View File

@ -1,22 +1,25 @@
<?php <?php
class ETTheme extends Themelet { class ETTheme extends Themelet
/* {
* Create a page showing info /*
* * Create a page showing info
* $info = an array of ($name => $value) *
*/ * $info = an array of ($name => $value)
public function display_info_page($info) { */
global $page; public function display_info_page($info)
{
global $page;
$page->set_title("System Info"); $page->set_title("System Info");
$page->set_heading("System Info"); $page->set_heading("System Info");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
$page->add_block(new Block("Information:", $this->build_data_form($info))); $page->add_block(new Block("Information:", $this->build_data_form($info)));
} }
protected function build_data_form($info) { protected function build_data_form($info)
$data = <<<EOD {
$data = <<<EOD
Optional: Optional:
Site title: {$info['site_title']} Site title: {$info['site_title']}
Theme: {$info['site_theme']} Theme: {$info['site_theme']}
@ -47,7 +50,7 @@ Tags: {$info['stat_tags']}
Applications: {$info['stat_image_tags']} Applications: {$info['stat_image_tags']}
Extensions: {$info['sys_extensions']} Extensions: {$info['sys_extensions']}
EOD; EOD;
$html = <<<EOD $html = <<<EOD
<form action='http://shimmie.shishnet.org/register.php' method='POST'> <form action='http://shimmie.shishnet.org/register.php' method='POST'>
<input type='hidden' name='registration_api' value='1'> <input type='hidden' name='registration_api' value='1'>
<textarea name='data' rows='20' cols='80'>$data</textarea> <textarea name='data' rows='20' cols='80'>$data</textarea>
@ -56,7 +59,6 @@ EOD;
of web servers / databases / etc I need to support. of web servers / databases / etc I need to support.
</form> </form>
EOD; EOD;
return $html; return $html;
} }
} }

View File

@ -12,193 +12,204 @@
* extensions and read their documentation * extensions and read their documentation
*/ */
function __extman_extcmp(ExtensionInfo $a, ExtensionInfo $b): int { function __extman_extcmp(ExtensionInfo $a, ExtensionInfo $b): int
return strcmp($a->name, $b->name); {
return strcmp($a->name, $b->name);
} }
class ExtensionInfo { class ExtensionInfo
public $ext_name, $name, $link, $author, $email; {
public $description, $documentation, $version, $visibility; public $ext_name;
public $enabled; public $name;
public $link;
public $author;
public $email;
public $description;
public $documentation;
public $version;
public $visibility;
public $enabled;
public function __construct($main) { public function __construct($main)
$matches = array(); {
$lines = file($main); $matches = [];
$number_of_lines = count($lines); $lines = file($main);
preg_match("#ext/(.*)/main.php#", $main, $matches); $number_of_lines = count($lines);
$this->ext_name = $matches[1]; preg_match("#ext/(.*)/main.php#", $main, $matches);
$this->name = $this->ext_name; $this->ext_name = $matches[1];
$this->enabled = $this->is_enabled($this->ext_name); $this->name = $this->ext_name;
$this->enabled = $this->is_enabled($this->ext_name);
for($i=0; $i<$number_of_lines; $i++) { for ($i=0; $i<$number_of_lines; $i++) {
$line = $lines[$i]; $line = $lines[$i];
if(preg_match("/Name: (.*)/", $line, $matches)) { if (preg_match("/Name: (.*)/", $line, $matches)) {
$this->name = $matches[1]; $this->name = $matches[1];
} } elseif (preg_match("/Visibility: (.*)/", $line, $matches)) {
else if(preg_match("/Visibility: (.*)/", $line, $matches)) { $this->visibility = $matches[1];
$this->visibility = $matches[1]; } elseif (preg_match("/Link: (.*)/", $line, $matches)) {
} $this->link = $matches[1];
else if(preg_match("/Link: (.*)/", $line, $matches)) { if ($this->link[0] == "/") {
$this->link = $matches[1]; $this->link = make_link(substr($this->link, 1));
if($this->link[0] == "/") { }
$this->link = make_link(substr($this->link, 1)); } elseif (preg_match("/Version: (.*)/", $line, $matches)) {
} $this->version = $matches[1];
} } elseif (preg_match("/Author: (.*) [<\(](.*@.*)[>\)]/", $line, $matches)) {
else if(preg_match("/Version: (.*)/", $line, $matches)) { $this->author = $matches[1];
$this->version = $matches[1]; $this->email = $matches[2];
} } elseif (preg_match("/Author: (.*)/", $line, $matches)) {
else if(preg_match("/Author: (.*) [<\(](.*@.*)[>\)]/", $line, $matches)) { $this->author = $matches[1];
$this->author = $matches[1]; } elseif (preg_match("/(.*)Description: ?(.*)/", $line, $matches)) {
$this->email = $matches[2]; $this->description = $matches[2];
} $start = $matches[1]." ";
else if(preg_match("/Author: (.*)/", $line, $matches)) { $start_len = strlen($start);
$this->author = $matches[1]; while (substr($lines[$i+1], 0, $start_len) == $start) {
} $this->description .= " ".substr($lines[$i+1], $start_len);
else if(preg_match("/(.*)Description: ?(.*)/", $line, $matches)) { $i++;
$this->description = $matches[2]; }
$start = $matches[1]." "; } elseif (preg_match("/(.*)Documentation: ?(.*)/", $line, $matches)) {
$start_len = strlen($start); $this->documentation = $matches[2];
while(substr($lines[$i+1], 0, $start_len) == $start) { $start = $matches[1]." ";
$this->description .= " ".substr($lines[$i+1], $start_len); $start_len = strlen($start);
$i++; while (substr($lines[$i+1], 0, $start_len) == $start) {
} $this->documentation .= " ".substr($lines[$i+1], $start_len);
} $i++;
else if(preg_match("/(.*)Documentation: ?(.*)/", $line, $matches)) { }
$this->documentation = $matches[2]; $this->documentation = str_replace('$site', make_http(get_base_href()), $this->documentation);
$start = $matches[1]." "; } elseif (preg_match("/\*\//", $line, $matches)) {
$start_len = strlen($start); break;
while(substr($lines[$i+1], 0, $start_len) == $start) { }
$this->documentation .= " ".substr($lines[$i+1], $start_len); }
$i++; }
}
$this->documentation = str_replace('$site', make_http(get_base_href()), $this->documentation);
}
else if(preg_match("/\*\//", $line, $matches)) {
break;
}
}
}
private function is_enabled(string $fname): ?bool { private function is_enabled(string $fname): ?bool
$core = explode(",", CORE_EXTS); {
$extra = explode(",", EXTRA_EXTS); $core = explode(",", CORE_EXTS);
$extra = explode(",", EXTRA_EXTS);
if(in_array($fname, $extra)) return true; // enabled if (in_array($fname, $extra)) {
if(in_array($fname, $core)) return null; // core return true;
return false; // not enabled } // enabled
} if (in_array($fname, $core)) {
return null;
} // core
return false; // not enabled
}
} }
class ExtManager extends Extension { class ExtManager extends Extension
public function onPageRequest(PageRequestEvent $event) { {
global $page, $user; public function onPageRequest(PageRequestEvent $event)
if($event->page_matches("ext_manager")) { {
if($user->can("manage_extension_list")) { global $page, $user;
if($event->get_arg(0) == "set" && $user->check_auth_token()) { if ($event->page_matches("ext_manager")) {
if(is_writable("data/config")) { if ($user->can("manage_extension_list")) {
$this->set_things($_POST); if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
log_warning("ext_manager", "Active extensions changed", "Active extensions changed"); if (is_writable("data/config")) {
$page->set_mode("redirect"); $this->set_things($_POST);
$page->set_redirect(make_link("ext_manager")); log_warning("ext_manager", "Active extensions changed", "Active extensions changed");
} $page->set_mode("redirect");
else { $page->set_redirect(make_link("ext_manager"));
$this->theme->display_error(500, "File Operation Failed", } else {
"The config file (data/config/extensions.conf.php) isn't writable by the web server :("); $this->theme->display_error(
} 500,
} "File Operation Failed",
else { "The config file (data/config/extensions.conf.php) isn't writable by the web server :("
$this->theme->display_table($page, $this->get_extensions(true), true); );
} }
} } else {
else { $this->theme->display_table($page, $this->get_extensions(true), true);
$this->theme->display_table($page, $this->get_extensions(false), false); }
} } else {
} $this->theme->display_table($page, $this->get_extensions(false), false);
}
}
if($event->page_matches("ext_doc")) { if ($event->page_matches("ext_doc")) {
$ext = $event->get_arg(0); $ext = $event->get_arg(0);
if(file_exists("ext/$ext/main.php")) { if (file_exists("ext/$ext/main.php")) {
$info = new ExtensionInfo("ext/$ext/main.php"); $info = new ExtensionInfo("ext/$ext/main.php");
$this->theme->display_doc($page, $info); $this->theme->display_doc($page, $info);
} } else {
else { $this->theme->display_table($page, $this->get_extensions(false), false);
$this->theme->display_table($page, $this->get_extensions(false), false); }
} }
} }
}
public function onCommand(CommandEvent $event) { public function onCommand(CommandEvent $event)
if($event->cmd == "help") { {
print "\tdisable-all-ext\n"; if ($event->cmd == "help") {
print "\t\tdisable all extensions\n\n"; print "\tdisable-all-ext\n";
} print "\t\tdisable all extensions\n\n";
if($event->cmd == "disable-all-ext") { }
$this->write_config(array()); if ($event->cmd == "disable-all-ext") {
} $this->write_config([]);
} }
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) { public function onUserBlockBuilding(UserBlockBuildingEvent $event)
global $user; {
if($user->can("manage_extension_list")) { global $user;
$event->add_link("Extension Manager", make_link("ext_manager")); if ($user->can("manage_extension_list")) {
} $event->add_link("Extension Manager", make_link("ext_manager"));
else { } else {
$event->add_link("Help", make_link("ext_doc")); $event->add_link("Help", make_link("ext_doc"));
} }
} }
/** /**
* #return ExtensionInfo[] * #return ExtensionInfo[]
*/ */
private function get_extensions(bool $all): array { private function get_extensions(bool $all): array
$extensions = array(); {
if($all) { $extensions = [];
$exts = zglob("ext/*/main.php"); if ($all) {
} $exts = zglob("ext/*/main.php");
else { } else {
$exts = zglob("ext/{".ENABLED_EXTS."}/main.php"); $exts = zglob("ext/{".ENABLED_EXTS."}/main.php");
} }
foreach($exts as $main) { foreach ($exts as $main) {
$extensions[] = new ExtensionInfo($main); $extensions[] = new ExtensionInfo($main);
} }
usort($extensions, "__extman_extcmp"); usort($extensions, "__extman_extcmp");
return $extensions; return $extensions;
} }
private function set_things($settings) { private function set_things($settings)
$core = explode(",", CORE_EXTS); {
$extras = array(); $core = explode(",", CORE_EXTS);
$extras = [];
foreach(glob("ext/*/main.php") as $main) { foreach (glob("ext/*/main.php") as $main) {
$matches = array(); $matches = [];
preg_match("#ext/(.*)/main.php#", $main, $matches); preg_match("#ext/(.*)/main.php#", $main, $matches);
$fname = $matches[1]; $fname = $matches[1];
if(!in_array($fname, $core) && isset($settings["ext_$fname"])) { if (!in_array($fname, $core) && isset($settings["ext_$fname"])) {
$extras[] = $fname; $extras[] = $fname;
} }
} }
$this->write_config($extras); $this->write_config($extras);
} }
/** /**
* #param string[] $extras * #param string[] $extras
*/ */
private function write_config(array $extras) { private function write_config(array $extras)
file_put_contents( {
"data/config/extensions.conf.php", file_put_contents(
'<'.'?php'."\n". "data/config/extensions.conf.php",
'define("EXTRA_EXTS", "'.implode(",", $extras).'");'."\n". '<'.'?php'."\n".
'?'.">" 'define("EXTRA_EXTS", "'.implode(",", $extras).'");'."\n".
); '?'.">"
);
// when the list of active extensions changes, we can be // when the list of active extensions changes, we can be
// pretty sure that the list of who reacts to what will // pretty sure that the list of who reacts to what will
// change too // change too
if(file_exists("data/cache/event_listeners.php")) { if (file_exists("data/cache/event_listeners.php")) {
unlink("data/cache/event_listeners.php"); unlink("data/cache/event_listeners.php");
} }
} }
} }

View File

@ -1,25 +1,27 @@
<?php <?php
class ExtManagerTest extends ShimmiePHPUnitTestCase { class ExtManagerTest extends ShimmiePHPUnitTestCase
public function testAuth() { {
$this->get_page('ext_manager'); public function testAuth()
$this->assert_title("Extensions"); {
$this->get_page('ext_manager');
$this->assert_title("Extensions");
$this->get_page('ext_doc'); $this->get_page('ext_doc');
$this->assert_title("Extensions"); $this->assert_title("Extensions");
$this->get_page('ext_doc/ext_manager'); $this->get_page('ext_doc/ext_manager');
$this->assert_title("Documentation for Extension Manager"); $this->assert_title("Documentation for Extension Manager");
$this->assert_text("view a list of all extensions"); $this->assert_text("view a list of all extensions");
# test author without email # test author without email
$this->get_page('ext_doc/user'); $this->get_page('ext_doc/user');
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page('ext_manager'); $this->get_page('ext_manager');
$this->assert_title("Extensions"); $this->assert_title("Extensions");
//$this->assert_text("SimpleTest integration"); // FIXME: something which still exists //$this->assert_text("SimpleTest integration"); // FIXME: something which still exists
$this->log_out(); $this->log_out();
# FIXME: test that some extensions can be added and removed? :S # FIXME: test that some extensions can be added and removed? :S
} }
} }

View File

@ -1,12 +1,14 @@
<?php <?php
class ExtManagerTheme extends Themelet { class ExtManagerTheme extends Themelet
/** {
* #param ExtensionInfo[] $extensions /**
*/ * #param ExtensionInfo[] $extensions
public function display_table(Page $page, array $extensions, bool $editable) { */
$h_en = $editable ? "<th>Enabled</th>" : ""; public function display_table(Page $page, array $extensions, bool $editable)
$html = " {
$h_en = $editable ? "<th>Enabled</th>" : "";
$html = "
".make_form(make_link("ext_manager/set"))." ".make_form(make_link("ext_manager/set"))."
<table id='extensions' class='zebra sortable'> <table id='extensions' class='zebra sortable'>
<thead> <thead>
@ -19,110 +21,112 @@ class ExtManagerTheme extends Themelet {
</thead> </thead>
<tbody> <tbody>
"; ";
foreach($extensions as $extension) { foreach ($extensions as $extension) {
if(!$editable && $extension->visibility == "admin") continue; if (!$editable && $extension->visibility == "admin") {
continue;
}
$h_name = html_escape(empty($extension->name) ? $extension->ext_name : $extension->name); $h_name = html_escape(empty($extension->name) ? $extension->ext_name : $extension->name);
$h_description = html_escape($extension->description); $h_description = html_escape($extension->description);
$h_link = make_link("ext_doc/".url_escape($extension->ext_name)); $h_link = make_link("ext_doc/".url_escape($extension->ext_name));
$h_enabled = ($extension->enabled === TRUE ? " checked='checked'" : ($extension->enabled === FALSE ? "" : " disabled checked='checked'")); $h_enabled = ($extension->enabled === true ? " checked='checked'" : ($extension->enabled === false ? "" : " disabled checked='checked'"));
$h_enabled_box = $editable ? "<td><input type='checkbox' name='ext_".html_escape($extension->ext_name)."'$h_enabled></td>" : ""; $h_enabled_box = $editable ? "<td><input type='checkbox' name='ext_".html_escape($extension->ext_name)."'$h_enabled></td>" : "";
$h_docs = ($extension->documentation ? "<a href='$h_link'>■</a>" : ""); //TODO: A proper "docs" symbol would be preferred here. $h_docs = ($extension->documentation ? "<a href='$h_link'>■</a>" : ""); //TODO: A proper "docs" symbol would be preferred here.
$html .= " $html .= "
<tr data-ext='{$extension->ext_name}'> <tr data-ext='{$extension->ext_name}'>
{$h_enabled_box} {$h_enabled_box}
<td>{$h_name}</td> <td>{$h_name}</td>
<td>{$h_docs}</td> <td>{$h_docs}</td>
<td style='text-align: left;'>{$h_description}</td> <td style='text-align: left;'>{$h_description}</td>
</tr>"; </tr>";
} }
$h_set = $editable ? "<tfoot><tr><td colspan='5'><input type='submit' value='Set Extensions'></td></tr></tfoot>" : ""; $h_set = $editable ? "<tfoot><tr><td colspan='5'><input type='submit' value='Set Extensions'></td></tr></tfoot>" : "";
$html .= " $html .= "
</tbody> </tbody>
$h_set $h_set
</table> </table>
</form> </form>
"; ";
$page->set_title("Extensions"); $page->set_title("Extensions");
$page->set_heading("Extensions"); $page->set_heading("Extensions");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
$page->add_block(new Block("Extension Manager", $html)); $page->add_block(new Block("Extension Manager", $html));
} }
/* /*
public function display_blocks(Page $page, $extensions) { public function display_blocks(Page $page, $extensions) {
global $user; global $user;
$col_1 = ""; $col_1 = "";
$col_2 = ""; $col_2 = "";
foreach($extensions as $extension) { foreach($extensions as $extension) {
$ext_name = $extension->ext_name; $ext_name = $extension->ext_name;
$h_name = empty($extension->name) ? $ext_name : html_escape($extension->name); $h_name = empty($extension->name) ? $ext_name : html_escape($extension->name);
$h_email = html_escape($extension->email); $h_email = html_escape($extension->email);
$h_link = isset($extension->link) ? $h_link = isset($extension->link) ?
"<a href=\"".html_escape($extension->link)."\">Original Site</a>" : ""; "<a href=\"".html_escape($extension->link)."\">Original Site</a>" : "";
$h_doc = isset($extension->documentation) ? $h_doc = isset($extension->documentation) ?
"<a href=\"".make_link("ext_doc/".html_escape($extension->ext_name))."\">Documentation</a>" : ""; "<a href=\"".make_link("ext_doc/".html_escape($extension->ext_name))."\">Documentation</a>" : "";
$h_author = html_escape($extension->author); $h_author = html_escape($extension->author);
$h_description = html_escape($extension->description); $h_description = html_escape($extension->description);
$h_enabled = $extension->enabled ? " checked='checked'" : ""; $h_enabled = $extension->enabled ? " checked='checked'" : "";
$h_author_link = empty($h_email) ? $h_author_link = empty($h_email) ?
"$h_author" : "$h_author" :
"<a href='mailto:$h_email'>$h_author</a>"; "<a href='mailto:$h_email'>$h_author</a>";
$html = " $html = "
<p><table border='1'> <p><table border='1'>
<tr> <tr>
<th colspan='2'>$h_name</th> <th colspan='2'>$h_name</th>
</tr> </tr>
<tr> <tr>
<td>By $h_author_link</td> <td>By $h_author_link</td>
<td width='25%'>Enabled:&nbsp;<input type='checkbox' name='ext_$ext_name'$h_enabled></td> <td width='25%'>Enabled:&nbsp;<input type='checkbox' name='ext_$ext_name'$h_enabled></td>
</tr> </tr>
<tr> <tr>
<td style='text-align: left' colspan='2'>$h_description<p>$h_link $h_doc</td> <td style='text-align: left' colspan='2'>$h_description<p>$h_link $h_doc</td>
</tr> </tr>
</table> </table>
"; ";
if($n++ % 2 == 0) { if($n++ % 2 == 0) {
$col_1 .= $html; $col_1 .= $html;
} }
else { else {
$col_2 .= $html; $col_2 .= $html;
} }
} }
$html = " $html = "
".make_form(make_link("ext_manager/set"))." ".make_form(make_link("ext_manager/set"))."
".$user->get_auth_html()." ".$user->get_auth_html()."
<table border='0'> <table border='0'>
<tr><td width='50%'>$col_1</td><td>$col_2</td></tr> <tr><td width='50%'>$col_1</td><td>$col_2</td></tr>
<tr><td colspan='2'><input type='submit' value='Set Extensions'></td></tr> <tr><td colspan='2'><input type='submit' value='Set Extensions'></td></tr>
</table> </table>
</form> </form>
"; ";
$page->set_title("Extensions"); $page->set_title("Extensions");
$page->set_heading("Extensions"); $page->set_heading("Extensions");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
$page->add_block(new Block("Extension Manager", $html)); $page->add_block(new Block("Extension Manager", $html));
} }
*/ */
public function display_doc(Page $page, ExtensionInfo $info) { public function display_doc(Page $page, ExtensionInfo $info)
$author = ""; {
if($info->author) { $author = "";
if($info->email) { if ($info->author) {
$author = "<br><b>Author:</b> <a href=\"mailto:".html_escape($info->email)."\">".html_escape($info->author)."</a>"; if ($info->email) {
} $author = "<br><b>Author:</b> <a href=\"mailto:".html_escape($info->email)."\">".html_escape($info->author)."</a>";
else { } else {
$author = "<br><b>Author:</b> ".html_escape($info->author); $author = "<br><b>Author:</b> ".html_escape($info->author);
} }
} }
$version = ($info->version) ? "<br><b>Version:</b> ".html_escape($info->version) : ""; $version = ($info->version) ? "<br><b>Version:</b> ".html_escape($info->version) : "";
$link = ($info->link) ? "<br><b>Home Page:</b> <a href=\"".html_escape($info->link)."\">Link</a>" : ""; $link = ($info->link) ? "<br><b>Home Page:</b> <a href=\"".html_escape($info->link)."\">Link</a>" : "";
$doc = $info->documentation; $doc = $info->documentation;
$html = " $html = "
<div style='margin: auto; text-align: left; width: 512px;'> <div style='margin: auto; text-align: left; width: 512px;'>
$author $author
$version $version
@ -132,10 +136,9 @@ class ExtManagerTheme extends Themelet {
<p><a href='".make_link("ext_manager")."'>Back to the list</a> <p><a href='".make_link("ext_manager")."'>Back to the list</a>
</div>"; </div>";
$page->set_title("Documentation for ".html_escape($info->name)); $page->set_title("Documentation for ".html_escape($info->name));
$page->set_heading(html_escape($info->name)); $page->set_heading(html_escape($info->name));
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
$page->add_block(new Block("Documentation", $html)); $page->add_block(new Block("Documentation", $html));
} }
} }

View File

@ -13,146 +13,158 @@
* using the $favorites placeholder * using the $favorites placeholder
*/ */
class FavoriteSetEvent extends Event { class FavoriteSetEvent extends Event
/** @var int */ {
public $image_id; /** @var int */
/** @var \User */ public $image_id;
public $user; /** @var \User */
/** @var bool */ public $user;
public $do_set; /** @var bool */
public $do_set;
public function __construct(int $image_id, User $user, bool $do_set) { public function __construct(int $image_id, User $user, bool $do_set)
assert(is_int($image_id)); {
assert(is_bool($do_set)); assert(is_int($image_id));
assert(is_bool($do_set));
$this->image_id = $image_id; $this->image_id = $image_id;
$this->user = $user; $this->user = $user;
$this->do_set = $do_set; $this->do_set = $do_set;
} }
} }
class Favorites extends Extension { class Favorites extends Extension
public function onInitExt(InitExtEvent $event) { {
global $config; public function onInitExt(InitExtEvent $event)
if($config->get_int("ext_favorites_version", 0) < 1) { {
$this->install(); global $config;
} if ($config->get_int("ext_favorites_version", 0) < 1) {
} $this->install();
}
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
global $database, $user; {
if(!$user->is_anonymous()) { global $database, $user;
$user_id = $user->id; if (!$user->is_anonymous()) {
$image_id = $event->image->id; $user_id = $user->id;
$image_id = $event->image->id;
$is_favorited = $database->get_one( $is_favorited = $database->get_one(
"SELECT COUNT(*) AS ct FROM user_favorites WHERE user_id = :user_id AND image_id = :image_id", "SELECT COUNT(*) AS ct FROM user_favorites WHERE user_id = :user_id AND image_id = :image_id",
array("user_id"=>$user_id, "image_id"=>$image_id)) > 0; ["user_id"=>$user_id, "image_id"=>$image_id]
) > 0;
$event->add_part($this->theme->get_voter_html($event->image, $is_favorited));
} $event->add_part($this->theme->get_voter_html($event->image, $is_favorited));
} }
}
public function onDisplayingImage(DisplayingImageEvent $event) { public function onDisplayingImage(DisplayingImageEvent $event)
$people = $this->list_persons_who_have_favorited($event->image); {
if(count($people) > 0) { $people = $this->list_persons_who_have_favorited($event->image);
$this->theme->display_people($people); if (count($people) > 0) {
} $this->theme->display_people($people);
} }
}
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
global $page, $user; {
if($event->page_matches("change_favorite") && !$user->is_anonymous() && $user->check_auth_token()) { global $page, $user;
$image_id = int_escape($_POST['image_id']); if ($event->page_matches("change_favorite") && !$user->is_anonymous() && $user->check_auth_token()) {
if((($_POST['favorite_action'] == "set") || ($_POST['favorite_action'] == "unset")) && ($image_id > 0)) { $image_id = int_escape($_POST['image_id']);
if($_POST['favorite_action'] == "set") { if ((($_POST['favorite_action'] == "set") || ($_POST['favorite_action'] == "unset")) && ($image_id > 0)) {
send_event(new FavoriteSetEvent($image_id, $user, true)); if ($_POST['favorite_action'] == "set") {
log_debug("favourite", "Favourite set for $image_id", "Favourite added"); send_event(new FavoriteSetEvent($image_id, $user, true));
} log_debug("favourite", "Favourite set for $image_id", "Favourite added");
else { } else {
send_event(new FavoriteSetEvent($image_id, $user, false)); send_event(new FavoriteSetEvent($image_id, $user, false));
log_debug("favourite", "Favourite removed for $image_id", "Favourite removed"); log_debug("favourite", "Favourite removed for $image_id", "Favourite removed");
} }
} }
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$image_id")); $page->set_redirect(make_link("post/view/$image_id"));
} }
} }
public function onUserPageBuilding(UserPageBuildingEvent $event) { public function onUserPageBuilding(UserPageBuildingEvent $event)
$i_favorites_count = Image::count_images(array("favorited_by={$event->display_user->name}")); {
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1; $i_favorites_count = Image::count_images(["favorited_by={$event->display_user->name}"]);
$h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old)); $i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
$favorites_link = make_link("post/list/favorited_by={$event->display_user->name}/1"); $h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old));
$event->add_stats("<a href='$favorites_link'>Images favorited</a>: $i_favorites_count, $h_favorites_rate per day"); $favorites_link = make_link("post/list/favorited_by={$event->display_user->name}/1");
} $event->add_stats("<a href='$favorites_link'>Images favorited</a>: $i_favorites_count, $h_favorites_rate per day");
}
public function onImageInfoSet(ImageInfoSetEvent $event) { public function onImageInfoSet(ImageInfoSetEvent $event)
global $user; {
if( global $user;
in_array('favorite_action', $_POST) && if (
(($_POST['favorite_action'] == "set") || ($_POST['favorite_action'] == "unset")) in_array('favorite_action', $_POST) &&
) { (($_POST['favorite_action'] == "set") || ($_POST['favorite_action'] == "unset"))
send_event(new FavoriteSetEvent($event->image->id, $user, ($_POST['favorite_action'] == "set"))); ) {
} send_event(new FavoriteSetEvent($event->image->id, $user, ($_POST['favorite_action'] == "set")));
} }
}
public function onFavoriteSet(FavoriteSetEvent $event) { public function onFavoriteSet(FavoriteSetEvent $event)
global $user; {
$this->add_vote($event->image_id, $user->id, $event->do_set); global $user;
} $this->add_vote($event->image_id, $user->id, $event->do_set);
}
// FIXME: this should be handled by the foreign key. Check that it // FIXME: this should be handled by the foreign key. Check that it
// is, and then remove this // is, and then remove this
public function onImageDeletion(ImageDeletionEvent $event) { public function onImageDeletion(ImageDeletionEvent $event)
global $database; {
$database->execute("DELETE FROM user_favorites WHERE image_id=:image_id", array("image_id"=>$event->image->id)); global $database;
} $database->execute("DELETE FROM user_favorites WHERE image_id=:image_id", ["image_id"=>$event->image->id]);
}
public function onParseLinkTemplate(ParseLinkTemplateEvent $event) { public function onParseLinkTemplate(ParseLinkTemplateEvent $event)
$event->replace('$favorites', $event->image->favorites); {
} $event->replace('$favorites', $event->image->favorites);
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) { public function onUserBlockBuilding(UserBlockBuildingEvent $event)
global $user; {
global $user;
$username = url_escape($user->name); $username = url_escape($user->name);
$event->add_link("My Favorites", make_link("post/list/favorited_by=$username/1"), 20); $event->add_link("My Favorites", make_link("post/list/favorited_by=$username/1"), 20);
} }
public function onSearchTermParse(SearchTermParseEvent $event) { public function onSearchTermParse(SearchTermParseEvent $event)
$matches = array(); {
if(preg_match("/^favorites([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) { $matches = [];
$cmp = ltrim($matches[1], ":") ?: "="; if (preg_match("/^favorites([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
$favorites = $matches[2]; $cmp = ltrim($matches[1], ":") ?: "=";
$event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE favorites $cmp $favorites)")); $favorites = $matches[2];
} $event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE favorites $cmp $favorites)"));
else if(preg_match("/^favorited_by[=|:](.*)$/i", $event->term, $matches)) { } elseif (preg_match("/^favorited_by[=|:](.*)$/i", $event->term, $matches)) {
$user = User::by_name($matches[1]); $user = User::by_name($matches[1]);
if(!is_null($user)) { if (!is_null($user)) {
$user_id = $user->id; $user_id = $user->id;
} } else {
else { $user_id = -1;
$user_id = -1; }
}
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)")); $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)"));
} } elseif (preg_match("/^favorited_by_userno[=|:](\d+)$/i", $event->term, $matches)) {
else if(preg_match("/^favorited_by_userno[=|:](\d+)$/i", $event->term, $matches)) { $user_id = int_escape($matches[1]);
$user_id = int_escape($matches[1]); $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)"));
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)")); }
} }
}
private function install() { private function install()
global $database; {
global $config; global $database;
global $config;
if($config->get_int("ext_favorites_version") < 1) { if ($config->get_int("ext_favorites_version") < 1) {
$database->Execute("ALTER TABLE images ADD COLUMN favorites INTEGER NOT NULL DEFAULT 0"); $database->Execute("ALTER TABLE images ADD COLUMN favorites INTEGER NOT NULL DEFAULT 0");
$database->Execute("CREATE INDEX images__favorites ON images(favorites)"); $database->Execute("CREATE INDEX images__favorites ON images(favorites)");
$database->create_table("user_favorites", " $database->create_table("user_favorites", "
image_id INTEGER NOT NULL, image_id INTEGER NOT NULL,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
created_at SCORE_DATETIME NOT NULL, created_at SCORE_DATETIME NOT NULL,
@ -160,47 +172,52 @@ class Favorites extends Extension {
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE
"); ");
$database->execute("CREATE INDEX user_favorites_image_id_idx ON user_favorites(image_id)", array()); $database->execute("CREATE INDEX user_favorites_image_id_idx ON user_favorites(image_id)", []);
$config->set_int("ext_favorites_version", 2); $config->set_int("ext_favorites_version", 2);
} }
if($config->get_int("ext_favorites_version") < 2) { if ($config->get_int("ext_favorites_version") < 2) {
log_info("favorites", "Cleaning user favourites"); log_info("favorites", "Cleaning user favourites");
$database->Execute("DELETE FROM user_favorites WHERE user_id NOT IN (SELECT id FROM users)"); $database->Execute("DELETE FROM user_favorites WHERE user_id NOT IN (SELECT id FROM users)");
$database->Execute("DELETE FROM user_favorites WHERE image_id NOT IN (SELECT id FROM images)"); $database->Execute("DELETE FROM user_favorites WHERE image_id NOT IN (SELECT id FROM images)");
log_info("favorites", "Adding foreign keys to user favourites"); log_info("favorites", "Adding foreign keys to user favourites");
$database->Execute("ALTER TABLE user_favorites ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;"); $database->Execute("ALTER TABLE user_favorites ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;");
$database->Execute("ALTER TABLE user_favorites ADD FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;"); $database->Execute("ALTER TABLE user_favorites ADD FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;");
$config->set_int("ext_favorites_version", 2); $config->set_int("ext_favorites_version", 2);
} }
} }
private function add_vote(int $image_id, int $user_id, bool $do_set) { private function add_vote(int $image_id, int $user_id, bool $do_set)
global $database; {
if ($do_set) { global $database;
$database->Execute( if ($do_set) {
"INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(:image_id, :user_id, NOW())", $database->Execute(
array("image_id"=>$image_id, "user_id"=>$user_id)); "INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(:image_id, :user_id, NOW())",
} else { ["image_id"=>$image_id, "user_id"=>$user_id]
$database->Execute( );
"DELETE FROM user_favorites WHERE image_id = :image_id AND user_id = :user_id", } else {
array("image_id"=>$image_id, "user_id"=>$user_id)); $database->Execute(
} "DELETE FROM user_favorites WHERE image_id = :image_id AND user_id = :user_id",
$database->Execute( ["image_id"=>$image_id, "user_id"=>$user_id]
"UPDATE images SET favorites=(SELECT COUNT(*) FROM user_favorites WHERE image_id=:image_id) WHERE id=:user_id", );
array("image_id"=>$image_id, "user_id"=>$user_id)); }
} $database->Execute(
"UPDATE images SET favorites=(SELECT COUNT(*) FROM user_favorites WHERE image_id=:image_id) WHERE id=:user_id",
["image_id"=>$image_id, "user_id"=>$user_id]
);
}
/** /**
* #return string[] * #return string[]
*/ */
private function list_persons_who_have_favorited(Image $image): array { private function list_persons_who_have_favorited(Image $image): array
global $database; {
global $database;
return $database->get_col( return $database->get_col(
"SELECT name FROM users WHERE id IN (SELECT user_id FROM user_favorites WHERE image_id = :image_id) ORDER BY name", "SELECT name FROM users WHERE id IN (SELECT user_id FROM user_favorites WHERE image_id = :image_id) ORDER BY name",
array("image_id"=>$image->id)); ["image_id"=>$image->id]
} );
}
} }

View File

@ -1,29 +1,30 @@
<?php <?php
class FavoritesTest extends ShimmiePHPUnitTestCase { class FavoritesTest extends ShimmiePHPUnitTestCase
public function testFavorites() { {
$this->log_in_as_user(); public function testFavorites()
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test"); {
$this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: test"); $this->assert_title("Image $image_id: test");
$this->assert_no_text("Favorited By"); $this->assert_no_text("Favorited By");
$this->markTestIncomplete(); $this->markTestIncomplete();
$this->click("Favorite"); $this->click("Favorite");
$this->assert_text("Favorited By"); $this->assert_text("Favorited By");
$this->get_page("post/list/favorited_by=test/1"); $this->get_page("post/list/favorited_by=test/1");
$this->assert_title("Image $image_id: test"); $this->assert_title("Image $image_id: test");
$this->assert_text("Favorited By"); $this->assert_text("Favorited By");
$this->get_page("user/test"); $this->get_page("user/test");
$this->assert_text("Images favorited: 1"); $this->assert_text("Images favorited: 1");
$this->click("Images favorited"); $this->click("Images favorited");
$this->assert_title("Image $image_id: test"); $this->assert_title("Image $image_id: test");
$this->click("Un-Favorite"); $this->click("Un-Favorite");
$this->assert_no_text("Favorited By"); $this->assert_no_text("Favorited By");
} }
} }

View File

@ -1,11 +1,13 @@
<?php <?php
class FavoritesTheme extends Themelet { class FavoritesTheme extends Themelet
public function get_voter_html(Image $image, $is_favorited) { {
$i_image_id = int_escape($image->id); public function get_voter_html(Image $image, $is_favorited)
$name = $is_favorited ? "unset" : "set"; {
$label = $is_favorited ? "Un-Favorite" : "Favorite"; $i_image_id = int_escape($image->id);
$html = " $name = $is_favorited ? "unset" : "set";
$label = $is_favorited ? "Un-Favorite" : "Favorite";
$html = "
".make_form(make_link("change_favorite"))." ".make_form(make_link("change_favorite"))."
<input type='hidden' name='image_id' value='$i_image_id'> <input type='hidden' name='image_id' value='$i_image_id'>
<input type='hidden' name='favorite_action' value='$name'> <input type='hidden' name='favorite_action' value='$name'>
@ -13,24 +15,23 @@ class FavoritesTheme extends Themelet {
</form> </form>
"; ";
return $html; return $html;
} }
public function display_people($username_array) { public function display_people($username_array)
global $page; {
global $page;
$i_favorites = count($username_array); $i_favorites = count($username_array);
$html = "$i_favorites people:"; $html = "$i_favorites people:";
reset($username_array); // rewind to first element in array. reset($username_array); // rewind to first element in array.
foreach($username_array as $row) { foreach ($username_array as $row) {
$username = html_escape($row); $username = html_escape($row);
$html .= "<br><a href='".make_link("user/$username")."'>$username</a>"; $html .= "<br><a href='".make_link("user/$username")."'>$username</a>";
} }
$page->add_block(new Block("Favorited By", $html, "left", 25)); $page->add_block(new Block("Favorited By", $html, "left", 25));
} }
} }

View File

@ -19,71 +19,75 @@
* every couple of hours. * every couple of hours.
*/ */
class Featured extends Extension { class Featured extends Extension
public function onInitExt(InitExtEvent $event) { {
global $config; public function onInitExt(InitExtEvent $event)
$config->set_default_int('featured_id', 0); {
} global $config;
$config->set_default_int('featured_id', 0);
}
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
global $config, $page, $user; {
if($event->page_matches("featured_image")) { global $config, $page, $user;
if($event->get_arg(0) == "set" && $user->check_auth_token()) { if ($event->page_matches("featured_image")) {
if($user->can("edit_feature") && isset($_POST['image_id'])) { if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
$id = int_escape($_POST['image_id']); if ($user->can("edit_feature") && isset($_POST['image_id'])) {
if($id > 0) { $id = int_escape($_POST['image_id']);
$config->set_int("featured_id", $id); if ($id > 0) {
log_info("featured", "Featured image set to $id", "Featured image set"); $config->set_int("featured_id", $id);
$page->set_mode("redirect"); log_info("featured", "Featured image set to $id", "Featured image set");
$page->set_redirect(make_link("post/view/$id")); $page->set_mode("redirect");
} $page->set_redirect(make_link("post/view/$id"));
} }
} }
if($event->get_arg(0) == "download") { }
$image = Image::by_id($config->get_int("featured_id")); if ($event->get_arg(0) == "download") {
if(!is_null($image)) { $image = Image::by_id($config->get_int("featured_id"));
$page->set_mode("data"); if (!is_null($image)) {
$page->set_type($image->get_mime_type()); $page->set_mode("data");
$page->set_data(file_get_contents($image->get_image_filename())); $page->set_type($image->get_mime_type());
} $page->set_data(file_get_contents($image->get_image_filename()));
} }
if($event->get_arg(0) == "view") { }
$image = Image::by_id($config->get_int("featured_id")); if ($event->get_arg(0) == "view") {
if(!is_null($image)) { $image = Image::by_id($config->get_int("featured_id"));
send_event(new DisplayingImageEvent($image)); if (!is_null($image)) {
} send_event(new DisplayingImageEvent($image));
} }
} }
} }
}
public function onPostListBuilding(PostListBuildingEvent $event) { public function onPostListBuilding(PostListBuildingEvent $event)
global $config, $database, $page, $user; {
$fid = $config->get_int("featured_id"); global $config, $database, $page, $user;
if($fid > 0) { $fid = $config->get_int("featured_id");
$image = $database->cache->get("featured_image_object:$fid"); if ($fid > 0) {
if($image === false) { $image = $database->cache->get("featured_image_object:$fid");
$image = Image::by_id($fid); if ($image === false) {
if($image) { // make sure the object is fully populated before saving $image = Image::by_id($fid);
$image->get_tag_array(); if ($image) { // make sure the object is fully populated before saving
} $image->get_tag_array();
$database->cache->set("featured_image_object:$fid", $image, 600); }
} $database->cache->set("featured_image_object:$fid", $image, 600);
if(!is_null($image)) { }
if(ext_is_live("Ratings")) { if (!is_null($image)) {
if(strpos(Ratings::get_user_privs($user), $image->rating) === FALSE) { if (ext_is_live("Ratings")) {
return; if (strpos(Ratings::get_user_privs($user), $image->rating) === false) {
} return;
} }
$this->theme->display_featured($page, $image); }
} $this->theme->display_featured($page, $image);
} }
} }
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
global $user; {
if($user->can("edit_feature")) { global $user;
$event->add_part($this->theme->get_buttons_html($event->image->id)); if ($user->can("edit_feature")) {
} $event->add_part($this->theme->get_buttons_html($event->image->id));
} }
}
} }

View File

@ -1,35 +1,36 @@
<?php <?php
class FeaturedTest extends ShimmiePHPUnitTestCase { class FeaturedTest extends ShimmiePHPUnitTestCase
public function testFeatured() { {
$this->log_in_as_user(); public function testFeatured()
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); {
$this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
# FIXME: test that regular users can't feature things # FIXME: test that regular users can't feature things
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: pbx"); $this->assert_title("Image $image_id: pbx");
$this->markTestIncomplete(); $this->markTestIncomplete();
$this->click("Feature This"); $this->click("Feature This");
$this->get_page("post/list"); $this->get_page("post/list");
$this->assert_text("Featured Image"); $this->assert_text("Featured Image");
# FIXME: test changing from one feature to another # FIXME: test changing from one feature to another
$this->get_page("featured_image/download"); $this->get_page("featured_image/download");
$this->assert_response(200); $this->assert_response(200);
$this->get_page("featured_image/view"); $this->get_page("featured_image/view");
$this->assert_response(200); $this->assert_response(200);
$this->delete_image($image_id); $this->delete_image($image_id);
$this->log_out(); $this->log_out();
# after deletion, there should be no feature # after deletion, there should be no feature
$this->get_page("post/list"); $this->get_page("post/list");
$this->assert_no_text("Featured Image"); $this->assert_no_text("Featured Image");
} }
} }

View File

@ -1,33 +1,36 @@
<?php <?php
class FeaturedTheme extends Themelet { class FeaturedTheme extends Themelet
public function display_featured(Page $page, Image $image): void { {
$page->add_block(new Block("Featured Image", $this->build_featured_html($image), "left", 3)); public function display_featured(Page $page, Image $image): void
} {
$page->add_block(new Block("Featured Image", $this->build_featured_html($image), "left", 3));
}
public function get_buttons_html(int $image_id): string { public function get_buttons_html(int $image_id): string
global $user; {
return " global $user;
return "
".make_form(make_link("featured_image/set"))." ".make_form(make_link("featured_image/set"))."
".$user->get_auth_html()." ".$user->get_auth_html()."
<input type='hidden' name='image_id' value='{$image_id}'> <input type='hidden' name='image_id' value='{$image_id}'>
<input type='submit' value='Feature This'> <input type='submit' value='Feature This'>
</form> </form>
"; ";
} }
public function build_featured_html(Image $image, ?string $query=null): string { public function build_featured_html(Image $image, ?string $query=null): string
$i_id = int_escape($image->id); {
$h_view_link = make_link("post/view/$i_id", $query); $i_id = int_escape($image->id);
$h_thumb_link = $image->get_thumb_link(); $h_view_link = make_link("post/view/$i_id", $query);
$h_tip = html_escape($image->get_tooltip()); $h_thumb_link = $image->get_thumb_link();
$tsize = get_thumbnail_size($image->width, $image->height); $h_tip = html_escape($image->get_tooltip());
$tsize = get_thumbnail_size($image->width, $image->height);
return " return "
<a href='$h_view_link'> <a href='$h_view_link'>
<img id='thumb_{$i_id}' title='{$h_tip}' alt='{$h_tip}' class='highlighted' style='height: {$tsize[1]}px; width: {$tsize[0]}px;' src='{$h_thumb_link}'> <img id='thumb_{$i_id}' title='{$h_tip}' alt='{$h_tip}' class='highlighted' style='height: {$tsize[1]}px; width: {$tsize[0]}px;' src='{$h_thumb_link}'>
</a> </a>
"; ";
} }
} }

View File

@ -15,14 +15,16 @@ Todo:
*Smiley filter, word filter, etc should work with our extension *Smiley filter, word filter, etc should work with our extension
*/ */
class Forum extends Extension { class Forum extends Extension
public function onInitExt(InitExtEvent $event) { {
global $config, $database; public function onInitExt(InitExtEvent $event)
{
global $config, $database;
// shortcut to latest // shortcut to latest
if ($config->get_int("forum_version") < 1) { if ($config->get_int("forum_version") < 1) {
$database->create_table("forum_threads", " $database->create_table("forum_threads", "
id SCORE_AIPK, id SCORE_AIPK,
sticky SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N, sticky SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N,
title VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
@ -31,9 +33,9 @@ class Forum extends Extension {
uptodate SCORE_DATETIME NOT NULL, uptodate SCORE_DATETIME NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT
"); ");
$database->execute("CREATE INDEX forum_threads_date_idx ON forum_threads(date)", array()); $database->execute("CREATE INDEX forum_threads_date_idx ON forum_threads(date)", []);
$database->create_table("forum_posts", " $database->create_table("forum_posts", "
id SCORE_AIPK, id SCORE_AIPK,
thread_id INTEGER NOT NULL, thread_id INTEGER NOT NULL,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
@ -42,367 +44,362 @@ class Forum extends Extension {
FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT, FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT,
FOREIGN KEY (thread_id) REFERENCES forum_threads (id) ON UPDATE CASCADE ON DELETE CASCADE FOREIGN KEY (thread_id) REFERENCES forum_threads (id) ON UPDATE CASCADE ON DELETE CASCADE
"); ");
$database->execute("CREATE INDEX forum_posts_date_idx ON forum_posts(date)", array()); $database->execute("CREATE INDEX forum_posts_date_idx ON forum_posts(date)", []);
$config->set_int("forum_version", 2); $config->set_int("forum_version", 2);
$config->set_int("forumTitleSubString", 25); $config->set_int("forumTitleSubString", 25);
$config->set_int("forumThreadsPerPage", 15); $config->set_int("forumThreadsPerPage", 15);
$config->set_int("forumPostsPerPage", 15); $config->set_int("forumPostsPerPage", 15);
$config->set_int("forumMaxCharsPerPost", 512); $config->set_int("forumMaxCharsPerPost", 512);
log_info("forum", "extension installed"); log_info("forum", "extension installed");
} }
if ($config->get_int("forum_version") < 2) { if ($config->get_int("forum_version") < 2) {
$database->execute("ALTER TABLE forum_threads ADD FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT"); $database->execute("ALTER TABLE forum_threads ADD FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT");
$database->execute("ALTER TABLE forum_posts ADD FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT"); $database->execute("ALTER TABLE forum_posts ADD FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE RESTRICT");
$config->set_int("forum_version", 2); $config->set_int("forum_version", 2);
} }
} }
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event)
$sb = new SetupBlock("Forum"); {
$sb->add_int_option("forumTitleSubString", "Title max long: "); $sb = new SetupBlock("Forum");
$sb->add_int_option("forumThreadsPerPage", "<br>Threads per page: "); $sb->add_int_option("forumTitleSubString", "Title max long: ");
$sb->add_int_option("forumPostsPerPage", "<br>Posts per page: "); $sb->add_int_option("forumThreadsPerPage", "<br>Threads per page: ");
$sb->add_int_option("forumPostsPerPage", "<br>Posts per page: ");
$sb->add_int_option("forumMaxCharsPerPost", "<br>Max chars per post: ");
$event->panel->add_block($sb); $sb->add_int_option("forumMaxCharsPerPost", "<br>Max chars per post: ");
} $event->panel->add_block($sb);
}
public function onUserPageBuilding(UserPageBuildingEvent $event) {
global $database; public function onUserPageBuilding(UserPageBuildingEvent $event)
{
$threads_count = $database->get_one("SELECT COUNT(*) FROM forum_threads WHERE user_id=?", array($event->display_user->id)); global $database;
$posts_count = $database->get_one("SELECT COUNT(*) FROM forum_posts WHERE user_id=?", array($event->display_user->id));
$threads_count = $database->get_one("SELECT COUNT(*) FROM forum_threads WHERE user_id=?", [$event->display_user->id]);
$posts_count = $database->get_one("SELECT COUNT(*) FROM forum_posts WHERE user_id=?", [$event->display_user->id]);
$days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1; $days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
$threads_rate = sprintf("%.1f", ($threads_count / $days_old)); $threads_rate = sprintf("%.1f", ($threads_count / $days_old));
$posts_rate = sprintf("%.1f", ($posts_count / $days_old)); $posts_rate = sprintf("%.1f", ($posts_count / $days_old));
$event->add_stats("Forum threads: $threads_count, $threads_rate per day"); $event->add_stats("Forum threads: $threads_count, $threads_rate per day");
$event->add_stats("Forum posts: $posts_count, $posts_rate per day"); $event->add_stats("Forum posts: $posts_count, $posts_rate per day");
} }
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event)
global $page, $user; {
global $page, $user;
if($event->page_matches("forum")) { if ($event->page_matches("forum")) {
switch($event->get_arg(0)) { switch ($event->get_arg(0)) {
case "index": case "index":
$this->show_last_threads($page, $event, $user->is_admin()); $this->show_last_threads($page, $event, $user->is_admin());
if(!$user->is_anonymous()) $this->theme->display_new_thread_composer($page); if (!$user->is_anonymous()) {
break; $this->theme->display_new_thread_composer($page);
case "view": }
$threadID = int_escape($event->get_arg(1)); break;
$pageNumber = int_escape($event->get_arg(2)); case "view":
list($errors) = $this->sanity_check_viewed_thread($threadID); $threadID = int_escape($event->get_arg(1));
$pageNumber = int_escape($event->get_arg(2));
list($errors) = $this->sanity_check_viewed_thread($threadID);
if($errors!=null) if ($errors!=null) {
{ $this->theme->display_error(500, "Error", $errors);
$this->theme->display_error(500, "Error", $errors); break;
break; }
}
$this->show_posts($event, $user->is_admin()); $this->show_posts($event, $user->is_admin());
if($user->is_admin()) $this->theme->add_actions_block($page, $threadID); if ($user->is_admin()) {
if(!$user->is_anonymous()) $this->theme->display_new_post_composer($page, $threadID); $this->theme->add_actions_block($page, $threadID);
break; }
case "new": if (!$user->is_anonymous()) {
global $page; $this->theme->display_new_post_composer($page, $threadID);
$this->theme->display_new_thread_composer($page); }
break; break;
case "create": case "new":
$redirectTo = "forum/index"; global $page;
if (!$user->is_anonymous()) $this->theme->display_new_thread_composer($page);
{ break;
list($errors) = $this->sanity_check_new_thread(); case "create":
$redirectTo = "forum/index";
if (!$user->is_anonymous()) {
list($errors) = $this->sanity_check_new_thread();
if($errors!=null) if ($errors!=null) {
{ $this->theme->display_error(500, "Error", $errors);
$this->theme->display_error(500, "Error", $errors); break;
break; }
}
$newThreadID = $this->save_new_thread($user); $newThreadID = $this->save_new_thread($user);
$this->save_new_post($newThreadID, $user); $this->save_new_post($newThreadID, $user);
$redirectTo = "forum/view/".$newThreadID."/1"; $redirectTo = "forum/view/".$newThreadID."/1";
} }
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link($redirectTo)); $page->set_redirect(make_link($redirectTo));
break; break;
case "delete": case "delete":
$threadID = int_escape($event->get_arg(1)); $threadID = int_escape($event->get_arg(1));
$postID = int_escape($event->get_arg(2)); $postID = int_escape($event->get_arg(2));
if ($user->is_admin()) {$this->delete_post($postID);} if ($user->is_admin()) {
$this->delete_post($postID);
}
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("forum/view/".$threadID)); $page->set_redirect(make_link("forum/view/".$threadID));
break; break;
case "nuke": case "nuke":
$threadID = int_escape($event->get_arg(1)); $threadID = int_escape($event->get_arg(1));
if ($user->is_admin()) if ($user->is_admin()) {
$this->delete_thread($threadID); $this->delete_thread($threadID);
}
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("forum/index")); $page->set_redirect(make_link("forum/index"));
break; break;
case "answer": case "answer":
$threadID = int_escape($_POST["threadID"]); $threadID = int_escape($_POST["threadID"]);
$total_pages = $this->get_total_pages_for_thread($threadID); $total_pages = $this->get_total_pages_for_thread($threadID);
if (!$user->is_anonymous()) if (!$user->is_anonymous()) {
{ list($errors) = $this->sanity_check_new_post();
list($errors) = $this->sanity_check_new_post();
if ($errors!=null) if ($errors!=null) {
{ $this->theme->display_error(500, "Error", $errors);
$this->theme->display_error(500, "Error", $errors); break;
break; }
} $this->save_new_post($threadID, $user);
$this->save_new_post($threadID, $user); }
} $page->set_mode("redirect");
$page->set_mode("redirect"); $page->set_redirect(make_link("forum/view/".$threadID."/".$total_pages));
$page->set_redirect(make_link("forum/view/".$threadID."/".$total_pages)); break;
break; default:
default: $page->set_mode("redirect");
$page->set_mode("redirect"); $page->set_redirect(make_link("forum/index"));
$page->set_redirect(make_link("forum/index")); //$this->theme->display_error(400, "Invalid action", "You should check forum/index.");
//$this->theme->display_error(400, "Invalid action", "You should check forum/index."); break;
break; }
} }
} }
}
private function get_total_pages_for_thread(int $threadID) private function get_total_pages_for_thread(int $threadID)
{ {
global $database, $config; global $database, $config;
$result = $database->get_row("SELECT COUNT(1) AS count FROM forum_posts WHERE thread_id = ?", array($threadID)); $result = $database->get_row("SELECT COUNT(1) AS count FROM forum_posts WHERE thread_id = ?", [$threadID]);
return ceil($result["count"] / $config->get_int("forumPostsPerPage")); return ceil($result["count"] / $config->get_int("forumPostsPerPage"));
} }
private function sanity_check_new_thread() private function sanity_check_new_thread()
{ {
$errors = null; $errors = null;
if (!array_key_exists("title", $_POST)) if (!array_key_exists("title", $_POST)) {
{ $errors .= "<div id='error'>No title supplied.</div>";
$errors .= "<div id='error'>No title supplied.</div>"; } elseif (strlen($_POST["title"]) == 0) {
} $errors .= "<div id='error'>You cannot have an empty title.</div>";
else if (strlen($_POST["title"]) == 0) } elseif (strlen(html_escape($_POST["title"])) > 255) {
{ $errors .= "<div id='error'>Your title is too long.</div>";
$errors .= "<div id='error'>You cannot have an empty title.</div>"; }
}
else if (strlen(html_escape($_POST["title"])) > 255)
{
$errors .= "<div id='error'>Your title is too long.</div>";
}
if (!array_key_exists("message", $_POST)) if (!array_key_exists("message", $_POST)) {
{ $errors .= "<div id='error'>No message supplied.</div>";
$errors .= "<div id='error'>No message supplied.</div>"; } elseif (strlen($_POST["message"]) == 0) {
} $errors .= "<div id='error'>You cannot have an empty message.</div>";
else if (strlen($_POST["message"]) == 0) }
{
$errors .= "<div id='error'>You cannot have an empty message.</div>";
}
return array($errors); return [$errors];
} }
private function sanity_check_new_post() private function sanity_check_new_post()
{ {
$errors = null; $errors = null;
if (!array_key_exists("threadID", $_POST)) if (!array_key_exists("threadID", $_POST)) {
{ $errors = "<div id='error'>No thread ID supplied.</div>";
$errors = "<div id='error'>No thread ID supplied.</div>"; } elseif (strlen($_POST["threadID"]) == 0) {
} $errors = "<div id='error'>No thread ID supplied.</div>";
else if (strlen($_POST["threadID"]) == 0) } elseif (is_numeric($_POST["threadID"])) {
{ if (!array_key_exists("message", $_POST)) {
$errors = "<div id='error'>No thread ID supplied.</div>"; $errors .= "<div id='error'>No message supplied.</div>";
} } elseif (strlen($_POST["message"]) == 0) {
else if (is_numeric($_POST["threadID"])) $errors .= "<div id='error'>You cannot have an empty message.</div>";
}
}
if (!array_key_exists("message", $_POST)) return [$errors];
{ }
$errors .= "<div id='error'>No message supplied.</div>";
}
else if (strlen($_POST["message"]) == 0)
{
$errors .= "<div id='error'>You cannot have an empty message.</div>";
}
return array($errors); private function sanity_check_viewed_thread(int $threadID)
} {
$errors = null;
if (!$this->threadExists($threadID)) {
$errors = "<div id='error'>Inexistent thread.</div>";
}
return [$errors];
}
private function sanity_check_viewed_thread(int $threadID) private function get_thread_title(int $threadID)
{ {
$errors = null; global $database;
if (!$this->threadExists($threadID)) $result = $database->get_row("SELECT t.title FROM forum_threads AS t WHERE t.id = ? ", [$threadID]);
{ return $result["title"];
$errors = "<div id='error'>Inexistent thread.</div>"; }
}
return array($errors); private function show_last_threads(Page $page, PageRequestEvent $event, $showAdminOptions = false)
} {
global $config, $database;
$pageNumber = $event->get_arg(1);
$threadsPerPage = $config->get_int('forumThreadsPerPage', 15);
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_threads") / $threadsPerPage);
private function get_thread_title(int $threadID) if (is_null($pageNumber) || !is_numeric($pageNumber)) {
{ $pageNumber = 0;
global $database; } elseif ($pageNumber <= 0) {
$result = $database->get_row("SELECT t.title FROM forum_threads AS t WHERE t.id = ? ", array($threadID)); $pageNumber = 0;
return $result["title"]; } elseif ($pageNumber >= $totalPages) {
} $pageNumber = $totalPages - 1;
} else {
private function show_last_threads(Page $page, PageRequestEvent $event, $showAdminOptions = false) $pageNumber--;
{ }
global $config, $database;
$pageNumber = $event->get_arg(1);
$threadsPerPage = $config->get_int('forumThreadsPerPage', 15);
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_threads") / $threadsPerPage);
if(is_null($pageNumber) || !is_numeric($pageNumber)) $threads = $database->get_all(
$pageNumber = 0; "SELECT f.id, f.sticky, f.title, f.date, f.uptodate, u.name AS user_name, u.email AS user_email, u.class AS user_class, sum(1) - 1 AS response_count ".
else if ($pageNumber <= 0) "FROM forum_threads AS f ".
$pageNumber = 0; "INNER JOIN users AS u ".
else if ($pageNumber >= $totalPages) "ON f.user_id = u.id ".
$pageNumber = $totalPages - 1; "INNER JOIN forum_posts AS p ".
else "ON p.thread_id = f.id ".
$pageNumber--; "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]
);
$threads = $database->get_all( $this->theme->display_thread_list($page, $threads, $showAdminOptions, $pageNumber + 1, $totalPages);
"SELECT f.id, f.sticky, f.title, f.date, f.uptodate, u.name AS user_name, u.email AS user_email, u.class AS user_class, sum(1) - 1 AS response_count ". }
"FROM forum_threads AS f ".
"INNER JOIN users AS u ".
"ON f.user_id = u.id ".
"INNER JOIN forum_posts AS p ".
"ON p.thread_id = f.id ".
"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"
, array("limit"=>$threadsPerPage, "offset"=>$pageNumber * $threadsPerPage)
);
$this->theme->display_thread_list($page, $threads, $showAdminOptions, $pageNumber + 1, $totalPages); private function show_posts(PageRequestEvent $event, $showAdminOptions = false)
} {
global $config, $database;
$threadID = $event->get_arg(1);
$pageNumber = $event->get_arg(2);
$postsPerPage = $config->get_int('forumPostsPerPage', 15);
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_posts WHERE thread_id = ?", [$threadID]) / $postsPerPage);
$threadTitle = $this->get_thread_title($threadID);
private function show_posts(PageRequestEvent $event, $showAdminOptions = false) if (is_null($pageNumber) || !is_numeric($pageNumber)) {
{ $pageNumber = 0;
global $config, $database; } elseif ($pageNumber <= 0) {
$threadID = $event->get_arg(1); $pageNumber = 0;
$pageNumber = $event->get_arg(2); } elseif ($pageNumber >= $totalPages) {
$postsPerPage = $config->get_int('forumPostsPerPage', 15); $pageNumber = $totalPages - 1;
$totalPages = ceil($database->get_one("SELECT COUNT(*) FROM forum_posts WHERE thread_id = ?", array($threadID)) / $postsPerPage); } else {
$threadTitle = $this->get_thread_title($threadID); $pageNumber--;
}
if(is_null($pageNumber) || !is_numeric($pageNumber)) $posts = $database->get_all(
$pageNumber = 0; "SELECT p.id, p.date, p.message, u.name as user_name, u.email AS user_email, u.class AS user_class ".
else if ($pageNumber <= 0) "FROM forum_posts AS p ".
$pageNumber = 0; "INNER JOIN users AS u ".
else if ($pageNumber >= $totalPages) "ON p.user_id = u.id ".
$pageNumber = $totalPages - 1; "WHERE thread_id = :thread_id ".
else "ORDER BY p.date ASC ".
$pageNumber--; "LIMIT :limit OFFSET :offset",
["thread_id"=>$threadID, "offset"=>$pageNumber * $postsPerPage, "limit"=>$postsPerPage]
);
$this->theme->display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber + 1, $totalPages);
}
$posts = $database->get_all( private function save_new_thread(User $user)
"SELECT p.id, p.date, p.message, u.name as user_name, u.email AS user_email, u.class AS user_class ". {
"FROM forum_posts AS p ". $title = html_escape($_POST["title"]);
"INNER JOIN users AS u ". $sticky = !empty($_POST["sticky"]) ? html_escape($_POST["sticky"]) : "N";
"ON p.user_id = u.id ".
"WHERE thread_id = :thread_id ".
"ORDER BY p.date ASC ".
"LIMIT :limit OFFSET :offset"
, array("thread_id"=>$threadID, "offset"=>$pageNumber * $postsPerPage, "limit"=>$postsPerPage)
);
$this->theme->display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber + 1, $totalPages);
}
private function save_new_thread(User $user) if ($sticky == "") {
{ $sticky = "N";
$title = html_escape($_POST["title"]); }
$sticky = !empty($_POST["sticky"]) ? html_escape($_POST["sticky"]) : "N";
if($sticky == ""){ global $database;
$sticky = "N"; $database->execute(
} "
global $database;
$database->execute("
INSERT INTO forum_threads INSERT INTO forum_threads
(title, sticky, user_id, date, uptodate) (title, sticky, user_id, date, uptodate)
VALUES VALUES
(?, ?, ?, now(), now())", (?, ?, ?, now(), now())",
array($title, $sticky, $user->id)); [$title, $sticky, $user->id]
);
$threadID = $database->get_last_insert_id("forum_threads_id_seq"); $threadID = $database->get_last_insert_id("forum_threads_id_seq");
log_info("forum", "Thread {$threadID} created by {$user->name}"); log_info("forum", "Thread {$threadID} created by {$user->name}");
return $threadID; return $threadID;
} }
private function save_new_post(int $threadID, User $user) private function save_new_post(int $threadID, User $user)
{ {
global $config; global $config;
$userID = $user->id; $userID = $user->id;
$message = html_escape($_POST["message"]); $message = html_escape($_POST["message"]);
$max_characters = $config->get_int('forumMaxCharsPerPost'); $max_characters = $config->get_int('forumMaxCharsPerPost');
$message = substr($message, 0, $max_characters); $message = substr($message, 0, $max_characters);
global $database; global $database;
$database->execute("INSERT INTO forum_posts $database->execute("INSERT INTO forum_posts
(thread_id, user_id, date, message) (thread_id, user_id, date, message)
VALUES VALUES
(?, ?, now(), ?)" (?, ?, now(), ?)", [$threadID, $userID, $message]);
, array($threadID, $userID, $message));
$postID = $database->get_last_insert_id("forum_posts_id_seq"); $postID = $database->get_last_insert_id("forum_posts_id_seq");
log_info("forum", "Post {$postID} created by {$user->name}"); log_info("forum", "Post {$postID} created by {$user->name}");
$database->execute("UPDATE forum_threads SET uptodate=now() WHERE id=?", array ($threadID)); $database->execute("UPDATE forum_threads SET uptodate=now() WHERE id=?", [$threadID]);
} }
private function retrieve_posts(int $threadID, int $pageNumber) private function retrieve_posts(int $threadID, int $pageNumber)
{ {
global $database, $config; global $database, $config;
$postsPerPage = $config->get_int('forumPostsPerPage', 15); $postsPerPage = $config->get_int('forumPostsPerPage', 15);
return $database->get_all( return $database->get_all(
"SELECT p.id, p.date, p.message, u.name as user_name, u.email AS user_email, u.class AS user_class ". "SELECT p.id, p.date, p.message, u.name as user_name, u.email AS user_email, u.class AS user_class ".
"FROM forum_posts AS p ". "FROM forum_posts AS p ".
"INNER JOIN users AS u ". "INNER JOIN users AS u ".
"ON p.user_id = u.id ". "ON p.user_id = u.id ".
"WHERE thread_id = :thread_id ". "WHERE thread_id = :thread_id ".
"ORDER BY p.date ASC ". "ORDER BY p.date ASC ".
"LIMIT :limit OFFSET :offset " "LIMIT :limit OFFSET :offset ",
, array("thread_id"=>$threadID, "offset"=>($pageNumber - 1) * $postsPerPage, "limit"=>$postsPerPage)); ["thread_id"=>$threadID, "offset"=>($pageNumber - 1) * $postsPerPage, "limit"=>$postsPerPage]
} );
}
private function delete_thread(int $threadID) private function delete_thread(int $threadID)
{ {
global $database; global $database;
$database->execute("DELETE FROM forum_threads WHERE id = ?", array($threadID)); $database->execute("DELETE FROM forum_threads WHERE id = ?", [$threadID]);
$database->execute("DELETE FROM forum_posts WHERE thread_id = ?", array($threadID)); $database->execute("DELETE FROM forum_posts WHERE thread_id = ?", [$threadID]);
} }
private function delete_post(int $postID) private function delete_post(int $postID)
{ {
global $database; global $database;
$database->execute("DELETE FROM forum_posts WHERE id = ?", array($postID)); $database->execute("DELETE FROM forum_posts WHERE id = ?", [$postID]);
} }
private function threadExists(int $threadID) private function threadExists(int $threadID)
{ {
global $database; global $database;
$result=$database->get_one("SELECT EXISTS (SELECT * FROM forum_threads WHERE id= ?)", array($threadID)); $result=$database->get_one("SELECT EXISTS (SELECT * FROM forum_threads WHERE id= ?)", [$threadID]);
if ($result==1){ if ($result==1) {
return true; return true;
}else{ } else {
return false; return false;
} }
} }
} }

View File

@ -1,17 +1,18 @@
<?php <?php
class ForumTheme extends Themelet { class ForumTheme extends Themelet
{
public function display_thread_list(Page $page, $threads, $showAdminOptions, $pageNumber, $totalPages) public function display_thread_list(Page $page, $threads, $showAdminOptions, $pageNumber, $totalPages)
{ {
if (count($threads) == 0) if (count($threads) == 0) {
$html = "There are no threads to show."; $html = "There are no threads to show.";
else } else {
$html = $this->make_thread_list($threads, $showAdminOptions); $html = $this->make_thread_list($threads, $showAdminOptions);
}
$page->set_title(html_escape("Forum")); $page->set_title(html_escape("Forum"));
$page->set_heading(html_escape("Forum")); $page->set_heading(html_escape("Forum"));
$page->add_block(new Block("Forum", $html, "main", 10)); $page->add_block(new Block("Forum", $html, "main", 10));
$this->display_paginator($page, "forum/index", null, $pageNumber, $totalPages); $this->display_paginator($page, "forum/index", null, $pageNumber, $totalPages);
} }
@ -19,55 +20,57 @@ class ForumTheme extends Themelet {
public function display_new_thread_composer(Page $page, $threadText = null, $threadTitle = null) public function display_new_thread_composer(Page $page, $threadText = null, $threadTitle = null)
{ {
global $config, $user; global $config, $user;
$max_characters = $config->get_int('forumMaxCharsPerPost'); $max_characters = $config->get_int('forumMaxCharsPerPost');
$html = make_form(make_link("forum/create")); $html = make_form(make_link("forum/create"));
if (!is_null($threadTitle)) if (!is_null($threadTitle)) {
$threadTitle = html_escape($threadTitle); $threadTitle = html_escape($threadTitle);
}
if(!is_null($threadText)) if (!is_null($threadText)) {
$threadText = html_escape($threadText); $threadText = html_escape($threadText);
}
$html .= "
$html .= "
<table style='width: 500px;'> <table style='width: 500px;'>
<tr><td>Title:</td><td><input type='text' name='title' value='$threadTitle'></td></tr> <tr><td>Title:</td><td><input type='text' name='title' value='$threadTitle'></td></tr>
<tr><td>Message:</td><td><textarea id='message' name='message' >$threadText</textarea></td></tr> <tr><td>Message:</td><td><textarea id='message' name='message' >$threadText</textarea></td></tr>
<tr><td></td><td><small>Max characters alowed: $max_characters.</small></td></tr>"; <tr><td></td><td><small>Max characters alowed: $max_characters.</small></td></tr>";
if($user->is_admin()){ if ($user->is_admin()) {
$html .= "<tr><td colspan='2'><label for='sticky'>Sticky:</label><input name='sticky' type='checkbox' value='Y' /></td></tr>"; $html .= "<tr><td colspan='2'><label for='sticky'>Sticky:</label><input name='sticky' type='checkbox' value='Y' /></td></tr>";
} }
$html .= "<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr> $html .= "<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
</table> </table>
</form> </form>
"; ";
$blockTitle = "Write a new thread"; $blockTitle = "Write a new thread";
$page->set_title(html_escape($blockTitle)); $page->set_title(html_escape($blockTitle));
$page->set_heading(html_escape($blockTitle)); $page->set_heading(html_escape($blockTitle));
$page->add_block(new Block($blockTitle, $html, "main", 120)); $page->add_block(new Block($blockTitle, $html, "main", 120));
} }
public function display_new_post_composer(Page $page, $threadID) public function display_new_post_composer(Page $page, $threadID)
{ {
global $config; global $config;
$max_characters = $config->get_int('forumMaxCharsPerPost'); $max_characters = $config->get_int('forumMaxCharsPerPost');
$html = make_form(make_link("forum/answer")); $html = make_form(make_link("forum/answer"));
$html .= '<input type="hidden" name="threadID" value="'.$threadID.'" />'; $html .= '<input type="hidden" name="threadID" value="'.$threadID.'" />';
$html .= " $html .= "
<table style='width: 500px;'> <table style='width: 500px;'>
<tr><td>Message:</td><td><textarea id='message' name='message' ></textarea> <tr><td>Message:</td><td><textarea id='message' name='message' ></textarea>
<tr><td></td><td><small>Max characters alowed: $max_characters.</small></td></tr> <tr><td></td><td><small>Max characters alowed: $max_characters.</small></td></tr>
</td></tr>"; </td></tr>";
$html .= "<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr> $html .= "<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
</table> </table>
</form> </form>
"; ";
@ -78,60 +81,59 @@ class ForumTheme extends Themelet {
public function display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber, $totalPages) public function display_thread($posts, $showAdminOptions, $threadTitle, $threadID, $pageNumber, $totalPages)
{ {
global $config, $page/*, $user*/; global $config, $page/*, $user*/;
$posts_per_page = $config->get_int('forumPostsPerPage'); $posts_per_page = $config->get_int('forumPostsPerPage');
$current_post = 0; $current_post = 0;
$html = $html =
"<div id=returnLink>[<a href=".make_link("forum/index/").">Return</a>]</div><br><br>". "<div id=returnLink>[<a href=".make_link("forum/index/").">Return</a>]</div><br><br>".
"<table id='threadPosts' class='zebra'>". "<table id='threadPosts' class='zebra'>".
"<thead><tr>". "<thead><tr>".
"<th id=threadHeadUser>User</th>". "<th id=threadHeadUser>User</th>".
"<th>Message</th>". "<th>Message</th>".
"</tr></thead>"; "</tr></thead>";
foreach ($posts as $post) foreach ($posts as $post) {
{ $current_post++;
$current_post++;
$message = $post["message"]; $message = $post["message"];
$tfe = new TextFormattingEvent($message); $tfe = new TextFormattingEvent($message);
send_event($tfe); send_event($tfe);
$message = $tfe->formatted; $message = $tfe->formatted;
$message = str_replace('\n\r', '<br>', $message); $message = str_replace('\n\r', '<br>', $message);
$message = str_replace('\r\n', '<br>', $message); $message = str_replace('\r\n', '<br>', $message);
$message = str_replace('\n', '<br>', $message); $message = str_replace('\n', '<br>', $message);
$message = str_replace('\r', '<br>', $message); $message = str_replace('\r', '<br>', $message);
$message = stripslashes($message); $message = stripslashes($message);
$user = "<a href='".make_link("user/".$post["user_name"]."")."'>".$post["user_name"]."</a>"; $user = "<a href='".make_link("user/".$post["user_name"]."")."'>".$post["user_name"]."</a>";
$poster = User::by_name($post["user_name"]); $poster = User::by_name($post["user_name"]);
$gravatar = $poster->get_avatar_html(); $gravatar = $poster->get_avatar_html();
$rank = "<sup class='user_rank'>{$post["user_class"]}</sup>"; $rank = "<sup class='user_rank'>{$post["user_class"]}</sup>";
$postID = $post['id']; $postID = $post['id'];
//if($user->is_admin()){ //if($user->is_admin()){
//$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>"; //$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>";
//} else { //} else {
//$delete_link = ""; //$delete_link = "";
//} //}
if($showAdminOptions){ if ($showAdminOptions) {
$delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>"; $delete_link = "<a href=".make_link("forum/delete/".$threadID."/".$postID).">Delete</a>";
}else{ } else {
$delete_link = ""; $delete_link = "";
} }
$post_number = (($pageNumber-1)*$posts_per_page)+$current_post; $post_number = (($pageNumber-1)*$posts_per_page)+$current_post;
$html .= "<tr > $html .= "<tr >
<tr class='postHead'> <tr class='postHead'>
<td class='forumSupuser'></td> <td class='forumSupuser'></td>
@ -149,20 +151,18 @@ class ForumTheme extends Themelet {
<td class='forumSubuser'></td> <td class='forumSubuser'></td>
<td class='forumSubmessage'></td> <td class='forumSubmessage'></td>
</tr>"; </tr>";
} }
$html .= "</tbody></table>"; $html .= "</tbody></table>";
$this->display_paginator($page, "forum/view/".$threadID, null, $pageNumber, $totalPages); $this->display_paginator($page, "forum/view/".$threadID, null, $pageNumber, $totalPages);
$page->set_title(html_escape($threadTitle)); $page->set_title(html_escape($threadTitle));
$page->set_heading(html_escape($threadTitle)); $page->set_heading(html_escape($threadTitle));
$page->add_block(new Block($threadTitle, $html, "main", 20)); $page->add_block(new Block($threadTitle, $html, "main", 20));
} }
public function add_actions_block(Page $page, $threadID) public function add_actions_block(Page $page, $threadID)
{ {
@ -179,11 +179,10 @@ class ForumTheme extends Themelet {
"<thead><tr>". "<thead><tr>".
"<th>Title</th>". "<th>Title</th>".
"<th>Author</th>". "<th>Author</th>".
"<th>Updated</th>". "<th>Updated</th>".
"<th>Responses</th>"; "<th>Responses</th>";
if($showAdminOptions) if ($showAdminOptions) {
{
$html .= "<th>Actions</th>"; $html .= "<th>Actions</th>";
} }
@ -191,35 +190,34 @@ class ForumTheme extends Themelet {
$current_post = 0; $current_post = 0;
foreach($threads as $thread) foreach ($threads as $thread) {
{
$oe = ($current_post++ % 2 == 0) ? "even" : "odd"; $oe = ($current_post++ % 2 == 0) ? "even" : "odd";
global $config; global $config;
$titleSubString = $config->get_int('forumTitleSubString'); $titleSubString = $config->get_int('forumTitleSubString');
if ($titleSubString < strlen($thread["title"])) if ($titleSubString < strlen($thread["title"])) {
{ $title = substr($thread["title"], 0, $titleSubString);
$title = substr($thread["title"], 0, $titleSubString); $title = $title."...";
$title = $title."..."; } else {
} else { $title = $thread["title"];
$title = $thread["title"]; }
}
if ($thread["sticky"] == "Y") {
if($thread["sticky"] == "Y"){ $sticky = "Sticky: ";
$sticky = "Sticky: "; } else {
} else { $sticky = "";
$sticky = ""; }
}
$html .= "<tr class='$oe'>". $html .= "<tr class='$oe'>".
'<td class="left">'.$sticky.'<a href="'.make_link("forum/view/".$thread["id"]).'">'.$title."</a></td>". '<td class="left">'.$sticky.'<a href="'.make_link("forum/view/".$thread["id"]).'">'.$title."</a></td>".
'<td><a href="'.make_link("user/".$thread["user_name"]).'">'.$thread["user_name"]."</a></td>". '<td><a href="'.make_link("user/".$thread["user_name"]).'">'.$thread["user_name"]."</a></td>".
"<td>".autodate($thread["uptodate"])."</td>". "<td>".autodate($thread["uptodate"])."</td>".
"<td>".$thread["response_count"]."</td>"; "<td>".$thread["response_count"]."</td>";
if ($showAdminOptions) if ($showAdminOptions) {
$html .= '<td><a href="'.make_link("forum/nuke/".$thread["id"]).'" title="Delete '.$title.'">Delete</a></td>'; $html .= '<td><a href="'.make_link("forum/nuke/".$thread["id"]).'" title="Delete '.$title.'">Delete</a></td>';
}
$html .= "</tr>"; $html .= "</tr>";
} }
@ -229,4 +227,3 @@ class ForumTheme extends Themelet {
return $html; return $html;
} }
} }

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