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}]
charset = utf-8
indent_style = tab
indent_style = space
indent_size = 4

1
.gitignore vendored
View file

@ -4,6 +4,7 @@ images
thumbs
*.phar
*.sqlite
.php_cs.cache
#Composer
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
$_shm_ctx->log_start("Opening files");
$_shm_files = array_merge(
zglob("core/*.php"),
zglob("core/{".ENABLED_MODS."}/*.php"),
zglob("ext/{".ENABLED_EXTS."}/main.php")
zglob("core/*.php"),
zglob("core/{".ENABLED_MODS."}/*.php"),
zglob("ext/{".ENABLED_EXTS."}/main.php")
);
foreach($_shm_files as $_shm_filename) {
if(basename($_shm_filename)[0] != "_") {
require_once $_shm_filename;
}
foreach ($_shm_files as $_shm_filename) {
if (basename($_shm_filename)[0] != "_") {
require_once $_shm_filename;
}
}
unset($_shm_files);
unset($_shm_filename);
@ -40,8 +40,8 @@ $_shm_ctx->log_endok();
// load the theme parts
$_shm_ctx->log_start("Loading themelets");
foreach(_get_themelet_files(get_theme()) as $themelet) {
require_once $themelet;
foreach (_get_themelet_files(get_theme()) as $themelet) {
require_once $themelet;
}
unset($themelet);
$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]
* @link http://code.shishnet.org/shimmie2/
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*
* Initialise the database, check that folder
* 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>
</head>
<body>
<?php if(FALSE) { ?>
<?php if (false) { ?>
<div id="installer">
<h1>Install Error</h1>
<div class="container">
@ -43,7 +43,7 @@ date_default_timezone_set('UTC');
</div>
</div>
<pre style="display:none">
<?php } elseif(!file_exists("vendor/")) { ?>
<?php } elseif (!file_exists("vendor/")) { ?>
<div id="installer">
<h1>Install Error</h1>
<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/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();
// 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 {
$gdversion = 0;
function check_gd_version(): int
{
$gdversion = 0;
if (function_exists('gd_info')){
$gd_info = gd_info();
if (substr_count($gd_info['GD Version'], '2.')) {
$gdversion = 2;
} else if (substr_count($gd_info['GD Version'], '1.')) {
$gdversion = 1;
}
}
if (function_exists('gd_info')) {
$gd_info = gd_info();
if (substr_count($gd_info['GD Version'], '2.')) {
$gdversion = 2;
} elseif (substr_count($gd_info['GD Version'], '1.')) {
$gdversion = 1;
}
}
return $gdversion;
return $gdversion;
}
function check_im_version(): int {
$convert_check = exec("convert");
function check_im_version(): int
{
$convert_check = exec("convert");
return (empty($convert_check) ? 0 : 1);
return (empty($convert_check) ? 0 : 1);
}
function eok($name, $value) {
echo "<br>$name ... ";
if($value) {
echo "<span style='color: green'>ok</span>\n";
}
else {
echo "<span style='color: green'>failed</span>\n";
}
function eok($name, $value)
{
echo "<br>$name ... ";
if ($value) {
echo "<span style='color: green'>ok</span>\n";
} else {
echo "<span style='color: green'>failed</span>\n";
}
}
// }}}
function do_install() { // {{{
if(file_exists("data/config/auto_install.conf.php")) {
require_once "data/config/auto_install.conf.php";
}
else if(@$_POST["database_type"] == "sqlite") {
$id = bin2hex(random_bytes(5));
define('DATABASE_DSN', "sqlite:data/shimmie.{$id}.sqlite");
}
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']}");
}
else {
ask_questions();
return;
}
function do_install()
{ // {{{
if (file_exists("data/config/auto_install.conf.php")) {
require_once "data/config/auto_install.conf.php";
} elseif (@$_POST["database_type"] == "sqlite") {
$id = bin2hex(random_bytes(5));
define('DATABASE_DSN', "sqlite:data/shimmie.{$id}.sqlite");
} elseif (isset($_POST['database_type']) && isset($_POST['database_host']) && isset($_POST['database_user']) && isset($_POST['database_name'])) {
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();
return;
}
define("CACHE_DSN", null);
define("DEBUG_SQL", false);
define("DATABASE_KA", true);
install_process();
define("CACHE_DSN", null);
define("DEBUG_SQL", false);
define("DATABASE_KA", true);
install_process();
} // }}}
function ask_questions() { // {{{
$warnings = array();
$errors = array();
function ask_questions()
{ // {{{
$warnings = [];
$errors = [];
if(check_gd_version() == 0 && check_im_version() == 0) {
$errors[] = "
if (check_gd_version() == 0 && check_im_version() == 0) {
$errors[] = "
No thumbnailers could be found - install the imagemagick
tools (or the PHP-GD library, if imagemagick is unavailable).
";
}
else if(check_im_version() == 0) {
$warnings[] = "
} elseif (check_im_version() == 0) {
$warnings[] = "
The 'convert' command (from the imagemagick package)
could not be found - PHP-GD can be used instead, but
the size of thumbnails will be limited.
";
}
}
if(!function_exists('mb_strlen')) {
$errors[] = "
if (!function_exists('mb_strlen')) {
$errors[] = "
The mbstring PHP extension is missing - multibyte languages
(eg non-english languages) may not work right.
";
}
}
$drivers = PDO::getAvailableDrivers();
if(
!in_array("mysql", $drivers) &&
!in_array("pgsql", $drivers) &&
!in_array("sqlite", $drivers)
) {
$errors[] = "
$drivers = PDO::getAvailableDrivers();
if (
!in_array("mysql", $drivers) &&
!in_array("pgsql", $drivers) &&
!in_array("sqlite", $drivers)
) {
$errors[] = "
No database connection library could be found; shimmie needs
PDO with either Postgres, MySQL, or SQLite drivers
";
}
}
$db_m = in_array("mysql", $drivers) ? '<option value="mysql">MySQL</option>' : "";
$db_p = in_array("pgsql", $drivers) ? '<option value="pgsql">PostgreSQL</option>' : "";
$db_s = in_array("sqlite", $drivers) ? '<option value="sqlite">SQLite</option>' : "";
$db_m = in_array("mysql", $drivers) ? '<option value="mysql">MySQL</option>' : "";
$db_p = in_array("pgsql", $drivers) ? '<option value="pgsql">PostgreSQL</option>' : "";
$db_s = in_array("sqlite", $drivers) ? '<option value="sqlite">SQLite</option>' : "";
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
print <<<EOD
print <<<EOD
<div id="installer">
<h1>Shimmie Installer</h1>
@ -243,19 +245,21 @@ EOD;
/**
* This is where the install really takes place.
*/
function install_process() { // {{{
build_dirs();
create_tables();
insert_defaults();
write_config();
function install_process()
{ // {{{
build_dirs();
create_tables();
insert_defaults();
write_config();
} // }}}
function create_tables() { // {{{
try {
$db = new Database();
function create_tables()
{ // {{{
try {
$db = new Database();
if ( $db->count_tables() > 0 ) {
print <<<EOD
if ($db->count_tables() > 0) {
print <<<EOD
<div id="installer">
<h1>Shimmie Installer</h1>
<h3>Warning: The Database schema is not empty!</h3>
@ -266,22 +270,22 @@ function create_tables() { // {{{
</div>
</div>
EOD;
exit(2);
}
exit(2);
}
$db->create_table("aliases", "
$db->create_table("aliases", "
oldtag VARCHAR(128) NOT NULL,
newtag VARCHAR(128) NOT NULL,
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,
value TEXT,
PRIMARY KEY (name)
");
$db->create_table("users", "
$db->create_table("users", "
id SCORE_AIPK,
name VARCHAR(32) UNIQUE NOT NULL,
pass VARCHAR(250),
@ -289,9 +293,9 @@ EOD;
class VARCHAR(32) NOT NULL DEFAULT 'user',
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,
owner_id INTEGER NOT NULL,
owner_ip SCORE_INET NOT NULL,
@ -306,69 +310,72 @@ EOD;
locked SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N,
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_width_idx ON images(width)", array());
$db->execute("CREATE INDEX images_height_idx ON images(height)", array());
$db->execute("CREATE INDEX images_hash_idx ON images(hash)", array());
$db->execute("CREATE INDEX images_owner_id_idx ON images(owner_id)", []);
$db->execute("CREATE INDEX images_width_idx ON images(width)", []);
$db->execute("CREATE INDEX images_height_idx ON images(height)", []);
$db->execute("CREATE INDEX images_hash_idx ON images(hash)", []);
$db->create_table("tags", "
$db->create_table("tags", "
id SCORE_AIPK,
tag VARCHAR(64) UNIQUE NOT NULL,
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,
tag_id INTEGER NOT NULL,
UNIQUE(image_id, tag_id),
FOREIGN KEY (image_id) REFERENCES images(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_tag_id_idx ON image_tags(tag_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)", []);
$db->execute("INSERT INTO config(name, value) VALUES('db_version', 11)");
$db->commit();
}
catch(PDOException $e) {
handle_db_errors(TRUE, "An error occurred while trying to create the database tables necessary for Shimmie.", $e->getMessage(), 3);
} catch (Exception $e) {
handle_db_errors(FALSE, "An unknown error occurred while trying to insert data into the database.", $e->getMessage(), 4);
}
$db->execute("INSERT INTO config(name, value) VALUES('db_version', 11)");
$db->commit();
} catch (PDOException $e) {
handle_db_errors(true, "An error occurred while trying to create the database tables necessary for Shimmie.", $e->getMessage(), 3);
} catch (Exception $e) {
handle_db_errors(false, "An unknown error occurred while trying to insert data into the database.", $e->getMessage(), 4);
}
} // }}}
function insert_defaults() { // {{{
try {
$db = new Database();
function insert_defaults()
{ // {{{
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 config(name, value) VALUES(:name, :value)", Array("name" => 'anon_id', "value" => $db->get_last_insert_id('users_id_seq')));
$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)", ["name" => 'anon_id', "value" => $db->get_last_insert_id('users_id_seq')]);
if(check_im_version() > 0) {
$db->execute("INSERT INTO config(name, value) VALUES(:name, :value)", Array("name" => 'thumb_engine', "value" => 'convert'));
}
$db->commit();
}
catch(PDOException $e) {
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);
}
if (check_im_version() > 0) {
$db->execute("INSERT INTO config(name, value) VALUES(:name, :value)", ["name" => 'thumb_engine', "value" => 'convert']);
}
$db->commit();
} catch (PDOException $e) {
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);
}
} // }}}
function build_dirs() { // {{{
// *try* and make default dirs. Ignore any errors --
// if something is amiss, we'll tell the user later
if(!file_exists("data")) @mkdir("data");
if(!is_writable("data")) @chmod("data", 0755);
function build_dirs()
{ // {{{
// *try* and make default dirs. Ignore any errors --
// if something is amiss, we'll tell the user later
if (!file_exists("data")) {
@mkdir("data");
}
if (!is_writable("data")) {
@chmod("data", 0755);
}
// Clear file status cache before checking again.
clearstatcache();
// Clear file status cache before checking again.
clearstatcache();
if(!file_exists("data") || !is_writable("data")) {
print "
if (!file_exists("data") || !is_writable("data")) {
print "
<div id='installer'>
<h1>Shimmie Installer</h1>
<h3>Directory Permissions Error:</h3>
@ -381,22 +388,23 @@ function build_dirs() { // {{{
</div>
</div>
";
exit(7);
}
exit(7);
}
} // }}}
function write_config() { // {{{
$file_content = '<' . '?php' . "\n" .
"define('DATABASE_DSN', '".DATABASE_DSN."');\n" .
'?' . '>';
function write_config()
{ // {{{
$file_content = '<' . '?php' . "\n" .
"define('DATABASE_DSN', '".DATABASE_DSN."');\n" .
'?' . '>';
if(!file_exists("data/config")) {
mkdir("data/config", 0755, true);
}
if (!file_exists("data/config")) {
mkdir("data/config", 0755, true);
}
if(file_put_contents("data/config/shimmie.conf.php", $file_content, LOCK_EX)) {
header("Location: index.php");
print <<<EOD
if (file_put_contents("data/config/shimmie.conf.php", $file_content, LOCK_EX)) {
header("Location: index.php");
print <<<EOD
<div id="installer">
<h1>Shimmie Installer</h1>
<h3>Things are OK \o/</h3>
@ -405,10 +413,9 @@ function write_config() { // {{{
</div>
</div>
EOD;
}
else {
$h_file_content = htmlentities($file_content);
print <<<EOD
} else {
$h_file_content = htmlentities($file_content);
print <<<EOD
<div id="installer">
<h1>Shimmie Installer</h1>
<h3>File Permissions Error:</h3>
@ -425,13 +432,14 @@ EOD;
</div>
</div>
EOD;
}
echo "\n";
}
echo "\n";
} // }}}
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
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
<div id="installer">
<h1>Shimmie Installer</h1>
<h3>Unknown Error:</h3>
@ -442,7 +450,7 @@ function handle_db_errors(bool $isPDO, string $errorMessage1, string $errorMessa
</div>
</div>
EOD;
exit($exitCode);
exit($exitCode);
}
?>
</body>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,142 +1,188 @@
<?php
class DBEngine {
/** @var null|string */
public $name = null;
class DBEngine
{
/** @var null|string */
public $name = null;
public function init(PDO $db) {}
public function init(PDO $db)
{
}
public function scoreql_to_sql(string $scoreql): string {
return $scoreql;
}
public function scoreql_to_sql(string $scoreql): string
{
return $scoreql;
}
public function create_table_sql(string $name, string $data): string {
return 'CREATE TABLE '.$name.' ('.$data.')';
}
public function create_table_sql(string $name, string $data): string
{
return 'CREATE TABLE '.$name.' ('.$data.')';
}
}
class MySQL extends DBEngine {
/** @var string */
public $name = "mysql";
class MySQL extends DBEngine
{
/** @var string */
public $name = "mysql";
public function init(PDO $db) {
$db->exec("SET NAMES utf8;");
}
public function init(PDO $db)
{
$db->exec("SET NAMES utf8;");
}
public function scoreql_to_sql(string $data): string {
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY auto_increment", $data);
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
$data = str_replace("SCORE_BOOL", "ENUM('Y', 'N')", $data);
$data = str_replace("SCORE_DATETIME", "DATETIME", $data);
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
$data = str_replace("SCORE_STRNORM", "", $data);
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
return $data;
}
public function scoreql_to_sql(string $data): string
{
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY auto_increment", $data);
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
$data = str_replace("SCORE_BOOL", "ENUM('Y', 'N')", $data);
$data = str_replace("SCORE_DATETIME", "DATETIME", $data);
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
$data = str_replace("SCORE_STRNORM", "", $data);
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
return $data;
}
public function create_table_sql(string $name, string $data): string {
$data = $this->scoreql_to_sql($data);
$ctes = "ENGINE=InnoDB DEFAULT CHARSET='utf8'";
return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes;
}
public function create_table_sql(string $name, string $data): string
{
$data = $this->scoreql_to_sql($data);
$ctes = "ENGINE=InnoDB DEFAULT CHARSET='utf8'";
return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes;
}
}
class PostgreSQL extends DBEngine {
/** @var string */
public $name = "pgsql";
class PostgreSQL extends DBEngine
{
/** @var string */
public $name = "pgsql";
public function init(PDO $db) {
if(array_key_exists('REMOTE_ADDR', $_SERVER)) {
$db->exec("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';");
}
else {
$db->exec("SET application_name TO 'shimmie [local]';");
}
$db->exec("SET statement_timeout TO 10000;");
}
public function init(PDO $db)
{
if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
$db->exec("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';");
} else {
$db->exec("SET application_name TO 'shimmie [local]';");
}
$db->exec("SET statement_timeout TO 10000;");
}
public function scoreql_to_sql(string $data): string {
$data = str_replace("SCORE_AIPK", "SERIAL PRIMARY KEY", $data);
$data = str_replace("SCORE_INET", "INET", $data);
$data = str_replace("SCORE_BOOL_Y", "'t'", $data);
$data = str_replace("SCORE_BOOL_N", "'f'", $data);
$data = str_replace("SCORE_BOOL", "BOOL", $data);
$data = str_replace("SCORE_DATETIME", "TIMESTAMP", $data);
$data = str_replace("SCORE_NOW", "current_timestamp", $data);
$data = str_replace("SCORE_STRNORM", "lower", $data);
$data = str_replace("SCORE_ILIKE", "ILIKE", $data);
return $data;
}
public function scoreql_to_sql(string $data): string
{
$data = str_replace("SCORE_AIPK", "SERIAL PRIMARY KEY", $data);
$data = str_replace("SCORE_INET", "INET", $data);
$data = str_replace("SCORE_BOOL_Y", "'t'", $data);
$data = str_replace("SCORE_BOOL_N", "'f'", $data);
$data = str_replace("SCORE_BOOL", "BOOL", $data);
$data = str_replace("SCORE_DATETIME", "TIMESTAMP", $data);
$data = str_replace("SCORE_NOW", "current_timestamp", $data);
$data = str_replace("SCORE_STRNORM", "lower", $data);
$data = str_replace("SCORE_ILIKE", "ILIKE", $data);
return $data;
}
public function create_table_sql(string $name, string $data): string {
$data = $this->scoreql_to_sql($data);
return "CREATE TABLE $name ($data)";
}
public function create_table_sql(string $name, string $data): string
{
$data = $this->scoreql_to_sql($data);
return "CREATE TABLE $name ($data)";
}
}
// shimmie functions for export to sqlite
function _unix_timestamp($date) { return strtotime($date); }
function _now() { return date("Y-m-d h:i:s"); }
function _floor($a) { return floor($a); }
function _log($a, $b=null) {
if(is_null($b)) return log($a);
else return log($a, $b);
function _unix_timestamp($date)
{
return strtotime($date);
}
function _isnull($a) { return is_null($a); }
function _md5($a) { return md5($a); }
function _concat($a, $b) { return $a . $b; }
function _lower($a) { return strtolower($a); }
function _rand() { return rand(); }
function _ln($n) { return log($n); }
class SQLite extends DBEngine {
/** @var string */
public $name = "sqlite";
public function init(PDO $db) {
ini_set('sqlite.assoc_case', 0);
$db->exec("PRAGMA foreign_keys = ON;");
$db->sqliteCreateFunction('UNIX_TIMESTAMP', '_unix_timestamp', 1);
$db->sqliteCreateFunction('now', '_now', 0);
$db->sqliteCreateFunction('floor', '_floor', 1);
$db->sqliteCreateFunction('log', '_log');
$db->sqliteCreateFunction('isnull', '_isnull', 1);
$db->sqliteCreateFunction('md5', '_md5', 1);
$db->sqliteCreateFunction('concat', '_concat', 2);
$db->sqliteCreateFunction('lower', '_lower', 1);
$db->sqliteCreateFunction('rand', '_rand', 0);
$db->sqliteCreateFunction('ln', '_ln', 1);
}
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 = array();
$extras = "";
foreach(explode(",", $data) as $bit) {
$matches = array();
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";
}
function _now()
{
return date("Y-m-d h:i:s");
}
function _floor($a)
{
return floor($a);
}
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 _md5($a)
{
return md5($a);
}
function _concat($a, $b)
{
return $a . $b;
}
function _lower($a)
{
return strtolower($a);
}
function _rand()
{
return rand();
}
function _ln($n)
{
return log($n);
}
class SQLite extends DBEngine
{
/** @var string */
public $name = "sqlite";
public function init(PDO $db)
{
ini_set('sqlite.assoc_case', 0);
$db->exec("PRAGMA foreign_keys = ON;");
$db->sqliteCreateFunction('UNIX_TIMESTAMP', '_unix_timestamp', 1);
$db->sqliteCreateFunction('now', '_now', 0);
$db->sqliteCreateFunction('floor', '_floor', 1);
$db->sqliteCreateFunction('log', '_log');
$db->sqliteCreateFunction('isnull', '_isnull', 1);
$db->sqliteCreateFunction('md5', '_md5', 1);
$db->sqliteCreateFunction('concat', '_concat', 2);
$db->sqliteCreateFunction('lower', '_lower', 1);
$db->sqliteCreateFunction('rand', '_rand', 0);
$db->sqliteCreateFunction('ln', '_ln', 1);
}
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.
*/
class Email {
/** @var string */
public $to;
/** @var string */
public $subject;
/** @var string */
public $header;
/** @var null|string */
public $style;
/** @var null|string */
public $header_img;
/** @var null|string */
public $sitename;
/** @var null|string */
public $sitedomain;
/** @var null|string */
public $siteemail;
/** @var string */
public $date;
/** @var string */
public $body;
/** @var null|string */
public $footer;
class Email
{
/** @var string */
public $to;
/** @var string */
public $subject;
/** @var string */
public $header;
/** @var null|string */
public $style;
/** @var null|string */
public $header_img;
/** @var null|string */
public $sitename;
/** @var null|string */
public $sitedomain;
/** @var null|string */
public $siteemail;
/** @var string */
public $date;
/** @var string */
public $body;
/** @var null|string */
public $footer;
public function __construct(string $to, string $subject, string $header, string $body) {
global $config;
$this->to = $to;
$sub_prefix = $config->get_string("mail_sub");
if(!isset($sub_prefix)){
$this->subject = $subject;
}
else{
$this->subject = $sub_prefix." ".$subject;
}
$this->style = $config->get_string("mail_style");
$this->header = html_escape($header);
$this->header_img = $config->get_string("mail_img");
$this->sitename = $config->get_string("site_title");
$this->sitedomain = make_http(make_link(""));
$this->siteemail = $config->get_string("site_email");
$this->date = date("F j, Y");
$this->body = $body;
$this->footer = $config->get_string("mail_fot");
}
public function send(): bool {
$headers = "From: ".$this->sitename." <".$this->siteemail.">\r\n";
$headers .= "Reply-To: ".$this->siteemail."\r\n";
$headers .= "X-Mailer: PHP/" . phpversion(). "\r\n";
$headers .= "errors-to: ".$this->siteemail."\r\n";
$headers .= "Date: " . date(DATE_RFC2822);
$headers .= 'MIME-Version: 1.0' . "\r\n";
$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
$message = '
public function __construct(string $to, string $subject, string $header, string $body)
{
global $config;
$this->to = $to;
$sub_prefix = $config->get_string("mail_sub");
if (!isset($sub_prefix)) {
$this->subject = $subject;
} else {
$this->subject = $sub_prefix." ".$subject;
}
$this->style = $config->get_string("mail_style");
$this->header = html_escape($header);
$this->header_img = $config->get_string("mail_img");
$this->sitename = $config->get_string("site_title");
$this->sitedomain = make_http(make_link(""));
$this->siteemail = $config->get_string("site_email");
$this->date = date("F j, Y");
$this->body = $body;
$this->footer = $config->get_string("mail_fot");
}
public function send(): bool
{
$headers = "From: ".$this->sitename." <".$this->siteemail.">\r\n";
$headers .= "Reply-To: ".$this->siteemail."\r\n";
$headers .= "X-Mailer: PHP/" . phpversion(). "\r\n";
$headers .= "errors-to: ".$this->siteemail."\r\n";
$headers .= "Date: " . date(DATE_RFC2822);
$headers .= 'MIME-Version: 1.0' . "\r\n";
$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
$message = '
<html>
<head>
@ -118,15 +120,13 @@ Copyright (C) <a href="'.$this->sitedomain.'">'.$this->sitename.'</a><br />
</body>
</html>
';
$sent = mail($this->to, $this->subject, $message, $headers);
if($sent){
log_info("mail", "Sent message '$this->subject' to '$this->to'");
}
else{
log_info("mail", "Error sending message '$this->subject' to '$this->to'");
}
return $sent;
}
$sent = mail($this->to, $this->subject, $message, $headers);
if ($sent) {
log_info("mail", "Sent message '$this->subject' to '$this->to'");
} else {
log_info("mail", "Error sending message '$this->subject' to '$this->to'");
}
return $sent;
}
}

View file

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

View file

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

View file

@ -3,99 +3,111 @@
/**
* An image is being added to the database.
*/
class ImageAdditionEvent extends Event {
/** @var User */
public $user;
class ImageAdditionEvent extends Event
{
/** @var User */
public $user;
/** @var Image */
public $image;
/** @var Image */
public $image;
/**
* Inserts a new image into the database with its associated
* information. Also calls TagSetEvent to set the tags for
* this new image.
*/
public function __construct(Image $image) {
$this->image = $image;
}
/**
* Inserts a new image into the database with its associated
* information. Also calls TagSetEvent to set the tags for
* this new image.
*/
public function __construct(Image $image)
{
$this->image = $image;
}
}
class ImageAdditionException extends SCoreException {
public $error;
class ImageAdditionException extends SCoreException
{
public $error;
public function __construct(string $error) {
$this->error = $error;
}
public function __construct(string $error)
{
$this->error = $error;
}
}
/**
* An image is being deleted.
*/
class ImageDeletionEvent extends Event {
/** @var \Image */
public $image;
class ImageDeletionEvent extends Event
{
/** @var \Image */
public $image;
/**
* Deletes an image.
*
* Used by things like tags and comments handlers to
* clean out related rows in their tables.
*/
public function __construct(Image $image) {
$this->image = $image;
}
/**
* Deletes an image.
*
* Used by things like tags and comments handlers to
* clean out related rows in their tables.
*/
public function __construct(Image $image)
{
$this->image = $image;
}
}
/**
* An image is being replaced.
*/
class ImageReplaceEvent extends Event {
/** @var int */
public $id;
/** @var \Image */
public $image;
class ImageReplaceEvent extends Event
{
/** @var int */
public $id;
/** @var \Image */
public $image;
/**
* Replaces an image.
*
* Updates an existing ID in the database to use a new image
* file, leaving the tags and such unchanged. Also removes
* the old image file and thumbnail from the disk.
*/
public function __construct(int $id, Image $image) {
$this->id = $id;
$this->image = $image;
}
/**
* Replaces an image.
*
* Updates an existing ID in the database to use a new image
* file, leaving the tags and such unchanged. Also removes
* the old image file and thumbnail from the disk.
*/
public function __construct(int $id, Image $image)
{
$this->id = $id;
$this->image = $image;
}
}
class ImageReplaceException extends SCoreException {
/** @var string */
public $error;
class ImageReplaceException extends SCoreException
{
/** @var string */
public $error;
public function __construct(string $error) {
$this->error = $error;
}
public function __construct(string $error)
{
$this->error = $error;
}
}
/**
* Request a thumbnail be made for an image object.
*/
class ThumbnailGenerationEvent extends Event {
/** @var string */
public $hash;
/** @var string */
public $type;
/** @var bool */
public $force;
class ThumbnailGenerationEvent extends Event
{
/** @var string */
public $hash;
/** @var string */
public $type;
/** @var bool */
public $force;
/**
* Request a thumbnail be made for an image object
*/
public function __construct(string $hash, string $type, bool $force=false) {
$this->hash = $hash;
$this->type = $type;
$this->force = $force;
}
/**
* Request a thumbnail be made for an image object
*/
public function __construct(string $hash, string $type, bool $force=false)
{
$this->hash = $hash;
$this->type = $type;
$this->force = $force;
}
}
@ -105,21 +117,24 @@ class ThumbnailGenerationEvent extends Event {
* $original -- the formatting string, for reference
* $image -- the image who's link is being parsed
*/
class ParseLinkTemplateEvent extends Event {
/** @var string */
public $link;
/** @var string */
public $original;
/** @var \Image */
public $image;
class ParseLinkTemplateEvent extends Event
{
/** @var string */
public $link;
/** @var string */
public $original;
/** @var \Image */
public $image;
public function __construct(string $link, Image $image) {
$this->link = $link;
$this->original = $link;
$this->image = $image;
}
public function __construct(string $link, Image $image)
{
$this->link = $link;
$this->original = $link;
$this->image = $image;
}
public function replace(string $needle, string $replace): void {
$this->link = str_replace($needle, $replace, $this->link);
}
public function replace(string $needle, string $replace): void
{
$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
*/
function move_upload_to_archive(DataUploadEvent $event) {
$target = warehouse_path("images", $event->hash);
if(!@copy($event->tmpname, $target)) {
$errors = error_get_last();
throw new UploadException(
"Failed to copy file from uploads ({$event->tmpname}) to archive ($target): ".
"{$errors['type']} / {$errors['message']}"
);
}
function move_upload_to_archive(DataUploadEvent $event)
{
$target = warehouse_path("images", $event->hash);
if (!@copy($event->tmpname, $target)) {
$errors = error_get_last();
throw new UploadException(
"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[]
*/
function add_dir(string $base): array {
$results = array();
function add_dir(string $base): array
{
$results = [];
foreach(list_files($base) as $full_path) {
$short_path = str_replace($base, "", $full_path);
$filename = basename($full_path);
foreach (list_files($base) as $full_path) {
$short_path = str_replace($base, "", $full_path);
$filename = basename($full_path);
$tags = path_to_tags($short_path);
$result = "$short_path (".str_replace(" ", ", ", $tags).")... ";
try {
add_image($full_path, $filename, $tags);
$result .= "ok";
}
catch(UploadException $ex) {
$result .= "failed: ".$ex->getMessage();
}
$results[] = $result;
}
$tags = path_to_tags($short_path);
$result = "$short_path (".str_replace(" ", ", ", $tags).")... ";
try {
add_image($full_path, $filename, $tags);
$result .= "ok";
} catch (UploadException $ex) {
$result .= "failed: ".$ex->getMessage();
}
$results[] = $result;
}
return $results;
return $results;
}
function add_image(string $tmpname, string $filename, string $tags): void {
assert(file_exists($tmpname));
function add_image(string $tmpname, string $filename, string $tags): void
{
assert(file_exists($tmpname));
$pathinfo = pathinfo($filename);
if(!array_key_exists('extension', $pathinfo)) {
throw new UploadException("File has no extension");
}
$metadata = array();
$metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = Tag::explode($tags);
$metadata['source'] = null;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
if($event->image_id == -1) {
throw new UploadException("File type not recognised");
}
$pathinfo = pathinfo($filename);
if (!array_key_exists('extension', $pathinfo)) {
throw new UploadException("File has no extension");
}
$metadata = [];
$metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = Tag::explode($tags);
$metadata['source'] = null;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
if ($event->image_id == -1) {
throw new UploadException("File type not recognised");
}
}
/**
@ -72,26 +74,34 @@ function add_image(string $tmpname, string $filename, string $tags): void {
*
* #return int[]
*/
function get_thumbnail_size(int $orig_width, int $orig_height): array {
global $config;
function get_thumbnail_size(int $orig_width, int $orig_height): array
{
global $config;
if($orig_width === 0) $orig_width = 192;
if($orig_height === 0) $orig_height = 192;
if ($orig_width === 0) {
$orig_width = 192;
}
if ($orig_height === 0) {
$orig_height = 192;
}
if($orig_width > $orig_height * 5) $orig_width = $orig_height * 5;
if($orig_height > $orig_width * 5) $orig_height = $orig_width * 5;
if ($orig_width > $orig_height * 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_height = $config->get_int('thumb_height');
$max_width = $config->get_int('thumb_width');
$max_height = $config->get_int('thumb_height');
$xscale = ($max_height / $orig_height);
$yscale = ($max_width / $orig_width);
$scale = ($xscale < $yscale) ? $xscale : $yscale;
$xscale = ($max_height / $orig_height);
$yscale = ($max_width / $orig_width);
$scale = ($xscale < $yscale) ? $xscale : $yscale;
if($scale > 1 && $config->get_bool('thumb_upscale')) {
return array((int)$orig_width, (int)$orig_height);
}
else {
return array((int)($orig_width*$scale), (int)($orig_height*$scale));
}
if ($scale > 1 && $config->get_bool('thumb_upscale')) {
return [(int)$orig_width, (int)$orig_height];
} else {
return [(int)($orig_width*$scale), (int)($orig_height*$scale)];
}
}

View file

@ -1,49 +1,58 @@
<?php
class Querylet {
/** @var string */
public $sql;
/** @var array */
public $variables;
class Querylet
{
/** @var string */
public $sql;
/** @var array */
public $variables;
public function __construct(string $sql, array $variables=array()) {
$this->sql = $sql;
$this->variables = $variables;
}
public function __construct(string $sql, array $variables=[])
{
$this->sql = $sql;
$this->variables = $variables;
}
public function append(Querylet $querylet) {
$this->sql .= $querylet->sql;
$this->variables = array_merge($this->variables, $querylet->variables);
}
public function append(Querylet $querylet)
{
$this->sql .= $querylet->sql;
$this->variables = array_merge($this->variables, $querylet->variables);
}
public function append_sql(string $sql) {
$this->sql .= $sql;
}
public function append_sql(string $sql)
{
$this->sql .= $sql;
}
public function add_variable($var) {
$this->variables[] = $var;
}
public function add_variable($var)
{
$this->variables[] = $var;
}
}
class TagQuerylet {
/** @var string */
public $tag;
/** @var bool */
public $positive;
class TagQuerylet
{
/** @var string */
public $tag;
/** @var bool */
public $positive;
public function __construct(string $tag, bool $positive) {
$this->tag = $tag;
$this->positive = $positive;
}
public function __construct(string $tag, bool $positive)
{
$this->tag = $tag;
$this->positive = $positive;
}
}
class ImgQuerylet {
/** @var \Querylet */
public $qlet;
/** @var bool */
public $positive;
class ImgQuerylet
{
/** @var \Querylet */
public $qlet;
/** @var bool */
public $positive;
public function __construct(Querylet $qlet, bool $positive) {
$this->qlet = $qlet;
$this->positive = $positive;
}
public function __construct(Querylet $qlet, bool $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.
*
*/
class Tag {
public static function implode(array $tags): string {
sort($tags);
$tags = implode(' ', $tags);
class Tag
{
public static function implode(array $tags): string
{
sort($tags);
$tags = implode(' ', $tags);
return $tags;
}
return $tags;
}
/**
* Turn a human-supplied string into a valid tag array.
*
* #return string[]
*/
public static function explode(string $tags, bool $tagme=true): array {
global $database;
/**
* Turn a human-supplied string into a valid tag array.
*
* #return string[]
*/
public static function explode(string $tags, bool $tagme=true): array
{
global $database;
$tags = explode(' ', trim($tags));
$tags = explode(' ', trim($tags));
/* sanitise by removing invisible / dodgy characters */
$tag_array = array();
foreach($tags as $tag) {
$tag = preg_replace("/\s/", "", $tag); # whitespace
$tag = preg_replace('/\x20(\x0e|\x0f)/', '', $tag); # unicode RTL
$tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
$tag = trim($tag, ", \t\n\r\0\x0B");
/* sanitise by removing invisible / dodgy characters */
$tag_array = [];
foreach ($tags as $tag) {
$tag = preg_replace("/\s/", "", $tag); # whitespace
$tag = preg_replace('/\x20(\x0e|\x0f)/', '', $tag); # unicode RTL
$tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
$tag = trim($tag, ", \t\n\r\0\x0B");
if(mb_strlen($tag, 'UTF-8') > 255){
flash_message("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n");
continue;
}
if (mb_strlen($tag, 'UTF-8') > 255) {
flash_message("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n");
continue;
}
if(!empty($tag)) {
$tag_array[] = $tag;
}
}
if (!empty($tag)) {
$tag_array[] = $tag;
}
}
/* if user supplied a blank string, add "tagme" */
if(count($tag_array) === 0 && $tagme) {
$tag_array = array("tagme");
}
/* if user supplied a blank string, add "tagme" */
if (count($tag_array) === 0 && $tagme) {
$tag_array = ["tagme"];
}
/* resolve aliases */
$new = array();
$i = 0;
$tag_count = count($tag_array);
while($i<$tag_count) {
$tag = $tag_array[$i];
$negative = '';
if(!empty($tag) && ($tag[0] == '-')) {
$negative = '-';
$tag = substr($tag, 1);
}
/* resolve aliases */
$new = [];
$i = 0;
$tag_count = count($tag_array);
while ($i<$tag_count) {
$tag = $tag_array[$i];
$negative = '';
if (!empty($tag) && ($tag[0] == '-')) {
$negative = '-';
$tag = substr($tag, 1);
}
$newtags = $database->get_one(
$database->scoreql_to_sql("
$newtags = $database->get_one(
$database->scoreql_to_sql("
SELECT newtag
FROM aliases
WHERE SCORE_STRNORM(oldtag)=SCORE_STRNORM(:tag)
"),
array("tag"=>$tag)
);
if(empty($newtags)) {
//tag has no alias, use old tag
$aliases = array($tag);
}
else {
$aliases = explode(" ", $newtags); // Tag::explode($newtags); - recursion can be infinite
}
["tag"=>$tag]
);
if (empty($newtags)) {
//tag has no alias, use old tag
$aliases = [$tag];
} else {
$aliases = explode(" ", $newtags); // Tag::explode($newtags); - recursion can be infinite
}
foreach($aliases as $alias) {
if(!in_array($alias, $new)) {
if($tag == $alias) {
$new[] = $negative.$alias;
}
elseif(!in_array($alias, $tag_array)) {
$tag_array[] = $negative.$alias;
$tag_count++;
}
}
}
$i++;
}
foreach ($aliases as $alias) {
if (!in_array($alias, $new)) {
if ($tag == $alias) {
$new[] = $negative.$alias;
} elseif (!in_array($alias, $tag_array)) {
$tag_array[] = $negative.$alias;
$tag_count++;
}
}
}
$i++;
}
/* remove any duplicate tags */
$tag_array = array_iunique($new);
/* remove any duplicate tags */
$tag_array = array_iunique($new);
/* tidy up */
sort($tag_array);
/* tidy up */
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
* 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()) {
send_event(new LogEvent($section, $priority, $message, $args));
$threshold = defined("CLI_LOG_LEVEL") ? CLI_LOG_LEVEL : 0;
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;
if((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && ($priority >= $threshold)) {
print date("c")." $section: $message\n";
}
if(!is_null($flash)) {
flash_message($flash);
}
if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && ($priority >= $threshold)) {
print date("c")." $section: $message\n";
}
if (!is_null($flash)) {
flash_message($flash);
}
}
// 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_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);}
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_debug(string $section, string $message, ?string $flash=null, $args=[])
{
log_msg($section, SCORE_LOG_DEBUG, $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.
*/
function get_request_id(): string {
static $request_id = null;
if(!$request_id) {
// not completely trustworthy, as a user can spoof this
if(@$_SERVER['HTTP_X_VARNISH']) {
$request_id = $_SERVER['HTTP_X_VARNISH'];
}
else {
$request_id = "P" . uniqid();
}
}
return $request_id;
function get_request_id(): string
{
static $request_id = null;
if (!$request_id) {
// not completely trustworthy, as a user can spoof this
if (@$_SERVER['HTTP_X_VARNISH']) {
$request_id = $_SERVER['HTTP_X_VARNISH'];
} else {
$request_id = "P" . uniqid();
}
}
return $request_id;
}

View file

@ -35,331 +35,352 @@
* The various extensions all add whatever they want to this structure,
* then Layout turns it into HTML.
*/
class Page {
/** @name Overall */
//@{
/** @var string */
public $mode = "page";
/** @var string */
public $type = "text/html; charset=utf-8";
class Page
{
/** @name Overall */
//@{
/** @var string */
public $mode = "page";
/** @var string */
public $type = "text/html; charset=utf-8";
/**
* Set what this page should do; "page", "data", or "redirect".
*/
public function set_mode(string $mode) {
$this->mode = $mode;
}
/**
* Set what this page should do; "page", "data", or "redirect".
*/
public function set_mode(string $mode)
{
$this->mode = $mode;
}
/**
* Set the page's MIME type.
*/
public function set_type(string $type) {
$this->type = $type;
}
/**
* Set the page's MIME type.
*/
public function set_type(string $type)
{
$this->type = $type;
}
//@}
// ==============================================
/** @name "data" mode */
//@{
//@}
// ==============================================
/** @name "data" mode */
//@{
/** @var string; public only for unit test */
public $data = "";
/** @var string; public only for unit test */
public $data = "";
/** @var string; public only for unit test */
public $filename = null;
/** @var string; public only for unit test */
public $filename = null;
/**
* Set the raw data to be sent.
*/
public function set_data(string $data) {
$this->data = $data;
}
/**
* Set the raw data to be sent.
*/
public function set_data(string $data)
{
$this->data = $data;
}
/**
* Set the recommended download filename.
*/
public function set_filename(string $filename) {
$this->filename = $filename;
}
/**
* Set the recommended download filename.
*/
public function set_filename(string $filename)
{
$this->filename = $filename;
}
//@}
// ==============================================
/** @name "redirect" mode */
//@{
//@}
// ==============================================
/** @name "redirect" mode */
//@{
/** @var string */
private $redirect = "";
/** @var string */
private $redirect = "";
/**
* Set the URL to redirect to (remember to use make_link() if linking
* to a page in the same site).
*/
public function set_redirect(string $redirect) {
$this->redirect = $redirect;
}
/**
* Set the URL to redirect to (remember to use make_link() if linking
* to a page in the same site).
*/
public function set_redirect(string $redirect)
{
$this->redirect = $redirect;
}
//@}
// ==============================================
/** @name "page" mode */
//@{
//@}
// ==============================================
/** @name "page" mode */
//@{
/** @var int */
public $code = 200;
/** @var int */
public $code = 200;
/** @var string */
public $title = "";
/** @var string */
public $title = "";
/** @var string */
public $heading = "";
/** @var string */
public $heading = "";
/** @var string */
public $subheading = "";
/** @var string */
public $subheading = "";
/** @var string */
public $quicknav = "";
/** @var string */
public $quicknav = "";
/** @var string[] */
public $html_headers = array();
/** @var string[] */
public $html_headers = [];
/** @var string[] */
public $http_headers = array();
/** @var string[] */
public $http_headers = [];
/** @var string[][] */
public $cookies = array();
/** @var string[][] */
public $cookies = [];
/** @var Block[] */
public $blocks = array();
/** @var Block[] */
public $blocks = [];
/**
* Set the HTTP status code
*/
public function set_code(int $code): void {
$this->code = $code;
}
/**
* Set the HTTP status code
*/
public function set_code(int $code): void
{
$this->code = $code;
}
public function set_title(string $title): void {
$this->title = $title;
}
public function set_title(string $title): void
{
$this->title = $title;
}
public function set_heading(string $heading): void {
$this->heading = $heading;
}
public function set_heading(string $heading): void
{
$this->heading = $heading;
}
public function set_subheading(string $subheading): void {
$this->subheading = $subheading;
}
public function set_subheading(string $subheading): void
{
$this->subheading = $subheading;
}
/**
* Add a line to the HTML head section.
*/
public function add_html_header(string $line, int $position=50): void {
while(isset($this->html_headers[$position])) $position++;
$this->html_headers[$position] = $line;
}
/**
* Add a line to the HTML head section.
*/
public function add_html_header(string $line, int $position=50): void
{
while (isset($this->html_headers[$position])) {
$position++;
}
$this->html_headers[$position] = $line;
}
/**
* Add a http header to be sent to the client.
*/
public function add_http_header(string $line, int $position=50): void {
while(isset($this->http_headers[$position])) $position++;
$this->http_headers[$position] = $line;
}
/**
* Add a http header to be sent to the client.
*/
public function add_http_header(string $line, int $position=50): void
{
while (isset($this->http_headers[$position])) {
$position++;
}
$this->http_headers[$position] = $line;
}
/**
* The counterpart for get_cookie, this works like php's
* setcookie method, but prepends the site-wide cookie prefix to
* the $name argument before doing anything.
*/
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);
}
/**
* The counterpart for get_cookie, this works like php's
* setcookie method, but prepends the site-wide cookie prefix to
* the $name argument before doing anything.
*/
public function add_cookie(string $name, string $value, int $time, string $path): void
{
$full_name = COOKIE_PREFIX."_".$name;
$this->cookies[] = [$full_name, $value, $time, $path];
}
public function get_cookie(string $name): ?string {
$full_name = COOKIE_PREFIX."_".$name;
if(isset($_COOKIE[$full_name])) {
return $_COOKIE[$full_name];
}
else {
return null;
}
}
public function get_cookie(string $name): ?string
{
$full_name = COOKIE_PREFIX."_".$name;
if (isset($_COOKIE[$full_name])) {
return $_COOKIE[$full_name];
} else {
return null;
}
}
/**
* Get all the HTML headers that are currently set and return as a string.
*/
public function get_all_html_headers(): string {
$data = '';
ksort($this->html_headers);
foreach ($this->html_headers as $line) {
$data .= "\t\t" . $line . "\n";
}
return $data;
}
/**
* Get all the HTML headers that are currently set and return as a string.
*/
public function get_all_html_headers(): string
{
$data = '';
ksort($this->html_headers);
foreach ($this->html_headers as $line) {
$data .= "\t\t" . $line . "\n";
}
return $data;
}
/**
* Removes all currently set HTML headers (Be careful..).
*/
public function delete_all_html_headers(): void {
$this->html_headers = array();
}
/**
* Removes all currently set HTML headers (Be careful..).
*/
public function delete_all_html_headers(): void
{
$this->html_headers = [];
}
/**
* Add a Block of data to the page.
*/
public function add_block(Block $block) {
$this->blocks[] = $block;
}
/**
* Add a Block of data to the page.
*/
public function add_block(Block $block)
{
$this->blocks[] = $block;
}
//@}
// ==============================================
//@}
// ==============================================
/**
* Display the page according to the mode and data given.
*/
public function display(): void {
global $page, $user;
/**
* Display the page according to the mode and data given.
*/
public function display(): void
{
global $page, $user;
header("HTTP/1.0 {$this->code} Shimmie");
header("Content-type: ".$this->type);
header("X-Powered-By: SCore-".SCORE_VERSION);
header("HTTP/1.0 {$this->code} Shimmie");
header("Content-type: ".$this->type);
header("X-Powered-By: SCore-".SCORE_VERSION);
if (!headers_sent()) {
foreach($this->http_headers as $head) {
header($head);
}
foreach($this->cookies as $c) {
setcookie($c[0], $c[1], $c[2], $c[3]);
}
} else {
print "Error: Headers have already been sent to the client.";
}
if (!headers_sent()) {
foreach ($this->http_headers as $head) {
header($head);
}
foreach ($this->cookies as $c) {
setcookie($c[0], $c[1], $c[2], $c[3]);
}
} else {
print "Error: Headers have already been sent to the client.";
}
switch($this->mode) {
case "page":
if(CACHE_HTTP) {
header("Vary: Cookie, Accept-Encoding");
if($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") {
header("Cache-control: public, max-age=600");
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT');
}
else {
#header("Cache-control: private, max-age=0");
header("Cache-control: no-cache");
header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
}
}
#else {
# header("Cache-control: no-cache");
# header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
#}
if($this->get_cookie("flash_message") !== null) {
$this->add_cookie("flash_message", "", -1, "/");
}
usort($this->blocks, "blockcmp");
$this->add_auto_html_headers();
$layout = new Layout();
$layout->display_page($page);
break;
case "data":
header("Content-Length: ".strlen($this->data));
if(!is_null($this->filename)) {
header('Content-Disposition: attachment; filename='.$this->filename);
}
print $this->data;
break;
case "redirect":
header('Location: '.$this->redirect);
print 'You should be redirected to <a href="'.$this->redirect.'">'.$this->redirect.'</a>';
break;
default:
print "Invalid page mode";
break;
}
}
switch ($this->mode) {
case "page":
if (CACHE_HTTP) {
header("Vary: Cookie, Accept-Encoding");
if ($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") {
header("Cache-control: public, max-age=600");
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT');
} else {
#header("Cache-control: private, max-age=0");
header("Cache-control: no-cache");
header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
}
}
#else {
# header("Cache-control: no-cache");
# header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
#}
if ($this->get_cookie("flash_message") !== null) {
$this->add_cookie("flash_message", "", -1, "/");
}
usort($this->blocks, "blockcmp");
$this->add_auto_html_headers();
$layout = new Layout();
$layout->display_page($page);
break;
case "data":
header("Content-Length: ".strlen($this->data));
if (!is_null($this->filename)) {
header('Content-Disposition: attachment; filename='.$this->filename);
}
print $this->data;
break;
case "redirect":
header('Location: '.$this->redirect);
print 'You should be redirected to <a href="'.$this->redirect.'">'.$this->redirect.'</a>';
break;
default:
print "Invalid page mode";
break;
}
}
/**
* 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
* them in the /cache/ directory for serving to the user.
*
* Why do this? Two reasons:
* 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.
*
* TODO: This should really be configurable somehow...
*/
public function add_auto_html_headers(): void {
global $config;
/**
* 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
* them in the /cache/ directory for serving to the user.
*
* Why do this? Two reasons:
* 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.
*
* TODO: This should really be configurable somehow...
*/
public function add_auto_html_headers(): void
{
global $config;
$data_href = get_base_href();
$theme_name = $config->get_string('theme', 'default');
$data_href = get_base_href();
$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
$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);
# 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='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.
$config_latest = 0;
foreach(zglob("data/config/*") as $conf) {
$config_latest = max($config_latest, filemtime($conf));
}
//We use $config_latest to make sure cache is reset if config is ever updated.
$config_latest = 0;
foreach (zglob("data/config/*") as $conf) {
$config_latest = max($config_latest, filemtime($conf));
}
/*** Generate CSS cache files ***/
$css_latest = $config_latest;
$css_files = array_merge(
zglob("ext/{".ENABLED_EXTS."}/style.css"),
zglob("themes/$theme_name/style.css")
);
foreach($css_files as $css) {
$css_latest = max($css_latest, filemtime($css));
}
$css_md5 = md5(serialize($css_files));
$css_cache_file = data_path("cache/style/{$theme_name}.{$css_latest}.{$css_md5}.css");
if(!file_exists($css_cache_file)) {
$css_data = "";
foreach($css_files as $file) {
$file_data = file_get_contents($file);
$pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/';
$replace = 'url("../../../'.dirname($file).'/$1")';
$file_data = preg_replace($pattern, $replace, $file_data);
$css_data .= $file_data . "\n";
}
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);
/*** Generate CSS cache files ***/
$css_latest = $config_latest;
$css_files = array_merge(
zglob("ext/{".ENABLED_EXTS."}/style.css"),
zglob("themes/$theme_name/style.css")
);
foreach ($css_files as $css) {
$css_latest = max($css_latest, filemtime($css));
}
$css_md5 = md5(serialize($css_files));
$css_cache_file = data_path("cache/style/{$theme_name}.{$css_latest}.{$css_md5}.css");
if (!file_exists($css_cache_file)) {
$css_data = "";
foreach ($css_files as $file) {
$file_data = file_get_contents($file);
$pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/';
$replace = 'url("../../../'.dirname($file).'/$1")';
$file_data = preg_replace($pattern, $replace, $file_data);
$css_data .= $file_data . "\n";
}
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);
/*** Generate JS cache files ***/
$js_latest = $config_latest;
$js_files = array_merge(
[
"vendor/bower-asset/jquery/dist/jquery.min.js",
"vendor/bower-asset/jquery-timeago/jquery.timeago.js",
"vendor/bower-asset/tablesorter/jquery.tablesorter.min.js",
"vendor/bower-asset/js-cookie/src/js.cookie.js",
"ext/handle_static/modernizr-3.3.1.custom.js",
],
zglob("ext/{".ENABLED_EXTS."}/script.js"),
zglob("themes/$theme_name/script.js")
);
foreach($js_files as $js) {
$js_latest = max($js_latest, filemtime($js));
}
$js_md5 = md5(serialize($js_files));
$js_cache_file = data_path("cache/script/{$theme_name}.{$js_latest}.{$js_md5}.js");
if(!file_exists($js_cache_file)) {
$js_data = "";
foreach($js_files as $file) {
$js_data .= file_get_contents($file) . "\n";
}
file_put_contents($js_cache_file, $js_data);
}
$this->add_html_header("<script src='$data_href/$js_cache_file' type='text/javascript'></script>", 44);
}
/*** Generate JS cache files ***/
$js_latest = $config_latest;
$js_files = array_merge(
[
"vendor/bower-asset/jquery/dist/jquery.min.js",
"vendor/bower-asset/jquery-timeago/jquery.timeago.js",
"vendor/bower-asset/tablesorter/jquery.tablesorter.min.js",
"vendor/bower-asset/js-cookie/src/js.cookie.js",
"ext/handle_static/modernizr-3.3.1.custom.js",
],
zglob("ext/{".ENABLED_EXTS."}/script.js"),
zglob("themes/$theme_name/script.js")
);
foreach ($js_files as $js) {
$js_latest = max($js_latest, filemtime($js));
}
$js_md5 = md5(serialize($js_files));
$js_cache_file = data_path("cache/script/{$theme_name}.{$js_latest}.{$js_md5}.js");
if (!file_exists($js_cache_file)) {
$js_data = "";
foreach ($js_files as $file) {
$js_data .= file_get_contents($file) . "\n";
}
file_put_contents($js_cache_file, $js_data);
}
$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 */
global $_shm_event_listeners;
$_shm_event_listeners = array();
$_shm_event_listeners = [];
function _load_event_listeners() {
global $_shm_event_listeners, $_shm_ctx;
function _load_event_listeners()
{
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");
if(COMPILE_ELS && file_exists($cache_path)) {
require_once($cache_path);
}
else {
_set_event_listeners();
$cache_path = data_path("cache/shm_event_listeners.php");
if (COMPILE_ELS && file_exists($cache_path)) {
require_once($cache_path);
} else {
_set_event_listeners();
if(COMPILE_ELS) {
_dump_event_listeners($_shm_event_listeners, $cache_path);
}
}
if (COMPILE_ELS) {
_dump_event_listeners($_shm_event_listeners, $cache_path);
}
}
$_shm_ctx->log_endok();
$_shm_ctx->log_endok();
}
function _set_event_listeners() {
global $_shm_event_listeners;
$_shm_event_listeners = array();
function _set_event_listeners()
{
global $_shm_event_listeners;
$_shm_event_listeners = [];
foreach(get_declared_classes() as $class) {
$rclass = new ReflectionClass($class);
if($rclass->isAbstract()) {
// don't do anything
}
elseif(is_subclass_of($class, "Extension")) {
/** @var Extension $extension */
$extension = new $class();
foreach (get_declared_classes() as $class) {
$rclass = new ReflectionClass($class);
if ($rclass->isAbstract()) {
// don't do anything
} elseif (is_subclass_of($class, "Extension")) {
/** @var Extension $extension */
$extension = new $class();
// skip extensions which don't support our current database
if(!$extension->is_live()) continue;
// skip extensions which don't support our current database
if (!$extension->is_live()) {
continue;
}
foreach(get_class_methods($extension) as $method) {
if(substr($method, 0, 2) == "on") {
$event = substr($method, 2) . "Event";
$pos = $extension->get_priority() * 100;
while(isset($_shm_event_listeners[$event][$pos])) {
$pos += 1;
}
$_shm_event_listeners[$event][$pos] = $extension;
}
}
}
}
foreach (get_class_methods($extension) as $method) {
if (substr($method, 0, 2) == "on") {
$event = substr($method, 2) . "Event";
$pos = $extension->get_priority() * 100;
while (isset($_shm_event_listeners[$event][$pos])) {
$pos += 1;
}
$_shm_event_listeners[$event][$pos] = $extension;
}
}
}
}
}
function _dump_event_listeners(array $event_listeners, string $path): void {
$p = "<"."?php\n";
function _dump_event_listeners(array $event_listeners, string $path): void
{
$p = "<"."?php\n";
foreach(get_declared_classes() as $class) {
$rclass = new ReflectionClass($class);
if($rclass->isAbstract()) {}
elseif(is_subclass_of($class, "Extension")) {
$p .= "\$$class = new $class(); ";
}
}
foreach (get_declared_classes() as $class) {
$rclass = new ReflectionClass($class);
if ($rclass->isAbstract()) {
} elseif (is_subclass_of($class, "Extension")) {
$p .= "\$$class = new $class(); ";
}
}
$p .= "\$_shm_event_listeners = array(\n";
foreach($event_listeners as $event => $listeners) {
$p .= "\t'$event' => array(\n";
foreach($listeners as $id => $listener) {
$p .= "\t\t$id => \$".get_class($listener).",\n";
}
$p .= "\t),\n";
}
$p .= ");\n";
$p .= "\$_shm_event_listeners = array(\n";
foreach ($event_listeners as $event => $listeners) {
$p .= "\t'$event' => array(\n";
foreach ($listeners as $id => $listener) {
$p .= "\t\t$id => \$".get_class($listener).",\n";
}
$p .= "\t),\n";
}
$p .= ");\n";
$p .= "?".">";
file_put_contents($path, $p);
$p .= "?".">";
file_put_contents($path, $p);
}
function ext_is_live(string $ext_name): bool {
if (class_exists($ext_name)) {
/** @var Extension $ext */
$ext = new $ext_name();
return $ext->is_live();
}
return false;
function ext_is_live(string $ext_name): bool
{
if (class_exists($ext_name)) {
/** @var Extension $ext */
$ext = new $ext_name();
return $ext->is_live();
}
return false;
}
@ -99,26 +103,37 @@ $_shm_event_count = 0;
/**
* Send an event to all registered Extensions.
*/
function send_event(Event $event): void {
global $_shm_event_listeners, $_shm_event_count, $_shm_ctx;
if(!isset($_shm_event_listeners[get_class($event)])) return;
$method_name = "on".str_replace("Event", "", get_class($event));
function send_event(Event $event): void
{
global $_shm_event_listeners, $_shm_event_count, $_shm_ctx;
if (!isset($_shm_event_listeners[get_class($event)])) {
return;
}
$method_name = "on".str_replace("Event", "", get_class($event));
// send_event() is performance sensitive, and with the number
// of times context gets called the time starts to add up
$ctx_enabled = constant('CONTEXT');
// send_event() is performance sensitive, and with the number
// of times context gets called the time starts to add up
$ctx_enabled = constant('CONTEXT');
if($ctx_enabled) $_shm_ctx->log_start(get_class($event));
// SHIT: http://bugs.php.net/bug.php?id=35106
$my_event_listeners = $_shm_event_listeners[get_class($event)];
ksort($my_event_listeners);
foreach($my_event_listeners as $listener) {
if($ctx_enabled) $_shm_ctx->log_start(get_class($listener));
if(method_exists($listener, $method_name)) {
$listener->$method_name($event);
}
if($ctx_enabled) $_shm_ctx->log_endok();
}
$_shm_event_count++;
if($ctx_enabled) $_shm_ctx->log_endok();
if ($ctx_enabled) {
$_shm_ctx->log_start(get_class($event));
}
// SHIT: http://bugs.php.net/bug.php?id=35106
$my_event_listeners = $_shm_event_listeners[get_class($event)];
ksort($my_event_listeners);
foreach ($my_event_listeners as $listener) {
if ($ctx_enabled) {
$_shm_ctx->log_start(get_class($listener));
}
if (method_exists($listener, $method_name)) {
$listener->$method_name($event);
}
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_KA", true); // string Keep database connection alive
_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("ENABLED_EXTS", CORE_EXTS.",".EXTRA_EXTS);

View file

@ -1,43 +1,48 @@
<?php
require_once "core/polyfills.php";
class PolyfillsTest extends \PHPUnit\Framework\TestCase {
public function test_html_escape() {
$this->assertEquals(
html_escape("Foo & <waffles>"),
"Foo &amp; &lt;waffles&gt;"
);
class PolyfillsTest extends \PHPUnit\Framework\TestCase
{
public function test_html_escape()
{
$this->assertEquals(
html_escape("Foo & <waffles>"),
"Foo &amp; &lt;waffles&gt;"
);
$this->assertEquals(
html_unescape("Foo &amp; &lt;waffles&gt;"),
"Foo & <waffles>"
);
$this->assertEquals(
html_unescape("Foo &amp; &lt;waffles&gt;"),
"Foo & <waffles>"
);
$x = "Foo &amp; &lt;waffles&gt;";
$this->assertEquals(html_escape(html_unescape($x)), $x);
}
$x = "Foo &amp; &lt;waffles&gt;";
$this->assertEquals(html_escape(html_unescape($x)), $x);
}
public function test_int_escape() {
$this->assertEquals(int_escape(""), 0);
$this->assertEquals(int_escape("1"), 1);
$this->assertEquals(int_escape("-1"), -1);
$this->assertEquals(int_escape("-1.5"), -1);
}
public function test_int_escape()
{
$this->assertEquals(int_escape(""), 0);
$this->assertEquals(int_escape("1"), 1);
$this->assertEquals(int_escape("-1"), -1);
$this->assertEquals(int_escape("-1.5"), -1);
}
public function test_clamp() {
$this->assertEquals(clamp(0, 5, 10), 5);
$this->assertEquals(clamp(5, 5, 10), 5);
$this->assertEquals(clamp(7, 5, 10), 7);
$this->assertEquals(clamp(10, 5, 10), 10);
$this->assertEquals(clamp(15, 5, 10), 10);
}
public function test_clamp()
{
$this->assertEquals(clamp(0, 5, 10), 5);
$this->assertEquals(clamp(5, 5, 10), 5);
$this->assertEquals(clamp(7, 5, 10), 7);
$this->assertEquals(clamp(10, 5, 10), 10);
$this->assertEquals(clamp(15, 5, 10), 10);
}
public function test_shorthand_int() {
$this->assertEquals(to_shorthand_int(1231231231), "1.1GB");
public function test_shorthand_int()
{
$this->assertEquals(to_shorthand_int(1231231231), "1.1GB");
$this->assertEquals(parse_shorthand_int("foo"), -1);
$this->assertEquals(parse_shorthand_int("32M"), 33554432);
$this->assertEquals(parse_shorthand_int("43.4KB"), 44441);
$this->assertEquals(parse_shorthand_int("1231231231"), 1231231231);
}
$this->assertEquals(parse_shorthand_int("foo"), -1);
$this->assertEquals(parse_shorthand_int("32M"), 33554432);
$this->assertEquals(parse_shorthand_int("43.4KB"), 44441);
$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"
*/
function make_link(?string $page=null, ?string $query=null): string {
global $config;
function make_link(?string $page=null, ?string $query=null): string
{
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)) {
$base = BASE_URL;
}
elseif(NICE_URLS || $config->get_bool('nice_urls', false)) {
$base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $_SERVER["PHP_SELF"]);
}
else {
$base = "./".basename($_SERVER["SCRIPT_FILENAME"])."?q=";
}
if (!is_null(BASE_URL)) {
$base = BASE_URL;
} elseif (NICE_URLS || $config->get_bool('nice_urls', false)) {
$base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $_SERVER["PHP_SELF"]);
} else {
$base = "./".basename($_SERVER["SCRIPT_FILENAME"])."?q=";
}
if(is_null($query)) {
return str_replace("//", "/", $base.'/'.$page );
}
else {
if(strpos($base, "?")) {
return $base .'/'. $page .'&'. $query;
}
else if(strpos($query, "#") === 0) {
return $base .'/'. $page . $query;
}
else {
return $base .'/'. $page .'?'. $query;
}
}
if (is_null($query)) {
return str_replace("//", "/", $base.'/'.$page);
} else {
if (strpos($base, "?")) {
return $base .'/'. $page .'&'. $query;
} elseif (strpos($query, "#") === 0) {
return $base .'/'. $page . $query;
} else {
return $base .'/'. $page .'?'. $query;
}
}
}
/**
* Take the current URL and modify some parameters
*/
function modify_current_url(array $changes): string {
return modify_url($_SERVER['QUERY_STRING'], $changes);
function modify_current_url(array $changes): string
{
return modify_url($_SERVER['QUERY_STRING'], $changes);
}
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.
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: 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
// whatever data the user supplied. Thankfully, 4.0.3 added an extra option to
// give it an array to use...
$params = array();
parse_str($url, $params);
// 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
// whatever data the user supplied. Thankfully, 4.0.3 added an extra option to
// give it an array to use...
$params = [];
parse_str($url, $params);
if(isset($changes['q'])) {
$base = $changes['q'];
unset($changes['q']);
}
else {
$base = _get_query();
}
if (isset($changes['q'])) {
$base = $changes['q'];
unset($changes['q']);
} else {
$base = _get_query();
}
if(isset($params['q'])) {
unset($params['q']);
}
if (isset($params['q'])) {
unset($params['q']);
}
foreach($changes as $k => $v) {
if(is_null($v) and isset($params[$k])) unset($params[$k]);
$params[$k] = $v;
}
foreach ($changes as $k => $v) {
if (is_null($v) and isset($params[$k])) {
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
*/
function make_http(string $link): string {
if(strpos($link, "://") > 0) {
return $link;
}
function make_http(string $link): string
{
if (strpos($link, "://") > 0) {
return $link;
}
if(strlen($link) > 0 && $link[0] != '/') {
$link = get_base_href() . '/' . $link;
}
if (strlen($link) > 0 && $link[0] != '/') {
$link = get_base_href() . '/' . $link;
}
$protocol = is_https_enabled() ? "https://" : "http://";
$link = $protocol . $_SERVER["HTTP_HOST"] . $link;
$link = str_replace("/./", "/", $link);
$protocol = is_https_enabled() ? "https://" : "http://";
$link = $protocol . $_SERVER["HTTP_HOST"] . $link;
$link = str_replace("/./", "/", $link);
return $link;
return $link;
}

View file

@ -1,7 +1,8 @@
<?php
function _new_user(array $row): User {
return new User($row);
function _new_user(array $row): User
{
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.
*/
class User {
/** @var int */
public $id;
class User
{
/** @var int */
public $id;
/** @var string */
public $name;
/** @var string */
public $name;
/** @var string */
public $email;
/** @var string */
public $email;
public $join_date;
public $join_date;
/** @var string */
public $passhash;
/** @var string */
public $passhash;
/** @var UserClass */
public $class;
/** @var UserClass */
public $class;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialisation *
* *
* User objects shouldn't be created directly, they should be *
* fetched from the database like so: *
* *
* $user = User::by_name("bob"); *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialisation *
* *
* User objects shouldn't be created directly, they should be *
* fetched from the database like so: *
* *
* $user = User::by_name("bob"); *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* One will very rarely construct a user directly, more common
* would be to use User::by_id, User::by_session, etc.
*
* @throws SCoreException
*/
public function __construct(array $row) {
global $_shm_user_classes;
/**
* One will very rarely construct a user directly, more common
* would be to use User::by_id, User::by_session, etc.
*
* @throws SCoreException
*/
public function __construct(array $row)
{
global $_shm_user_classes;
$this->id = int_escape($row['id']);
$this->name = $row['name'];
$this->email = $row['email'];
$this->join_date = $row['joindate'];
$this->passhash = $row['pass'];
$this->id = int_escape($row['id']);
$this->name = $row['name'];
$this->email = $row['email'];
$this->join_date = $row['joindate'];
$this->passhash = $row['pass'];
if(array_key_exists($row["class"], $_shm_user_classes)) {
$this->class = $_shm_user_classes[$row["class"]];
}
else {
throw new SCoreException("User '{$this->name}' has invalid class '{$row["class"]}'");
}
}
if (array_key_exists($row["class"], $_shm_user_classes)) {
$this->class = $_shm_user_classes[$row["class"]];
} else {
throw new SCoreException("User '{$this->name}' has invalid class '{$row["class"]}'");
}
}
public static function by_session(string $name, string $session) {
global $config, $database;
$row = $database->cache->get("user-session:$name-$session");
if(!$row) {
if($database->get_driver_name() === "mysql") {
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
}
else {
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
}
$row = $database->get_row($query, array("name"=>$name, "ip"=>get_session_ip($config), "sess"=>$session));
$database->cache->set("user-session:$name-$session", $row, 600);
}
return is_null($row) ? null : new User($row);
}
public static function by_session(string $name, string $session)
{
global $config, $database;
$row = $database->cache->get("user-session:$name-$session");
if (!$row) {
if ($database->get_driver_name() === "mysql") {
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
} else {
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
}
$row = $database->get_row($query, ["name"=>$name, "ip"=>get_session_ip($config), "sess"=>$session]);
$database->cache->set("user-session:$name-$session", $row, 600);
}
return is_null($row) ? null : new User($row);
}
public static function by_id(int $id) {
global $database;
if($id === 1) {
$cached = $database->cache->get('user-id:'.$id);
if($cached) return new User($cached);
}
$row = $database->get_row("SELECT * FROM users WHERE id = :id", array("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_id(int $id)
{
global $database;
if ($id === 1) {
$cached = $database->cache->get('user-id:'.$id);
if ($cached) {
return new User($cached);
}
}
$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) {
global $database;
$row = $database->get_row($database->scoreql_to_sql("SELECT * FROM users WHERE SCORE_STRNORM(name) = SCORE_STRNORM(:name)"), array("name"=>$name));
return is_null($row) ? null : new User($row);
}
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)"), ["name"=>$name]);
return is_null($row) ? null : new User($row);
}
public static function by_name_and_pass(string $name, string $pass) {
$user = User::by_name($name);
if($user) {
if($user->passhash == md5(strtolower($name) . $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})");
return $user;
}
else {
log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid password)");
}
}
else {
log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid username)");
}
return null;
}
public static function by_name_and_pass(string $name, string $pass)
{
$user = User::by_name($name);
if ($user) {
if ($user->passhash == md5(strtolower($name) . $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})");
return $user;
} else {
log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid password)");
}
} else {
log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid username)");
}
return null;
}
/* useful user object functions start here */
/* useful user object functions start here */
public function can(string $ability): bool {
return $this->class->can($ability);
}
public function can(string $ability): bool
{
return $this->class->can($ability);
}
public function is_anonymous(): bool {
global $config;
return ($this->id === $config->get_int('anon_id'));
}
public function is_anonymous(): bool
{
global $config;
return ($this->id === $config->get_int('anon_id'));
}
public function is_logged_in(): bool {
global $config;
return ($this->id !== $config->get_int('anon_id'));
}
public function is_logged_in(): bool
{
global $config;
return ($this->id !== $config->get_int('anon_id'));
}
public function is_admin(): bool {
return ($this->class->name === "admin");
}
public function is_admin(): bool
{
return ($this->class->name === "admin");
}
public function set_class(string $class) {
global $database;
$database->Execute("UPDATE users SET class=:class WHERE id=:id", array("class"=>$class, "id"=>$this->id));
log_info("core-user", 'Set class for '.$this->name.' to '.$class);
}
public function set_class(string $class)
{
global $database;
$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) {
global $database;
if(User::by_name($name)) {
throw new Exception("Desired username is already in use");
}
$old_name = $this->name;
$this->name = $name;
$database->Execute("UPDATE users SET name=:name WHERE id=:id", array("name"=>$this->name, "id"=>$this->id));
log_info("core-user", "Changed username for {$old_name} to {$this->name}");
}
public function set_name(string $name)
{
global $database;
if (User::by_name($name)) {
throw new Exception("Desired username is already in use");
}
$old_name = $this->name;
$this->name = $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) {
global $database;
$hash = password_hash($password, PASSWORD_BCRYPT);
if(is_string($hash)) {
$this->passhash = $hash;
$database->Execute("UPDATE users SET pass=:hash WHERE id=:id", array("hash"=>$this->passhash, "id"=>$this->id));
log_info("core-user", 'Set password for '.$this->name);
}
else {
throw new SCoreException("Failed to hash password");
}
}
public function set_password(string $password)
{
global $database;
$hash = password_hash($password, PASSWORD_BCRYPT);
if (is_string($hash)) {
$this->passhash = $hash;
$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 {
throw new SCoreException("Failed to hash password");
}
}
public function set_email(string $address) {
global $database;
$database->Execute("UPDATE users SET email=:email WHERE id=:id", array("email"=>$address, "id"=>$this->id));
log_info("core-user", 'Set email for '.$this->name);
}
public function set_email(string $address)
{
global $database;
$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
* a local file, a remote file, a gravatar, a something else, etc.
*/
public function get_avatar_html(): string {
// FIXME: configurable
global $config;
if($config->get_string("avatar_host") === "gravatar") {
if(!empty($this->email)) {
$hash = md5(strtolower($this->email));
$s = $config->get_string("avatar_gravatar_size");
$d = urlencode($config->get_string("avatar_gravatar_default"));
$r = $config->get_string("avatar_gravatar_rating");
$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 "";
}
/**
* 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.
*/
public function get_avatar_html(): string
{
// FIXME: configurable
global $config;
if ($config->get_string("avatar_host") === "gravatar") {
if (!empty($this->email)) {
$hash = md5(strtolower($this->email));
$s = $config->get_string("avatar_gravatar_size");
$d = urlencode($config->get_string("avatar_gravatar_default"));
$r = $config->get_string("avatar_gravatar_rating");
$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 "";
}
/**
* Get an auth token to be used in POST forms
*
* password = secret, avoid storing directly
* 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,
* 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
* 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
*/
public function get_auth_token(): string {
global $config;
$salt = DATABASE_DSN;
$addr = get_session_ip($config);
return md5(md5($this->passhash . $addr) . "salty-csrf-" . $salt);
}
/**
* Get an auth token to be used in POST forms
*
* password = secret, avoid storing directly
* 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,
* 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
* 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
*/
public function get_auth_token(): string
{
global $config;
$salt = DATABASE_DSN;
$addr = get_session_ip($config);
return md5(md5($this->passhash . $addr) . "salty-csrf-" . $salt);
}
public function get_auth_html(): string {
$at = $this->get_auth_token();
return '<input type="hidden" name="auth_token" value="'.$at.'">';
}
public function get_auth_html(): string
{
$at = $this->get_auth_token();
return '<input type="hidden" name="auth_token" value="'.$at.'">';
}
public function check_auth_token(): bool {
return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token());
}
public function check_auth_token(): bool
{
return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token());
}
}

View file

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

View file

@ -5,117 +5,126 @@ require_once "vendor/shish/libcontext-php/context.php";
* Misc *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function mtimefile(string $file): string {
$data_href = get_base_href();
$mtime = filemtime($file);
return "$data_href/$file?$mtime";
function mtimefile(string $file): string
{
$data_href = get_base_href();
$mtime = filemtime($file);
return "$data_href/$file?$mtime";
}
function get_theme(): string {
global $config;
$theme = $config->get_string("theme", "default");
if(!file_exists("themes/$theme")) $theme = "default";
return $theme;
function get_theme(): string
{
global $config;
$theme = $config->get_string("theme", "default");
if (!file_exists("themes/$theme")) {
$theme = "default";
}
return $theme;
}
function contact_link(): ?string {
global $config;
$text = $config->get_string('contact_link');
if(is_null($text)) return null;
function contact_link(): ?string
{
global $config;
$text = $config->get_string('contact_link');
if (is_null($text)) {
return null;
}
if(
startsWith($text, "http:") ||
startsWith($text, "https:") ||
startsWith($text, "mailto:")
) {
return $text;
}
if (
startsWith($text, "http:") ||
startsWith($text, "https:") ||
startsWith($text, "mailto:")
) {
return $text;
}
if(strpos($text, "@")) {
return "mailto:$text";
}
if (strpos($text, "@")) {
return "mailto:$text";
}
if(strpos($text, "/")) {
return "http://$text";
}
if (strpos($text, "/")) {
return "http://$text";
}
return $text;
return $text;
}
/**
* Check if HTTPS is enabled for the server.
*/
function is_https_enabled(): bool {
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
function is_https_enabled(): bool
{
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
}
/**
* Compare two Block objects, used to sort them before being displayed
*/
function blockcmp(Block $a, Block $b): int {
if($a->position == $b->position) {
return 0;
}
else {
return ($a->position > $b->position);
}
function blockcmp(Block $a, Block $b): int
{
if ($a->position == $b->position) {
return 0;
} else {
return ($a->position > $b->position);
}
}
/**
* Figure out PHP's internal memory limit
*/
function get_memory_limit(): int {
global $config;
function get_memory_limit(): int
{
global $config;
// thumbnail generation requires lots of memory
$default_limit = 8*1024*1024; // 8 MB of memory is PHP's default.
$shimmie_limit = parse_shorthand_int($config->get_int("thumb_mem_limit"));
// thumbnail generation requires lots of memory
$default_limit = 8*1024*1024; // 8 MB of memory is PHP's default.
$shimmie_limit = parse_shorthand_int($config->get_int("thumb_mem_limit"));
if($shimmie_limit < 3*1024*1024) {
// we aren't going to fit, override
$shimmie_limit = $default_limit;
}
if ($shimmie_limit < 3*1024*1024) {
// we aren't going to fit, override
$shimmie_limit = $default_limit;
}
/*
Get PHP's configured memory limit.
Note that this is set to -1 for NO memory limit.
/*
Get PHP's configured 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
*/
$memory = parse_shorthand_int(ini_get("memory_limit"));
http://ca2.php.net/manual/en/ini.core.php#ini.memory-limit
*/
$memory = parse_shorthand_int(ini_get("memory_limit"));
if($memory == -1) {
// No memory limit.
// Return the larger of the set limits.
return max($shimmie_limit, $default_limit);
}
else {
// PHP has a memory limit set.
if ($shimmie_limit > $memory) {
// Shimmie wants more memory than what PHP is currently set for.
if ($memory == -1) {
// No memory limit.
// Return the larger of the set limits.
return max($shimmie_limit, $default_limit);
} else {
// PHP has a memory limit set.
if ($shimmie_limit > $memory) {
// Shimmie wants more memory than what PHP is currently set for.
// Attempt to set PHP's memory limit.
if ( ini_set("memory_limit", $shimmie_limit) === false ) {
/* We can't change PHP's limit, oh well, return whatever its currently set to */
return $memory;
}
$memory = parse_shorthand_int(ini_get("memory_limit"));
}
// Attempt to set PHP's memory limit.
if (ini_set("memory_limit", $shimmie_limit) === false) {
/* We can't change PHP's limit, oh well, return whatever its currently set to */
return $memory;
}
$memory = parse_shorthand_int(ini_get("memory_limit"));
}
// PHP's memory limit is more than Shimmie needs.
return $memory; // return the current setting
}
// PHP's memory limit is more than Shimmie needs.
return $memory; // return the current setting
}
}
/**
* 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
*/
function get_session_ip(Config $config): string {
$mask = $config->get_string("session_hash_mask", "255.255.0.0");
$addr = $_SERVER['REMOTE_ADDR'];
$addr = inet_ntop(inet_pton($addr) & inet_pton($mask));
return $addr;
function get_session_ip(Config $config): string
{
$mask = $config->get_string("session_hash_mask", "255.255.0.0");
$addr = $_SERVER['REMOTE_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
* are taken from within onPageRequest...
*/
function flash_message(string $text, string $type="info") {
global $page;
$current = $page->get_cookie("flash_message");
if($current) {
$text = $current . "\n" . $text;
}
# the message should be viewed pretty much immediately,
# so 60s timeout should be more than enough
$page->add_cookie("flash_message", $text, time()+60, "/");
function flash_message(string $text, string $type="info")
{
global $page;
$current = $page->get_cookie("flash_message");
if ($current) {
$text = $current . "\n" . $text;
}
# the message should be viewed pretty much immediately,
# 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.
*/
function format_text(string $string): string {
$tfe = new TextFormattingEvent($string);
send_event($tfe);
return $tfe->formatted;
function format_text(string $string): string
{
$tfe = new TextFormattingEvent($string);
send_event($tfe);
return $tfe->formatted;
}
function warehouse_path(string $base, string $hash, bool $create=true): string {
$ab = substr($hash, 0, 2);
$cd = substr($hash, 2, 2);
if(WH_SPLITS == 2) {
$pa = 'data/'.$base.'/'.$ab.'/'.$cd.'/'.$hash;
}
else {
$pa = 'data/'.$base.'/'.$ab.'/'.$hash;
}
if($create && !file_exists(dirname($pa))) mkdir(dirname($pa), 0755, true);
return $pa;
function warehouse_path(string $base, string $hash, bool $create=true): string
{
$ab = substr($hash, 0, 2);
$cd = substr($hash, 2, 2);
if (WH_SPLITS == 2) {
$pa = 'data/'.$base.'/'.$ab.'/'.$cd.'/'.$hash;
} else {
$pa = 'data/'.$base.'/'.$ab.'/'.$hash;
}
if ($create && !file_exists(dirname($pa))) {
mkdir(dirname($pa), 0755, true);
}
return $pa;
}
function data_path(string $filename): string {
$filename = "data/" . $filename;
if(!file_exists(dirname($filename))) mkdir(dirname($filename), 0755, true);
return $filename;
function data_path(string $filename): string
{
$filename = "data/" . $filename;
if (!file_exists(dirname($filename))) {
mkdir(dirname($filename), 0755, true);
}
return $filename;
}
function transload(string $url, string $mfile): ?array {
global $config;
function transload(string $url, string $mfile): ?array
{
global $config;
if($config->get_string("transload_engine") === "curl" && function_exists("curl_init")) {
$ch = curl_init($url);
$fp = fopen($mfile, "w");
if ($config->get_string("transload_engine") === "curl" && function_exists("curl_init")) {
$ch = curl_init($url);
$fp = fopen($mfile, "w");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_REFERER, $url);
curl_setopt($ch, CURLOPT_USERAGENT, "Shimmie-".VERSION);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_REFERER, $url);
curl_setopt($ch, CURLOPT_USERAGENT, "Shimmie-".VERSION);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$response = curl_exec($ch);
$response = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = http_parse_headers(implode("\n", preg_split('/\R/', rtrim(substr($response, 0, $header_size)))));
$body = substr($response, $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)))));
$body = substr($response, $header_size);
curl_close($ch);
fwrite($fp, $body);
fclose($fp);
curl_close($ch);
fwrite($fp, $body);
fclose($fp);
return $headers;
}
return $headers;
}
if($config->get_string("transload_engine") === "wget") {
$s_url = escapeshellarg($url);
$s_mfile = escapeshellarg($mfile);
system("wget --no-check-certificate $s_url --output-document=$s_mfile");
if ($config->get_string("transload_engine") === "wget") {
$s_url = escapeshellarg($url);
$s_mfile = escapeshellarg($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") {
$fp_in = @fopen($url, "r");
$fp_out = fopen($mfile, "w");
if(!$fp_in || !$fp_out) {
return null;
}
$length = 0;
while(!feof($fp_in) && $length <= $config->get_int('upload_size')) {
$data = fread($fp_in, 8192);
$length += strlen($data);
fwrite($fp_out, $data);
}
fclose($fp_in);
fclose($fp_out);
if ($config->get_string("transload_engine") === "fopen") {
$fp_in = @fopen($url, "r");
$fp_out = fopen($mfile, "w");
if (!$fp_in || !$fp_out) {
return null;
}
$length = 0;
while (!feof($fp_in) && $length <= $config->get_int('upload_size')) {
$data = fread($fp_in, 8192);
$length += strlen($data);
fwrite($fp_out, $data);
}
fclose($fp_in);
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
*/
function manual_include(string $fname): ?string {
static $included = array();
function manual_include(string $fname): ?string
{
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
$text = str_replace('<'.'?php', '', $text);
$text = str_replace('?'.'>', '', $text);
// we want one continuous file
$text = str_replace('<'.'?php', '', $text);
$text = str_replace('?'.'>', '', $text);
// most requires are built-in, but we want /lib separately
$text = str_replace('require_', '// require_', $text);
$text = str_replace('// require_once "lib', 'require_once "lib', $text);
// most requires are built-in, but we want /lib separately
$text = str_replace('require_', '// require_', $text);
$text = str_replace('// require_once "lib', 'require_once "lib', $text);
// @include_once is used for user-creatable config files
$text = preg_replace('/@include_once "(.*)";/e', "manual_include('$1')", $text);
// @include_once is used for user-creatable config files
$text = preg_replace('/@include_once "(.*)";/e', "manual_include('$1')", $text);
return $text;
return $text;
}
function path_to_tags(string $path): string {
$matches = array();
if(preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", basename($path), $matches)) {
$tags = $matches[1];
}
else {
$tags = dirname($path);
$tags = str_replace("/", " ", $tags);
$tags = str_replace("__", " ", $tags);
$tags = trim($tags);
}
return $tags;
function path_to_tags(string $path): string
{
$matches = [];
if (preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", basename($path), $matches)) {
$tags = $matches[1];
} else {
$tags = dirname($path);
$tags = str_replace("/", " ", $tags);
$tags = str_replace("__", " ", $tags);
$tags = trim($tags);
}
return $tags;
}
@ -284,52 +306,54 @@ $_shm_load_start = microtime(true);
* Collects some debug information (execution time, memory usage, queries, etc)
* and formats it to stick in the footer of the page.
*/
function get_debug_info(): string {
global $config, $_shm_event_count, $database, $_shm_load_start;
function get_debug_info(): string
{
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"){
$commit = "";
}
else {
$commit = " (".$config->get_string("commit_hash").")";
}
$time = sprintf("%.2f", microtime(true) - $_shm_load_start);
$dbtime = sprintf("%.2f", $database->dbtime);
$i_files = count(get_included_files());
$hits = $database->cache->get_hits();
$miss = $database->cache->get_misses();
if ($config->get_string("commit_hash", "unknown") == "unknown") {
$commit = "";
} else {
$commit = " (".$config->get_string("commit_hash").")";
}
$time = sprintf("%.2f", microtime(true) - $_shm_load_start);
$dbtime = sprintf("%.2f", $database->dbtime);
$i_files = count(get_included_files());
$hits = $database->cache->get_hits();
$miss = $database->cache->get_misses();
$debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM";
$debug .= "; Used $i_files files and {$database->query_count} queries";
$debug .= "; Sent $_shm_event_count events";
$debug .= "; $hits cache hits and $miss misses";
$debug .= "; Shimmie version ". VERSION . $commit; // .", SCore Version ". SCORE_VERSION;
$debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM";
$debug .= "; Used $i_files files and {$database->query_count} queries";
$debug .= "; Sent $_shm_event_count events";
$debug .= "; $hits cache hits and $miss misses";
$debug .= "; Shimmie version ". VERSION . $commit; // .", SCore Version ". SCORE_VERSION;
return $debug;
return $debug;
}
function log_slow() {
global $_shm_load_start;
if(!is_null(SLOW_PAGES)) {
$_time = microtime(true) - $_shm_load_start;
if($_time > SLOW_PAGES) {
$_query = _get_query();
$_dbg = get_debug_info();
file_put_contents("data/slow-pages.log", "$_time $_query $_dbg\n", FILE_APPEND | LOCK_EX);
}
}
function log_slow()
{
global $_shm_load_start;
if (!is_null(SLOW_PAGES)) {
$_time = microtime(true) - $_shm_load_start;
if ($_time > SLOW_PAGES) {
$_query = _get_query();
$_dbg = get_debug_info();
file_put_contents("data/slow-pages.log", "$_time $_query $_dbg\n", FILE_APPEND | LOCK_EX);
}
}
}
function score_assert_handler($file, $line, $code, $desc = null) {
$file = basename($file);
print("Assertion failed at $file:$line: $code ($desc)");
/*
print("<pre>");
debug_print_backtrace();
print("</pre>");
*/
function score_assert_handler($file, $line, $code, $desc = null)
{
$file = basename($file);
print("Assertion failed at $file:$line: $code ($desc)");
/*
print("<pre>");
debug_print_backtrace();
print("</pre>");
*/
}
@ -339,88 +363,94 @@ function score_assert_handler($file, $line, $code, $desc = null) {
/** @privatesection */
function _version_check() {
if(MIN_PHP_VERSION) {
if(version_compare(phpversion(), MIN_PHP_VERSION, ">=") === FALSE) {
print "
function _version_check()
{
if (MIN_PHP_VERSION) {
if (version_compare(phpversion(), MIN_PHP_VERSION, ">=") === false) {
print "
Shimmie (SCore Engine) does not support versions of PHP lower than ".MIN_PHP_VERSION."
(PHP reports that it is version ".phpversion().")
If your web host is running an older version, they are dangerously out of
date and you should plan on moving elsewhere.
";
exit;
}
}
exit;
}
}
}
function _sanitise_environment() {
global $_shm_ctx;
function _sanitise_environment()
{
global $_shm_ctx;
if(TIMEZONE) {
date_default_timezone_set(TIMEZONE);
}
if (TIMEZONE) {
date_default_timezone_set(TIMEZONE);
}
# ini_set('zend.assertions', 1); // generate assertions
ini_set('assert.exception', 1); // throw exceptions when failed
if(DEBUG) {
error_reporting(E_ALL);
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_BAIL, 1);
assert_options(ASSERT_WARNING, 0);
assert_options(ASSERT_QUIET_EVAL, 1);
assert_options(ASSERT_CALLBACK, 'score_assert_handler');
}
# ini_set('zend.assertions', 1); // generate assertions
ini_set('assert.exception', 1); // throw exceptions when failed
if (DEBUG) {
error_reporting(E_ALL);
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_BAIL, 1);
assert_options(ASSERT_WARNING, 0);
assert_options(ASSERT_QUIET_EVAL, 1);
assert_options(ASSERT_CALLBACK, 'score_assert_handler');
}
$_shm_ctx = new Context();
if(CONTEXT) {
$_shm_ctx->set_log(CONTEXT);
}
$_shm_ctx = new Context();
if (CONTEXT) {
$_shm_ctx->set_log(CONTEXT);
}
if(COVERAGE) {
_start_coverage();
register_shutdown_function("_end_coverage");
}
if (COVERAGE) {
_start_coverage();
register_shutdown_function("_end_coverage");
}
ob_start();
ob_start();
if(PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
if(isset($_SERVER['REMOTE_ADDR'])) {
die("CLI with remote addr? Confused, not taking the risk.");
}
$_SERVER['REMOTE_ADDR'] = "0.0.0.0";
$_SERVER['HTTP_HOST'] = "<cli command>";
}
if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
if (isset($_SERVER['REMOTE_ADDR'])) {
die("CLI with remote addr? Confused, not taking the risk.");
}
$_SERVER['REMOTE_ADDR'] = "0.0.0.0";
$_SERVER['HTTP_HOST'] = "<cli command>";
}
}
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[] = 'themes/'.$_theme.'/layout.class.php';
$base_themelets[] = 'themes/'.$_theme.'/themelet.class.php';
function _get_themelet_files(string $_theme): array
{
$base_themelets = [];
if (file_exists('themes/'.$_theme.'/custompage.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");
$custom_themelets = zglob('themes/'.$_theme.'/{'.ENABLED_EXTS.'}.theme.php');
$ext_themelets = zglob("ext/{".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.
*/
function _fatal_error(Exception $e) {
$version = VERSION;
$message = $e->getMessage();
function _fatal_error(Exception $e)
{
$version = VERSION;
$message = $e->getMessage();
//$trace = var_dump($e->getTrace());
//$trace = var_dump($e->getTrace());
//$hash = exec("git rev-parse HEAD");
//$h_hash = $hash ? "<p><b>Hash:</b> $hash" : "";
//'.$h_hash.'
//$hash = exec("git rev-parse HEAD");
//$h_hash = $hash ? "<p><b>Hash:</b> $hash" : "";
//'.$h_hash.'
header("HTTP/1.0 500 Internal Error");
echo '
header("HTTP/1.0 500 Internal Error");
echo '
<html>
<head>
<title>Internal error - SCore-'.$version.'</title>
@ -440,42 +470,50 @@ function _fatal_error(Exception $e) {
* Necessary because various servers and various clients
* think that / is special...
*/
function _decaret(string $str): string {
$out = "";
$length = strlen($str);
for($i=0; $i<$length; $i++) {
if($str[$i] == "^") {
$i++;
if($str[$i] == "^") $out .= "^";
if($str[$i] == "s") $out .= "/";
if($str[$i] == "b") $out .= "\\";
}
else {
$out .= $str[$i];
}
}
return $out;
function _decaret(string $str): string
{
$out = "";
$length = strlen($str);
for ($i=0; $i<$length; $i++) {
if ($str[$i] == "^") {
$i++;
if ($str[$i] == "^") {
$out .= "^";
}
if ($str[$i] == "s") {
$out .= "/";
}
if ($str[$i] == "b") {
$out .= "\\";
}
} else {
$out .= $str[$i];
}
}
return $out;
}
function _get_user(): User {
global $config, $page;
$user = null;
if($page->get_cookie("user") && $page->get_cookie("session")) {
$tmp_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session"));
if(!is_null($tmp_user)) {
$user = $tmp_user;
}
}
if(is_null($user)) {
$user = User::by_id($config->get_int("anon_id", 0));
}
assert(!is_null($user));
function _get_user(): User
{
global $config, $page;
$user = null;
if ($page->get_cookie("user") && $page->get_cookie("session")) {
$tmp_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session"));
if (!is_null($tmp_user)) {
$user = $tmp_user;
}
}
if (is_null($user)) {
$user = User::by_id($config->get_int("anon_id", 0));
}
assert(!is_null($user));
return $user;
return $user;
}
function _get_query(): string {
return (@$_POST["q"]?:@$_GET["q"])?:"/";
function _get_query(): string
{
return (@$_POST["q"]?:@$_GET["q"])?:"/";
}
@ -483,24 +521,30 @@ function _get_query(): string {
* Code coverage *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function _start_coverage(): void {
if(function_exists("xdebug_start_code_coverage")) {
#xdebug_start_code_coverage(XDEBUG_CC_UNUSED|XDEBUG_CC_DEAD_CODE);
xdebug_start_code_coverage(XDEBUG_CC_UNUSED);
}
function _start_coverage(): void
{
if (function_exists("xdebug_start_code_coverage")) {
#xdebug_start_code_coverage(XDEBUG_CC_UNUSED|XDEBUG_CC_DEAD_CODE);
xdebug_start_code_coverage(XDEBUG_CC_UNUSED);
}
}
function _end_coverage(): void {
if(function_exists("xdebug_get_code_coverage")) {
// Absolute path is necessary because working directory
// inside register_shutdown_function is unpredictable.
$absolute_path = dirname(dirname(__FILE__)) . "/data/coverage";
if(!file_exists($absolute_path)) mkdir($absolute_path);
$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())));
}
function _end_coverage(): void
{
if (function_exists("xdebug_get_code_coverage")) {
// Absolute path is necessary because working directory
// inside register_shutdown_function is unpredictable.
$absolute_path = dirname(dirname(__FILE__)) . "/data/coverage";
if (!file_exists($absolute_path)) {
mkdir($absolute_path);
}
$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
*/
function show_ip(string $ip, string $ban_reason): string {
global $user;
$u_reason = url_escape($ban_reason);
$u_end = url_escape("+1 week");
$ban = $user->can("ban_ip") ? ", <a href='".make_link("ip_ban/list", "ip=$ip&reason=$u_reason&end=$u_end#add")."'>Ban</a>" : "";
$ip = $user->can("view_ip") ? $ip.$ban : "";
return $ip;
function show_ip(string $ip, string $ban_reason): string
{
global $user;
$u_reason = url_escape($ban_reason);
$u_end = url_escape("+1 week");
$ban = $user->can("ban_ip") ? ", <a href='".make_link("ip_ban/list", "ip=$ip&reason=$u_reason&end=$u_end#add")."'>Ban</a>" : "";
$ip = $user->can("view_ip") ? $ip.$ban : "";
return $ip;
}
/**
* 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 {
global $user;
if($method == "GET") {
$link = html_escape($target);
$target = make_link($target);
$extra_inputs = "<input type='hidden' name='q' value='$link'>";
}
else {
$extra_inputs = $user->get_auth_html();
}
function make_form(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit=""): string
{
global $user;
if ($method == "GET") {
$link = html_escape($target);
$target = make_link($target);
$extra_inputs = "<input type='hidden' name='q' value='$link'>";
} else {
$extra_inputs = $user->get_auth_html();
}
$extra = empty($form_id) ? '' : 'id="'. $form_id .'"';
if($multipart) {
$extra .= " enctype='multipart/form-data'";
}
if($onsubmit) {
$extra .= ' onsubmit="'.$onsubmit.'"';
}
return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$extra_inputs;
$extra = empty($form_id) ? '' : 'id="'. $form_id .'"';
if ($multipart) {
$extra .= " enctype='multipart/form-data'";
}
if ($onsubmit) {
$extra .= ' onsubmit="'.$onsubmit.'"';
}
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
*/
class AdminBuildingEvent extends Event {
/** @var \Page */
public $page;
class AdminBuildingEvent extends Event
{
/** @var \Page */
public $page;
public function __construct(Page $page) {
$this->page = $page;
}
public function __construct(Page $page)
{
$this->page = $page;
}
}
class AdminActionEvent extends Event {
/** @var string */
public $action;
/** @var bool */
public $redirect = true;
class AdminActionEvent extends Event
{
/** @var string */
public $action;
/** @var bool */
public $redirect = true;
public function __construct(string $action) {
$this->action = $action;
}
public function __construct(string $action)
{
$this->action = $action;
}
}
class AdminPage extends Extension {
public function onPageRequest(PageRequestEvent $event) {
global $page, $user;
class AdminPage extends Extension
{
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
if($event->page_matches("admin")) {
if(!$user->can("manage_admintools")) {
$this->theme->display_permission_denied();
}
else {
if($event->count_args() == 0) {
send_event(new AdminBuildingEvent($page));
}
else {
$action = $event->get_arg(0);
$aae = new AdminActionEvent($action);
if ($event->page_matches("admin")) {
if (!$user->can("manage_admintools")) {
$this->theme->display_permission_denied();
} else {
if ($event->count_args() == 0) {
send_event(new AdminBuildingEvent($page));
} else {
$action = $event->get_arg(0);
$aae = new AdminActionEvent($action);
if($user->check_auth_token()) {
log_info("admin", "Util: $action");
set_time_limit(0);
send_event($aae);
}
if ($user->check_auth_token()) {
log_info("admin", "Util: $action");
set_time_limit(0);
send_event($aae);
}
if($aae->redirect) {
$page->set_mode("redirect");
$page->set_redirect(make_link("admin"));
}
}
}
}
}
if ($aae->redirect) {
$page->set_mode("redirect");
$page->set_redirect(make_link("admin"));
}
}
}
}
}
public function onCommand(CommandEvent $event) {
if($event->cmd == "help") {
print "\tget-page [query string]\n";
print "\t\teg 'get-page post/list'\n\n";
print "\tregen-thumb [hash]\n";
print "\t\tregenerate a thumbnail\n\n";
}
if($event->cmd == "get-page") {
global $page;
send_event(new PageRequestEvent($event->args[0]));
$page->display();
}
if($event->cmd == "regen-thumb") {
$image = Image::by_hash($event->args[0]);
if($image) {
print("Regenerating thumb for image {$image->id} ({$image->hash})\n");
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
}
else {
print("Can't find image with hash {$event->args[0]}\n");
}
}
}
public function onCommand(CommandEvent $event)
{
if ($event->cmd == "help") {
print "\tget-page [query string]\n";
print "\t\teg 'get-page post/list'\n\n";
print "\tregen-thumb [hash]\n";
print "\t\tregenerate a thumbnail\n\n";
}
if ($event->cmd == "get-page") {
global $page;
send_event(new PageRequestEvent($event->args[0]));
$page->display();
}
if ($event->cmd == "regen-thumb") {
$image = Image::by_hash($event->args[0]);
if ($image) {
print("Regenerating thumb for image {$image->id} ({$image->hash})\n");
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
} else {
print("Can't find image with hash {$event->args[0]}\n");
}
}
}
public function onAdminBuilding(AdminBuildingEvent $event) {
$this->theme->display_page();
$this->theme->display_form();
}
public function onAdminBuilding(AdminBuildingEvent $event)
{
$this->theme->display_page();
$this->theme->display_form();
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
global $user;
if($user->can("manage_admintools")) {
$event->add_link("Board Admin", make_link("admin"));
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
if ($user->can("manage_admintools")) {
$event->add_link("Board Admin", make_link("admin"));
}
}
public function onAdminAction(AdminActionEvent $event) {
$action = $event->action;
if(method_exists($this, $action)) {
$event->redirect = $this->$action();
}
}
public function onAdminAction(AdminActionEvent $event)
{
$action = $event->action;
if (method_exists($this, $action)) {
$event->redirect = $this->$action();
}
}
public function onPostListBuilding(PostListBuildingEvent $event) {
global $user;
if($user->can("manage_admintools") && !empty($event->search_terms)) {
$event->add_control($this->theme->dbq_html(Tag::implode($event->search_terms)));
}
}
public function onPostListBuilding(PostListBuildingEvent $event)
{
global $user;
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() {
global $page;
$query = $_POST['query'];
$reason = @$_POST['reason'];
assert(strlen($query) > 1);
private function delete_by_query()
{
global $page;
$query = $_POST['query'];
$reason = @$_POST['reason'];
assert(strlen($query) > 1);
$images = Image::find_images(0, 1000000, Tag::explode($query));
$count = count($images);
log_warning("admin", "Mass-deleting $count images from $query", "Mass deleted $count images");
foreach($images as $image) {
if($reason && class_exists("ImageBan")) {
send_event(new AddImageHashBanEvent($image->hash, $reason));
}
send_event(new ImageDeletionEvent($image));
}
$images = Image::find_images(0, 1000000, Tag::explode($query));
$count = count($images);
log_warning("admin", "Mass-deleting $count images from $query", "Mass deleted $count images");
foreach ($images as $image) {
if ($reason && class_exists("ImageBan")) {
send_event(new AddImageHashBanEvent($image->hash, $reason));
}
send_event(new ImageDeletionEvent($image));
}
$page->set_mode("redirect");
$page->set_redirect(make_link("post/list"));
return false;
}
$page->set_mode("redirect");
$page->set_redirect(make_link("post/list"));
return false;
}
private function set_tag_case() {
global $database;
$database->execute($database->scoreql_to_sql(
"UPDATE tags SET tag=:tag1 WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag2)"
), array("tag1" => $_POST['tag'], "tag2" => $_POST['tag']));
log_info("admin", "Fixed the case of ".html_escape($_POST['tag']), "Fixed case");
return true;
}
private function set_tag_case()
{
global $database;
$database->execute($database->scoreql_to_sql(
"UPDATE tags SET tag=:tag1 WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag2)"
), ["tag1" => $_POST['tag'], "tag2" => $_POST['tag']]);
log_info("admin", "Fixed the case of ".html_escape($_POST['tag']), "Fixed case");
return true;
}
private function lowercase_all_tags() {
global $database;
$database->execute("UPDATE tags SET tag=lower(tag)");
log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase");
return true;
}
private function lowercase_all_tags()
{
global $database;
$database->execute("UPDATE tags SET tag=lower(tag)");
log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase");
return true;
}
private function recount_tag_use() {
global $database;
$database->Execute("
private function recount_tag_use()
{
global $database;
$database->Execute("
UPDATE tags
SET count = COALESCE(
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
0
)
");
$database->Execute("DELETE FROM tags WHERE count=0");
log_warning("admin", "Re-counted tags", "Re-counted tags");
return true;
}
$database->Execute("DELETE FROM tags WHERE count=0");
log_warning("admin", "Re-counted tags", "Re-counted tags");
return true;
}
private function database_dump() {
global $page;
private function database_dump()
{
global $page;
$matches = array();
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'];
$username = $matches['user'];
$password = $matches['password'];
$hostname = $matches['host'];
$database = $matches['dbname'];
$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'];
$username = $matches['user'];
$password = $matches['password'];
$hostname = $matches['host'];
$database = $matches['dbname'];
switch($software) {
case 'mysql':
$cmd = "mysqldump -h$hostname -u$username -p$password $database";
break;
case 'pgsql':
putenv("PGPASSWORD=$password");
$cmd = "pg_dump -h $hostname -U $username $database";
break;
case 'sqlite':
$cmd = "sqlite3 $database .dump";
break;
default:
$cmd = false;
}
switch ($software) {
case 'mysql':
$cmd = "mysqldump -h$hostname -u$username -p$password $database";
break;
case 'pgsql':
putenv("PGPASSWORD=$password");
$cmd = "pg_dump -h $hostname -U $username $database";
break;
case 'sqlite':
$cmd = "sqlite3 $database .dump";
break;
default:
$cmd = false;
}
//FIXME: .SQL dump is empty if cmd doesn't exist
//FIXME: .SQL dump is empty if cmd doesn't exist
if($cmd) {
$page->set_mode("data");
$page->set_type("application/x-unknown");
$page->set_filename('shimmie-'.date('Ymd').'.sql');
$page->set_data(shell_exec($cmd));
}
if ($cmd) {
$page->set_mode("data");
$page->set_type("application/x-unknown");
$page->set_filename('shimmie-'.date('Ymd').'.sql');
$page->set_data(shell_exec($cmd));
}
return false;
}
return false;
}
private function download_all_images() {
global $database, $page;
private function download_all_images()
{
global $database, $page;
$images = $database->get_all("SELECT hash, ext FROM images");
$filename = data_path('imgdump-'.date('Ymd').'.zip');
$images = $database->get_all("SELECT hash, ext FROM images");
$filename = data_path('imgdump-'.date('Ymd').'.zip');
$zip = new ZipArchive;
if($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === TRUE){
foreach($images as $img){
$img_loc = warehouse_path("images", $img["hash"], FALSE);
$zip->addFile($img_loc, $img["hash"].".".$img["ext"]);
}
$zip->close();
}
$zip = new ZipArchive;
if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
foreach ($images as $img) {
$img_loc = warehouse_path("images", $img["hash"], false);
$zip->addFile($img_loc, $img["hash"].".".$img["ext"]);
}
$zip->close();
}
$page->set_mode("redirect");
$page->set_redirect(make_link($filename)); //TODO: Delete file after downloaded?
$page->set_mode("redirect");
$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;
//TODO: Make work with PostgreSQL + SQLite
//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);
//TODO: Make work with PostgreSQL + SQLite
//TODO: Update score_log (Having an optional ID column for score_log would be nice..)
preg_match("#^(?P<proto>\w+)\:(?:user=(?P<user>\w+)(?:;|$)|password=(?P<password>\w*)(?:;|$)|host=(?P<host>[\w\.\-]+)(?:;|$)|dbname=(?P<dbname>[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches);
if($matches['proto'] == "mysql"){
$tables = $database->get_col("SELECT TABLE_NAME
if ($matches['proto'] == "mysql") {
$tables = $database->get_col("SELECT TABLE_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = :db
AND REFERENCED_COLUMN_NAME = 'id'
AND REFERENCED_TABLE_NAME = 'images'", array("db" => $matches['dbname']));
AND REFERENCED_TABLE_NAME = 'images'", ["db" => $matches['dbname']]);
$i = 1;
$ids = $database->get_col("SELECT id FROM images ORDER BY images.id ASC");
foreach($ids as $id){
$sql = "SET FOREIGN_KEY_CHECKS=0;
$i = 1;
$ids = $database->get_col("SELECT id FROM images ORDER BY images.id ASC");
foreach ($ids as $id) {
$sql = "SET FOREIGN_KEY_CHECKS=0;
UPDATE images SET id={$i} WHERE image_id={$id};";
foreach($tables as $table){
$sql .= "UPDATE {$table} SET image_id={$i} WHERE image_id={$id};";
}
foreach ($tables as $table) {
$sql .= "UPDATE {$table} SET image_id={$i} WHERE image_id={$id};";
}
$sql .= " SET FOREIGN_KEY_CHECKS=1;";
$database->execute($sql);
$sql .= " SET FOREIGN_KEY_CHECKS=1;";
$database->execute($sql);
$i++;
}
$database->execute("ALTER TABLE images AUTO_INCREMENT=".(count($ids) + 1));
}elseif($matches['proto'] == "pgsql"){
//TODO: Make this work with PostgreSQL
}elseif($matches['proto'] == "sqlite"){
//TODO: Make this work with SQLite
}
$i++;
}
$database->execute("ALTER TABLE images AUTO_INCREMENT=".(count($ids) + 1));
} elseif ($matches['proto'] == "pgsql") {
//TODO: Make this work with PostgreSQL
} elseif ($matches['proto'] == "sqlite") {
//TODO: Make this work with SQLite
}
return true;
}
}

View file

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

View file

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

View file

@ -10,155 +10,156 @@
* site admins can edit it, other people can view and download it
*/
class AddAliasEvent extends Event {
/** @var string */
public $oldtag;
/** @var string */
public $newtag;
class AddAliasEvent extends Event
{
/** @var string */
public $oldtag;
/** @var string */
public $newtag;
public function __construct(string $oldtag, string $newtag) {
$this->oldtag = trim($oldtag);
$this->newtag = trim($newtag);
}
public function __construct(string $oldtag, string $newtag)
{
$this->oldtag = trim($oldtag);
$this->newtag = trim($newtag);
}
}
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 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());
}
}
}
} 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
class AliasEditorTest extends ShimmiePHPUnitTestCase {
public function testAliasList() {
$this->get_page('alias/list');
$this->assert_response(200);
$this->assert_title("Alias List");
}
class AliasEditorTest extends ShimmiePHPUnitTestCase
{
public function testAliasList()
{
$this->get_page('alias/list');
$this->assert_response(200);
$this->assert_title("Alias List");
}
public function testAliasListReadOnly() {
// Check that normal users can't add aliases.
$this->log_in_as_user();
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("Add");
}
public function testAliasListReadOnly()
{
// Check that normal users can't add aliases.
$this->log_in_as_user();
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("Add");
}
public function testAliasEditor() {
/*
**********************************************************************
* FIXME: TODO:
* For some reason the alias tests always fail when they are running
* 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.
*
*********************************************************************
*/
$this->markTestIncomplete();
public function testAliasEditor()
{
/*
**********************************************************************
* FIXME: TODO:
* For some reason the alias tests always fail when they are running
* 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.
*
*********************************************************************
*/
$this->markTestIncomplete();
$this->log_in_as_admin();
$this->log_in_as_admin();
# test one to one
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->set_field('oldtag', "test1");
$this->set_field('newtag', "test2");
$this->clickSubmit('Add');
$this->assert_no_text("Error adding alias");
# test one to one
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->set_field('oldtag', "test1");
$this->set_field('newtag', "test2");
$this->clickSubmit('Add');
$this->assert_no_text("Error adding alias");
$this->get_page('alias/list');
$this->assert_text("test1");
$this->get_page('alias/list');
$this->assert_text("test1");
$this->get_page("alias/export/aliases.csv");
$this->assert_text("test1,test2");
$this->get_page("alias/export/aliases.csv");
$this->assert_text("test1,test2");
$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->assert_title("Image $image_id: test2");
$this->get_page("post/list/test1/1"); # searching for an alias should find the master tag
$this->assert_title("Image $image_id: test2");
$this->get_page("post/list/test2/1"); # check that searching for the main tag still works
$this->assert_title("Image $image_id: test2");
$this->delete_image($image_id);
$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->assert_title("Image $image_id: test2");
$this->get_page("post/list/test1/1"); # searching for an alias should find the master tag
$this->assert_title("Image $image_id: test2");
$this->get_page("post/list/test2/1"); # check that searching for the main tag still works
$this->assert_title("Image $image_id: test2");
$this->delete_image($image_id);
$this->get_page('alias/list');
$this->click("Remove");
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("test1");
$this->get_page('alias/list');
$this->click("Remove");
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("test1");
# test one to many
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->set_field('oldtag', "onetag");
$this->set_field('newtag', "multi tag");
$this->click("Add");
$this->get_page('alias/list');
$this->assert_text("multi");
$this->assert_text("tag");
# test one to many
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->set_field('oldtag', "onetag");
$this->set_field('newtag', "multi tag");
$this->click("Add");
$this->get_page('alias/list');
$this->assert_text("multi");
$this->assert_text("tag");
$this->get_page("alias/export/aliases.csv");
$this->assert_text("onetag,multi tag");
$this->get_page("alias/export/aliases.csv");
$this->assert_text("onetag,multi tag");
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "onetag");
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "onetag");
// FIXME: known broken
//$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases
//$this->assert_title("onetag");
//$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi/1");
$this->assert_title("multi");
$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi%20tag/1");
$this->assert_title("multi tag");
$this->assert_no_text("No Images Found");
$this->delete_image($image_id_1);
$this->delete_image($image_id_2);
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "onetag");
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "onetag");
// FIXME: known broken
//$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases
//$this->assert_title("onetag");
//$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi/1");
$this->assert_title("multi");
$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi%20tag/1");
$this->assert_title("multi tag");
$this->assert_no_text("No Images Found");
$this->delete_image($image_id_1);
$this->delete_image($image_id_2);
$this->get_page('alias/list');
$this->click("Remove");
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("test1");
$this->get_page('alias/list');
$this->click("Remove");
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("test1");
$this->log_out();
$this->log_out();
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("Add");
}
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("Add");
}
}

View file

@ -1,18 +1,20 @@
<?php
class AliasEditorTheme extends Themelet {
/**
* Show a page of aliases.
*
* 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;
class AliasEditorTheme extends Themelet
{
/**
* Show a page of aliases.
*
* 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;
$can_manage = $user->can("manage_alias_list");
if($can_manage) {
$h_action = "<th width='10%'>Action</th>";
$h_add = "
$can_manage = $user->can("manage_alias_list");
if ($can_manage) {
$h_action = "<th width='10%'>Action</th>";
$h_add = "
<tr>
".make_form(make_link("alias/add"))."
<td><input type='text' name='oldtag' class='autocomplete_tags' autocomplete='off'></td>
@ -21,20 +23,19 @@ class AliasEditorTheme extends Themelet {
</form>
</tr>
";
}
else {
$h_action = "";
$h_add = "";
}
} else {
$h_action = "";
$h_add = "";
}
$h_aliases = "";
foreach($aliases as $old => $new) {
$h_old = html_escape($old);
$h_new = "<a href='".make_link("post/list/".url_escape($new)."/1")."'>".html_escape($new)."</a>";
$h_aliases = "";
foreach ($aliases as $old => $new) {
$h_old = html_escape($old);
$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>";
if($can_manage) {
$h_aliases .= "
$h_aliases .= "<tr><td>$h_old</td><td>$h_new</td>";
if ($can_manage) {
$h_aliases .= "
<td>
".make_form(make_link("alias/remove"))."
<input type='hidden' name='oldtag' value='$h_old'>
@ -42,10 +43,10 @@ class AliasEditorTheme extends Themelet {
</form>
</td>
";
}
$h_aliases .= "</tr>";
}
$html = "
}
$h_aliases .= "</tr>";
}
$html = "
<table id='aliases' class='sortable zebra'>
<thead><tr><th>From</th><th>To</th>$h_action</tr></thead>
<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>
";
$bulk_html = "
$bulk_html = "
".make_form(make_link("alias/import"), 'post', true)."
<input type='file' name='alias_file'>
<input type='submit' value='Upload List'>
</form>
";
$page->set_title("Alias List");
$page->set_heading("Alias List");
$page->add_block(new NavBlock());
$page->add_block(new Block("Aliases", $html));
if($can_manage) {
$page->add_block(new Block("Bulk Upload", $bulk_html, "main", 51));
}
$page->set_title("Alias List");
$page->set_heading("Alias List");
$page->add_block(new NavBlock());
$page->add_block(new Block("Aliases", $html));
if ($can_manage) {
$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";
class UploadS3 extends Extension {
public function onInitExt(InitExtEvent $event) {
global $config;
$config->set_default_string("amazon_s3_access", "");
$config->set_default_string("amazon_s3_secret", "");
$config->set_default_string("amazon_s3_bucket", "");
}
class UploadS3 extends Extension
{
public function onInitExt(InitExtEvent $event)
{
global $config;
$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) {
$sb = new SetupBlock("Amazon S3");
$sb->add_text_option("amazon_s3_access", "Access key: ");
$sb->add_text_option("amazon_s3_secret", "<br>Secret key: ");
$sb->add_text_option("amazon_s3_bucket", "<br>Bucket: ");
$event->panel->add_block($sb);
}
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = new SetupBlock("Amazon S3");
$sb->add_text_option("amazon_s3_access", "Access key: ");
$sb->add_text_option("amazon_s3_secret", "<br>Secret key: ");
$sb->add_text_option("amazon_s3_bucket", "<br>Bucket: ");
$event->panel->add_block($sb);
}
public function onImageAddition(ImageAdditionEvent $event) {
global $config;
$access = $config->get_string("amazon_s3_access");
$secret = $config->get_string("amazon_s3_secret");
$bucket = $config->get_string("amazon_s3_bucket");
if(!empty($bucket)) {
log_debug("amazon_s3", "Mirroring Image #".$event->image->id." to S3 #$bucket");
$s3 = new S3($access, $secret);
$s3->putBucket($bucket, S3::ACL_PUBLIC_READ);
$s3->putObjectFile(
warehouse_path("thumbs", $event->image->hash),
$bucket,
'thumbs/'.$event->image->hash,
S3::ACL_PUBLIC_READ,
array(),
array(
"Content-Type" => "image/jpeg",
"Content-Disposition" => "inline; filename=image-" . $event->image->id . ".jpg",
)
);
$s3->putObjectFile(
warehouse_path("images", $event->image->hash),
$bucket,
'images/'.$event->image->hash,
S3::ACL_PUBLIC_READ,
array(),
array(
"Content-Type" => $event->image->get_mime_type(),
"Content-Disposition" => "inline; filename=image-" . $event->image->id . "." . $event->image->ext,
)
);
}
}
public function onImageAddition(ImageAdditionEvent $event)
{
global $config;
$access = $config->get_string("amazon_s3_access");
$secret = $config->get_string("amazon_s3_secret");
$bucket = $config->get_string("amazon_s3_bucket");
if (!empty($bucket)) {
log_debug("amazon_s3", "Mirroring Image #".$event->image->id." to S3 #$bucket");
$s3 = new S3($access, $secret);
$s3->putBucket($bucket, S3::ACL_PUBLIC_READ);
$s3->putObjectFile(
warehouse_path("thumbs", $event->image->hash),
$bucket,
'thumbs/'.$event->image->hash,
S3::ACL_PUBLIC_READ,
[],
[
"Content-Type" => "image/jpeg",
"Content-Disposition" => "inline; filename=image-" . $event->image->id . ".jpg",
]
);
$s3->putObjectFile(
warehouse_path("images", $event->image->hash),
$bucket,
'images/'.$event->image->hash,
S3::ACL_PUBLIC_READ,
[],
[
"Content-Type" => $event->image->get_mime_type(),
"Content-Disposition" => "inline; filename=image-" . $event->image->id . "." . $event->image->ext,
]
);
}
}
public function onImageDeletion(ImageDeletionEvent $event) {
global $config;
$access = $config->get_string("amazon_s3_access");
$secret = $config->get_string("amazon_s3_secret");
$bucket = $config->get_string("amazon_s3_bucket");
if(!empty($bucket)) {
log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3");
$s3 = new S3($access, $secret);
$s3->deleteObject($bucket, "images/" . $event->image->hash);
$s3->deleteObject($bucket, "thumbs/" . $event->image->hash);
}
}
public function onImageDeletion(ImageDeletionEvent $event)
{
global $config;
$access = $config->get_string("amazon_s3_access");
$secret = $config->get_string("amazon_s3_secret");
$bucket = $config->get_string("amazon_s3_bucket");
if (!empty($bucket)) {
log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3");
$s3 = new S3($access, $secret);
$s3->deleteObject($bucket, "images/" . $event->image->hash);
$s3->deleteObject($bucket, "thumbs/" . $event->image->hash);
}
}
}

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -20,10 +20,12 @@
* from Essex"
*/
class BanWords extends Extension {
public function onInitExt(InitExtEvent $event) {
global $config;
$config->set_default_string('banned_words', "
class BanWords extends Extension
{
public function onInitExt(InitExtEvent $event)
{
global $config;
$config->set_default_string('banned_words', "
a href=
anal
blowjob
@ -51,80 +53,87 @@ very nice site
viagra
xanax
");
}
}
public function onCommentPosting(CommentPostingEvent $event) {
global $user;
if(!$user->can("bypass_comment_checks")) {
$this->test_text($event->comment, new CommentPostingException("Comment contains banned terms"));
}
}
public function onCommentPosting(CommentPostingEvent $event)
{
global $user;
if (!$user->can("bypass_comment_checks")) {
$this->test_text($event->comment, new CommentPostingException("Comment contains banned terms"));
}
}
public function onSourceSet(SourceSetEvent $event) {
$this->test_text($event->source, new SCoreException("Source contains banned terms"));
}
public function onSourceSet(SourceSetEvent $event)
{
$this->test_text($event->source, new SCoreException("Source contains banned terms"));
}
public function onTagSet(TagSetEvent $event) {
$this->test_text(Tag::implode($event->tags), new SCoreException("Tags contain banned terms"));
}
public function onTagSet(TagSetEvent $event)
{
$this->test_text(Tag::implode($event->tags), new SCoreException("Tags contain banned terms"));
}
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->add_longtext_option("banned_words");
$failed = array();
foreach($this->get_words() as $word) {
if($word[0] == '/') {
if(preg_match($word, "") === false) {
$failed[] = $word;
}
}
}
if($failed) {
$sb->add_label("Failed regexes: ".join(", ", $failed));
}
$event->panel->add_block($sb);
}
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->add_longtext_option("banned_words");
$failed = [];
foreach ($this->get_words() as $word) {
if ($word[0] == '/') {
if (preg_match($word, "") === false) {
$failed[] = $word;
}
}
}
if ($failed) {
$sb->add_label("Failed regexes: ".join(", ", $failed));
}
$event->panel->add_block($sb);
}
/**
* Throws if the comment contains banned words.
*/
private function test_text(string $comment, Exception $ex): void {
$comment = strtolower($comment);
/**
* Throws if the comment contains banned words.
*/
private function test_text(string $comment, Exception $ex): void
{
$comment = strtolower($comment);
foreach($this->get_words() as $word) {
if($word[0] == '/') {
// lines that start with slash are regex
if(preg_match($word, $comment) === 1) {
throw $ex;
}
}
else {
// other words are literal
if(strpos($comment, $word) !== false) {
throw $ex;
}
}
}
}
foreach ($this->get_words() as $word) {
if ($word[0] == '/') {
// lines that start with slash are regex
if (preg_match($word, $comment) === 1) {
throw $ex;
}
} else {
// other words are literal
if (strpos($comment, $word) !== false) {
throw $ex;
}
}
}
}
private function get_words(): array {
global $config;
$words = array();
private function get_words(): array
{
global $config;
$words = [];
$banned = $config->get_string("banned_words");
foreach(explode("\n", $banned) as $word) {
$word = trim(strtolower($word));
if(strlen($word) == 0) {
// line is blank
continue;
}
$words[] = $word;
}
$banned = $config->get_string("banned_words");
foreach (explode("\n", $banned) as $word) {
$word = trim(strtolower($word));
if (strlen($word) == 0) {
// line is blank
continue;
}
$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
class BanWordsTest extends ShimmiePHPUnitTestCase {
public function check_blocked($image_id, $words) {
global $user;
try {
send_event(new CommentPostingEvent($image_id, $user, $words));
$this->fail("Exception not thrown");
}
catch(CommentPostingException $e) {
$this->assertEquals($e->getMessage(), "Comment contains banned terms");
}
}
class BanWordsTest extends ShimmiePHPUnitTestCase
{
public function check_blocked($image_id, $words)
{
global $user;
try {
send_event(new CommentPostingEvent($image_id, $user, $words));
$this->fail("Exception not thrown");
} catch (CommentPostingException $e) {
$this->assertEquals($e->getMessage(), "Comment contains banned terms");
}
}
public function testWordBan() {
global $config;
$config->set_string("banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
public function testWordBan()
{
global $config;
$config->set_string("banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
$this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$this->log_in_as_user();
$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, "some link to http://something.cn/");
$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->get_page('comment/list');
$this->assert_title('Comments');
$this->assert_no_text('viagra');
$this->assert_no_text('ViagrA');
$this->assert_no_text('http://something.cn/');
}
$this->get_page('comment/list');
$this->assert_title('Comments');
$this->assert_no_text('viagra');
$this->assert_no_text('ViagrA');
$this->assert_no_text('http://something.cn/');
}
}

View file

@ -25,135 +25,162 @@
* </ul>
*/
class BBCode extends FormatterExtension {
public function format(string $text): string {
$text = $this->extract_code($text);
foreach(array(
"b", "i", "u", "s", "sup", "sub", "h1", "h2", "h3", "h4",
) as $el) {
$text = preg_replace("!\[$el\](.*?)\[/$el\]!s", "<$el>$1</$el>", $text);
}
$text = preg_replace('!^&gt;&gt;([^\d].+)!', '<blockquote><small>$1</small></blockquote>', $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('!\[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\]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('!\[url=((?:https?|ftp|irc|mailto)://.*?)\](.*?)\[/url\]!s', '<a href="$1">$2</a>', $text);
$text = preg_replace('!\[url\]((?:https?|ftp|irc|mailto)://.*?)\[/url\]!s', '<a href="$1">$1</a>', $text);
$text = preg_replace('!\[email\](.*?)\[/email\]!s', '<a href="mailto:$1">$1</a>', $text);
$text = preg_replace('!\[img\](https?:\/\/.*?)\[/img\]!s', '<img src="$1">', $text);
$text = preg_replace('!\[\[([^\|\]]+)\|([^\]]+)\]\]!s', '<a href="'.make_link('wiki/$1').'">$2</a>', $text);
$text = preg_replace('!\[\[([^\]]+)\]\]!s', '<a href="'.make_link('wiki/$1').'">$1</a>', $text);
$text = preg_replace("!\n\s*\n!", "\n\n", $text);
$text = str_replace("\n", "\n<br>", $text);
$text = preg_replace("/\[quote\](.*?)\[\/quote\]/s", "<blockquote><small>\\1</small></blockquote>", $text);
$text = preg_replace("/\[quote=(.*?)\](.*?)\[\/quote\]/s", "<blockquote><em>\\1 said:</em><br><small>\\2</small></blockquote>", $text);
while(preg_match("/\[list\](.*?)\[\/list\]/s", $text))
$text = preg_replace("/\[list\](.*?)\[\/list\]/s", "<ul>\\1</ul>", $text);
while(preg_match("/\[ul\](.*?)\[\/ul\]/s", $text))
$text = preg_replace("/\[ul\](.*?)\[\/ul\]/s", "<ul>\\1</ul>", $text);
while(preg_match("/\[ol\](.*?)\[\/ol\]/s", $text))
$text = preg_replace("/\[ol\](.*?)\[\/ol\]/s", "<ol>\\1</ol>", $text);
$text = preg_replace("/\[li\](.*?)\[\/li\]/s", "<li>\\1</li>", $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;
}
class BBCode extends FormatterExtension
{
public function format(string $text): string
{
$text = $this->extract_code($text);
foreach ([
"b", "i", "u", "s", "sup", "sub", "h1", "h2", "h3", "h4",
] as $el) {
$text = preg_replace("!\[$el\](.*?)\[/$el\]!s", "<$el>$1</$el>", $text);
}
$text = preg_replace('!^&gt;&gt;([^\d].+)!', '<blockquote><small>$1</small></blockquote>', $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('!\[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\]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('!\[url=((?:https?|ftp|irc|mailto)://.*?)\](.*?)\[/url\]!s', '<a href="$1">$2</a>', $text);
$text = preg_replace('!\[url\]((?:https?|ftp|irc|mailto)://.*?)\[/url\]!s', '<a href="$1">$1</a>', $text);
$text = preg_replace('!\[email\](.*?)\[/email\]!s', '<a href="mailto:$1">$1</a>', $text);
$text = preg_replace('!\[img\](https?:\/\/.*?)\[/img\]!s', '<img src="$1">', $text);
$text = preg_replace('!\[\[([^\|\]]+)\|([^\]]+)\]\]!s', '<a href="'.make_link('wiki/$1').'">$2</a>', $text);
$text = preg_replace('!\[\[([^\]]+)\]\]!s', '<a href="'.make_link('wiki/$1').'">$1</a>', $text);
$text = preg_replace("!\n\s*\n!", "\n\n", $text);
$text = str_replace("\n", "\n<br>", $text);
$text = preg_replace("/\[quote\](.*?)\[\/quote\]/s", "<blockquote><small>\\1</small></blockquote>", $text);
$text = preg_replace("/\[quote=(.*?)\](.*?)\[\/quote\]/s", "<blockquote><em>\\1 said:</em><br><small>\\2</small></blockquote>", $text);
while (preg_match("/\[list\](.*?)\[\/list\]/s", $text)) {
$text = preg_replace("/\[list\](.*?)\[\/list\]/s", "<ul>\\1</ul>", $text);
}
while (preg_match("/\[ul\](.*?)\[\/ul\]/s", $text)) {
$text = preg_replace("/\[ul\](.*?)\[\/ul\]/s", "<ul>\\1</ul>", $text);
}
while (preg_match("/\[ol\](.*?)\[\/ol\]/s", $text)) {
$text = preg_replace("/\[ol\](.*?)\[\/ol\]/s", "<ol>\\1</ol>", $text);
}
$text = preg_replace("/\[li\](.*?)\[\/li\]/s", "<li>\\1</li>", $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 {
foreach(array(
"b", "i", "u", "s", "sup", "sub", "h1", "h2", "h3", "h4",
"code", "url", "email", "li",
) 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("!\[img\](.*?)\[/img\]!s", "", $text);
$text = preg_replace("!\[\[([^\|\]]+)\|([^\]]+)\]\]!s", '$2', $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("!\[\*\](.*?)!s", '$1', $text);
$text = $this->strip_spoiler($text);
return $text;
}
public function strip(string $text): string
{
foreach ([
"b", "i", "u", "s", "sup", "sub", "h1", "h2", "h3", "h4",
"code", "url", "email", "li",
] 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("!\[img\](.*?)\[/img\]!s", "", $text);
$text = preg_replace("!\[\[([^\|\]]+)\|([^\]]+)\]\]!s", '$2', $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("!\[\*\](.*?)!s", '$1', $text);
$text = $this->strip_spoiler($text);
return $text;
}
private function filter_spoiler(string $text): string {
return str_replace(
array("[spoiler]","[/spoiler]"),
array("<span style=\"background-color:#000; color:#000;\">","</span>"),
$text);
}
private function filter_spoiler(string $text): string
{
return str_replace(
["[spoiler]","[/spoiler]"],
["<span style=\"background-color:#000; color:#000;\">","</span>"],
$text
);
}
private function strip_spoiler(string $text): string {
$l1 = strlen("[spoiler]");
$l2 = strlen("[/spoiler]");
while(true) {
$start = strpos($text, "[spoiler]");
if($start === false) break;
private function strip_spoiler(string $text): string
{
$l1 = strlen("[spoiler]");
$l2 = strlen("[/spoiler]");
while (true) {
$start = strpos($text, "[spoiler]");
if ($start === false) {
break;
}
$end = strpos($text, "[/spoiler]");
if($end === false) break;
$end = strpos($text, "[/spoiler]");
if ($end === false) {
break;
}
if($end < $start) break;
if ($end < $start) {
break;
}
$beginning = substr($text, 0, $start);
$middle = str_rot13(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$beginning = substr($text, 0, $start);
$middle = str_rot13(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$text = $beginning . $middle . $ending;
}
return $text;
}
$text = $beginning . $middle . $ending;
}
return $text;
}
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,
# which would only appear after decoding
$text = str_replace("[code!]", "[code]", $text);
$text = str_replace("[/code!]", "[/code]", $text);
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,
# which would only appear after decoding
$text = str_replace("[code!]", "[code]", $text);
$text = str_replace("[/code!]", "[/code]", $text);
$l1 = strlen("[code]");
$l2 = strlen("[/code]");
while(true) {
$start = strpos($text, "[code]");
if($start === false) break;
$l1 = strlen("[code]");
$l2 = strlen("[/code]");
while (true) {
$start = strpos($text, "[code]");
if ($start === false) {
break;
}
$end = strpos($text, "[/code]", $start);
if($end === false) break;
$end = strpos($text, "[/code]", $start);
if ($end === false) {
break;
}
if($end < $start) break;
if ($end < $start) {
break;
}
$beginning = substr($text, 0, $start);
$middle = base64_encode(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$beginning = substr($text, 0, $start);
$middle = base64_encode(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$text = $beginning . "[code!]" . $middle . "[/code!]" . $ending;
}
return $text;
}
$text = $beginning . "[code!]" . $middle . "[/code!]" . $ending;
}
return $text;
}
private function insert_code(string $text): string {
$l1 = strlen("[code!]");
$l2 = strlen("[/code!]");
while(true) {
$start = strpos($text, "[code!]");
if($start === false) break;
private function insert_code(string $text): string
{
$l1 = strlen("[code!]");
$l2 = strlen("[/code!]");
while (true) {
$start = strpos($text, "[code!]");
if ($start === false) {
break;
}
$end = strpos($text, "[/code!]");
if($end === false) break;
$end = strpos($text, "[/code!]");
if ($end === false) {
break;
}
$beginning = substr($text, 0, $start);
$middle = base64_decode(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$beginning = substr($text, 0, $start);
$middle = base64_decode(substr($text, $start+$l1, ($end-$start-$l1)));
$ending = substr($text, $end + $l2, (strlen($text)-$end+$l2));
$text = $beginning . "<pre>" . $middle . "</pre>" . $ending;
}
return $text;
}
$text = $beginning . "<pre>" . $middle . "</pre>" . $ending;
}
return $text;
}
}

View file

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

View file

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

View file

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

View file

@ -1,46 +1,47 @@
<?php
class BlocksTheme extends Themelet {
public function display_blocks($blocks) {
global $page;
class BlocksTheme extends Themelet
{
public function display_blocks($blocks)
{
global $page;
$html = "<table class='form' style='width: 100%;'>";
foreach($blocks as $block) {
$html .= make_form(make_link("blocks/update"));
$html .= "<input type='hidden' name='id' value='".html_escape($block['id'])."'>";
$html .= "<tr>";
$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>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>Delete</th><td><input type='checkbox' name='delete'></td>";
$html .= "<td><input type='submit' value='Save'></td>";
$html .= "</tr>";
$html .= "<tr>";
$html .= "<td colspan='11'><textarea rows='5' name='content'>".html_escape($block['content'])."</textarea></td>";
$html .= "</tr>\n";
$html .= "<tr>";
$html .= "<td colspan='11'>&nbsp;</td>";
$html .= "</tr>\n";
$html .= "</form>\n";
}
$html .= make_form(make_link("blocks/add"));
$html .= "<tr>";
$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>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 .= "<td colspan='3'><input type='submit' value='Add'></td>";
$html .= "</tr>";
$html .= "<tr>";
$html .= "<td colspan='11'><textarea rows='5' name='content'></textarea></td>";
$html .= "</tr>\n";
$html .= "</form>";
$html .= "</table>";
$html = "<table class='form' style='width: 100%;'>";
foreach ($blocks as $block) {
$html .= make_form(make_link("blocks/update"));
$html .= "<input type='hidden' name='id' value='".html_escape($block['id'])."'>";
$html .= "<tr>";
$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>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>Delete</th><td><input type='checkbox' name='delete'></td>";
$html .= "<td><input type='submit' value='Save'></td>";
$html .= "</tr>";
$html .= "<tr>";
$html .= "<td colspan='11'><textarea rows='5' name='content'>".html_escape($block['content'])."</textarea></td>";
$html .= "</tr>\n";
$html .= "<tr>";
$html .= "<td colspan='11'>&nbsp;</td>";
$html .= "</tr>\n";
$html .= "</form>\n";
}
$html .= make_form(make_link("blocks/add"));
$html .= "<tr>";
$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>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 .= "<td colspan='3'><input type='submit' value='Add'></td>";
$html .= "</tr>";
$html .= "<tr>";
$html .= "<td colspan='11'><textarea rows='5' name='content'></textarea></td>";
$html .= "</tr>\n";
$html .= "</form>";
$html .= "</table>";
$page->set_title("Blocks");
$page->set_heading("Blocks");
$page->add_block(new NavBlock());
$page->add_block(new Block("Block Editor", $html));
}
$page->set_title("Blocks");
$page->set_heading("Blocks");
$page->add_block(new NavBlock());
$page->add_block(new Block("Block Editor", $html));
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,9 @@
<?php
class BrowserSearchTest extends ShimmiePHPUnitTestCase {
public function testBasic() {
$this->get_page("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml");
$this->get_page("browser_search/test");
}
class BrowserSearchTest extends ShimmiePHPUnitTestCase
{
public function testBasic()
{
$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
*/
class BulkAddEvent extends Event {
public $dir, $results;
class BulkAddEvent extends Event
{
public $dir;
public $results;
public function __construct(string $dir) {
$this->dir = $dir;
$this->results = array();
}
public function __construct(string $dir)
{
$this->dir = $dir;
$this->results = [];
}
}
class BulkAdd extends Extension {
public function onPageRequest(PageRequestEvent $event) {
global $page, $user;
if($event->page_matches("bulk_add")) {
if($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) {
set_time_limit(0);
$bae = new BulkAddEvent($_POST['dir']);
send_event($bae);
if(is_array($bae->results)) {
foreach($bae->results as $result) {
$this->theme->add_status("Adding files", $result);
}
} else if(strlen($bae->results) > 0) {
$this->theme->add_status("Adding files", $bae->results);
}
$this->theme->display_upload_results($page);
}
}
}
class BulkAdd extends Extension
{
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
if ($event->page_matches("bulk_add")) {
if ($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) {
set_time_limit(0);
$bae = new BulkAddEvent($_POST['dir']);
send_event($bae);
if (is_array($bae->results)) {
foreach ($bae->results as $result) {
$this->theme->add_status("Adding files", $result);
}
} elseif (strlen($bae->results) > 0) {
$this->theme->add_status("Adding files", $bae->results);
}
$this->theme->display_upload_results($page);
}
}
}
public function onCommand(CommandEvent $event) {
if($event->cmd == "help") {
print "\tbulk-add [directory]\n";
print "\t\tImport this directory\n\n";
}
if($event->cmd == "bulk-add") {
if(count($event->args) == 1) {
$bae = new BulkAddEvent($event->args[0]);
send_event($bae);
print(implode("\n", $bae->results));
}
}
}
public function onCommand(CommandEvent $event)
{
if ($event->cmd == "help") {
print "\tbulk-add [directory]\n";
print "\t\tImport this directory\n\n";
}
if ($event->cmd == "bulk-add") {
if (count($event->args) == 1) {
$bae = new BulkAddEvent($event->args[0]);
send_event($bae);
print(implode("\n", $bae->results));
}
}
}
public function onAdminBuilding(AdminBuildingEvent $event) {
$this->theme->display_admin_block();
}
public function onAdminBuilding(AdminBuildingEvent $event)
{
$this->theme->display_admin_block();
}
public function onBulkAdd(BulkAddEvent $event) {
if(is_dir($event->dir) && is_readable($event->dir)) {
$event->results = add_dir($event->dir);
}
else {
$h_dir = html_escape($event->dir);
$event->results[] = "Error, $h_dir is not a readable directory";
}
}
public function onBulkAdd(BulkAddEvent $event)
{
if (is_dir($event->dir) && is_readable($event->dir)) {
$event->results = add_dir($event->dir);
} else {
$h_dir = html_escape($event->dir);
$event->results[] = "Error, $h_dir is not a readable directory";
}
}
}

View file

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

View file

@ -1,30 +1,33 @@
<?php
class BulkAddTheme extends Themelet {
private $messages = array();
class BulkAddTheme extends Themelet
{
private $messages = [];
/*
* Show a standard page for results to be put into
*/
public function display_upload_results(Page $page) {
$page->set_title("Adding folder");
$page->set_heading("Adding folder");
$page->add_block(new NavBlock());
$html = "";
foreach($this->messages as $block) {
$html .= "<br/>" . $block->body;
}
$page->add_block(new Block("Results", $html));
}
/*
* Show a standard page for results to be put into
*/
public function display_upload_results(Page $page)
{
$page->set_title("Adding folder");
$page->set_heading("Adding folder");
$page->add_block(new NavBlock());
$html = "";
foreach ($this->messages as $block) {
$html .= "<br/>" . $block->body;
}
$page->add_block(new Block("Results", $html));
}
/*
* 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
* directory full of images
*/
public function display_admin_block() {
global $page;
$html = "
/*
* 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
* directory full of images
*/
public function display_admin_block()
{
global $page;
$html = "
Add a folder full of images; any subfolders will have their names
used as tags for the images within.
<br>Note: this is the folder as seen by the server -- you need to
@ -37,10 +40,11 @@ class BulkAddTheme extends Themelet {
</table>
</form>
";
$page->add_block(new Block("Bulk Add", $html));
}
$page->add_block(new Block("Bulk Add", $html));
}
public function add_status($title, $body) {
$this->messages[] = new Block($title, $body);
}
public function add_status($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>
* 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>
*
*
*/
class BulkAddCSV extends Extension {
public function onPageRequest(PageRequestEvent $event) {
global $page, $user;
if($event->page_matches("bulk_add_csv")) {
if($user->is_admin() && $user->check_auth_token() && isset($_POST['csv'])) {
set_time_limit(0);
$this->add_csv($_POST['csv']);
$this->theme->display_upload_results($page);
}
}
}
class BulkAddCSV extends Extension
{
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
if ($event->page_matches("bulk_add_csv")) {
if ($user->is_admin() && $user->check_auth_token() && isset($_POST['csv'])) {
set_time_limit(0);
$this->add_csv($_POST['csv']);
$this->theme->display_upload_results($page);
}
}
}
public function onCommand(CommandEvent $event) {
if($event->cmd == "help") {
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;
//Nag until CLI is admin by default
if (!$user->is_admin()) {
print "Not running as an admin, which can cause problems.\n";
print "Please add the parameter: -u admin_username";
} elseif(count($event->args) == 1) {
$this->add_csv($event->args[0]);
}
}
}
public function onCommand(CommandEvent $event)
{
if ($event->cmd == "help") {
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;
//Nag until CLI is admin by default
if (!$user->is_admin()) {
print "Not running as an admin, which can cause problems.\n";
print "Please add the parameter: -u admin_username";
} elseif (count($event->args) == 1) {
$this->add_csv($event->args[0]);
}
}
}
public function onAdminBuilding(AdminBuildingEvent $event) {
$this->theme->display_admin_block();
}
public function onAdminBuilding(AdminBuildingEvent $event)
{
$this->theme->display_admin_block();
}
/**
* 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) {
assert(file_exists($tmpname));
/**
* 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)
{
assert(file_exists($tmpname));
$pathinfo = pathinfo($filename);
if(!array_key_exists('extension', $pathinfo)) {
throw new UploadException("File has no extension");
}
$metadata = array();
$metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = Tag::explode($tags);
$metadata['source'] = $source;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
if($event->image_id == -1) {
throw new UploadException("File type not recognised");
} else {
if(class_exists("RatingSetEvent") && in_array($rating, array("s", "q", "e"))) {
$ratingevent = new RatingSetEvent(Image::by_id($event->image_id), $rating);
send_event($ratingevent);
}
if (file_exists($thumbfile)) {
copy($thumbfile, warehouse_path("thumbs", $event->hash));
}
}
}
$pathinfo = pathinfo($filename);
if (!array_key_exists('extension', $pathinfo)) {
throw new UploadException("File has no extension");
}
$metadata = [];
$metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = Tag::explode($tags);
$metadata['source'] = $source;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
if ($event->image_id == -1) {
throw new UploadException("File type not recognised");
} else {
if (class_exists("RatingSetEvent") && in_array($rating, ["s", "q", "e"])) {
$ratingevent = new RatingSetEvent(Image::by_id($event->image_id), $rating);
send_event($ratingevent);
}
if (file_exists($thumbfile)) {
copy($thumbfile, warehouse_path("thumbs", $event->hash));
}
}
}
private function add_csv(string $csvfile) {
if(!file_exists($csvfile)) {
$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");
return;
}
$linenum = 1;
$list = "";
$csvhandle = fopen($csvfile, "r");
while (($csvdata = fgetcsv($csvhandle, 0, ",")) !== FALSE) {
if(count($csvdata) != 5) {
if(strlen($list) > 0) {
$this->theme->add_status("Error", "<b>Encountered malformed data. Line $linenum $csvfile</b><br>".$list);
fclose($csvhandle);
return;
} else {
$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");
fclose($csvhandle);
return;
}
}
$fullpath = $csvdata[0];
$tags = trim($csvdata[1]);
$source = $csvdata[2];
$rating = $csvdata[3];
$thumbfile = $csvdata[4];
$pathinfo = pathinfo($fullpath);
$shortpath = $pathinfo["basename"];
$list .= "<br>".html_escape("$shortpath (".str_replace(" ", ", ", $tags).")... ");
if (file_exists($csvdata[0]) && is_file($csvdata[0])) {
try{
$this->add_image($fullpath, $pathinfo["basename"], $tags, $source, $rating, $thumbfile);
$list .= "ok\n";
}
catch(Exception $ex) {
$list .= "failed:<br>". $ex->getMessage();
}
} else {
$list .= "failed:<br> File doesn't exist ".html_escape($csvdata[0]);
}
$linenum += 1;
}
if(strlen($list) > 0) {
$this->theme->add_status("Adding $csvfile", $list);
}
fclose($csvhandle);
}
private function add_csv(string $csvfile)
{
if (!file_exists($csvfile)) {
$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");
return;
}
$linenum = 1;
$list = "";
$csvhandle = fopen($csvfile, "r");
while (($csvdata = fgetcsv($csvhandle, 0, ",")) !== false) {
if (count($csvdata) != 5) {
if (strlen($list) > 0) {
$this->theme->add_status("Error", "<b>Encountered malformed data. Line $linenum $csvfile</b><br>".$list);
fclose($csvhandle);
return;
} else {
$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");
fclose($csvhandle);
return;
}
}
$fullpath = $csvdata[0];
$tags = trim($csvdata[1]);
$source = $csvdata[2];
$rating = $csvdata[3];
$thumbfile = $csvdata[4];
$pathinfo = pathinfo($fullpath);
$shortpath = $pathinfo["basename"];
$list .= "<br>".html_escape("$shortpath (".str_replace(" ", ", ", $tags).")... ");
if (file_exists($csvdata[0]) && is_file($csvdata[0])) {
try {
$this->add_image($fullpath, $pathinfo["basename"], $tags, $source, $rating, $thumbfile);
$list .= "ok\n";
} catch (Exception $ex) {
$list .= "failed:<br>". $ex->getMessage();
}
} else {
$list .= "failed:<br> File doesn't exist ".html_escape($csvdata[0]);
}
$linenum += 1;
}
if (strlen($list) > 0) {
$this->theme->add_status("Adding $csvfile", $list);
}
fclose($csvhandle);
}
}

View file

@ -1,28 +1,31 @@
<?php
class BulkAddCSVTheme extends Themelet {
private $messages = array();
class BulkAddCSVTheme extends Themelet
{
private $messages = [];
/*
* Show a standard page for results to be put into
*/
public function display_upload_results(Page $page) {
$page->set_title("Adding images from csv");
$page->set_heading("Adding images from csv");
$page->add_block(new NavBlock());
foreach($this->messages as $block) {
$page->add_block($block);
}
}
/*
* Show a standard page for results to be put into
*/
public function display_upload_results(Page $page)
{
$page->set_title("Adding images from csv");
$page->set_heading("Adding images from csv");
$page->add_block(new NavBlock());
foreach ($this->messages as $block) {
$page->add_block($block);
}
}
/*
* 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
* csv file
*/
public function display_admin_block() {
global $page;
$html = "
/*
* 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
* csv file
*/
public function display_admin_block()
{
global $page;
$html = "
Add images from a csv. Images will be tagged and have their
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.
@ -34,11 +37,11 @@ class BulkAddCSVTheme extends Themelet {
</table>
</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) {
$this->messages[] = new Block($title, $body);
}
public function add_status($title, $body)
{
$this->messages[] = new Block($title, $body);
}
}

View file

@ -6,22 +6,28 @@
* License: GPLv2
* Description: Allows admin to delete many images at once through Board Admin.
* 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
class BulkRemove extends Extension {
public function onPageRequest(PageRequestEvent $event) {
global $user;
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();
}
}
class BulkRemove extends Extension
{
public function onPageRequest(PageRequestEvent $event)
{
global $user;
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) {
global $page;
$html = "<b>Be extremely careful when using this!</b><br>
public function onAdminBuilding(AdminBuildingEvent $event)
{
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
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.
@ -40,94 +46,95 @@ class BulkRemove extends Extension {
</table>
</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
private function determine_images()
{
// set vars
$images_for_removal = array();
$error = "";
// returns a list of images to be removed
private function determine_images()
{
// set vars
$images_for_removal = [];
$error = "";
$min_id = $_POST['remove_id_min'];
$max_id = $_POST['remove_id_max'];
$tags = $_POST['remove_tags'];
$min_id = $_POST['remove_id_min'];
$max_id = $_POST['remove_id_max'];
$tags = $_POST['remove_tags'];
// if using id range to remove (comined removal with tags)
if ($min_id != "" && $max_id != "")
{
// error if values are not correctly entered
if (!is_numeric($min_id) || !is_numeric($max_id) ||
intval($max_id) < intval($min_id))
$error = "Values not correctly entered for removal between id.";
else { // if min & max id are valid
// if using id range to remove (comined removal with tags)
if ($min_id != "" && $max_id != "") {
// error if values are not correctly entered
if (!is_numeric($min_id) || !is_numeric($max_id) ||
intval($max_id) < intval($min_id)) {
$error = "Values not correctly entered for removal between id.";
} else { // if min & max id are valid
// Grab the list of images & place it in the removing array
foreach (Image::find_images(intval($min_id), intval($max_id)) as $image)
// Grab the list of images & place it in the removing array
foreach (Image::find_images(intval($min_id), intval($max_id)) as $image) {
array_push($images_for_removal, $image);
}
}
}
}
// refine previous results or create results from tags
if ($tags != "")
{
$tags_arr = explode(" ", $_POST['remove_tags']);
// refine previous results or create results from tags
if ($tags != "") {
$tags_arr = explode(" ", $_POST['remove_tags']);
// Search all images with the specified tags & add to list
foreach (Image::find_images(1, 2147483647, $tags_arr) as $image)
array_push($images_for_removal, $image);
// Search all images with the specified tags & add to list
foreach (Image::find_images(1, 2147483647, $tags_arr) as $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
private function show_confirm()
{
global $page;
// displays confirmation to admin before removal
private function show_confirm()
{
global $page;
// set vars
$determined_imgs = $this->determine_images();
$error = $determined_imgs["error"];
$images_for_removal = $determined_imgs["images_for_removal"];
// set vars
$determined_imgs = $this->determine_images();
$error = $determined_imgs["error"];
$images_for_removal = $determined_imgs["images_for_removal"];
// if there was an error in determine_images()
if ($error != "") {
$page->add_block(new Block("Cannot remove images", $error));
return;
}
// generates the image array & places it in $_POST["bulk_remove_images"]
$_POST["bulk_remove_images"] = $images_for_removal;
// if there was an error in determine_images()
if ($error != "") {
$page->add_block(new Block("Cannot remove images", $error));
return;
}
// generates the image array & places it in $_POST["bulk_remove_images"]
$_POST["bulk_remove_images"] = $images_for_removal;
// Display confirmation message
$html = make_form(make_link("bulk_remove")).
"Are you sure you want to PERMANENTLY remove ".
// Display confirmation message
$html = make_form(make_link("bulk_remove")).
"Are you sure you want to PERMANENTLY remove ".
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()
{
global $page;
// display error if user didn't go through admin board
if (!isset($_POST["bulk_remove_images"])) {
$page->add_block(new Block("Bulk Remove Error",
"Please use Board Admin to use bulk remove."));
}
//
$image_arr = $_POST["bulk_remove_images"];
private function do_bulk_remove()
{
global $page;
// display error if user didn't go through admin board
if (!isset($_POST["bulk_remove_images"])) {
$page->add_block(new Block(
"Bulk Remove Error",
"Please use Board Admin to use bulk remove."
));
}
//
$image_arr = $_POST["bulk_remove_images"];
}
}

View file

@ -8,185 +8,211 @@ include '../php/functions.php';
include '../php/yshout.class.php';
include '../php/ajaxcall.class.php';
if (isset($_POST['mode']))
switch($_POST['mode']) {
case 'login':
doLogin();
break;
case 'logout':
doLogout();
break;
case 'unban':
doUnban();
break;
case 'unbanall':
doUnbanAll();
break;
case 'setpreference':
doSetPreference();
break;
case 'resetpreferences':
doResetPreferences();
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);
if (isset($_POST['mode'])) {
switch ($_POST['mode']) {
case 'login':
doLogin();
break;
case 'logout':
doLogout();
break;
case 'unban':
doUnban();
break;
case 'unbanall':
doUnbanAll();
break;
case 'setpreference':
doSetPreference();
break;
case 'resetpreferences':
doResetPreferences();
break;
}
}
function doLogout() {
logout();
function doLogin()
{
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(
'error' => false
);
echo json_encode($result);
echo json_encode($result);
}
function doUnban() {
global $kioskMode;
if ($kioskMode) {
$result = array(
'error' => false
);
echo json_encode($result);
return;
}
if (!loggedIn()) return;
function doLogout()
{
logout();
$ys = ys();
$result = array();
$result = [
'error' => false
];
$ip = $_POST['ip'];
if ($ys->banned($ip)) {
$ys->unban($ip);
$result['error'] = false;
} else
$result['error'] = 'notbanned';
echo json_encode($result);
echo json_encode($result);
}
function doUnbanAll() {
global $kioskMode;
if ($kioskMode) {
$result = array(
'error' => false
);
echo json_encode($result);
return;
}
if (!loggedIn()) return;
function doUnban()
{
global $kioskMode;
if ($kioskMode) {
$result = [
'error' => false
];
echo json_encode($result);
return;
}
if (!loggedIn()) {
return;
}
$ys = ys();
$ys->unbanAll();
$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()
{
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() {
global $prefs, $kioskMode;
if ($kioskMode) {
$result = array(
'error' => false
);
echo json_encode($result);
return;
}
if (!loggedIn()) return;
function doSetPreference()
{
global $prefs, $kioskMode;
if ($kioskMode) {
$result = [
'error' => false
];
echo json_encode($result);
return;
}
if (!loggedIn()) {
return;
}
$pref = $_POST['preference'];
$value = magic($_POST['value']);
$pref = $_POST['preference'];
$value = magic($_POST['value']);
if ($value === 'true') $value = true;
if ($value === 'false') $value = false;
if ($value === 'true') {
$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(
'error' => false
);
$result = [
'error' => false
];
echo json_encode($result);
echo json_encode($result);
}
function doResetPreferences() {
global $prefs, $kioskMode;
if ($kioskMode) {
$result = array(
'error' => false
);
echo json_encode($result);
return;
}
if (!loggedIn()) return;
function doResetPreferences()
{
global $prefs, $kioskMode;
if ($kioskMode) {
$result = [
'error' => false
];
echo json_encode($result);
return;
}
if (!loggedIn()) {
return;
}
resetPrefs();
login(md5($prefs['password']));
resetPrefs();
login(md5($prefs['password']));
// $prefs['password'] = 'lol no';
$result = array(
'error' => false,
'prefs' => $prefs
);
// $prefs['password'] = 'lol no';
$result = [
'error' => false,
'prefs' => $prefs
];
echo json_encode($result);
echo json_encode($result);
}
/* CP Display */
function cp() {
global $kioskMode;
if (!loggedIn() && !$kioskMode) return 'You\'re not logged in!';
function cp()
{
global $kioskMode;
if (!loggedIn() && !$kioskMode) {
return 'You\'re not logged in!';
}
return '
return '
<div class="section" id="preferences">
<span style="display: none;" id="cp-loaded">true</span>
@ -236,38 +262,41 @@ function cp() {
</div>';
}
function bansList() {
global $kioskMode;
$ys = ys();
$bans = $ys->bans();
function bansList()
{
global $kioskMode;
$ys = ys();
$bans = $ys->bans();
$html = '<ul id="bans-list">';
$html = '<ul id="bans-list">';
$hasBans = false;
foreach($bans as $ban) {
$hasBans = true;
$html .= '
$hasBans = false;
foreach ($bans as $ban) {
$hasBans = true;
$html .= '
<li>
<span class="nickname">' . $ban['nickname']. '</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>
</li>
';
}
if (!$hasBans)
$html = '<p id="no-bans">No one is banned.</p>';
else
$html .= '</ul>';
}
if (!$hasBans) {
$html = '<p id="no-bans">No one is banned.</p>';
} else {
$html .= '</ul>';
}
return $html;
return $html;
}
function preferencesForm() {
global $prefs, $kioskMode;
function preferencesForm()
{
global $prefs, $kioskMode;
return '
return '
<form id="preferences-form">
<div id="cp-pane-administration" class="cp-pane">
<fieldset id="prefs-cat-cp">
@ -434,10 +463,11 @@ function preferencesForm() {
';
}
function about() {
global $prefs;
function about()
{
global $prefs;
$html = '
$html = '
<div id="cp-pane-about" class="cp-pane">
<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>
@ -451,7 +481,6 @@ function about() {
</div>
';
return $html;
return $html;
}

View file

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

View file

@ -1,92 +1,90 @@
<?php
error_reporting(E_ALL);
error_reporting(E_ALL);
include '../php/filestorage.class.php';
include '../preferences.php';
include '../php/functions.php';
include '../php/yshout.class.php';
include '../php/filestorage.class.php';
include '../preferences.php';
include '../php/functions.php';
include '../php/yshout.class.php';
$html = '<div id="history-posts">';
$html = '<div id="history-posts">';
$admin = loggedIn();
$log = 1;
if (isset($_GET['log']))
{
$log = $_GET['log'];
}
if (isset($_POST['log']))
{
$log = $_POST['log'];
}
$admin = loggedIn();
$log = 1;
if (isset($_GET['log'])) {
$log = $_GET['log'];
}
if (isset($_POST['log'])) {
$log = $_POST['log'];
}
if (filter_var($log, FILTER_VALIDATE_INT) === false)
{
$log = 1;
}
$ys = ys($log);
$posts = $ys->posts();
if (filter_var($log, FILTER_VALIDATE_INT) === false) {
$log = 1;
}
$ys = ys($log);
$posts = $ys->posts();
if (sizeof($posts) === 0)
$html .= '
if (sizeof($posts) === 0) {
$html .= '
<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-nickname">Yurivish:<span>
<span class="ys-post-message">Hey, there aren\'t any posts in this log.</span>
</div>
';
}
$id = 0;
$id = 0;
foreach($posts as $post) {
$id++;
foreach ($posts as $post) {
$id++;
$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";
$ts = '';
switch($prefs['timestamp']) {
case 12:
$ts = date('h:i', $post['timestamp']);
break;
case 24:
$ts = date('H:i', $post['timestamp']);
break;
case 0:
$ts = '';
break;
}
$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";
$ts = '';
switch ($prefs['timestamp']) {
case 12:
$ts = date('h:i', $post['timestamp']);
break;
case 24:
$ts = date('H:i', $post['timestamp']);
break;
case 0:
$ts = '';
break;
}
$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-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-timestamp">' . $ts . '</span> ' . "\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-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 .= ' <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 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 .= ' </span>' . "\n";
if ($admin) {
$html .= '<div class="ys-history" style="display: none;">';
$html .= ' <span class="ys-h-ip">' . $post['adminInfo']['ip'] . '</span>';
$html .= ' <span class="ys-h-nickname">' . $post['nickname'] . '</span>';
$html .= ' <span class="ys-h-uid">' . $post['uid'] . '</span>';
$html .= '</div>';
}
if ($admin) {
$html .= '<div class="ys-history" style="display: none;">';
$html .= ' <span class="ys-h-ip">' . $post['adminInfo']['ip'] . '</span>';
$html .= ' <span class="ys-h-nickname">' . $post['nickname'] . '</span>';
$html .= ' <span class="ys-h-uid">' . $post['uid'] . '</span>';
$html .= '</div>';
}
$html .= '</div>' . "\n";
}
$html .= '</div>' . "\n";
}
$html .= '</div>' . "\n";
$html .= '</div>' . "\n";
if (isset($_POST['p'])) {
echo $html;
exit;
echo $html;
exit;
}
?>
@ -115,16 +113,17 @@ if (isset($_POST['p'])) {
<div id="top">
<h1>YShout.History</h1>
<div id="controls">
<?php if($admin) : ?>
<?php if ($admin) : ?>
<a id="clear-log" href="#">Clear this log</a>, or
<a id="clear-logs" href="#">Clear all logs</a>.
<?php endif; ?>
<select id="log">
<?php
for ($i = 1; $i <= $prefs['logs']; $i++)
echo '<option' . ($log == $i ? ' selected' : '') . ' rel="' . $i . '">Log ' . $i . '</option>' . "\n";
?>
for ($i = 1; $i <= $prefs['logs']; $i++) {
echo '<option' . ($log == $i ? ' selected' : '') . ' rel="' . $i . '">Log ' . $i . '</option>' . "\n";
}
?>
</select>
</div>
</div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,251 +1,292 @@
<?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) {
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.');
$this->storage = new $storage($path, true);
$this->admin = $admin;
}
$this->storage = new $storage($path, true);
$this->admin = $admin;
}
public function posts()
{
global $null;
$this->storage->open();
$s = $this->storage->load();
$this->storage->close($null);
function posts() {
global $null;
$this->storage->open();
$s = $this->storage->load();
$this->storage->close($null);
if ($s) {
return $s['posts'];
}
}
if ($s)
return $s['posts'];
}
public function info()
{
global $null;
$s = $this->storage->open(true);
function info() {
global $null;
$s = $this->storage->open(true);
$this->storage->close($null);
$this->storage->close($null);
if ($s) {
return $s['info'];
}
}
if ($s)
return $s['info'];
}
public function postsAfter($ts)
{
$allPosts = $this->posts();
function postsAfter($ts) {
$allPosts = $this->posts();
$posts = [];
$posts = array();
/* for ($i = sizeof($allPosts) - 1; $i > -1; $i--) {
$post = $allPosts[$i];
/* for ($i = sizeof($allPosts) - 1; $i > -1; $i--) {
$post = $allPosts[$i];
if ($post['timestamp'] > $ts)
$posts[] = $post;
} */
if ($post['timestamp'] > $ts)
$posts[] = $post;
} */
foreach ($allPosts as $post) {
if ($post['timestamp'] > $ts) {
$posts[] = $post;
}
}
foreach($allPosts as $post) {
if ($post['timestamp'] > $ts)
$posts[] = $post;
}
$this->postProcess($posts);
return $posts;
}
$this->postProcess($posts);
return $posts;
}
public function latestPosts($num)
{
$allPosts = $this->posts();
$posts = array_slice($allPosts, -$num, $num);
function latestPosts($num) {
$allPosts = $this->posts();
$posts = array_slice($allPosts, -$num, $num);
$this->postProcess($posts);
return array_values($posts);
}
$this->postProcess($posts);
return array_values($posts);
}
public function hasPostsAfter($ts)
{
$info = $this->info();
$timestamp = $info['latestTimestamp'];
return $timestamp > $ts;
}
function hasPostsAfter($ts) {
$info = $this->info();
$timestamp = $info['latestTimestamp'];
return $timestamp > $ts;
}
public function post($nickname, $message)
{
global $prefs;
function post($nickname, $message) {
global $prefs;
if ($this->banned(ip()) /* && !$this->admin*/) {
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;
if (!$this->validate($nickname, $prefs['nicknameLength'])) return false;
$message = trim(clean($message));
$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));
$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 = $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'])
$this->truncate($s['posts']);
$s['info']['latestTimestamp'] = $post['timestamp'];
$s['info']['latestTimestamp'] = $post['timestamp'];
$this->storage->close($s);
$this->postProcess($post);
return $post;
}
$this->storage->close($s);
$this->postProcess($post);
return $post;
}
public function truncate(&$array)
{
global $prefs;
function truncate(&$array) {
global $prefs;
$array = array_slice($array, -$prefs['history']);
$array = array_values($array);
}
$array = array_slice($array, -$prefs['history']);
$array = array_values($array);
}
public function clear()
{
global $null;
function clear() {
global $null;
$this->storage->open(true);
$this->storage->resetArray();
// ? Scared to touch it... Misspelled though. Update: Touched! Used to be $nulls...
$this->storage->close($null);
}
$this->storage->open(true);
$this->storage->resetArray();
// ? Scared to touch it... Misspelled though. Update: Touched! Used to be $nulls...
$this->storage->close($null);
}
public function bans()
{
global $storage, $null;
function bans() {
global $storage, $null;
$s = new $storage('yshout.bans');
$s->open();
$bans = $s->load();
$s->close($null);
$s = new $storage('yshout.bans');
$s->open();
$bans = $s->load();
$s->close($null);
return $bans;
}
return $bans;
}
public function ban($ip, $nickname = '', $info = '')
{
global $storage;
function ban($ip, $nickname = '', $info = '') {
global $storage;
$s = new $storage('yshout.bans');
$bans = $s->open(true);
$s = new $storage('yshout.bans');
$bans = $s->open(true);
$bans[] = [
'ip' => $ip,
'nickname' => $nickname,
'info' => $info,
'timestamp' => ts()
];
$bans[] = array(
'ip' => $ip,
'nickname' => $nickname,
'info' => $info,
'timestamp' => ts()
);
$s->close($bans);
}
$s->close($bans);
}
public function banned($ip)
{
global $storage, $null;
function banned($ip) {
global $storage, $null;
$s = new $storage('yshout.bans');
$bans = $s->open(true);
$s->close($null);
$s = new $storage('yshout.bans');
$bans = $s->open(true);
$s->close($null);
foreach ($bans as $ban) {
if ($ban['ip'] == $ip) {
return true;
}
}
foreach($bans as $ban) {
if ($ban['ip'] == $ip)
return true;
}
return false;
}
return false;
}
public function unban($ip)
{
global $storage;
function unban($ip) {
global $storage;
$s = new $storage('yshout.bans');
$bans = $s->open(true);
$s = new $storage('yshout.bans');
$bans = $s->open(true);
foreach ($bans as $key=>$value) {
if ($value['ip'] == $ip) {
unset($bans[$key]);
}
}
foreach($bans as $key=>$value)
if ($value['ip'] == $ip) {
unset($bans[$key]);
}
$bans = array_values($bans);
$s->close($bans);
}
$bans = array_values($bans);
$s->close($bans);
public function unbanAll()
{
global $storage, $null;
}
$s = new $storage('yshout.bans');
$s->open(true);
$s->resetArray();
$s->close($null);
}
function unbanAll() {
global $storage, $null;
public function delete($uid)
{
global $prefs, $storage;
$s = new $storage('yshout.bans');
$s->open(true);
$s->resetArray();
$s->close($null);
}
$s = $this->storage->open(true);
function delete($uid) {
global $prefs, $storage;
$posts = $s['posts'];
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);
$s = $this->storage->open(true);
return 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'];
foreach($posts as $key=>$value) {
if (!isset($value['uid']))
unset($posts['key']);
else
if($value['uid'] == $uid)
unset($posts[$key]);
}
$s['posts'] = array_values($posts);
$this->storage->close($s);
return true;
}
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']);
}
}
}
public 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
// 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,
// or leave it null to use the set nicknames.
$overrideNickname = null;
$storage = 'FileStorage';
function loadPrefs() {
global $prefs, $storage, $null;
$s = new $storage('yshout.prefs');
$s->open();
$prefs = $s->load();
$s->close($null);
}
// 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,
// or leave it null to use the set nicknames.
$overrideNickname = null;
$storage = 'FileStorage';
function loadPrefs()
{
global $prefs, $storage, $null;
$s = new $storage('yshout.prefs');
$s->open();
$prefs = $s->load();
$s->close($null);
}
function savePrefs($newPrefs) {
global $prefs, $storage;
function savePrefs($newPrefs)
{
global $prefs, $storage;
$s = new $storage('yshout.prefs');
$s->open(true);
$s->close($newPrefs);
$prefs = $newPrefs;
}
function resetPrefs() {
$defaultPrefs = array(
'password' => 'fortytwo', // The password for the CP
$s = new $storage('yshout.prefs');
$s->open(true);
$s->close($newPrefs);
$prefs = $newPrefs;
}
function resetPrefs()
{
$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
'history' => 200, // Shouts to keep in history
'logs' => 5, // Amount of different log files to allow
'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
'doTruncate' => true, // Truncate messages?
'truncate' => 15, // Truncate messages client-side
'doTruncate' => true, // Truncate messages?
'timestamp' => 12, // Timestamp format 12- or 24-hour
'timestamp' => 12, // Timestamp format 12- or 24-hour
'defaultNickname' => 'Nickname',
'defaultMessage' => 'Message Text',
'defaultSubmit' => 'Shout!',
'showSubmit' => true,
'nicknameLength' => 25,
'messageLength' => 175,
'defaultNickname' => 'Nickname',
'defaultMessage' => 'Message Text',
'defaultSubmit' => 'Shout!',
'showSubmit' => true,
'nicknameLength' => 25,
'messageLength' => 175,
'nicknameSeparator' => ':',
'flood' => true,
'floodTimeout' => 5000,
'floodMessages' => 4,
'floodDisable' => 8000,
'floodDelete' => false,
'autobanFlood' => 0, // Autoban people for flooding after X messages
'nicknameSeparator' => ':',
'flood' => true,
'floodTimeout' => 5000,
'floodMessages' => 4,
'floodDisable' => 8000,
'floodDelete' => false,
'autobanFlood' => 0, // Autoban people for flooding after X messages
'censorWords' => 'fuck shit bitch ass',
'postFormLink' => 'history',
'info' => 'inline'
);
savePrefs($defaultPrefs);
}
resetPrefs();
//loadPrefs();
'censorWords' => 'fuck shit bitch ass',
'postFormLink' => 'history',
'info' => 'inline'
];
savePrefs($defaultPrefs);
}
resetPrefs();
//loadPrefs();

View file

@ -5,34 +5,35 @@ ob_start();
set_error_handler('errorOccurred');
include 'include.php';
if (isset($_POST['reqFor']))
switch($_POST['reqFor']) {
case 'shout':
if (isset($_POST['reqFor'])) {
switch ($_POST['reqFor']) {
case 'shout':
$ajax = new AjaxCall();
$ajax->process();
break;
$ajax = new AjaxCall();
$ajax->process();
break;
case 'history':
// echo $_POST['log'];
$ajax = new AjaxCall($_POST['log']);
$ajax->process();
break;
case 'history':
// echo $_POST['log'];
$ajax = new AjaxCall($_POST['log']);
$ajax->process();
break;
default:
exit;
} 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;
default:
exit;
}
} else {
include 'example.html';
}
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
class CommentListTest extends ShimmiePHPUnitTestCase {
public function setUp() {
global $config;
parent::setUp();
$config->set_int("comment_limit", 100);
$this->log_out();
}
class CommentListTest extends ShimmiePHPUnitTestCase
{
public function setUp()
{
global $config;
parent::setUp();
$config->set_int("comment_limit", 100);
$this->log_out();
}
public function tearDown() {
global $config;
$config->set_int("comment_limit", 10);
parent::tearDown();
}
public function tearDown()
{
global $config;
$config->set_int("comment_limit", 10);
parent::tearDown();
}
public function testCommentsPage() {
global $user;
public function testCommentsPage()
{
global $user;
$this->log_in_as_user();
$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");
# a good comment
send_event(new CommentPostingEvent($image_id, $user, "Test Comment ASDFASDF"));
$this->get_page("post/view/$image_id");
$this->assert_text("ASDFASDF");
# a good comment
send_event(new CommentPostingEvent($image_id, $user, "Test Comment ASDFASDF"));
$this->get_page("post/view/$image_id");
$this->assert_text("ASDFASDF");
# dupe
try {
send_event(new CommentPostingEvent($image_id, $user, "Test Comment ASDFASDF"));
}
catch(CommentPostingException $e) {
$this->assertContains("try and be more original", $e->getMessage());
}
# dupe
try {
send_event(new CommentPostingEvent($image_id, $user, "Test Comment ASDFASDF"));
} catch (CommentPostingException $e) {
$this->assertContains("try and be more original", $e->getMessage());
}
# empty comment
try {
send_event(new CommentPostingEvent($image_id, $user, ""));
}
catch(CommentPostingException $e) {
$this->assertContains("Comments need text", $e->getMessage());
}
# empty comment
try {
send_event(new CommentPostingEvent($image_id, $user, ""));
} catch (CommentPostingException $e) {
$this->assertContains("Comments need text", $e->getMessage());
}
# whitespace is still empty...
try {
send_event(new CommentPostingEvent($image_id, $user, " \t\r\n"));
}
catch(CommentPostingException $e) {
$this->assertContains("Comments need text", $e->getMessage());
}
# whitespace is still empty...
try {
send_event(new CommentPostingEvent($image_id, $user, " \t\r\n"));
} catch (CommentPostingException $e) {
$this->assertContains("Comments need text", $e->getMessage());
}
# repetitive (aka. gzip gives >= 10x improvement)
try {
send_event(new CommentPostingEvent($image_id, $user, str_repeat("U", 5000)));
}
catch(CommentPostingException $e) {
$this->assertContains("Comment too repetitive", $e->getMessage());
}
# repetitive (aka. gzip gives >= 10x improvement)
try {
send_event(new CommentPostingEvent($image_id, $user, str_repeat("U", 5000)));
} catch (CommentPostingException $e) {
$this->assertContains("Comment too repetitive", $e->getMessage());
}
# test UTF8
send_event(new CommentPostingEvent($image_id, $user, "Test Comment むちむち"));
$this->get_page("post/view/$image_id");
$this->assert_text("むちむち");
# test UTF8
send_event(new CommentPostingEvent($image_id, $user, "Test Comment むちむち"));
$this->get_page("post/view/$image_id");
$this->assert_text("むちむち");
# test that search by comment metadata works
// $this->get_page("post/list/commented_by=test/1");
// $this->assert_title("Image $image_id: pbx");
// $this->get_page("post/list/comments=2/1");
// $this->assert_title("Image $image_id: pbx");
# test that search by comment metadata works
// $this->get_page("post/list/commented_by=test/1");
// $this->assert_title("Image $image_id: pbx");
// $this->get_page("post/list/comments=2/1");
// $this->assert_title("Image $image_id: pbx");
$this->log_out();
$this->log_out();
$this->get_page('comment/list');
$this->assert_title('Comments');
$this->assert_text('ASDFASDF');
$this->get_page('comment/list');
$this->assert_title('Comments');
$this->assert_text('ASDFASDF');
$this->get_page('comment/list/2');
$this->assert_title('Comments');
$this->get_page('comment/list/2');
$this->assert_title('Comments');
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
$this->get_page('comment/list');
$this->assert_title('Comments');
$this->assert_no_text('ASDFASDF');
}
$this->get_page('comment/list');
$this->assert_title('Comments');
$this->assert_no_text('ASDFASDF');
}
public function testSingleDel() {
$this->markTestIncomplete();
public function testSingleDel()
{
$this->markTestIncomplete();
$this->log_in_as_admin();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
$this->log_in_as_admin();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
# make a comment
$this->get_page("post/view/$image_id");
$this->set_field('comment', "Test Comment ASDFASDF");
$this->click("Post Comment");
$this->assert_title("Image $image_id: pbx");
$this->assert_text("ASDFASDF");
# make a comment
$this->get_page("post/view/$image_id");
$this->set_field('comment', "Test Comment ASDFASDF");
$this->click("Post Comment");
$this->assert_title("Image $image_id: pbx");
$this->assert_text("ASDFASDF");
# delete it
$this->click("Del");
$this->assert_title("Image $image_id: pbx");
$this->assert_no_text("ASDFASDF");
# delete it
$this->click("Del");
$this->assert_title("Image $image_id: pbx");
$this->assert_no_text("ASDFASDF");
# tidy up
$this->delete_image($image_id);
$this->log_out();
}
# tidy up
$this->delete_image($image_id);
$this->log_out();
}
}

View file

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

View file

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

View file

@ -8,65 +8,75 @@
* Documentation:
* 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;
*
*
* 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.
*/
class custom_html_headers extends Extension {
class custom_html_headers extends Extension
{
# Adds setup block for custom <head> content
public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Custom HTML Headers");
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = new SetupBlock("Custom HTML Headers");
// custom headers
$sb->add_longtext_option("custom_html_headers",
"HTML Code to place within &lt;head&gt;&lt;/head&gt; on all pages<br>");
// custom headers
$sb->add_longtext_option(
"custom_html_headers",
"HTML Code to place within &lt;head&gt;&lt;/head&gt; on all pages<br>"
);
// modified title
$sb->add_choice_option("sitename_in_title", array(
"none" => 0,
"as prefix" => 1,
"as suffix" => 2
), "<br>Add website name in title");
// modified title
$sb->add_choice_option("sitename_in_title", [
"none" => 0,
"as prefix" => 1,
"as suffix" => 2
], "<br>Add website name in title");
$event->panel->add_block($sb);
}
$event->panel->add_block($sb);
}
public function onInitExt(InitExtEvent $event) {
global $config;
$config->set_default_int("sitename_in_title", 0);
}
public function onInitExt(InitExtEvent $event)
{
global $config;
$config->set_default_int("sitename_in_title", 0);
}
# Load Analytics tracking code on page request
public function onPageRequest(PageRequestEvent $event) {
$this->handle_custom_html_headers();
$this->handle_modified_page_title();
}
# Load Analytics tracking code on page request
public function onPageRequest(PageRequestEvent $event)
{
$this->handle_custom_html_headers();
$this->handle_modified_page_title();
}
private function handle_custom_html_headers() {
global $config, $page;
private function handle_custom_html_headers()
{
global $config, $page;
$header = $config->get_string('custom_html_headers','');
if ($header!='') $page->add_html_header($header);
$header = $config->get_string('custom_html_headers', '');
if ($header!='') {
$page->add_html_header($header);
}
}
private function handle_modified_page_title() {
global $config, $page;
private function handle_modified_page_title()
{
global $config, $page;
// get config values
$site_title = $config->get_string("title");
$sitename_in_title = $config->get_int("sitename_in_title");
// get config values
$site_title = $config->get_string("title");
$sitename_in_title = $config->get_int("sitename_in_title");
// if feature is enabled & sitename isn't already in title
// (can occur on index & other pages)
if ($sitename_in_title != 0 && !strstr($page->title, $site_title))
{
if ($sitename_in_title == 1)
$page->title = "$site_title - $page->title"; // as prefix
else if ($sitename_in_title == 2)
$page->title = "$page->title - $site_title"; // as suffix
}
// if feature is enabled & sitename isn't already in title
// (can occur on index & other pages)
if ($sitename_in_title != 0 && !strstr($page->title, $site_title)) {
if ($sitename_in_title == 1) {
$page->title = "$site_title - $page->title";
} // as prefix
elseif ($sitename_in_title == 2) {
$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 {
public function onPageRequest(PageRequestEvent $event) {
if($event->page_matches("api") && ($event->get_arg(0) == 'danbooru')) {
$this->api_danbooru($event);
}
}
class DanbooruApi extends Extension
{
public function onPageRequest(PageRequestEvent $event)
{
if ($event->page_matches("api") && ($event->get_arg(0) == 'danbooru')) {
$this->api_danbooru($event);
}
}
// Danbooru API
private function api_danbooru(PageRequestEvent $event) {
global $page;
$page->set_mode("data");
// Danbooru API
private function api_danbooru(PageRequestEvent $event)
{
global $page;
$page->set_mode("data");
if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) {
// No XML data is returned from this function
$page->set_type("text/plain");
$this->api_add_post();
}
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
$page->set_type("text/plain");
$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'))) {
$page->set_type("application/xml");
$page->set_data($this->api_find_posts());
}
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
// Shimmie view page
// 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");
$page->set_data($this->api_find_tags());
}
/**
* 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;
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
// Shimmie view page
// 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);
}
}
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));
}
}
}
/**
* 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 all tags that match the search criteria.
*
* Find all tags that match the search criteria.
*
* Parameters
* - id: A comma delimited list of tag id numbers.
* - name: A comma delimited list of tag names.
* - 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
*
* #return string
*/
private function api_find_tags() {
global $database;
$results = array();
if(isset($_GET['id'])) {
$idlist = explode(",", $_GET['id']);
foreach ($idlist as $id) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE id = ?",
array($id));
foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']);
}
}
}
elseif(isset($_GET['name'])) {
$namelist = explode(",", $_GET['name']);
foreach ($namelist as $name) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE tag = ?",
array($name));
foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']);
}
}
}
// Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags
elseif(false && isset($_GET['tags'])) {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$tags = Tag::explode($_GET['tags']);
}
else {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC",
array($start));
foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']);
}
}
*/
private function api_find_tags(): string
{
global $database;
$results = [];
if (isset($_GET['id'])) {
$idlist = explode(",", $_GET['id']);
foreach ($idlist as $id) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE id = ?",
[$id]
);
foreach ($sqlresult as $row) {
$results[] = [$row['count'], $row['tag'], $row['id']];
}
}
} elseif (isset($_GET['name'])) {
$namelist = explode(",", $_GET['name']);
foreach ($namelist as $name) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE tag = ?",
[$name]
);
foreach ($sqlresult as $row) {
$results[] = [$row['count'], $row['tag'], $row['id']];
}
}
}
// Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags
elseif (false && isset($_GET['tags'])) {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$tags = Tag::explode($_GET['tags']);
} else {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC",
[$start]
);
foreach ($sqlresult as $row) {
$results[] = [$row['count'], $row['tag'], $row['id']];
}
}
// Tag results collected, build XML output
$xml = "<tags>\n";
foreach ($results as $tag) {
$xml .= xml_tag("tag", array(
"type" => "0",
"counts" => $tag[0],
"name" => $tag[1],
"id" => $tag[2],
));
}
$xml .= "</tags>";
return $xml;
}
// Tag results collected, build XML output
$xml = "<tags>\n";
foreach ($results as $tag) {
$xml .= xml_tag("tag", [
"type" => "0",
"counts" => $tag[0],
"name" => $tag[1],
"id" => $tag[2],
]);
}
$xml .= "</tags>";
return $xml;
}
/**
* find_posts()
* Find all posts that match the search criteria. Posts will be ordered by id descending.
*
* Parameters:
* - md5: md5 hash to search for (comma delimited)
* - id: id to search for (comma delimited)
* - tags: what tags to search for
* - limit: limit
* - page: page number
* - after_id: limit results to posts added after this id
*
* #return string
*/
private function api_find_posts() {
$results = array();
/**
* find_posts()
* Find all posts that match the search criteria. Posts will be ordered by id descending.
*
* Parameters:
* - md5: md5 hash to search for (comma delimited)
* - id: id to search for (comma delimited)
* - tags: what tags to search for
* - limit: limit
* - page: page number
* - after_id: limit results to posts added after this id
*
* #return string
*/
private function api_find_posts()
{
$results = [];
$this->authenticate_user();
$start = 0;
$this->authenticate_user();
$start = 0;
if(isset($_GET['md5'])) {
$md5list = explode(",", $_GET['md5']);
foreach ($md5list as $md5) {
$results[] = Image::by_hash($md5);
}
$count = count($results);
}
elseif(isset($_GET['id'])) {
$idlist = explode(",", $_GET['id']);
foreach ($idlist as $id) {
$results[] = Image::by_id($id);
}
$count = count($results);
}
else {
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
if (isset($_GET['md5'])) {
$md5list = explode(",", $_GET['md5']);
foreach ($md5list as $md5) {
$results[] = Image::by_hash($md5);
}
$count = count($results);
} elseif (isset($_GET['id'])) {
$idlist = explode(",", $_GET['id']);
foreach ($idlist as $id) {
$results[] = Image::by_id($id);
}
$count = count($results);
} else {
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
// Calculate start offset.
if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1
$start = (int_escape($_GET['page']) - 1) * $limit;
else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0
$start = int_escape($_GET['pid']) * $limit;
else
$start = 0;
// Calculate start offset.
if (isset($_GET['page'])) { // Danbooru API uses 'page' >= 1
$start = (int_escape($_GET['page']) - 1) * $limit;
} elseif (isset($_GET['pid'])) { // Gelbooru API uses 'pid' >= 0
$start = int_escape($_GET['pid']) * $limit;
} else {
$start = 0;
}
$tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array();
$count = Image::count_images($tags);
$results = Image::find_images(max($start, 0), min($limit, 100), $tags);
}
$tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : [];
$count = Image::count_images($tags);
$results = Image::find_images(max($start, 0), min($limit, 100), $tags);
}
// Now we have the array $results filled with Image objects
// Let's display them
$xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n";
foreach ($results as $img) {
// 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 (!is_object($img))
continue;
$taglist = $img->get_tag_list();
$owner = $img->get_owner();
$previewsize = get_thumbnail_size($img->width, $img->height);
$xml .= xml_tag("post", array(
"id" => $img->id,
"md5" => $img->hash,
"file_name" => $img->filename,
"file_url" => $img->get_image_link(),
"height" => $img->height,
"width" => $img->width,
"preview_url" => $img->get_thumb_link(),
"preview_height" => $previewsize[1],
"preview_width" => $previewsize[0],
"rating" => "u",
"date" => $img->posted,
"is_warehoused" => false,
"tags" => $taglist,
"source" => $img->source,
"score" => 0,
"author" => $owner->name
));
}
$xml .= "</posts>";
return $xml;
}
// Now we have the array $results filled with Image objects
// Let's display them
$xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n";
foreach ($results as $img) {
// 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 (!is_object($img)) {
continue;
}
$taglist = $img->get_tag_list();
$owner = $img->get_owner();
$previewsize = get_thumbnail_size($img->width, $img->height);
$xml .= xml_tag("post", [
"id" => $img->id,
"md5" => $img->hash,
"file_name" => $img->filename,
"file_url" => $img->get_image_link(),
"height" => $img->height,
"width" => $img->width,
"preview_url" => $img->get_thumb_link(),
"preview_height" => $previewsize[1],
"preview_width" => $previewsize[0],
"rating" => "u",
"date" => $img->posted,
"is_warehoused" => false,
"tags" => $taglist,
"source" => $img->source,
"score" => 0,
"author" => $owner->name
]);
}
$xml .= "</posts>";
return $xml;
}
/**
/**
* add_post()
* Adds a post to the database.
*
*
* Parameters:
* - login: login
* - password: password
@ -272,124 +272,127 @@ class DanbooruApi extends Extension {
* - tags: list of tags as a string, delimited by whitespace
* - md5: MD5 hash of upload in hexadecimal format
* - rating: rating of the post. can be explicit, questionable, or safe. **IGNORED**
*
*
* Notes:
* - 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 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.
*
*
* Response
* The response depends on the method used:
* Post:
* - X-Danbooru-Location set to the URL for newly uploaded post.
* Get:
* - Redirected to the newly uploaded post.
*/
private function api_add_post() {
global $user, $config, $page;
$danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path
*/
private function api_add_post()
{
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
// If all that fails, it's an anonymous upload
$this->authenticate_user();
// Now we check if a file was uploaded or a url was provided to transload
// Much of this code is borrowed from /ext/upload
// 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
$this->authenticate_user();
// Now we check if a file was uploaded or a url was provided to transload
// Much of this code is borrowed from /ext/upload
if (!$user->can("create_image")) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: authentication error");
return;
}
if (!$user->can("create_image")) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: authentication error");
return;
}
if (isset($_FILES['file'])) { // A file was POST'd in
$file = $_FILES['file']['tmp_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 (isset($_REQUEST['source']) && !empty($_REQUEST['source'])) {
$source = $_REQUEST['source'];
} else {
$source = null;
}
} elseif (isset($_FILES['post'])) {
$file = $_FILES['post']['tmp_name']['file'];
$filename = $_FILES['post']['name']['file'];
if (isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source'])) {
$source = $_REQUEST['post']['source'];
} else {
$source = null;
}
} elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided
$source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
$file = tempnam("/tmp", "shimmie_transload");
$ok = transload($source, $file);
if (!$ok) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: fopen read error");
return;
}
$filename = basename($source);
} else { // Nothing was specified at all
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: no input files");
return;
}
if (isset($_FILES['file'])) { // A file was POST'd in
$file = $_FILES['file']['tmp_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 (isset($_REQUEST['source']) && !empty($_REQUEST['source'])) {
$source = $_REQUEST['source'];
} else {
$source = null;
}
} elseif (isset($_FILES['post'])) {
$file = $_FILES['post']['tmp_name']['file'];
$filename = $_FILES['post']['name']['file'];
if (isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source'])) {
$source = $_REQUEST['post']['source'];
} else {
$source = null;
}
} elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided
$source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
$file = tempnam("/tmp", "shimmie_transload");
$ok = transload($source, $file);
if (!$ok) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: fopen read error");
return;
}
$filename = basename($source);
} else { // Nothing was specified at all
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: no input files");
return;
}
// Get tags out of url
$posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']);
// Get tags out of url
$posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']);
// Was an md5 supplied? Does it match the file hash?
$hash = md5_file($file);
if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: md5 mismatch");
return;
}
// Upload size checking is now performed in the upload extension
// It is also currently broken due to some confusion over file variable ($tmp_filename?)
// Was an md5 supplied? Does it match the file hash?
$hash = md5_file($file);
if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: md5 mismatch");
return;
}
// Upload size checking is now performed in the upload extension
// It is also currently broken due to some confusion over file variable ($tmp_filename?)
// Does it exist already?
$existing = Image::by_hash($hash);
if (!is_null($existing)) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: duplicate");
$existinglink = make_link("post/view/" . $existing->id);
if ($danboorup_kludge) $existinglink = make_http($existinglink);
$page->add_http_header("X-Danbooru-Location: $existinglink");
return;
}
// Does it exist already?
$existing = Image::by_hash($hash);
if (!is_null($existing)) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: duplicate");
$existinglink = make_link("post/view/" . $existing->id);
if ($danboorup_kludge) {
$existinglink = make_http($existinglink);
}
$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
$fileinfo = pathinfo($filename);
$metadata = array();
$metadata['filename'] = $fileinfo['basename'];
$metadata['extension'] = $fileinfo['extension'];
$metadata['tags'] = $posttags;
$metadata['source'] = $source;
//log_debug("danbooru_api","========== NEW($filename) =========");
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
// Fire off an event which should process the new file and add it to the db
$fileinfo = pathinfo($filename);
$metadata = [];
$metadata['filename'] = $fileinfo['basename'];
$metadata['extension'] = $fileinfo['extension'];
$metadata['tags'] = $posttags;
$metadata['source'] = $source;
//log_debug("danbooru_api","========== NEW($filename) =========");
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
try {
$nevent = new DataUploadEvent($file, $metadata);
//log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
send_event($nevent);
// 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?
$newid = make_link("post/view/" . $newimg->id);
if ($danboorup_kludge) $newid = make_http($newid);
try {
$nevent = new DataUploadEvent($file, $metadata);
//log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
send_event($nevent);
// 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?
$newid = make_link("post/view/" . $newimg->id);
if ($danboorup_kludge) {
$newid = make_http($newid);
}
// Did we POST or GET this call?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$page->add_http_header("X-Danbooru-Location: $newid");
} else {
$page->add_http_header("Location: $newid");
}
} catch (UploadException $ex) {
// Did something screw up?
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage());
}
}
// Did we POST or GET this call?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$page->add_http_header("X-Danbooru-Location: $newid");
} else {
$page->add_http_header("Location: $newid");
}
} catch (UploadException $ex) {
// Did something screw up?
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage());
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,21 +1,24 @@
<?php
class EmoticonListTheme extends Themelet {
public function display_emotes(array $list) {
global $page;
$data_href = get_base_href();
$html = "<html><head><title>Emoticon list</title></head><body>";
$html .= "<table><tr>";
$n = 1;
foreach($list as $item) {
$pathinfo = pathinfo($item);
$name = $pathinfo["filename"];
$html .= "<td><img src='$data_href/$item'> :$name:</td>";
if($n++ % 3 == 0) $html .= "</tr><tr>";
}
$html .= "</tr></table>";
$html .= "</body></html>";
$page->set_mode("data");
$page->set_data($html);
}
class EmoticonListTheme extends Themelet
{
public function display_emotes(array $list)
{
global $page;
$data_href = get_base_href();
$html = "<html><head><title>Emoticon list</title></head><body>";
$html .= "<table><tr>";
$n = 1;
foreach ($list as $item) {
$pathinfo = pathinfo($item);
$name = $pathinfo["filename"];
$html .= "<td><img src='$data_href/$item'> :$name:</td>";
if ($n++ % 3 == 0) {
$html .= "</tr><tr>";
}
}
$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.
*/
class ET extends Extension {
public function onPageRequest(PageRequestEvent $event) {
global $user;
if($event->page_matches("system_info")) {
if($user->can("view_sysinfo")) {
$this->theme->display_info_page($this->get_info());
}
}
}
class ET extends Extension
{
public function onPageRequest(PageRequestEvent $event)
{
global $user;
if ($event->page_matches("system_info")) {
if ($user->can("view_sysinfo")) {
$this->theme->display_info_page($this->get_info());
}
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
global $user;
if($user->can("view_sysinfo")) {
$event->add_link("System Info", make_link("system_info"));
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
if ($user->can("view_sysinfo")) {
$event->add_link("System Info", make_link("system_info"));
}
}
/**
* Collect the information and return it in a keyed array.
*/
private function get_info() {
global $config, $database;
/**
* Collect the information and return it in a keyed array.
*/
private function get_info()
{
global $config, $database;
$info = array();
$info['site_title'] = $config->get_string("title");
$info['site_theme'] = $config->get_string("theme");
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
$info = [];
$info['site_title'] = $config->get_string("title");
$info['site_theme'] = $config->get_string("theme");
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
$info['sys_shimmie'] = VERSION;
$info['sys_schema'] = $config->get_string("db_version");
$info['sys_php'] = phpversion();
$info['sys_db'] = $database->get_driver_name();
$info['sys_os'] = php_uname();
$info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " .
to_shorthand_int(disk_total_space("./"));
$info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown';
$info['thumb_engine'] = $config->get_string("thumb_engine");
$info['thumb_quality'] = $config->get_int('thumb_quality');
$info['thumb_width'] = $config->get_int('thumb_width');
$info['thumb_height'] = $config->get_int('thumb_height');
$info['thumb_mem'] = $config->get_int("thumb_mem_limit");
$info['sys_shimmie'] = VERSION;
$info['sys_schema'] = $config->get_string("db_version");
$info['sys_php'] = phpversion();
$info['sys_db'] = $database->get_driver_name();
$info['sys_os'] = php_uname();
$info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " .
to_shorthand_int(disk_total_space("./"));
$info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown';
$info['thumb_engine'] = $config->get_string("thumb_engine");
$info['thumb_quality'] = $config->get_int('thumb_quality');
$info['thumb_width'] = $config->get_int('thumb_width');
$info['thumb_height'] = $config->get_int('thumb_height');
$info['thumb_mem'] = $config->get_int("thumb_mem_limit");
$info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images");
$info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments");
$info['stat_users'] = $database->get_one("SELECT COUNT(*) FROM users");
$info['stat_tags'] = $database->get_one("SELECT COUNT(*) FROM tags");
$info['stat_image_tags'] = $database->get_one("SELECT COUNT(*) FROM image_tags");
$info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images");
$info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments");
$info['stat_users'] = $database->get_one("SELECT COUNT(*) FROM users");
$info['stat_tags'] = $database->get_one("SELECT COUNT(*) FROM tags");
$info['stat_image_tags'] = $database->get_one("SELECT COUNT(*) FROM image_tags");
$els = array();
foreach(get_declared_classes() as $class) {
$rclass = new ReflectionClass($class);
if($rclass->isAbstract()) {
// don't do anything
}
elseif(is_subclass_of($class, "Extension")) {
$els[] = $class;
}
}
$info['sys_extensions'] = join(', ', $els);
$els = [];
foreach (get_declared_classes() as $class) {
$rclass = new ReflectionClass($class);
if ($rclass->isAbstract()) {
// don't do anything
} elseif (is_subclass_of($class, "Extension")) {
$els[] = $class;
}
}
$info['sys_extensions'] = join(', ', $els);
//$cfs = array();
//foreach($database->get_all("SELECT name, value FROM config") as $pair) {
// $cfs[] = $pair['name']."=".$pair['value'];
//}
//$info[''] = "Config: ".join(", ", $cfs);
//$cfs = array();
//foreach($database->get_all("SELECT name, value FROM config") as $pair) {
// $cfs[] = $pair['name']."=".$pair['value'];
//}
//$info[''] = "Config: ".join(", ", $cfs);
return $info;
}
return $info;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,13 @@
<?php
class FavoritesTheme extends Themelet {
public function get_voter_html(Image $image, $is_favorited) {
$i_image_id = int_escape($image->id);
$name = $is_favorited ? "unset" : "set";
$label = $is_favorited ? "Un-Favorite" : "Favorite";
$html = "
class FavoritesTheme extends Themelet
{
public function get_voter_html(Image $image, $is_favorited)
{
$i_image_id = int_escape($image->id);
$name = $is_favorited ? "unset" : "set";
$label = $is_favorited ? "Un-Favorite" : "Favorite";
$html = "
".make_form(make_link("change_favorite"))."
<input type='hidden' name='image_id' value='$i_image_id'>
<input type='hidden' name='favorite_action' value='$name'>
@ -13,24 +15,23 @@ class FavoritesTheme extends Themelet {
</form>
";
return $html;
}
return $html;
}
public function display_people($username_array) {
global $page;
public function display_people($username_array)
{
global $page;
$i_favorites = count($username_array);
$html = "$i_favorites people:";
$i_favorites = count($username_array);
$html = "$i_favorites people:";
reset($username_array); // rewind to first element in array.
foreach($username_array as $row) {
$username = html_escape($row);
$html .= "<br><a href='".make_link("user/$username")."'>$username</a>";
}
reset($username_array); // rewind to first element in array.
foreach ($username_array as $row) {
$username = html_escape($row);
$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.
*/
class Featured extends Extension {
public function onInitExt(InitExtEvent $event) {
global $config;
$config->set_default_int('featured_id', 0);
}
class Featured extends Extension
{
public function onInitExt(InitExtEvent $event)
{
global $config;
$config->set_default_int('featured_id', 0);
}
public function onPageRequest(PageRequestEvent $event) {
global $config, $page, $user;
if($event->page_matches("featured_image")) {
if($event->get_arg(0) == "set" && $user->check_auth_token()) {
if($user->can("edit_feature") && isset($_POST['image_id'])) {
$id = int_escape($_POST['image_id']);
if($id > 0) {
$config->set_int("featured_id", $id);
log_info("featured", "Featured image set to $id", "Featured image set");
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$id"));
}
}
}
if($event->get_arg(0) == "download") {
$image = Image::by_id($config->get_int("featured_id"));
if(!is_null($image)) {
$page->set_mode("data");
$page->set_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(!is_null($image)) {
send_event(new DisplayingImageEvent($image));
}
}
}
}
public function onPageRequest(PageRequestEvent $event)
{
global $config, $page, $user;
if ($event->page_matches("featured_image")) {
if ($event->get_arg(0) == "set" && $user->check_auth_token()) {
if ($user->can("edit_feature") && isset($_POST['image_id'])) {
$id = int_escape($_POST['image_id']);
if ($id > 0) {
$config->set_int("featured_id", $id);
log_info("featured", "Featured image set to $id", "Featured image set");
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$id"));
}
}
}
if ($event->get_arg(0) == "download") {
$image = Image::by_id($config->get_int("featured_id"));
if (!is_null($image)) {
$page->set_mode("data");
$page->set_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 (!is_null($image)) {
send_event(new DisplayingImageEvent($image));
}
}
}
}
public function onPostListBuilding(PostListBuildingEvent $event) {
global $config, $database, $page, $user;
$fid = $config->get_int("featured_id");
if($fid > 0) {
$image = $database->cache->get("featured_image_object:$fid");
if($image === false) {
$image = Image::by_id($fid);
if($image) { // make sure the object is fully populated before saving
$image->get_tag_array();
}
$database->cache->set("featured_image_object:$fid", $image, 600);
}
if(!is_null($image)) {
if(ext_is_live("Ratings")) {
if(strpos(Ratings::get_user_privs($user), $image->rating) === FALSE) {
return;
}
}
$this->theme->display_featured($page, $image);
}
}
}
public function onPostListBuilding(PostListBuildingEvent $event)
{
global $config, $database, $page, $user;
$fid = $config->get_int("featured_id");
if ($fid > 0) {
$image = $database->cache->get("featured_image_object:$fid");
if ($image === false) {
$image = Image::by_id($fid);
if ($image) { // make sure the object is fully populated before saving
$image->get_tag_array();
}
$database->cache->set("featured_image_object:$fid", $image, 600);
}
if (!is_null($image)) {
if (ext_is_live("Ratings")) {
if (strpos(Ratings::get_user_privs($user), $image->rating) === false) {
return;
}
}
$this->theme->display_featured($page, $image);
}
}
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) {
global $user;
if($user->can("edit_feature")) {
$event->add_part($this->theme->get_buttons_html($event->image->id));
}
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
{
global $user;
if ($user->can("edit_feature")) {
$event->add_part($this->theme->get_buttons_html($event->image->id));
}
}
}

View file

@ -1,35 +1,36 @@
<?php
class FeaturedTest extends ShimmiePHPUnitTestCase {
public function testFeatured() {
$this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
class FeaturedTest extends ShimmiePHPUnitTestCase
{
public function testFeatured()
{
$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->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: pbx");
$this->log_in_as_admin();
$this->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: pbx");
$this->markTestIncomplete();
$this->markTestIncomplete();
$this->click("Feature This");
$this->get_page("post/list");
$this->assert_text("Featured Image");
$this->click("Feature This");
$this->get_page("post/list");
$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->assert_response(200);
$this->get_page("featured_image/download");
$this->assert_response(200);
$this->get_page("featured_image/view");
$this->assert_response(200);
$this->get_page("featured_image/view");
$this->assert_response(200);
$this->delete_image($image_id);
$this->log_out();
$this->delete_image($image_id);
$this->log_out();
# after deletion, there should be no feature
$this->get_page("post/list");
$this->assert_no_text("Featured Image");
}
# after deletion, there should be no feature
$this->get_page("post/list");
$this->assert_no_text("Featured Image");
}
}

View file

@ -1,33 +1,36 @@
<?php
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));
}
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 get_buttons_html(int $image_id): string {
global $user;
return "
public function get_buttons_html(int $image_id): string
{
global $user;
return "
".make_form(make_link("featured_image/set"))."
".$user->get_auth_html()."
<input type='hidden' name='image_id' value='{$image_id}'>
<input type='submit' value='Feature This'>
</form>
";
}
}
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);
$h_thumb_link = $image->get_thumb_link();
$h_tip = html_escape($image->get_tooltip());
$tsize = get_thumbnail_size($image->width, $image->height);
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);
$h_thumb_link = $image->get_thumb_link();
$h_tip = html_escape($image->get_tooltip());
$tsize = get_thumbnail_size($image->width, $image->height);
return "
return "
<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}'>
</a>
";
}
}
}

View file

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

View file

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

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