merge develop, fix conflicts, bump

This commit is contained in:
Shish 2015-10-26 12:13:17 +00:00
commit 7b0933ea54
264 changed files with 4091 additions and 25169 deletions

2
.gitignore vendored
View file

@ -3,6 +3,8 @@ data
images images
thumbs thumbs
!lib/images !lib/images
*.phar
*.sqlite
# Created by http://www.gitignore.io # Created by http://www.gitignore.io

View file

@ -3,4 +3,7 @@ imports:
- php - php
filter: filter:
excluded_paths: [lib/*,ext/tagger/script.js] excluded_paths: [lib/*,ext/tagger/script.js,ext/chatbox/*]
tools:
external_code_coverage: true

View file

@ -1,54 +1,43 @@
language: php language: php
sudo: false
php: php:
# Here is where we can list the versions of PHP you want to test against
# using major version aliases
- 5.3
- 5.4 - 5.4
- 5.5 - 5.5
- 5.6
- nightly
# optionally specify a list of environments, for example to test different RDBMS
env: env:
matrix:
- DB=mysql - DB=mysql
- DB=pgsql - DB=pgsql
- DB=sqlite
before_install:
- sudo apt-get update > /dev/null
- sudo chmod u+x tests/setup_test_env.sh
install: install:
# Install nginx, php5-fpm and configure them - mkdir -p data/config
- sudo ./tests/setup_test_env.sh $TRAVIS_BUILD_DIR
# Enable logging of all queries (for debugging) and create the database schema for shimmie.
- if [[ "$DB" == "pgsql" ]]; then psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres; fi - if [[ "$DB" == "pgsql" ]]; then psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres; fi
- if [[ "$DB" == "pgsql" ]]; then psql -c "CREATE DATABASE shimmie;" -U postgres; fi - if [[ "$DB" == "pgsql" ]]; then psql -c "CREATE DATABASE shimmie;" -U postgres; fi
- if [[ "$DB" == "pgsql" ]]; then echo '<?php define("DATABASE_DSN", "pgsql:user=postgres;password=;host=;dbname=shimmie");' > data/config/auto_install.conf.php ; fi
- if [[ "$DB" == "mysql" ]]; then mysql -e "SET GLOBAL general_log = 'ON';" -uroot; fi - if [[ "$DB" == "mysql" ]]; then mysql -e "SET GLOBAL general_log = 'ON';" -uroot; fi
- if [[ "$DB" == "mysql" ]]; then mysql -e "CREATE DATABASE shimmie;" -uroot; fi - if [[ "$DB" == "mysql" ]]; then mysql -e "CREATE DATABASE shimmie;" -uroot; fi
- if [[ "$DB" == "mysql" ]]; then echo '<?php define("DATABASE_DSN", "mysql:user=root;password=;host=localhost;dbname=shimmie");' > data/config/auto_install.conf.php ; fi
- if [[ "$DB" == "sqlite" ]]; then echo '<?php define("DATABASE_DSN", "sqlite:shimmie.sqlite");' > data/config/auto_install.conf.php ; fi
- wget https://scrutinizer-ci.com/ocular.phar
script: script:
- php tests/test_install.php -d $DB -h "http://127.0.0.1/" - php install.php
- php tests/test_all.php -h "http://127.0.0.1/" - phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
# If a failure occured then dump out a bunch of logs for debugging purposes.
after_failure: after_failure:
- sudo ls -al - head -n 100 data/config/*
- sudo ls -al data/config/ - ls /var/run/mysql*
- sudo cat data/config/shimmie.conf.php - ls /var/log/*mysql*
- sudo cat data/config/extensions.conf.php - cat /var/log/mysql.err
- sudo cat /etc/nginx/sites-enabled/default - cat /var/log/mysql.log
- sudo cat /var/log/nginx/error.log - cat /var/log/mysql/error.log
- sudo cat /var/log/php5-fpm.log - cat /var/log/mysql/slow.log
- sudo ls /var/run/mysql* - ls /var/log/postgresql
- sudo ls /var/log/*mysql* - cat /var/log/postgresql/postgresql*
- sudo cat /var/log/mysql.err
- sudo cat /var/log/mysql.log
- sudo cat /var/log/mysql/error.log
- sudo cat /var/log/mysql/slow.log
- sudo ls /var/log/postgresql
- sudo cat /var/log/postgresql/postgresql*
# configure notifications (email, IRC, campfire etc) after_script:
#notifications: - php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover
# irc: "irc.freenode.org#shimmie"
#

View file

@ -20,8 +20,8 @@ check out one of the versioned branches.
# Requirements # Requirements
- MySQL/MariaDB 5.1+ (with experimental support for PostgreSQL 8+ and SQLite 3) - MySQL/MariaDB 5.1+ (with experimental support for PostgreSQL 9+ and SQLite 3)
- PHP 5.3.7+ - PHP 5.4.8+
- GD or ImageMagick - GD or ImageMagick
# Installation # Installation

49
core/_bootstrap.inc.php Normal file
View file

@ -0,0 +1,49 @@
<?php
/*
* Load all the files into memory, sanitise the environment, but don't
* actually do anything as far as the app is concerned
*/
global $config, $database, $user, $page;
require_once "core/sys_config.inc.php";
require_once "core/util.inc.php";
require_once "lib/context.php";
// set up and purify the environment
_version_check();
_sanitise_environment();
// load base files
ctx_log_start("Opening files");
$files = array_merge(
zglob("core/*.php"),
zglob("ext/{".ENABLED_EXTS."}/main.php")
);
foreach($files as $filename) {
if(basename($filename)[0] != "_") {
require_once $filename;
}
}
unset($files);
unset($filename);
ctx_log_endok();
// connect to the database
ctx_log_start("Connecting to DB");
$database = new Database();
$config = new DatabaseConfig($database);
ctx_log_endok();
// load the theme parts
ctx_log_start("Loading themelets");
foreach(_get_themelet_files(get_theme()) as $themelet) {
require_once $themelet;
}
unset($themelet);
$page = class_exists("CustomPage") ? new CustomPage() : new Page();
ctx_log_endok();
// hook up event handlers
_load_event_listeners();
send_event(new InitExtEvent());

View file

@ -16,7 +16,7 @@ class BaseThemelet {
*/ */
public function display_error(/*int*/ $code, /*string*/ $title, /*string*/ $message) { public function display_error(/*int*/ $code, /*string*/ $title, /*string*/ $message) {
global $page; global $page;
$page->add_http_header("HTTP/1.0 $code $title"); $page->set_code($code);
$page->set_title($title); $page->set_title($title);
$page->set_heading($title); $page->set_heading($title);
$has_nav = false; $has_nav = false;
@ -66,8 +66,8 @@ class BaseThemelet {
$custom_classes = ""; $custom_classes = "";
if(class_exists("Relationships")){ if(class_exists("Relationships")){
if(property_exists('Image', 'parent_id') && $image->parent_id !== NULL){ $custom_classes .= "shm-thumb-has_parent "; } if(property_exists($image, 'parent_id') && $image->parent_id !== NULL){ $custom_classes .= "shm-thumb-has_parent "; }
if(property_exists('Image', 'has_children') && $image->has_children == TRUE){ $custom_classes .= "shm-thumb-has_child "; } if(property_exists($image, 'has_children') && $image->has_children == TRUE){ $custom_classes .= "shm-thumb-has_child "; }
} }
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>". return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>".
@ -83,10 +83,11 @@ class BaseThemelet {
* @param string $query * @param string $query
* @param int $page_number * @param int $page_number
* @param int $total_pages * @param int $total_pages
* @param bool $show_random
*/ */
public function display_paginator(Page $page, $base, $query, $page_number, $total_pages) { public function display_paginator(Page $page, $base, $query, $page_number, $total_pages, $show_random = FALSE) {
if($total_pages == 0) $total_pages = 1; if($total_pages == 0) $total_pages = 1;
$body = $this->build_paginator($page_number, $total_pages, $base, $query); $body = $this->build_paginator($page_number, $total_pages, $base, $query, $show_random);
$page->add_block(new Block(null, $body, "main", 90, "paginator")); $page->add_block(new Block(null, $body, "main", 90, "paginator"));
} }
@ -95,7 +96,7 @@ class BaseThemelet {
* *
* @param string $base_url * @param string $base_url
* @param string $query * @param string $query
* @param int|string $page * @param string $page
* @param string $name * @param string $name
* @return string * @return string
*/ */
@ -107,8 +108,8 @@ class BaseThemelet {
/** /**
* @param string $base_url * @param string $base_url
* @param string $query * @param string $query
* @param int|string $page * @param string $page
* @param int|string $current_page * @param int $current_page
* @param string $name * @param string $name
* @return string * @return string
*/ */
@ -127,19 +128,25 @@ class BaseThemelet {
* @param int $total_pages * @param int $total_pages
* @param string $base_url * @param string $base_url
* @param string $query * @param string $query
* @param bool $show_random
* @return string * @return string
*/ */
private function build_paginator($current_page, $total_pages, $base_url, $query) { private function build_paginator($current_page, $total_pages, $base_url, $query, $show_random) {
$next = $current_page + 1; $next = $current_page + 1;
$prev = $current_page - 1; $prev = $current_page - 1;
$rand = mt_rand(1, $total_pages);
$at_start = ($current_page <= 1 || $total_pages <= 1); $at_start = ($current_page <= 1 || $total_pages <= 1);
$at_end = ($current_page >= $total_pages); $at_end = ($current_page >= $total_pages);
$first_html = $at_start ? "First" : $this->gen_page_link($base_url, $query, 1, "First"); $first_html = $at_start ? "First" : $this->gen_page_link($base_url, $query, 1, "First");
$prev_html = $at_start ? "Prev" : $this->gen_page_link($base_url, $query, $prev, "Prev"); $prev_html = $at_start ? "Prev" : $this->gen_page_link($base_url, $query, $prev, "Prev");
$random_html = "-";
if($show_random) {
$rand = mt_rand(1, $total_pages);
$random_html = $this->gen_page_link($base_url, $query, $rand, "Random"); $random_html = $this->gen_page_link($base_url, $query, $rand, "Random");
}
$next_html = $at_end ? "Next" : $this->gen_page_link($base_url, $query, $next, "Next"); $next_html = $at_end ? "Next" : $this->gen_page_link($base_url, $query, $next, "Next");
$last_html = $at_end ? "Last" : $this->gen_page_link($base_url, $query, $total_pages, "Last"); $last_html = $at_end ? "Last" : $this->gen_page_link($base_url, $query, $total_pages, "Last");

View file

@ -88,10 +88,10 @@ interface Config {
* parameter won't show up. * parameter won't show up.
* *
* @param string $name * @param string $name
* @param bool|string|null $value * @param bool $value
* @return void * @return void
*/ */
public function set_default_bool(/*string*/ $name, $value); public function set_default_bool(/*string*/ $name, /*bool*/ $value);
/** /**
* Set a configuration option to a new value, if there is no value currently. * Set a configuration option to a new value, if there is no value currently.
@ -218,10 +218,10 @@ abstract class BaseConfig implements Config {
/** /**
* @param string $name * @param string $name
* @param bool|null|string $value * @param bool $value
* @return void * @return void
*/ */
public function set_default_bool(/*string*/ $name, $value) { public function set_default_bool(/*string*/ $name, /*bool*/ $value) {
if(is_null($this->get($name))) { if(is_null($this->get($name))) {
$this->values[$name] = (($value == 'on' || $value === true) ? 'Y' : 'N'); $this->values[$name] = (($value == 'on' || $value === true) ? 'Y' : 'N');
} }
@ -328,8 +328,9 @@ class StaticConfig extends BaseConfig {
*/ */
public function __construct($filename) { public function __construct($filename) {
if(file_exists($filename)) { if(file_exists($filename)) {
$config = array();
require_once $filename; require_once $filename;
if(isset($config)) { if(!empty($config)) {
$this->values = $config; $this->values = $config;
} }
else { else {

View file

@ -20,7 +20,7 @@ class Querylet {
* @param \Querylet $querylet * @param \Querylet $querylet
*/ */
public function append($querylet) { public function append($querylet) {
assert(!is_null($querylet)); assert('!is_null($querylet)');
$this->sql .= $querylet->sql; $this->sql .= $querylet->sql;
$this->variables = array_merge($this->variables, $querylet->variables); $this->variables = array_merge($this->variables, $querylet->variables);
} }
@ -146,7 +146,12 @@ class PostgreSQL extends DBEngine {
* @param \PDO $db * @param \PDO $db
*/ */
public function init($db) { public function init($db) {
if(array_key_exists('REMOTE_ADDR', $_SERVER)) {
$db->exec("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';"); $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;"); $db->exec("SET statement_timeout TO 10000;");
} }
@ -174,7 +179,7 @@ class PostgreSQL extends DBEngine {
*/ */
public function create_table_sql($name, $data) { public function create_table_sql($name, $data) {
$data = $this->scoreql_to_sql($data); $data = $this->scoreql_to_sql($data);
return 'CREATE TABLE '.$name.' ('.$data.')'; return "CREATE TABLE $name ($data)";
} }
} }
@ -190,6 +195,8 @@ function _isnull($a) { return is_null($a); }
function _md5($a) { return md5($a); } function _md5($a) { return md5($a); }
function _concat($a, $b) { return $a . $b; } function _concat($a, $b) { return $a . $b; }
function _lower($a) { return strtolower($a); } function _lower($a) { return strtolower($a); }
function _rand() { return rand(); }
function _ln($n) { return log($n); }
class SQLite extends DBEngine { class SQLite extends DBEngine {
/** @var string */ /** @var string */
@ -209,6 +216,8 @@ class SQLite extends DBEngine {
$db->sqliteCreateFunction('md5', '_md5', 1); $db->sqliteCreateFunction('md5', '_md5', 1);
$db->sqliteCreateFunction('concat', '_concat', 2); $db->sqliteCreateFunction('concat', '_concat', 2);
$db->sqliteCreateFunction('lower', '_lower', 1); $db->sqliteCreateFunction('lower', '_lower', 1);
$db->sqliteCreateFunction('rand', '_rand', 0);
$db->sqliteCreateFunction('ln', '_ln', 1);
} }
/** /**
@ -238,9 +247,10 @@ class SQLite extends DBEngine {
$extras = ""; $extras = "";
foreach(explode(",", $data) as $bit) { foreach(explode(",", $data) as $bit) {
$matches = array(); $matches = array();
if(preg_match("/INDEX\s*\((.*)\)/", $bit, $matches)) { if(preg_match("/(UNIQUE)? ?INDEX\s*\((.*)\)/", $bit, $matches)) {
$col = $matches[1]; $uni = $matches[1];
$extras .= "CREATE INDEX {$name}_{$col} on {$name}({$col});"; $col = $matches[2];
$extras .= "CREATE $uni INDEX {$name}_{$col} ON {$name}({$col});";
} }
else { else {
$cols[] = $bit; $cols[] = $bit;
@ -295,7 +305,7 @@ class MemcacheCache implements CacheEngine {
* @return array|bool|string * @return array|bool|string
*/ */
public function get($key) { public function get($key) {
assert(!is_null($key)); assert('!is_null($key)');
$val = $this->memcache->get($key); $val = $this->memcache->get($key);
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
$hit = $val === false ? "miss" : "hit"; $hit = $val === false ? "miss" : "hit";
@ -317,7 +327,7 @@ class MemcacheCache implements CacheEngine {
* @param int $time * @param int $time
*/ */
public function set($key, $val, $time=0) { public function set($key, $val, $time=0) {
assert(!is_null($key)); assert('!is_null($key)');
$this->memcache->set($key, $val, false, $time); $this->memcache->set($key, $val, false, $time);
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND); file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND);
@ -328,7 +338,7 @@ class MemcacheCache implements CacheEngine {
* @param string $key * @param string $key
*/ */
public function delete($key) { public function delete($key) {
assert(!is_null($key)); assert('!is_null($key)');
$this->memcache->delete($key); $this->memcache->delete($key);
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND); file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND);
@ -354,7 +364,7 @@ class APCCache implements CacheEngine {
} }
public function get($key) { public function get($key) {
assert(!is_null($key)); assert('!is_null($key)');
$val = apc_fetch($key); $val = apc_fetch($key);
if($val) { if($val) {
$this->hits++; $this->hits++;
@ -367,12 +377,12 @@ class APCCache implements CacheEngine {
} }
public function set($key, $val, $time=0) { public function set($key, $val, $time=0) {
assert(!is_null($key)); assert('!is_null($key)');
apc_store($key, $val, $time); apc_store($key, $val, $time);
} }
public function delete($key) { public function delete($key) {
assert(!is_null($key)); assert('!is_null($key)');
apc_delete($key); apc_delete($key);
} }
@ -413,6 +423,11 @@ class Database {
*/ */
public $transaction = false; public $transaction = false;
/**
* 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 * For now, only connect to the cache, as we will pretty much certainly
* need it. There are some pages where all the data is in cache, so the * need it. There are some pages where all the data is in cache, so the
@ -450,8 +465,14 @@ class Database {
if(preg_match("/user=([^;]*)/", DATABASE_DSN, $matches)) $db_user=$matches[1]; if(preg_match("/user=([^;]*)/", DATABASE_DSN, $matches)) $db_user=$matches[1];
if(preg_match("/password=([^;]*)/", DATABASE_DSN, $matches)) $db_pass=$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;
}
$db_params = array( $db_params = array(
PDO::ATTR_PERSISTENT => DATABASE_KA, PDO::ATTR_PERSISTENT => $ka,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
); );
$this->db = new PDO(DATABASE_DSN, $db_user, $db_pass, $db_params); $this->db = new PDO(DATABASE_DSN, $db_user, $db_pass, $db_params);
@ -545,6 +566,26 @@ class Database {
return $this->engine->name; return $this->engine->name;
} }
private function count_execs($db, $sql, $inputarray) {
if ((defined('DEBUG_SQL') && DEBUG_SQL === true) || (!defined('DEBUG_SQL') && @$_GET['DEBUG_SQL'])) {
$fp = @fopen("data/sql.log", "a");
if($fp) {
$sql = trim(preg_replace('/\s+/msi', ' ', $sql));
if(isset($inputarray) && is_array($inputarray) && !empty($inputarray)) {
fwrite($fp, $sql." -- ".join(", ", $inputarray)."\n");
}
else {
fwrite($fp, $sql."\n");
}
fclose($fp);
}
}
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++;
}
/** /**
* Execute an SQL query and return an PDO result-set. * Execute an SQL query and return an PDO result-set.
* *
@ -556,7 +597,7 @@ class Database {
public function execute($query, $args=array()) { public function execute($query, $args=array()) {
try { try {
if(is_null($this->db)) $this->connect_db(); if(is_null($this->db)) $this->connect_db();
_count_execs($this->db, $query, $args); $this->count_execs($this->db, $query, $args);
$stmt = $this->db->prepare($query); $stmt = $this->db->prepare($query);
if (!array_key_exists(0, $args)) { if (!array_key_exists(0, $args)) {
foreach($args as $name=>$value) { foreach($args as $name=>$value) {
@ -598,7 +639,7 @@ class Database {
* *
* @param string $query * @param string $query
* @param array $args * @param array $args
* @return mixed|null * @return array|null
*/ */
public function get_row($query, $args=array()) { public function get_row($query, $args=array()) {
$_start = microtime(true); $_start = microtime(true);
@ -661,7 +702,7 @@ class Database {
* Get the ID of the last inserted row. * Get the ID of the last inserted row.
* *
* @param string|null $seq * @param string|null $seq
* @return string * @return int
*/ */
public function get_last_insert_id($seq) { public function get_last_insert_id($seq) {
if($this->engine->name == "pgsql") { if($this->engine->name == "pgsql") {
@ -690,6 +731,7 @@ class Database {
* @return int|null * @return int|null
*/ */
public function count_tables() { public function count_tables() {
if(is_null($this->db) || is_null($this->engine)) $this->connect_db(); if(is_null($this->db) || is_null($this->engine)) $this->connect_db();
if($this->engine->name === "mysql") { if($this->engine->name === "mysql") {
@ -702,7 +744,7 @@ class Database {
); );
} else if ($this->engine->name === "sqlite") { } else if ($this->engine->name === "sqlite") {
return count( return count(
$this->get_all(".tables") $this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'")
); );
} else { } else {
// Hard to find a universal way to do this... // Hard to find a universal way to do this...

View file

@ -13,6 +13,8 @@ abstract class Event {
* A wake-up call for extensions. Upon recieving an InitExtEvent an extension * A wake-up call for extensions. Upon recieving an InitExtEvent an extension
* should check that it's database tables are there and install them if not, * should check that it's database tables are there and install them if not,
* and set any defaults with Config::set_default_int() and such. * and set any defaults with Config::set_default_int() and such.
*
* This event is sent before $user is set to anything
*/ */
class InitExtEvent extends Event {} class InitExtEvent extends Event {}

View file

@ -22,3 +22,8 @@ class PermissionDeniedException extends SCoreException {}
* Example: Image::by_id(-1) returns null * Example: Image::by_id(-1) returns null
*/ */
class ImageDoesNotExist extends SCoreException {} class ImageDoesNotExist extends SCoreException {}
/*
* For validate_input()
*/
class InvalidInput extends SCoreException {}

View file

@ -47,7 +47,7 @@
* } * }
* *
* // ext/hello/test.php * // ext/hello/test.php
* public class HelloTest extends SCoreWebTestCase { * public class HelloTest extends SCorePHPUnitTestCase {
* public function testHello() { * public function testHello() {
* $this->get_page("post/list"); // View a page, any page * $this->get_page("post/list"); // View a page, any page
* $this->assert_text("Hello there"); // Check that the specified text is in that page * $this->assert_text("Hello there"); // Check that the specified text is in that page
@ -82,6 +82,9 @@
* find the thread where the original was posted >_< * find the thread where the original was posted >_<
*/ */
abstract class Extension { abstract class Extension {
/** @var array which DBs this ext supports (blank for 'all') */
protected $db_support = [];
/** this theme's Themelet object */ /** this theme's Themelet object */
public $theme; public $theme;
@ -97,6 +100,15 @@ abstract class Extension {
if(is_null($this->theme)) $this->theme = $this->get_theme_object($child, false); if(is_null($this->theme)) $this->theme = $this->get_theme_object($child, false);
} }
public function is_live() {
global $database;
return (
empty($this->db_support) ||
in_array($database->get_driver_name(), $this->db_support)
);
}
/** /**
* Find the theme object for a given extension. * Find the theme object for a given extension.
* *
@ -270,7 +282,7 @@ abstract class DataHandlerExtension extends Extension {
abstract protected function supported_ext($ext); abstract protected function supported_ext($ext);
/** /**
* @param $tmpname * @param string $tmpname
* @return bool * @return bool
*/ */
abstract protected function check_contents($tmpname); abstract protected function check_contents($tmpname);

View file

@ -23,10 +23,6 @@
* Classes * * Classes *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
$tag_n = 0; // temp hack
$_flexihash = null;
$_fh_last_opts = null;
$order_sql = null; // this feels ugly
require_once "lib/flexihash.php"; require_once "lib/flexihash.php";
@ -40,6 +36,9 @@ require_once "lib/flexihash.php";
* other supported upload type. * other supported upload type.
*/ */
class Image { class Image {
private static $tag_n = 0; // temp hack
public static $order_sql = null; // this feels ugly
/** @var null|int */ /** @var null|int */
public $id = null; public $id = null;
@ -64,7 +63,7 @@ class Image {
public $tag_array; public $tag_array;
public $owner_id, $owner_ip; public $owner_id, $owner_ip;
public $posted, $posted_timestamp; public $posted;
public $source; public $source;
public $locked; public $locked;
@ -75,13 +74,14 @@ class Image {
* @param null|mixed $row * @param null|mixed $row
*/ */
public function __construct($row=null) { public function __construct($row=null) {
assert('is_null($row) || is_array($row)');
if(!is_null($row)) { if(!is_null($row)) {
foreach($row as $name => $value) { foreach($row as $name => $value) {
// some databases use table.name rather than name // some databases use table.name rather than name
$name = str_replace("images.", "", $name); $name = str_replace("images.", "", $name);
$this->$name = $value; // hax, this is likely the cause of much scrutinizer-ci complaints. $this->$name = $value; // hax, this is likely the cause of much scrutinizer-ci complaints.
} }
$this->posted_timestamp = strtotime($this->posted); // pray
$this->locked = bool_escape($this->locked); $this->locked = bool_escape($this->locked);
assert(is_numeric($this->id)); assert(is_numeric($this->id));
@ -97,7 +97,7 @@ class Image {
* @return Image * @return Image
*/ */
public static function by_id(/*int*/ $id) { public static function by_id(/*int*/ $id) {
assert(is_numeric($id)); assert('is_numeric($id)');
global $database; global $database;
$row = $database->get_row("SELECT * FROM images WHERE images.id=:id", array("id"=>$id)); $row = $database->get_row("SELECT * FROM images WHERE images.id=:id", array("id"=>$id));
return ($row ? new Image($row) : null); return ($row ? new Image($row) : null);
@ -110,7 +110,7 @@ class Image {
* @return Image * @return Image
*/ */
public static function by_hash(/*string*/ $hash) { public static function by_hash(/*string*/ $hash) {
assert(is_string($hash)); assert('is_string($hash)');
global $database; global $database;
$row = $database->get_row("SELECT images.* FROM images WHERE hash=:hash", array("hash"=>$hash)); $row = $database->get_row("SELECT images.* FROM images WHERE hash=:hash", array("hash"=>$hash));
return ($row ? new Image($row) : null); return ($row ? new Image($row) : null);
@ -123,7 +123,7 @@ class Image {
* @return Image * @return Image
*/ */
public static function by_random($tags=array()) { public static function by_random($tags=array()) {
assert(is_array($tags)); assert('is_array($tags)');
$max = Image::count_images($tags); $max = Image::count_images($tags);
if ($max < 1) return null; // From Issue #22 - opened by HungryFeline on May 30, 2011. if ($max < 1) return null; // From Issue #22 - opened by HungryFeline on May 30, 2011.
$rand = mt_rand(0, $max-1); $rand = mt_rand(0, $max-1);
@ -142,10 +142,10 @@ class Image {
* @return Image[] * @return Image[]
*/ */
public static function find_images(/*int*/ $start, /*int*/ $limit, $tags=array()) { public static function find_images(/*int*/ $start, /*int*/ $limit, $tags=array()) {
assert(is_numeric($start)); assert('is_numeric($start)');
assert(is_numeric($limit)); assert('is_numeric($limit)');
assert(is_array($tags)); assert('is_array($tags)');
global $database, $user, $config, $order_sql; global $database, $user, $config;
$images = array(); $images = array();
@ -158,19 +158,90 @@ class Image {
} }
} }
$result = null;
if(SEARCH_ACCEL) {
$result = Image::get_accelerated_result($tags, $start, $limit);
}
if(!$result) {
$querylet = Image::build_search_querylet($tags); $querylet = Image::build_search_querylet($tags);
$querylet->append(new Querylet(" ORDER BY ".($order_sql ?: "images.".$config->get_string("index_order")))); $querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order"))));
$querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start))); $querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start)));
#var_dump($querylet->sql); var_dump($querylet->variables); #var_dump($querylet->sql); var_dump($querylet->variables);
$result = $database->execute($querylet->sql, $querylet->variables); $result = $database->execute($querylet->sql, $querylet->variables);
}
while($row = $result->fetch()) { while($row = $result->fetch()) {
$images[] = new Image($row); $images[] = new Image($row);
} }
$order_sql = null; Image::$order_sql = null;
return $images; return $images;
} }
public function validate_accel($tags) {
$yays = 0;
$nays = 0;
foreach($tags as $tag) {
if(!preg_match("/^-?[a-zA-Z0-9_]+$/", $tag)) {
return false;
}
if($tag[0] == "-") $nays++;
else $yays++;
}
return ($yays > 1 || $nays > 0);
}
/**
* @param string[] $tags
* @param int $offset
* @param int $limit
* @return null|PDOStatement
* @throws SCoreException
*/
public function get_accelerated_result($tags, $offset, $limit) {
global $database;
$tags = Tag::resolve_aliases($tags);
if(!Image::validate_accel($tags)) {
return null;
}
$yays = array();
$nays = array();
foreach($tags as $tag) {
if($tag[0] == "-") {
$nays[] = substr($tag, 1);
}
else {
$yays[] = $tag;
}
}
$req = array(
"yays" => $yays,
"nays" => $nays,
"offset" => $offset,
"limit" => $limit,
);
$fp = fsockopen("127.0.0.1", 21212);
if (!$fp) {
return null;
}
fwrite($fp, json_encode($req));
$data = fgets($fp, 1024);
fclose($fp);
$response = json_decode($data);
$list = implode(",", $response);
if($list) {
$result = $database->execute("SELECT * FROM images WHERE id IN ($list) ORDER BY images.id DESC");
}
else {
$result = $database->execute("SELECT * FROM images WHERE 1=0 ORDER BY images.id DESC");
}
return $result;
}
/* /*
* Image-related utility functions * Image-related utility functions
*/ */
@ -179,10 +250,10 @@ class Image {
* Count the number of image results for a given search * Count the number of image results for a given search
* *
* @param string[] $tags * @param string[] $tags
* @return mixed * @return int
*/ */
public static function count_images($tags=array()) { public static function count_images($tags=array()) {
assert(is_array($tags)); assert('is_array($tags)');
global $database; global $database;
$tag_count = count($tags); $tag_count = count($tags);
@ -214,12 +285,11 @@ class Image {
* @return float * @return float
*/ */
public static function count_pages($tags=array()) { public static function count_pages($tags=array()) {
assert(is_array($tags)); assert('is_array($tags)');
global $config, $database; global $config;
return ceil(Image::count_images($tags) / $config->get_int('index_images')); return ceil(Image::count_images($tags) / $config->get_int('index_images'));
} }
/* /*
* Accessors & mutators * Accessors & mutators
*/ */
@ -235,8 +305,8 @@ class Image {
* @return Image * @return Image
*/ */
public function get_next($tags=array(), $next=true) { public function get_next($tags=array(), $next=true) {
assert(is_array($tags)); assert('is_array($tags)');
assert(is_bool($next)); assert('is_bool($next)');
global $database; global $database;
if($next) { if($next) {
@ -321,33 +391,7 @@ class Image {
* @return string * @return string
*/ */
public function get_image_link() { public function get_image_link() {
global $config; return $this->get_link('image_ilink', '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.jpg');
$image_ilink = $config->get_string('image_ilink'); // store a copy for speed.
if( !empty($image_ilink) ) { /* empty is faster than strlen */
if(!startsWith($image_ilink, "http://") && !startsWith($image_ilink, "/")) {
$image_ilink = make_link($image_ilink);
}
return $this->parse_link_template($image_ilink);
}
else if($config->get_bool('nice_urls', false)) {
return $this->parse_link_template(make_link('_images/$hash/$id%20-%20$tags.$ext'));
}
else {
return $this->parse_link_template(make_link('image/$id.$ext'));
}
}
/**
* Get a short link to the full size image
*
* @deprecated
* @return string
*/
public function get_short_link() {
global $config;
return $this->parse_link_template($config->get_string('image_slink'));
} }
/** /**
@ -356,21 +400,33 @@ class Image {
* @return string * @return string
*/ */
public function get_thumb_link() { public function get_thumb_link() {
return $this->get_link('image_tlink', '_thumbs/$hash/thumb.jpg', 'thumb/$id.jpg');
}
/**
* Check configured template for a link, then try nice URL, then plain URL
*
* @param string $template
* @param string $nice
* @param string $plain
* @return string
*/
private function get_link($template, $nice, $plain) {
global $config; global $config;
$image_tlink = $config->get_string('image_tlink'); // store a copy for speed. $image_link = $config->get_string($template);
if( !empty($image_tlink) ) { /* empty is faster than strlen */ if(!empty($image_link)) {
if(!startsWith($image_tlink, "http://") && !startsWith($image_tlink, "/")) { if(!(strpos($image_link, "://") > 0) && !startsWith($image_link, "/")) {
$image_tlink = make_link($image_tlink); $image_link = make_link($image_link);
} }
return $this->parse_link_template($image_tlink); return $this->parse_link_template($image_link);
} }
else if($config->get_bool('nice_urls', false)) { else if($config->get_bool('nice_urls', false)) {
return $this->parse_link_template(make_link('_thumbs/$hash/thumb.jpg')); return $this->parse_link_template(make_link($nice));
} }
else { else {
return $this->parse_link_template(make_link('thumb/$id.jpg')); return $this->parse_link_template(make_link($plain));
} }
} }
@ -481,6 +537,10 @@ class Image {
return $this->locked; return $this->locked;
} }
/**
* @param bool $tf
* @throws SCoreException
*/
public function set_locked($tf) { public function set_locked($tf) {
global $database; global $database;
$ln = $tf ? "Y" : "N"; $ln = $tf ? "Y" : "N";
@ -510,20 +570,20 @@ class Image {
* Set the tags for this image. * Set the tags for this image.
* *
* @param string[] $tags * @param string[] $tags
* @throws Exception
*/ */
public function set_tags($tags) { public function set_tags($tags) {
assert('is_array($tags) && count($tags) > 0', var_export($tags, true));
global $database; global $database;
assert(is_array($tags));
$tags = array_map(array('Tag', 'sanitise'), $tags); $tags = array_map(array('Tag', 'sanitise'), $tags);
$tags = Tag::resolve_aliases($tags); $tags = Tag::resolve_aliases($tags);
assert(is_array($tags)); if(count($tags) <= 0) {
assert(count($tags) > 0); throw new SCoreException('Tried to set zero tags');
$new_tags = implode(" ", $tags); }
if($new_tags != $this->get_tag_list()) { if(implode(" ", $tags) != $this->get_tag_list()) {
// delete old // delete old
$this->delete_tags_from_image(); $this->delete_tags_from_image();
// insert each new tags // insert each new tags
@ -637,16 +697,17 @@ class Image {
$tmpl = $plte->link; $tmpl = $plte->link;
} }
global $_flexihash, $_fh_last_opts; static $flexihash = null;
static $fh_last_opts = null;
$matches = array(); $matches = array();
if(preg_match("/(.*){(.*)}(.*)/", $tmpl, $matches)) { if(preg_match("/(.*){(.*)}(.*)/", $tmpl, $matches)) {
$pre = $matches[1]; $pre = $matches[1];
$opts = $matches[2]; $opts = $matches[2];
$post = $matches[3]; $post = $matches[3];
if($opts != $_fh_last_opts) { if($opts != $fh_last_opts) {
$_fh_last_opts = $opts; $fh_last_opts = $opts;
$_flexihash = new Flexihash(); $flexihash = new Flexihash();
foreach(explode(",", $opts) as $opt) { foreach(explode(",", $opts) as $opt) {
$parts = explode("=", $opt); $parts = explode("=", $opt);
$parts_count = count($parts); $parts_count = count($parts);
@ -660,11 +721,11 @@ class Image {
$opt_val = $parts[0]; $opt_val = $parts[0];
$opt_weight = 1; $opt_weight = 1;
} }
$_flexihash->addTarget($opt_val, $opt_weight); $flexihash->addTarget($opt_val, $opt_weight);
} }
} }
$choice = $_flexihash->lookup($pre.$post); $choice = $flexihash->lookup($pre.$post);
$tmpl = $pre.$choice.$post; $tmpl = $pre.$choice.$post;
} }
@ -676,7 +737,7 @@ class Image {
* @return \Querylet * @return \Querylet
*/ */
private static function build_search_querylet($terms) { private static function build_search_querylet($terms) {
assert(is_array($terms)); assert('is_array($terms)');
global $database; global $database;
if($database->get_driver_name() === "mysql") if($database->get_driver_name() === "mysql")
return Image::build_ugly_search_querylet($terms); return Image::build_ugly_search_querylet($terms);
@ -684,9 +745,58 @@ class Image {
return Image::build_accurate_search_querylet($terms); return Image::build_accurate_search_querylet($terms);
} }
/**
* @param string[] $terms
* @return ImgQuerylet[]
*/
private static function parse_meta_terms($terms) {
$img_querylets = array();
$stpe = new SearchTermParseEvent(null, $terms);
send_event($stpe);
if ($stpe->is_querylet_set()) {
foreach ($stpe->get_querylets() as $querylet) {
$img_querylets[] = new ImgQuerylet($querylet, true);
}
}
return $img_querylets;
}
/**
* @param ImgQuerylet[] $img_querylets
* @return Querylet
*/
private static function build_img_search($img_querylets) {
// merge all the image metadata searches into one generic querylet
$n = 0;
$sql = "";
$terms = array();
foreach ($img_querylets as $iq) {
if ($n++ > 0) $sql .= " AND";
if (!$iq->positive) $sql .= " NOT";
$sql .= " (" . $iq->qlet->sql . ")";
$terms = array_merge($terms, $iq->qlet->variables);
}
return new Querylet($sql, $terms);
}
/**
* @param Querylet $img_search
* @return Querylet
*/
private static function build_simple_query($img_search) {
$query = new Querylet("SELECT images.* FROM images ");
if (!empty($img_search->sql)) {
$query->append_sql(" WHERE ");
$query->append($img_search);
return $query;
}
return $query;
}
/** /**
* WARNING: this description is no longer accurate, though it does get across * WARNING: this description is no longer accurate, though it does get across
* the general idea - the actual method has a few extra optimisiations * the general idea - the actual method has a few extra optimisations
* *
* "foo bar -baz user=foo" becomes * "foo bar -baz user=foo" becomes
* *
@ -700,7 +810,7 @@ class Image {
* A) Incredibly simple: * A) Incredibly simple:
* Each search term maps to a list of image IDs * Each search term maps to a list of image IDs
* B) Runs really fast on a good database: * B) Runs really fast on a good database:
* These lists are calucalted once, and the set intersection taken * These lists are calculated once, and the set intersection taken
* C) Runs really slow on bad databases: * C) Runs really slow on bad databases:
* All the subqueries are executed every time for every row in the * All the subqueries are executed every time for every row in the
* images table. Yes, MySQL does suck this much. * images table. Yes, MySQL does suck this much.
@ -712,21 +822,12 @@ class Image {
global $database; global $database;
$tag_querylets = array(); $tag_querylets = array();
$img_querylets = array(); $img_querylets = self::parse_meta_terms($terms);
$positive_tag_count = 0; $positive_tag_count = 0;
$stpe = new SearchTermParseEvent(null, $terms);
send_event($stpe);
if($stpe->is_querylet_set()) {
foreach($stpe->get_querylets() as $querylet) {
$img_querylets[] = new ImgQuerylet($querylet, true);
}
}
$terms = Tag::resolve_aliases($terms);
// parse the words that are searched for into // parse the words that are searched for into
// various types of querylet // various types of querylet
$terms = Tag::resolve_aliases($terms);
foreach($terms as $term) { foreach($terms as $term) {
$positive = true; $positive = true;
if(is_string($term) && !empty($term) && ($term[0] == '-')) { if(is_string($term) && !empty($term) && ($term[0] == '-')) {
@ -752,37 +853,21 @@ class Image {
} }
} }
} }
$img_search = self::build_img_search($img_querylets);
// merge all the image metadata searches into one generic querylet
$n = 0;
$sql = "";
$terms = array();
foreach($img_querylets as $iq) {
if($n++ > 0) $sql .= " AND";
if(!$iq->positive) $sql .= " NOT";
$sql .= " (" . $iq->qlet->sql . ")";
$terms = array_merge($terms, $iq->qlet->variables);
}
$img_search = new Querylet($sql, $terms);
// How many tag querylets are there? // How many tag querylets are there?
$count_tag_querylets = count($tag_querylets); $count_tag_querylets = count($tag_querylets);
// no tags, do a simple search (+image metadata if we have any) // no tags, do a simple search (+image metadata if we have any)
if($count_tag_querylets === 0) { if($count_tag_querylets === 0) {
$query = new Querylet("SELECT images.* FROM images "); $query = self::build_simple_query($img_search);
if(!empty($img_search->sql)) {
$query->append_sql(" WHERE ");
$query->append($img_search);
}
} }
// one positive tag (a common case), do an optimised search // one positive tag (a common case), do an optimised search
else if($count_tag_querylets === 1 && $tag_querylets[0]->positive) { else if($count_tag_querylets === 1 && $tag_querylets[0]->positive) {
$query = new Querylet($database->scoreql_to_sql(" $query = new Querylet($database->scoreql_to_sql("
SELECT images.* FROM images SELECT images.*
FROM images
JOIN image_tags ON images.id=image_tags.image_id JOIN image_tags ON images.id=image_tags.image_id
JOIN tags ON image_tags.tag_id=tags.id JOIN tags ON image_tags.tag_id=tags.id
WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag) WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)
@ -802,10 +887,12 @@ class Image {
foreach($tag_querylets as $tq) { foreach($tag_querylets as $tq) {
$tag_ids = $database->get_col( $tag_ids = $database->get_col(
$database->scoreql_to_sql( $database->scoreql_to_sql("
"SELECT id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)" SELECT id
), FROM tags
array("tag"=>$tq->tag)); WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)
"), array("tag"=>$tq->tag)
);
if($tq->positive) { if($tq->positive) {
$positive_tag_id_array = array_merge($positive_tag_id_array, $tag_ids); $positive_tag_id_array = array_merge($positive_tag_id_array, $tag_ids);
$tags_ok = count($tag_ids) > 0; $tags_ok = count($tag_ids) > 0;
@ -820,7 +907,7 @@ class Image {
$have_pos = count($positive_tag_id_array) > 0; $have_pos = count($positive_tag_id_array) > 0;
$have_neg = count($negative_tag_id_array) > 0; $have_neg = count($negative_tag_id_array) > 0;
$sql = "SELECT images.* FROM images WHERE images.id IN ("; $sql = "";
if($have_pos) { if($have_pos) {
$positive_tag_id_list = join(', ', $positive_tag_id_array); $positive_tag_id_list = join(', ', $positive_tag_id_array);
$sql .= " $sql .= "
@ -842,8 +929,11 @@ class Image {
WHERE tag_id IN ($negative_tag_id_list) WHERE tag_id IN ($negative_tag_id_list)
"; ";
} }
$sql .= ")"; $query = new Querylet("
$query = new Querylet($sql); SELECT images.*
FROM images
WHERE images.id IN ($sql)
");
if(strlen($img_search->sql) > 0) { if(strlen($img_search->sql) > 0) {
$query->append_sql(" AND "); $query->append_sql(" AND ");
@ -867,23 +957,18 @@ class Image {
/** /**
* this function exists because mysql is a turd, see the docs for * this function exists because mysql is a turd, see the docs for
* build_accurate_search_querylet() for a full explanation * build_accurate_search_querylet() for a full explanation
*
* @param array $terms
* @return Querylet
*/ */
private static function build_ugly_search_querylet($terms) { private static function build_ugly_search_querylet($terms) {
global $database; global $database;
$tag_querylets = array(); $tag_querylets = array();
$img_querylets = array(); $img_querylets = self::parse_meta_terms($terms);
$positive_tag_count = 0; $positive_tag_count = 0;
$negative_tag_count = 0; $negative_tag_count = 0;
$stpe = new SearchTermParseEvent(null, $terms);
send_event($stpe);
if($stpe->is_querylet_set()) {
foreach($stpe->get_querylets() as $querylet) {
$img_querylets[] = new ImgQuerylet($querylet, true);
}
}
$terms = Tag::resolve_aliases($terms); $terms = Tag::resolve_aliases($terms);
reset($terms); // rewind to first element in array. reset($terms); // rewind to first element in array.
@ -916,56 +1001,33 @@ class Image {
$sql = "0"; $sql = "0";
$terms = array(); $terms = array();
foreach($tag_querylets as $tq) { foreach($tag_querylets as $tq) {
global $tag_n;
$sign = $tq->positive ? "+" : "-"; $sign = $tq->positive ? "+" : "-";
//$sql .= " $sign (tag LIKE :tag$tag_n)"; $sql .= ' '.$sign.' (tag LIKE :tag'.Image::$tag_n.')';
$sql .= ' '.$sign.' (tag LIKE :tag'.$tag_n.')'; $terms['tag'.Image::$tag_n] = $tq->tag;
//$terms["tag$tag_n"] = $tq->tag; Image::$tag_n++;
$terms['tag'.$tag_n] = $tq->tag;
$tag_n++;
if($sign === "+") $positive_tag_count++; if($sign === "+") $positive_tag_count++;
else $negative_tag_count++; else $negative_tag_count++;
} }
$tag_search = new Querylet($sql, $terms); $tag_search = new Querylet($sql, $terms);
$img_search = self::build_img_search($img_querylets);
// merge all the image metadata searches into one generic querylet
$n = 0;
$sql = "";
$terms = array();
foreach($img_querylets as $iq) {
if($n++ > 0) $sql .= " AND";
if(!$iq->positive) $sql .= " NOT";
$sql .= " (" . $iq->qlet->sql . ")";
$terms = array_merge($terms, $iq->qlet->variables);
}
$img_search = new Querylet($sql, $terms);
// no tags, do a simple search (+image metadata if we have any) // no tags, do a simple search (+image metadata if we have any)
if($positive_tag_count + $negative_tag_count == 0) { if($positive_tag_count + $negative_tag_count == 0) {
$query = new Querylet("SELECT images.*,UNIX_TIMESTAMP(posted) AS posted_timestamp FROM images "); $query = self::build_simple_query($img_search);
if(!empty($img_search->sql)) {
$query->append_sql(" WHERE ");
$query->append($img_search);
}
} }
// one positive tag (a common case), do an optimised search // one positive tag (a common case), do an optimised search
else if($positive_tag_count === 1 && $negative_tag_count === 0) { else if($positive_tag_count === 1 && $negative_tag_count === 0) {
$query = new Querylet(
// MySQL is braindead, and does a full table scan on images, running the subquery once for each row -_- // MySQL is braindead, and does a full table scan on images, running the subquery once for each row -_-
// "{$this->get_images} WHERE images.id IN (SELECT image_id FROM tags WHERE tag LIKE ?) ", // "{$this->get_images} WHERE images.id IN (SELECT image_id FROM tags WHERE tag LIKE ?) ",
" $query = new Querylet("
SELECT images.*, UNIX_TIMESTAMP(posted) AS posted_timestamp SELECT images.*
FROM tags, image_tags, images FROM images
WHERE JOIN image_tags ON images.id=image_tags.image_id
tag LIKE :tag0 JOIN tags ON image_tags.tag_id=tags.id
AND tags.id = image_tags.tag_id WHERE tag LIKE :tag0
AND image_tags.image_id = images.id ", $tag_search->variables);
",
$tag_search->variables);
if(!empty($img_search->sql)) { if(!empty($img_search->sql)) {
$query->append_sql(" AND "); $query->append_sql(" AND ");
@ -980,7 +1042,10 @@ class Image {
$x = 0; $x = 0;
foreach($tag_search->variables as $tag) { foreach($tag_search->variables as $tag) {
$tag_ids = $database->get_col("SELECT id FROM tags WHERE tag LIKE :tag", array("tag"=>$tag)); $tag_ids = $database->get_col(
"SELECT id FROM tags WHERE tag LIKE :tag",
array("tag"=>$tag)
);
$tag_id_array = array_merge($tag_id_array, $tag_ids); $tag_id_array = array_merge($tag_id_array, $tag_ids);
$tags_ok = count($tag_ids) > 0 || !$tag_querylets[$x]->positive; $tags_ok = count($tag_ids) > 0 || !$tag_querylets[$x]->positive;
@ -1006,7 +1071,7 @@ class Image {
) )
); );
$query = new Querylet(' $query = new Querylet('
SELECT *, UNIX_TIMESTAMP(posted) AS posted_timestamp SELECT *
FROM ('.$subquery->sql.') AS images ', $subquery->variables); FROM ('.$subquery->sql.') AS images ', $subquery->variables);
if(!empty($img_search->sql)) { if(!empty($img_search->sql)) {
@ -1033,7 +1098,7 @@ class Image {
WHERE 1=0 WHERE 1=0
"); ");
} }
$tag_n = 0; Image::$tag_n = 0;
return $query; return $query;
} }
} }
@ -1051,13 +1116,14 @@ class Tag {
* Remove any excess fluff from a user-input tag * Remove any excess fluff from a user-input tag
* *
* @param string $tag * @param string $tag
* @return mixed * @return string
*/ */
public static function sanitise($tag) { public static function sanitise($tag) {
assert(is_string($tag)); assert('is_string($tag)');
$tag = preg_replace("/[\s?*]/", "", $tag); $tag = preg_replace("/[\s?*]/", "", $tag); # whitespace
$tag = preg_replace("/\.+/", ".", $tag); $tag = preg_replace('/\x20(\x0e|\x0f)/', '', $tag); # unicode RTL
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); $tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
return $tag; return $tag;
} }
@ -1066,10 +1132,10 @@ class Tag {
* *
* @param string|string[] $tags * @param string|string[] $tags
* @param bool $tagme * @param bool $tagme
* @return array * @return string[]
*/ */
public static function explode($tags, $tagme=true) { public static function explode($tags, $tagme=true) {
assert(is_string($tags) || is_array($tags)); assert('is_string($tags) || is_array($tags)');
if(is_string($tags)) { if(is_string($tags)) {
$tags = explode(' ', trim($tags)); $tags = explode(' ', trim($tags));
@ -1100,7 +1166,7 @@ class Tag {
* @return string * @return string
*/ */
public static function implode($tags) { public static function implode($tags) {
assert(is_string($tags) || is_array($tags)); assert('is_string($tags) || is_array($tags)');
if(is_array($tags)) { if(is_array($tags)) {
sort($tags); sort($tags);
@ -1118,7 +1184,8 @@ class Tag {
* @return string * @return string
*/ */
public static function resolve_alias($tag) { public static function resolve_alias($tag) {
assert(is_string($tag)); assert('is_string($tag)');
global $database;
$negative = false; $negative = false;
if(!empty($tag) && ($tag[0] == '-')) { if(!empty($tag) && ($tag[0] == '-')) {
@ -1126,14 +1193,18 @@ class Tag {
$tag = substr($tag, 1); $tag = substr($tag, 1);
} }
global $database;
$newtag = $database->get_one( $newtag = $database->get_one(
$database->scoreql_to_sql("SELECT newtag FROM aliases WHERE SCORE_STRNORM(oldtag)=SCORE_STRNORM(:tag)"), $database->scoreql_to_sql("SELECT newtag FROM aliases WHERE SCORE_STRNORM(oldtag)=SCORE_STRNORM(:tag)"),
array("tag"=>$tag)); array("tag"=>$tag)
);
if(empty($newtag)) { if(empty($newtag)) {
//tag has no alias, use old tag
$newtag = $tag; $newtag = $tag;
} }
return $negative ? "-$newtag" : $newtag;
return !$negative ? $newtag : preg_replace("/(\S+)/", "-$1", $newtag);
} }
/** /**
@ -1156,7 +1227,10 @@ class Tag {
global $database; global $database;
$db_wild_tag = str_replace("%", "\%", $tag); $db_wild_tag = str_replace("%", "\%", $tag);
$db_wild_tag = str_replace("*", "%", $db_wild_tag); $db_wild_tag = str_replace("*", "%", $db_wild_tag);
$newtags = $database->get_col($database->scoreql_to_sql("SELECT tag FROM tags WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(?)"), array($db_wild_tag)); $newtags = $database->get_col(
$database->scoreql_to_sql("SELECT tag FROM tags WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(?)"),
array($db_wild_tag)
);
if(count($newtags) > 0) { if(count($newtags) > 0) {
$resolved = $newtags; $resolved = $newtags;
} else { } else {
@ -1173,7 +1247,7 @@ class Tag {
* @return array * @return array
*/ */
public static function resolve_aliases($tags) { public static function resolve_aliases($tags) {
assert(is_array($tags)); assert('is_array($tags)');
$new = array(); $new = array();
@ -1221,13 +1295,66 @@ function move_upload_to_archive(DataUploadEvent $event) {
return true; return true;
} }
/**
* Add a directory full of images
*
* @param $base string
* @return array
*/
function add_dir($base) {
$results = array();
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;
}
return $results;
}
/**
* @param string $tmpname
* @param string $filename
* @param string $tags
* @throws UploadException
*/
function add_image($tmpname, $filename, $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'] = $tags;
$metadata['source'] = null;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
if($event->image_id == -1) {
throw new UploadException("File type not recognised");
}
}
/** /**
* Given a full size pair of dimensions, return a pair scaled down to fit * Given a full size pair of dimensions, return a pair scaled down to fit
* into the configured thumbnail square, with ratio intact * into the configured thumbnail square, with ratio intact
* *
* @param int $orig_width * @param int $orig_width
* @param int $orig_height * @param int $orig_height
* @return array * @return int[]
*/ */
function get_thumbnail_size(/*int*/ $orig_width, /*int*/ $orig_height) { function get_thumbnail_size(/*int*/ $orig_width, /*int*/ $orig_height) {
global $config; global $config;
@ -1253,4 +1380,3 @@ function get_thumbnail_size(/*int*/ $orig_width, /*int*/ $orig_height) {
} }
} }

View file

@ -65,11 +65,11 @@ class Page {
/** @name "data" mode */ /** @name "data" mode */
//@{ //@{
/** @var string */ /** @var string; public only for unit test */
private $data = ""; public $data = "";
/** @var string */ /** @var string; public only for unit test */
private $filename = null; public $filename = null;
/** /**
* Set the raw data to be sent. * Set the raw data to be sent.
@ -111,6 +111,9 @@ class Page {
/** @name "page" mode */ /** @name "page" mode */
//@{ //@{
/** @var int */
public $code = 200;
/** @var string */ /** @var string */
public $title = ""; public $title = "";
@ -129,9 +132,19 @@ class Page {
/** @var string[] */ /** @var string[] */
public $http_headers = array(); public $http_headers = array();
/** @var string[][] */
public $cookies = array();
/** @var Block[] */ /** @var Block[] */
public $blocks = array(); public $blocks = array();
/**
* Set the HTTP status code
* @param int $code
*/
public function set_code($code) {
$this->code = $code;
}
/** /**
* Set the window title. * Set the window title.
@ -177,6 +190,35 @@ class Page {
$this->http_headers[$position] = $line; $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.
*
* @param string $name
* @param string $value
* @param int $time
* @param string $path
*/
public function add_cookie($name, $value, $time, $path) {
$full_name = COOKIE_PREFIX."_".$name;
$this->cookies[] = array($full_name, $value, $time, $path);
}
/**
* @param string $name
* @return string|null
*/
public function get_cookie(/*string*/ $name) {
$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. * Get all the HTML headers that are currently set and return as a string.
* @return string * @return string
@ -214,11 +256,17 @@ class Page {
public function display() { public function display() {
global $page, $user; global $page, $user;
header("HTTP/1.0 {$this->code} Shimmie");
header("Content-type: ".$this->type); header("Content-type: ".$this->type);
header("X-Powered-By: SCore-".SCORE_VERSION); header("X-Powered-By: SCore-".SCORE_VERSION);
if (!headers_sent()) { if (!headers_sent()) {
foreach($this->http_headers as $head){ header($head); } foreach($this->http_headers as $head) {
header($head);
}
foreach($this->cookies as $c) {
setcookie($c[0], $c[1], $c[2], $c[3]);
}
} else { } else {
print "Error: Headers have already been sent to the client."; print "Error: Headers have already been sent to the client.";
} }
@ -241,6 +289,9 @@ class Page {
# header("Cache-control: no-cache"); # header("Cache-control: no-cache");
# header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT'); # header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
#} #}
if($this->get_cookie("flash_message")) {
$this->add_cookie("flash_message", "", -1, "/");
}
usort($this->blocks, "blockcmp"); usort($this->blocks, "blockcmp");
$this->add_auto_html_headers(); $this->add_auto_html_headers();
$layout = new Layout(); $layout = new Layout();
@ -274,7 +325,7 @@ class Page {
* *
* TODO: This should really be configurable somehow... * TODO: This should really be configurable somehow...
*/ */
protected function add_auto_html_headers() { public function add_auto_html_headers() {
global $config; global $config;
$data_href = get_base_href(); $data_href = get_base_href();
@ -286,8 +337,13 @@ class Page {
$this->add_html_header("<link rel='icon' type='image/x-icon' href='$data_href/favicon.ico'>", 41); $this->add_html_header("<link rel='icon' type='image/x-icon' href='$data_href/favicon.ico'>", 41);
$this->add_html_header("<link rel='apple-touch-icon' href='$data_href/apple-touch-icon.png'>", 42); $this->add_html_header("<link rel='apple-touch-icon' href='$data_href/apple-touch-icon.png'>", 42);
$config_latest = 0;
foreach(zglob("data/config/*") as $conf) {
$config_latest = max($config_latest, filemtime($conf));
}
$css_files = array(); $css_files = array();
$css_latest = 0; $css_latest = $config_latest;
foreach(array_merge(zglob("lib/*.css"), zglob("ext/*/style.css"), zglob("themes/$theme_name/style.css")) as $css) { foreach(array_merge(zglob("lib/*.css"), zglob("ext/*/style.css"), zglob("themes/$theme_name/style.css")) as $css) {
$css_files[] = $css; $css_files[] = $css;
$css_latest = max($css_latest, filemtime($css)); $css_latest = max($css_latest, filemtime($css));
@ -307,7 +363,7 @@ class Page {
$this->add_html_header("<link rel='stylesheet' href='$data_href/$css_cache_file' type='text/css'>", 43); $this->add_html_header("<link rel='stylesheet' href='$data_href/$css_cache_file' type='text/css'>", 43);
$js_files = array(); $js_files = array();
$js_latest = 0; $js_latest = $config_latest;
foreach(array_merge(zglob("lib/*.js"), zglob("ext/*/script.js"), zglob("themes/$theme_name/script.js")) as $js) { foreach(array_merge(zglob("lib/*.js"), zglob("ext/*/script.js"), zglob("themes/$theme_name/script.js")) as $js) {
$js_files[] = $js; $js_files[] = $js;
$js_latest = max($js_latest, filemtime($js)); $js_latest = max($js_latest, filemtime($js));
@ -326,4 +382,3 @@ class Page {
class MockPage extends Page { class MockPage extends Page {
} }

View file

@ -18,6 +18,8 @@
* define("SPEED_HAX", true); * define("SPEED_HAX", true);
* *
*/ */
/** @private */
function _d($name, $value) {if(!defined($name)) define($name, $value);} function _d($name, $value) {if(!defined($name)) define($name, $value);}
_d("DATABASE_DSN", null); // string PDO database connection details _d("DATABASE_DSN", null); // string PDO database connection details
_d("DATABASE_KA", true); // string Keep database connection alive _d("DATABASE_KA", true); // string Keep database connection alive
@ -32,8 +34,9 @@ _d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-
_d("SPEED_HAX", false); // boolean do some questionable things in the name of performance _d("SPEED_HAX", false); // boolean do some questionable things in the name of performance
_d("COMPILE_ELS", false); // boolean pre-build the list of event listeners _d("COMPILE_ELS", false); // boolean pre-build the list of event listeners
_d("NICE_URLS", false); // boolean force niceurl mode _d("NICE_URLS", false); // boolean force niceurl mode
_d("SEARCH_ACCEL", false); // boolean use search accelerator
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse _d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
_d("VERSION", '2.5.4'); // string shimmie version _d("VERSION", '2.5.5'); // string shimmie version
_d("TIMEZONE", null); // string timezone _d("TIMEZONE", null); // string timezone
_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable _d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable
_d("EXTRA_EXTS", ""); // optional extra extensions _d("EXTRA_EXTS", ""); // optional extra extensions

View file

@ -49,7 +49,7 @@ class User {
* @throws SCoreException * @throws SCoreException
*/ */
public function __construct($row) { public function __construct($row) {
global $_user_classes; global $_shm_user_classes;
$this->id = int_escape($row['id']); $this->id = int_escape($row['id']);
$this->name = $row['name']; $this->name = $row['name'];
@ -57,8 +57,8 @@ class User {
$this->join_date = $row['joindate']; $this->join_date = $row['joindate'];
$this->passhash = $row['pass']; $this->passhash = $row['pass'];
if(array_key_exists($row["class"], $_user_classes)) { if(array_key_exists($row["class"], $_shm_user_classes)) {
$this->class = $_user_classes[$row["class"]]; $this->class = $_shm_user_classes[$row["class"]];
} }
else { else {
throw new SCoreException("User '{$this->name}' has invalid class '{$row["class"]}'"); throw new SCoreException("User '{$this->name}' has invalid class '{$row["class"]}'");
@ -94,7 +94,7 @@ class User {
* @return null|User * @return null|User
*/ */
public static function by_id(/*int*/ $id) { public static function by_id(/*int*/ $id) {
assert(is_numeric($id)); assert('is_numeric($id)', var_export($id, true));
global $database; global $database;
if($id === 1) { if($id === 1) {
$cached = $database->cache->get('user-id:'.$id); $cached = $database->cache->get('user-id:'.$id);
@ -111,7 +111,7 @@ class User {
* @return null|User * @return null|User
*/ */
public static function by_name(/*string*/ $name) { public static function by_name(/*string*/ $name) {
assert(is_string($name)); assert('is_string($name)', var_export($name, true));
global $database; global $database;
$row = $database->get_row($database->scoreql_to_sql("SELECT * FROM users WHERE SCORE_STRNORM(name) = SCORE_STRNORM(:name)"), array("name"=>$name)); $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); return is_null($row) ? null : new User($row);
@ -124,8 +124,8 @@ class User {
* @return null|User * @return null|User
*/ */
public static function by_name_and_pass(/*string*/ $name, /*string*/ $pass) { public static function by_name_and_pass(/*string*/ $name, /*string*/ $pass) {
assert(is_string($name)); assert('is_string($name)', var_export($name, true));
assert(is_string($pass)); assert('is_string($pass)', var_export($pass, true));
$user = User::by_name($name); $user = User::by_name($name);
if($user) { if($user) {
if($user->passhash == md5(strtolower($name) . $pass)) { if($user->passhash == md5(strtolower($name) . $pass)) {
@ -143,8 +143,8 @@ class User {
* @return array * @return array
*/ */
public static function by_list(/*int*/ $offset, /*int*/ $limit=50) { public static function by_list(/*int*/ $offset, /*int*/ $limit=50) {
assert(is_numeric($offset)); assert('is_numeric($offset)', var_export($offset, true));
assert(is_numeric($limit)); assert('is_numeric($limit)', var_export($limit, true));
global $database; global $database;
$rows = $database->get_all("SELECT * FROM users WHERE id >= :start AND id < :end", array("start"=>$offset, "end"=>$offset+$limit)); $rows = $database->get_all("SELECT * FROM users WHERE id >= :start AND id < :end", array("start"=>$offset, "end"=>$offset+$limit));
return array_map("_new_user", $rows); return array_map("_new_user", $rows);
@ -196,12 +196,27 @@ class User {
* @param string $class * @param string $class
*/ */
public function set_class(/*string*/ $class) { public function set_class(/*string*/ $class) {
assert(is_string($class)); assert('is_string($class)', var_export($class, true));
global $database; global $database;
$database->Execute("UPDATE users SET class=:class WHERE id=:id", array("class"=>$class, "id"=>$this->id)); $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); log_info("core-user", 'Set class for '.$this->name.' to '.$class);
} }
/**
* @param string $name
* @throws Exception
*/
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}");
}
/** /**
* @param string $password * @param string $password
*/ */

View file

@ -1,8 +1,9 @@
<?php <?php
/** /**
* @global UserClass[] $_user_classes * @global UserClass[] $_shm_user_classes
*/ */
$_user_classes = array(); global $_shm_user_classes;
$_shm_user_classes = array();
/** /**
* Class UserClass * Class UserClass
@ -30,16 +31,16 @@ class UserClass {
* @param array $abilities * @param array $abilities
*/ */
public function __construct($name, $parent=null, $abilities=array()) { public function __construct($name, $parent=null, $abilities=array()) {
global $_user_classes; global $_shm_user_classes;
$this->name = $name; $this->name = $name;
$this->abilities = $abilities; $this->abilities = $abilities;
if(!is_null($parent)) { if(!is_null($parent)) {
$this->parent = $_user_classes[$parent]; $this->parent = $_shm_user_classes[$parent];
} }
$_user_classes[$name] = $this; $_shm_user_classes[$name] = $this;
} }
/** /**
@ -50,8 +51,6 @@ class UserClass {
* @throws SCoreException * @throws SCoreException
*/ */
public function can(/*string*/ $ability) { public function can(/*string*/ $ability) {
global $config;
if(array_key_exists($ability, $this->abilities)) { if(array_key_exists($ability, $this->abilities)) {
$val = $this->abilities[$ability]; $val = $this->abilities[$ability];
return $val; return $val;
@ -60,10 +59,10 @@ class UserClass {
return $this->parent->can($ability); return $this->parent->can($ability);
} }
else { else {
global $_user_classes; global $_shm_user_classes;
$min_dist = 9999; $min_dist = 9999;
$min_ability = null; $min_ability = null;
foreach($_user_classes['base']->abilities as $a => $cando) { foreach($_shm_user_classes['base']->abilities as $a => $cando) {
$v = levenshtein($ability, $a); $v = levenshtein($ability, $a);
if($v < $min_dist) { if($v < $min_dist) {
$min_dist = $v; $min_dist = $v;
@ -90,6 +89,7 @@ new UserClass("base", null, array(
"view_ip" => False, # view IP addresses associated with things "view_ip" => False, # view IP addresses associated with things
"ban_ip" => False, "ban_ip" => False,
"edit_user_name" => False,
"edit_user_password" => False, "edit_user_password" => False,
"edit_user_info" => False, # email address, etc "edit_user_info" => False, # email address, etc
"edit_user_class" => False, "edit_user_class" => False,
@ -155,6 +155,7 @@ new UserClass("admin", "base", array(
"edit_image_lock" => True, "edit_image_lock" => True,
"view_ip" => True, "view_ip" => True,
"ban_ip" => True, "ban_ip" => True,
"edit_user_name" => True,
"edit_user_password" => True, "edit_user_password" => True,
"edit_user_info" => True, "edit_user_info" => True,
"edit_user_class" => True, "edit_user_class" => True,

View file

@ -82,7 +82,7 @@ function sql_escape($input) {
* Turn all manner of HTML / INI / JS / DB booleans into a PHP one * Turn all manner of HTML / INI / JS / DB booleans into a PHP one
* *
* @param $input * @param $input
* @return boolean * @return bool
*/ */
function bool_escape($input) { function bool_escape($input) {
/* /*
@ -124,6 +124,25 @@ function no_escape($input) {
return $input; return $input;
} }
/**
* @param int $val
* @param int|null $min
* @param int|null $max
* @return int
*/
function clamp($val, $min, $max) {
if(!is_numeric($val) || (!is_null($min) && $val < $min)) {
$val = $min;
}
if(!is_null($max) && $val > $max) {
$val = $max;
}
if(!is_null($min) && !is_null($max)) {
assert('$val >= $min && $val <= $max', "$min <= $val <= $max");
}
return $val;
}
/** /**
* @param string $name * @param string $name
* @param array $attrs * @param array $attrs
@ -234,7 +253,7 @@ function autodate($date, $html=true) {
* Check if a given string is a valid date-time. ( Format: yyyy-mm-dd hh:mm:ss ) * Check if a given string is a valid date-time. ( Format: yyyy-mm-dd hh:mm:ss )
* *
* @param $dateTime * @param $dateTime
* @return boolean * @return bool
*/ */
function isValidDateTime($dateTime) { function isValidDateTime($dateTime) {
if (preg_match("/^(\d{4})-(\d{2})-(\d{2}) ([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/", $dateTime, $matches)) { if (preg_match("/^(\d{4})-(\d{2})-(\d{2}) ([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/", $dateTime, $matches)) {
@ -250,7 +269,7 @@ function isValidDateTime($dateTime) {
* Check if a given string is a valid date. ( Format: yyyy-mm-dd ) * Check if a given string is a valid date. ( Format: yyyy-mm-dd )
* *
* @param $date * @param $date
* @return boolean * @return bool
*/ */
function isValidDate($date) { function isValidDate($date) {
if (preg_match("/^(\d{4})-(\d{2})-(\d{2})$/", $date, $matches)) { if (preg_match("/^(\d{4})-(\d{2})-(\d{2})$/", $date, $matches)) {
@ -263,6 +282,94 @@ function isValidDate($date) {
return false; return false;
} }
function validate_input($inputs) {
$outputs = array();
foreach($inputs as $key => $validations) {
$flags = explode(',', $validations);
if(in_array('bool', $flags) && !isset($_POST[$key])) {
$_POST[$key] = 'off';
}
if(in_array('optional', $flags)) {
if(!isset($_POST[$key]) || trim($_POST[$key]) == "") {
$outputs[$key] = null;
continue;
}
}
if(!isset($_POST[$key]) || trim($_POST[$key]) == "") {
throw new InvalidInput("Input '$key' not set");
}
$value = trim($_POST[$key]);
if(in_array('user_id', $flags)) {
$id = int_escape($value);
if(in_array('exists', $flags)) {
if(is_null(User::by_id($id))) {
throw new InvalidInput("User #$id does not exist");
}
}
$outputs[$key] = $id;
}
else if(in_array('user_name', $flags)) {
if(strlen($value) < 1) {
throw new InvalidInput("Username must be at least 1 character");
}
else if(!preg_match('/^[a-zA-Z0-9-_]+$/', $value)) {
throw new InvalidInput(
"Username contains invalid characters. Allowed characters are ".
"letters, numbers, dash, and underscore");
}
$outputs[$key] = $value;
}
else if(in_array('user_class', $flags)) {
global $_shm_user_classes;
if(!array_key_exists($value, $_shm_user_classes)) {
throw new InvalidInput("Invalid user class: ".html_escape($value));
}
$outputs[$key] = $value;
}
else if(in_array('email', $flags)) {
$outputs[$key] = trim($value);
}
else if(in_array('password', $flags)) {
$outputs[$key] = $value;
}
else if(in_array('int', $flags)) {
$value = trim($value);
if(empty($value) || !is_numeric($value)) {
throw new InvalidInput("Invalid int: ".html_escape($value));
}
$outputs[$key] = (int)$value;
}
else if(in_array('bool', $flags)) {
$outputs[$key] = bool_escape($value);
}
else if(in_array('string', $flags)) {
if(in_array('trim', $flags)) {
$value = trim($value);
}
if(in_array('lower', $flags)) {
$value = strtolower($value);
}
if(in_array('not-empty', $flags)) {
throw new InvalidInput("$key must not be blank");
}
if(in_array('nullify', $flags)) {
if(empty($value)) $value = null;
}
$outputs[$key] = $value;
}
else {
throw new InvalidInput("Unknown validation '$validations'");
}
}
return $outputs;
}
/** /**
* Give a HTML string which shows an IP (if the user is allowed to see IPs), * Give a HTML string which shows an IP (if the user is allowed to see IPs),
* and a link to ban that IP (if the user is allowed to ban IPs) * and a link to ban that IP (if the user is allowed to ban IPs)
@ -328,9 +435,7 @@ function make_link($page=null, $query=null) {
if(is_null($page)) $page = $config->get_string('main_page'); if(is_null($page)) $page = $config->get_string('main_page');
if(NICE_URLS || $config->get_bool('nice_urls', false)) { if(NICE_URLS || $config->get_bool('nice_urls', false)) {
#$full = "http://" . $_SERVER["SERVER_NAME"] . $_SERVER["PHP_SELF"]; $base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $_SERVER["PHP_SELF"]);
$full = $_SERVER["PHP_SELF"];
$base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $full);
} }
else { else {
$base = "./".basename($_SERVER["SCRIPT_FILENAME"])."?q="; $base = "./".basename($_SERVER["SCRIPT_FILENAME"])."?q=";
@ -379,7 +484,7 @@ function modify_url($url, $changes) {
unset($changes['q']); unset($changes['q']);
} }
else { else {
$base = $_GET['q']; $base = _get_query();
} }
if(isset($params['q'])) { if(isset($params['q'])) {
@ -402,7 +507,7 @@ function modify_url($url, $changes) {
* @return string * @return string
*/ */
function make_http(/*string*/ $link) { function make_http(/*string*/ $link) {
if(strpos($link, "ttp://") > 0) { if(strpos($link, "://") > 0) {
return $link; return $link;
} }
@ -573,7 +678,6 @@ function is_https_enabled() {
* from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Schönknecht * from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Schönknecht
* and released under the 'Simplified BSD License'. * and released under the 'Simplified BSD License'.
* *
* @internal Used to get mime types
* @param string &$file File path * @param string &$file File path
* @param string $ext * @param string $ext
* @param bool $list * @param bool $list
@ -643,66 +747,6 @@ function getExtension ($mime_type){
return ($ext ? $ext : false); return ($ext ? $ext : false);
} }
/**
* @private
*/
function _version_check() {
$min_version = "5.3.7";
if(version_compare(PHP_VERSION, $min_version) == -1) {
print "
Currently SCore Engine doesn't support versions of PHP lower than $min_version --
if your web host is running an older version, they are dangerously out of
date and you should plan on moving elsewhere.
";
exit;
}
}
/**
* @private
*/
function is_cli() {
return (PHP_SAPI === 'cli');
}
$_execs = 0;
/**
* $db is the connection object
*
* @private
*/
function _count_execs($db, $sql, $inputarray) {
global $_execs;
if ((defined(DEBUG_SQL) && DEBUG_SQL === true) || (!defined(DEBUG_SQL) && @$_GET['DEBUG_SQL'])) {
$fp = @fopen("data/sql.log", "a");
if($fp) {
if(isset($inputarray) && is_array($inputarray)) {
fwrite($fp, preg_replace('/\s+/msi', ' ', $sql)." -- ".join(", ", $inputarray)."\n");
}
else {
fwrite($fp, preg_replace('/\s+/msi', ' ', $sql)."\n");
}
fclose($fp);
}
else {
# WARNING:
# SQL queries happen before the event system is fully initialised
# (eg, "select theme from config" happens before "load themes"),
# so using the event system to report an error will create some
# really weird looking bugs.
#
#log_error("core", "failed to open sql.log for appending");
}
}
if (!is_array($inputarray)) $_execs++;
# handle 2-dimensional input arrays
else if (is_array(reset($inputarray))) $_execs += sizeof($inputarray);
else $_execs++;
# in PHP4.4 and PHP5, we need to return a value by reference
$null = null; return $null;
}
/** /**
* Compare two Block objects, used to sort them before being displayed * Compare two Block objects, used to sort them before being displayed
* *
@ -781,37 +825,6 @@ function get_session_ip(Config $config) {
return $addr; return $addr;
} }
/**
* similar to $_COOKIE[$name], but $name has the site-wide cookie
* prefix prepended to it, eg username -> shm_username, to prevent
* conflicts from multiple installs within one domain.
*/
function get_prefixed_cookie(/*string*/ $name) {
global $config;
$full_name = COOKIE_PREFIX."_".$name;
if(isset($_COOKIE[$full_name])) {
return $_COOKIE[$full_name];
}
else {
return null;
}
}
/**
* The counterpart for get_prefixed_cookie, this works like php's
* setcookie method, but prepends the site-wide cookie prefix to
* the $name argument before doing anything.
*
* @param string $name
* @param string $value
* @param int $time
* @param string $path
*/
function set_prefixed_cookie($name, $value, $time, $path) {
global $config;
$full_name = COOKIE_PREFIX."_".$name;
setcookie($full_name, $value, $time, $path);
}
/** /**
* Set (or extend) a flash-message cookie. * Set (or extend) a flash-message cookie.
@ -826,13 +839,14 @@ function set_prefixed_cookie($name, $value, $time, $path) {
* @param string $type * @param string $type
*/ */
function flash_message(/*string*/ $text, /*string*/ $type="info") { function flash_message(/*string*/ $text, /*string*/ $type="info") {
$current = get_prefixed_cookie("flash_message"); global $page;
$current = $page->get_cookie("flash_message");
if($current) { if($current) {
$text = $current . "\n" . $text; $text = $current . "\n" . $text;
} }
# the message should be viewed pretty much immediately, # the message should be viewed pretty much immediately,
# so 60s timeout should be more than enough # so 60s timeout should be more than enough
set_prefixed_cookie("flash_message", $text, time()+60, "/"); $page->add_cookie("flash_message", $text, time()+60, "/");
} }
/** /**
@ -846,10 +860,11 @@ function flash_message(/*string*/ $text, /*string*/ $type="info") {
* @return string * @return string
*/ */
function get_base_href() { function get_base_href() {
if(defined("BASE_HREF")) return BASE_HREF;
$possible_vars = array('SCRIPT_NAME', 'PHP_SELF', 'PATH_INFO', 'ORIG_PATH_INFO'); $possible_vars = array('SCRIPT_NAME', 'PHP_SELF', 'PATH_INFO', 'ORIG_PATH_INFO');
$ok_var = null; $ok_var = null;
foreach($possible_vars as $var) { foreach($possible_vars as $var) {
if(substr($_SERVER[$var], -4) === '.php') { if(isset($_SERVER[$var]) && substr($_SERVER[$var], -4) === '.php') {
$ok_var = $_SERVER[$var]; $ok_var = $_SERVER[$var];
break; break;
} }
@ -944,21 +959,19 @@ function transload($url, $mfile) {
} }
if($config->get_string("transload_engine") === "fopen") { if($config->get_string("transload_engine") === "fopen") {
$fp = @fopen($url, "r"); $fp_in = @fopen($url, "r");
if(!$fp) { $fp_out = fopen($mfile, "w");
if(!$fp_in || !$fp_out) {
return false; return false;
} }
$data = "";
$length = 0; $length = 0;
while(!feof($fp) && $length <= $config->get_int('upload_size')) { while(!feof($fp_in) && $length <= $config->get_int('upload_size')) {
$data .= fread($fp, 8192); $data = fread($fp_in, 8192);
$length = strlen($data); $length += strlen($data);
fwrite($fp_out, $data);
} }
fclose($fp); fclose($fp_in);
fclose($fp_out);
$fp = fopen($mfile, "w");
fwrite($fp, $data);
fclose($fp);
$headers = http_parse_headers(implode("\n", $http_response_header)); $headers = http_parse_headers(implode("\n", $http_response_header));
@ -991,24 +1004,35 @@ if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/func
} }
} }
/**
* HTTP Headers can sometimes be lowercase which will cause issues.
* In cases like these, we need to make sure to check for them if the camelcase version does not exist.
*
* @param array $headers
* @param mixed $name
* @return mixed
*/
function findHeader ($headers, $name) { function findHeader ($headers, $name) {
//HTTP Headers can sometimes be lowercase which will cause issues. if (!is_array($headers)) {
//In cases like these, we need to make sure to check for them if the camelcase version does not exist. return false;
$header = FALSE; }
$header = false;
if(array_key_exists($name, $headers)) { if(array_key_exists($name, $headers)) {
$header = $headers[$name]; $header = $headers[$name];
} else { } else {
$headers = array_change_key_case($headers); $headers = array_change_key_case($headers); // convert all to lower case.
if(array_key_exists(strtolower($name), $headers)){ $lc_name = strtolower($name);
$header = $headers[strtolower($name)];
if(array_key_exists($lc_name, $headers)) {
$header = $headers[$lc_name];
} }
} }
return $header; return $header;
} }
$_included = array();
/** /**
* Get the active contents of a .php file * Get the active contents of a .php file
* *
@ -1016,13 +1040,13 @@ $_included = array();
* @return string|null * @return string|null
*/ */
function manual_include($fname) { function manual_include($fname) {
global $_included; static $included = array();
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";
@ -1039,10 +1063,6 @@ function manual_include($fname) {
// @include_once is used for user-creatable config files // @include_once is used for user-creatable config files
$text = preg_replace('/@include_once "(.*)";/e', "manual_include('$1')", $text); $text = preg_replace('/@include_once "(.*)";/e', "manual_include('$1')", $text);
// wibble the defines for HipHop's sake
$text = str_replace('function _d(', '// function _messed_d(', $text);
$text = preg_replace('/_d\("(.*)", (.*)\);/', 'if(!defined("$1")) define("$1", $2);', $text);
return $text; return $text;
} }
@ -1072,49 +1092,49 @@ define("SCORE_LOG_NOTSET", 0);
* @param string $section * @param string $section
* @param int $priority * @param int $priority
* @param string $message * @param string $message
* @param null|bool|string $flash * @param bool|string $flash
* @param array $args * @param array $args
*/ */
function log_msg(/*string*/ $section, /*int*/ $priority, /*string*/ $message, $flash=null, $args=array()) { function log_msg(/*string*/ $section, /*int*/ $priority, /*string*/ $message, $flash=false, $args=array()) {
send_event(new LogEvent($section, $priority, $message, $args)); send_event(new LogEvent($section, $priority, $message, $args));
$threshold = defined("CLI_LOG_LEVEL") ? CLI_LOG_LEVEL : 0; $threshold = defined("CLI_LOG_LEVEL") ? CLI_LOG_LEVEL : 0;
if(is_cli() && ($priority >= $threshold)) {
if((PHP_SAPI === 'cli') && ($priority >= $threshold)) {
print date("c")." $section: $message\n"; print date("c")." $section: $message\n";
} }
if($flash === True) { if($flash === true) {
flash_message($message); flash_message($message);
} }
else if(!is_null($flash)) { else if(is_string($flash)) {
flash_message($flash); flash_message($flash);
} }
} }
// More shorthand ways of logging // More shorthand ways of logging
function log_debug( /*string*/ $section, /*string*/ $message, $flash=null, $args=array()) {log_msg($section, SCORE_LOG_DEBUG, $message, $flash, $args);} function log_debug( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_DEBUG, $message, $flash, $args);}
function log_info( /*string*/ $section, /*string*/ $message, $flash=null, $args=array()) {log_msg($section, SCORE_LOG_INFO, $message, $flash, $args);} function log_info( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_INFO, $message, $flash, $args);}
function log_warning( /*string*/ $section, /*string*/ $message, $flash=null, $args=array()) {log_msg($section, SCORE_LOG_WARNING, $message, $flash, $args);} function log_warning( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_WARNING, $message, $flash, $args);}
function log_error( /*string*/ $section, /*string*/ $message, $flash=null, $args=array()) {log_msg($section, SCORE_LOG_ERROR, $message, $flash, $args);} function log_error( /*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_ERROR, $message, $flash, $args);}
function log_critical(/*string*/ $section, /*string*/ $message, $flash=null, $args=array()) {log_msg($section, SCORE_LOG_CRITICAL, $message, $flash, $args);} function log_critical(/*string*/ $section, /*string*/ $message, $flash=false, $args=array()) {log_msg($section, SCORE_LOG_CRITICAL, $message, $flash, $args);}
$_request_id = null;
/** /**
* Get a unique ID for this request, useful for grouping log messages. * Get a unique ID for this request, useful for grouping log messages.
* *
* @return null|string * @return null|string
*/ */
function get_request_id() { function get_request_id() {
global $_request_id; static $request_id = null;
if(!$_request_id) { if(!$request_id) {
// not completely trustworthy, as a user can spoof this // not completely trustworthy, as a user can spoof this
if(@$_SERVER['HTTP_X_VARNISH']) { if(@$_SERVER['HTTP_X_VARNISH']) {
$_request_id = $_SERVER['HTTP_X_VARNISH']; $request_id = $_SERVER['HTTP_X_VARNISH'];
} }
else { else {
$_request_id = "P" . uniqid(); $request_id = "P" . uniqid();
} }
} }
return $_request_id; return $request_id;
} }
@ -1206,6 +1226,8 @@ function ip_in_range($IP, $CIDR) {
* *
* from a patch by Christian Walde; only intended for use in the * from a patch by Christian Walde; only intended for use in the
* "extension manager" extension, but it seems to fit better here * "extension manager" extension, but it seems to fit better here
*
* @param string $f
*/ */
function deltree($f) { function deltree($f) {
//Because Windows (I know, bad excuse) //Because Windows (I know, bad excuse)
@ -1249,6 +1271,9 @@ function deltree($f) {
* Copy an entire file hierarchy * Copy an entire file hierarchy
* *
* from a comment on http://uk.php.net/copy * from a comment on http://uk.php.net/copy
*
* @param string $source
* @param string $target
*/ */
function full_copy($source, $target) { function full_copy($source, $target) {
if(is_dir($source)) { if(is_dir($source)) {
@ -1275,34 +1300,168 @@ function full_copy($source, $target) {
} }
} }
/**
* Return a list of all the regular files in a directory and subdirectories
*
* @param string $base
* @param string $_sub_dir
* @return array file list
*/
function list_files(/*string*/ $base, $_sub_dir="") {
assert(is_dir($base));
$file_list = array();
$files = array();
$dir = opendir("$base/$_sub_dir");
while($f = readdir($dir)) {
$files[] = $f;
}
closedir($dir);
sort($files);
foreach($files as $filename) {
$full_path = "$base/$_sub_dir/$filename";
if(is_link($full_path)) {
// ignore
}
else if(is_dir($full_path)) {
if(!($filename == "." || $filename == "..")) {
//subdirectory found
$file_list = array_merge(
$file_list,
list_files($base, "$_sub_dir/$filename")
);
}
}
else {
$full_path = str_replace("//", "/", $full_path);
$file_list[] = $full_path;
}
}
return $file_list;
}
function path_to_tags($path) {
$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;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Event API * * Event API *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/** @private */ /** @private */
$_event_listeners = array(); global $_shm_event_listeners;
$_shm_event_listeners = array();
/** function _load_event_listeners() {
* Register an Extension. global $_shm_event_listeners;
*
* @param Extension $extension ctx_log_start("Loading extensions");
* @param int $pos
* @param array $events $cache_path = data_path("cache/shm_event_listeners.php");
*/ if(COMPILE_ELS && file_exists($cache_path)) {
function add_event_listener(Extension $extension, $pos=50, $events=array()) { require_once($cache_path);
global $_event_listeners; }
$pos *= 100; else {
foreach($events as $event) { _set_event_listeners();
while(isset($_event_listeners[$event][$pos])) {
if(COMPILE_ELS) {
_dump_event_listeners($_shm_event_listeners, $cache_path);
}
}
ctx_log_endok();
}
function _set_event_listeners() {
global $_shm_event_listeners;
$_shm_event_listeners = array();
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();
$extension->i_am($extension);
// 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; $pos += 1;
} }
$_event_listeners[$event][$pos] = $extension; $_shm_event_listeners[$event][$pos] = $extension;
}
}
}
} }
} }
function _dump_event_listeners($event_listeners, $path) {
$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(); ";
$p .= "\${$class}->i_am(\$$class);\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);
}
/**
* @param $ext_name string
* @return bool
*/
function ext_is_live($ext_name) {
if (class_exists($ext_name)) {
/** @var Extension $ext */
$ext = new $ext_name();
return $ext->is_live();
}
return false;
}
/** @private */ /** @private */
$_event_count = 0; global $_shm_event_count;
$_shm_event_count = 0;
/** /**
* Send an event to all registered Extensions. * Send an event to all registered Extensions.
@ -1310,23 +1469,27 @@ $_event_count = 0;
* @param Event $event * @param Event $event
*/ */
function send_event(Event $event) { function send_event(Event $event) {
global $_event_listeners, $_event_count; global $_shm_event_listeners, $_shm_event_count;
if(!isset($_event_listeners[get_class($event)])) return; if(!isset($_shm_event_listeners[get_class($event)])) return;
$method_name = "on".str_replace("Event", "", get_class($event)); $method_name = "on".str_replace("Event", "", get_class($event));
ctx_log_start(get_class($event)); // send_event() is performance sensitive, and with the number
// of times context gets called the time starts to add up
$ctx = constant('CONTEXT');
if($ctx) ctx_log_start(get_class($event));
// SHIT: http://bugs.php.net/bug.php?id=35106 // SHIT: http://bugs.php.net/bug.php?id=35106
$my_event_listeners = $_event_listeners[get_class($event)]; $my_event_listeners = $_shm_event_listeners[get_class($event)];
ksort($my_event_listeners); ksort($my_event_listeners);
foreach($my_event_listeners as $listener) { foreach($my_event_listeners as $listener) {
ctx_log_start(get_class($listener)); if($ctx) ctx_log_start(get_class($listener));
if(method_exists($listener, $method_name)) { if(method_exists($listener, $method_name)) {
$listener->$method_name($event); $listener->$method_name($event);
} }
ctx_log_endok(); if($ctx) ctx_log_endok();
} }
$_event_count++; $_shm_event_count++;
ctx_log_endok(); if($ctx) ctx_log_endok();
} }
@ -1337,7 +1500,7 @@ function send_event(Event $event) {
// SHIT by default this returns the time as a string. And it's not even a // SHIT by default this returns the time as a string. And it's not even a
// string representation of a number, it's two numbers separated by a space. // string representation of a number, it's two numbers separated by a space.
// What the fuck were the PHP developers smoking. // What the fuck were the PHP developers smoking.
$_load_start = microtime(true); $_shm_load_start = microtime(true);
/** /**
* Collects some debug information (execution time, memory usage, queries, etc) * Collects some debug information (execution time, memory usage, queries, etc)
@ -1346,7 +1509,7 @@ $_load_start = microtime(true);
* @return string debug info to add to the page. * @return string debug info to add to the page.
*/ */
function get_debug_info() { function get_debug_info() {
global $config, $_event_count, $database, $_execs, $_load_start; global $config, $_shm_event_count, $database, $_shm_load_start;
$i_mem = sprintf("%5.2f", ((memory_get_peak_usage(true)+512)/1024)/1024); $i_mem = sprintf("%5.2f", ((memory_get_peak_usage(true)+512)/1024)/1024);
@ -1356,21 +1519,31 @@ function get_debug_info() {
else { else {
$commit = " (".$config->get_string("commit_hash").")"; $commit = " (".$config->get_string("commit_hash").")";
} }
$time = sprintf("%.2f", microtime(true) - $_load_start); $time = sprintf("%.2f", microtime(true) - $_shm_load_start);
$dbtime = sprintf("%.2f", $database->dbtime); $dbtime = sprintf("%.2f", $database->dbtime);
$i_files = count(get_included_files()); $i_files = count(get_included_files());
$hits = $database->cache->get_hits(); $hits = $database->cache->get_hits();
$miss = $database->cache->get_misses(); $miss = $database->cache->get_misses();
$debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM"; $debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM";
$debug .= "; Used $i_files files and $_execs queries"; $debug .= "; Used $i_files files and {$database->query_count} queries";
$debug .= "; Sent $_event_count events"; $debug .= "; Sent $_shm_event_count events";
$debug .= "; $hits cache hits and $miss misses"; $debug .= "; $hits cache hits and $miss misses";
$debug .= "; Shimmie version ". VERSION . $commit; // .", SCore Version ". SCORE_VERSION; $debug .= "; Shimmie version ". VERSION . $commit; // .", SCore Version ". SCORE_VERSION;
return $debug; return $debug;
} }
function 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>");
*/
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Request initialisation stuff * * Request initialisation stuff *
@ -1378,12 +1551,16 @@ function get_debug_info() {
/** @privatesection */ /** @privatesection */
/** function _version_check() {
* @param array|string $arr $min_version = "5.4.8";
* @return array|string if(version_compare(PHP_VERSION, $min_version) == -1) {
*/ print "
function _stripslashes_r($arr) { Currently SCore Engine doesn't support versions of PHP lower than $min_version --
return is_array($arr) ? array_map('_stripslashes_r', $arr) : stripslashes($arr); if your web host is running an older version, they are dangerously out of
date and you should plan on moving elsewhere.
";
exit;
}
} }
function _sanitise_environment() { function _sanitise_environment() {
@ -1393,11 +1570,15 @@ function _sanitise_environment() {
if(DEBUG) { if(DEBUG) {
error_reporting(E_ALL); 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');
} }
if(CONTEXT) { if(CONTEXT) {
ctx_set_log(CONTEXT); ctx_set_log(CONTEXT);
ctx_log_start(@$_SERVER["REQUEST_URI"], true, true);
} }
if(COVERAGE) { if(COVERAGE) {
@ -1405,18 +1586,9 @@ function _sanitise_environment() {
register_shutdown_function("_end_coverage"); register_shutdown_function("_end_coverage");
} }
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_BAIL, 1);
ob_start(); ob_start();
if(get_magic_quotes_gpc()) { if(PHP_SAPI === 'cli') {
$_GET = _stripslashes_r($_GET);
$_POST = _stripslashes_r($_POST);
$_COOKIE = _stripslashes_r($_COOKIE);
}
if(is_cli()) {
if(isset($_SERVER['REMOTE_ADDR'])) { if(isset($_SERVER['REMOTE_ADDR'])) {
die("CLI with remote addr? Confused, not taking the risk."); die("CLI with remote addr? Confused, not taking the risk.");
} }
@ -1425,6 +1597,7 @@ function _sanitise_environment() {
} }
} }
/** /**
* @param string $_theme * @param string $_theme
* @return array * @return array
@ -1441,73 +1614,6 @@ function _get_themelet_files($_theme) {
return array_merge($base_themelets, $ext_themelets, $custom_themelets); return array_merge($base_themelets, $ext_themelets, $custom_themelets);
} }
function _set_event_listeners($classes) {
global $_event_listeners;
$_event_listeners = array();
foreach($classes as $class) {
$rclass = new ReflectionClass($class);
if($rclass->isAbstract()) {
// don't do anything
}
elseif(is_subclass_of($class, "Extension")) {
$c = new $class();
$c->i_am($c);
$my_events = array();
foreach(get_class_methods($c) as $method) {
if(substr($method, 0, 2) == "on") {
$my_events[] = substr($method, 2) . "Event";
}
}
add_event_listener($c, $c->get_priority(), $my_events);
}
}
}
function _dump_event_listeners($event_listeners, $path) {
$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(); ";
$p .= "\${$class}->i_am(\$$class);\n";
}
}
$p .= "\$_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);
}
function _load_extensions() {
global $_event_listeners;
ctx_log_start("Loading extensions");
if(COMPILE_ELS && file_exists("data/cache/event_listeners.php")) {
require_once("data/cache/event_listeners.php");
}
else {
_set_event_listeners(get_declared_classes());
if(COMPILE_ELS) {
_dump_event_listeners($_event_listeners, data_path("cache/event_listeners.php"));
}
}
ctx_log_endok();
}
/** /**
* Used to display fatal errors to the web user. * Used to display fatal errors to the web user.
@ -1532,7 +1638,7 @@ function _fatal_error(Exception $e) {
<body> <body>
<h1>Internal Error</h1> <h1>Internal Error</h1>
<p><b>Message:</b> '.$message.' <p><b>Message:</b> '.$message.'
<p><b>Version:</b> '.$version.' <p><b>Version:</b> '.$version.' (on '.phpversion().')
</body> </body>
</html> </html>
'; ';
@ -1568,10 +1674,10 @@ function _decaret($str) {
* @return User * @return User
*/ */
function _get_user() { function _get_user() {
global $config; global $config, $page;
$user = null; $user = null;
if(get_prefixed_cookie("user") && get_prefixed_cookie("session")) { if($page->get_cookie("user") && $page->get_cookie("session")) {
$tmp_user = User::by_session(get_prefixed_cookie("user"), get_prefixed_cookie("session")); $tmp_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session"));
if(!is_null($tmp_user)) { if(!is_null($tmp_user)) {
$user = $tmp_user; $user = $tmp_user;
} }
@ -1584,6 +1690,10 @@ function _get_user() {
return $user; return $user;
} }
function _get_query() {
return @$_POST["q"]?:@$_GET["q"];
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Code coverage * * Code coverage *

View file

@ -119,7 +119,7 @@ class AdminPage extends Extension {
} }
private function delete_by_query() { private function delete_by_query() {
global $page, $user; global $page;
$query = $_POST['query']; $query = $_POST['query'];
$reason = @$_POST['reason']; $reason = @$_POST['reason'];
assert(strlen($query) > 1); assert(strlen($query) > 1);

View file

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

View file

@ -20,7 +20,7 @@ class AdminPageTheme extends Themelet {
*/ */
protected function button(/*string*/ $name, /*string*/ $action, /*boolean*/ $protected=false) { protected function button(/*string*/ $name, /*string*/ $action, /*boolean*/ $protected=false) {
$c_protected = $protected ? " protected" : ""; $c_protected = $protected ? " protected" : "";
$html = make_form(make_link("admin/$action"), "POST", false, null, null, "admin$c_protected"); $html = make_form(make_link("admin/$action"), "POST", false, "admin$c_protected");
if($protected) { if($protected) {
$html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>"; $html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>";
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>"; $html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
@ -36,7 +36,6 @@ class AdminPageTheme extends Themelet {
* Show a form which links to admin_utils with POST[action] set to one of: * Show a form which links to admin_utils with POST[action] set to one of:
* 'lowercase all tags' * 'lowercase all tags'
* 'recount tag use' * 'recount tag use'
* 'purge unused tags'
* etc * etc
*/ */
public function display_form() { public function display_form() {
@ -53,7 +52,7 @@ class AdminPageTheme extends Themelet {
$page->add_block(new Block("Misc Admin Tools", $html)); $page->add_block(new Block("Misc Admin Tools", $html));
$html = make_form(make_link("admin/set_tag_case"), "POST"); $html = make_form(make_link("admin/set_tag_case"), "POST");
$html .= "<input type='text' name='tag' placeholder='Enter tag with correct case'>"; $html .= "<input type='text' name='tag' placeholder='Enter tag with correct case' class='autocomplete_tags' autocomplete='off'>";
$html .= "<input type='submit' value='Set Tag Case'>"; $html .= "<input type='submit' value='Set Tag Case'>";
$html .= "</form>\n"; $html .= "</form>\n";
$page->add_block(new Block("Set Tag Case", $html)); $page->add_block(new Block("Set Tag Case", $html));

View file

@ -84,7 +84,8 @@ class AliasEditor extends Extension {
} }
else if($event->get_arg(0) == "export") { else if($event->get_arg(0) == "export") {
$page->set_mode("data"); $page->set_mode("data");
$page->set_type("text/plain"); $page->set_type("text/csv");
$page->set_filename("aliases.csv");
$page->set_data($this->get_alias_csv($database)); $page->set_data($this->get_alias_csv($database));
} }
else if($event->get_arg(0) == "import") { else if($event->get_arg(0) == "import") {
@ -152,11 +153,12 @@ class AliasEditor extends Extension {
foreach(explode("\n", $csv) as $line) { foreach(explode("\n", $csv) as $line) {
$parts = str_getcsv($line); $parts = str_getcsv($line);
if(count($parts) == 2) { if(count($parts) == 2) {
$pair = array("oldtag" => $parts[0], "newtag" => $parts[1]); try {
if(!$database->get_row("SELECT * FROM aliases WHERE oldtag=:oldtag AND lower(newtag)=lower(:newtag)", $pair)){ $aae = new AddAliasEvent($parts[0], $parts[1]);
if(!$database->get_row("SELECT * FROM aliases WHERE oldtag=:newtag", array("newtag" => $pair['newtag']))){ send_event($aae);
$database->execute("INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)", $pair);
} }
catch(AddAliasException $ex) {
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
} }
} }
} }

View file

@ -1,16 +1,20 @@
<?php <?php
class AliasEditorTest extends ShimmieWebTestCase { class AliasEditorTest extends ShimmiePHPUnitTestCase {
public function testAliasEditor() { public function testAliasList() {
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_response(200);
$this->assert_title("Alias List"); $this->assert_title("Alias List");
}
public function testAliasListReadOnly() {
// Check that normal users can't add aliases. // Check that normal users can't add aliases.
$this->log_in_as_user(); $this->log_in_as_user();
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_title("Alias List"); $this->assert_title("Alias List");
$this->assert_no_text("Add"); $this->assert_no_text("Add");
$this->log_out(); }
public function testAliasEditor() {
/* /*
********************************************************************** **********************************************************************
* FIXME: TODO: * FIXME: TODO:
@ -22,6 +26,8 @@ class AliasEditorTest extends ShimmieWebTestCase {
* dig into this and determine exactly what is happening. * dig into this and determine exactly what is happening.
* *
********************************************************************* *********************************************************************
*/
$this->markTestIncomplete();
$this->log_in_as_admin(); $this->log_in_as_admin();
@ -39,7 +45,7 @@ class AliasEditorTest extends ShimmieWebTestCase {
$this->get_page("alias/export/aliases.csv"); $this->get_page("alias/export/aliases.csv");
$this->assert_text("test1,test2"); $this->assert_text("test1,test2");
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test1"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
$this->get_page("post/view/$image_id"); # check that the tag has been replaced $this->get_page("post/view/$image_id"); # check that the tag has been replaced
$this->assert_title("Image $image_id: test2"); $this->assert_title("Image $image_id: test2");
$this->get_page("post/list/test1/1"); # searching for an alias should find the master tag $this->get_page("post/list/test1/1"); # searching for an alias should find the master tag
@ -67,8 +73,8 @@ class AliasEditorTest extends ShimmieWebTestCase {
$this->get_page("alias/export/aliases.csv"); $this->get_page("alias/export/aliases.csv");
$this->assert_text("onetag,multi tag"); $this->assert_text("onetag,multi tag");
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "onetag"); $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "onetag");
$image_id_2 = $this->post_image("ext/simpletest/data/bedroom_workshop.jpg", "onetag"); $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "onetag");
// FIXME: known broken // FIXME: known broken
//$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases //$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases
//$this->assert_title("onetag"); //$this->assert_title("onetag");
@ -93,8 +99,6 @@ class AliasEditorTest extends ShimmieWebTestCase {
$this->get_page('alias/list'); $this->get_page('alias/list');
$this->assert_title("Alias List"); $this->assert_title("Alias List");
$this->assert_no_text("Add"); $this->assert_no_text("Add");
*/
} }
} }

View file

@ -19,8 +19,8 @@ class AliasEditorTheme extends Themelet {
$h_add = " $h_add = "
<tr> <tr>
".make_form(make_link("alias/add"))." ".make_form(make_link("alias/add"))."
<td><input type='text' name='oldtag'></td> <td><input type='text' name='oldtag' class='autocomplete_tags' autocomplete='off'></td>
<td><input type='text' name='newtag'></td> <td><input type='text' name='newtag' class='autocomplete_tags' autocomplete='off'></td>
<td><input type='submit' value='Add'></td> <td><input type='submit' value='Add'></td>
</form> </form>
</tr> </tr>
@ -55,7 +55,7 @@ class AliasEditorTheme extends Themelet {
<tbody>$h_aliases</tbody> <tbody>$h_aliases</tbody>
<tfoot>$h_add</tfoot> <tfoot>$h_add</tfoot>
</table> </table>
<p><a href='".make_link("alias/export/aliases.csv")."'>Download as CSV</a></p> <p><a href='".make_link("alias/export/aliases.csv")."' download='aliases.csv'>Download as CSV</a></p>
"; ";
$bulk_html = " $bulk_html = "

View file

@ -67,8 +67,8 @@ class UploadS3 extends Extension {
if(!empty($bucket)) { if(!empty($bucket)) {
log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3"); log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3");
$s3 = new S3($access, $secret); $s3 = new S3($access, $secret);
$s3->deleteObject($bucket, "images/"+$event->image->hash); $s3->deleteObject($bucket, "images/" . $event->image->hash);
$s3->deleteObject($bucket, "thumbs/"+$event->image->hash); $s3->deleteObject($bucket, "thumbs/" . $event->image->hash);
} }
} }
} }

View file

@ -67,7 +67,7 @@ class ArrowkeyNavigation extends Extension {
$images_per_page = $config->get_int('index_images'); $images_per_page = $config->get_int('index_images');
// if there are no tags, use default // if there are no tags, use default
if ($event->get_arg(1) == null){ if (is_null($event->get_arg(1))){
$prefix = ""; $prefix = "";
$page_number = int_escape($event->get_arg(0)); $page_number = int_escape($event->get_arg(0));
$total_pages = ceil($database->get_one( $total_pages = ceil($database->get_one(

File diff suppressed because it is too large Load diff

View file

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

View file

@ -77,8 +77,7 @@ class ArtistsTheme extends Themelet {
if($html) $page->add_block(new Block("Manage Artists", $html, "left", 10)); if($html) $page->add_block(new Block("Manage Artists", $html, "left", 10));
} }
public function show_artist_editor($artist, $aliases, $members, $urls) public function show_artist_editor($artist, $aliases, $members, $urls) {
{
global $user; global $user;
$artistName = $artist['name']; $artistName = $artist['name'];
@ -88,8 +87,7 @@ class ArtistsTheme extends Themelet {
// aliases // aliases
$aliasesString = ""; $aliasesString = "";
$aliasesIDsString = ""; $aliasesIDsString = "";
foreach ($aliases as $alias) foreach ($aliases as $alias) {
{
$aliasesString .= $alias["alias_name"]." "; $aliasesString .= $alias["alias_name"]." ";
$aliasesIDsString .= $alias["alias_id"]." "; $aliasesIDsString .= $alias["alias_id"]." ";
} }
@ -99,8 +97,7 @@ class ArtistsTheme extends Themelet {
// members // members
$membersString = ""; $membersString = "";
$membersIDsString = ""; $membersIDsString = "";
foreach ($members as $member) foreach ($members as $member) {
{
$membersString .= $member["name"]." "; $membersString .= $member["name"]." ";
$membersIDsString .= $member["id"]." "; $membersIDsString .= $member["id"]." ";
} }
@ -110,16 +107,14 @@ class ArtistsTheme extends Themelet {
// urls // urls
$urlsString = ""; $urlsString = "";
$urlsIDsString = ""; $urlsIDsString = "";
foreach ($urls as $url) foreach ($urls as $url) {
{
$urlsString .= $url["url"]."\n"; $urlsString .= $url["url"]."\n";
$urlsIDsString .= $url["id"]." "; $urlsIDsString .= $url["id"]." ";
} }
$urlsString = substr($urlsString, 0, strlen($urlsString) -1); $urlsString = substr($urlsString, 0, strlen($urlsString) -1);
$urlsIDsString = rtrim($urlsIDsString); $urlsIDsString = rtrim($urlsIDsString);
$html = $html = '
'
<form method="POST" action="'.make_link("artist/edited/".$artist['id']).'"> <form method="POST" action="'.make_link("artist/edited/".$artist['id']).'">
'.$user->get_auth_html().' '.$user->get_auth_html().'
<table> <table>
@ -135,15 +130,13 @@ class ArtistsTheme extends Themelet {
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr> <tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table> </table>
</form> </form>
'; ';
global $page; global $page;
$page->add_block(new Block("Edit artist", $html, "main", 10)); $page->add_block(new Block("Edit artist", $html, "main", 10));
} }
public function new_artist_composer() public function new_artist_composer() {
{
global $page, $user; global $page, $user;
$html = "<form action=".make_link("artist/create")." method='POST'> $html = "<form action=".make_link("artist/create")." method='POST'>
@ -163,8 +156,7 @@ class ArtistsTheme extends Themelet {
$page->add_block(new Block("Artists", $html, "main", 10)); $page->add_block(new Block("Artists", $html, "main", 10));
} }
public function list_artists($artists, $pageNumber, $totalPages) public function list_artists($artists, $pageNumber, $totalPages) {
{
global $user, $page; global $user, $page;
$html = "<table id='poolsList' class='zebra'>". $html = "<table id='poolsList' class='zebra'>".
@ -174,27 +166,26 @@ class ArtistsTheme extends Themelet {
"<th>Last updater</th>". "<th>Last updater</th>".
"<th>Posts</th>"; "<th>Posts</th>";
if(!$user->is_anonymous()) $html .= "<th colspan='2'>Action</th>"; // space for edit link if(!$user->is_anonymous()) $html .= "<th colspan='2'>Action</th>"; // space for edit link
$html .= "</tr></thead>"; $html .= "</tr></thead>";
$deletionLinkActionArray = $deletionLinkActionArray = array(
array('artist' => 'artist/nuke/' 'artist' => 'artist/nuke/',
, 'alias' => 'artist/alias/delete/' 'alias' => 'artist/alias/delete/',
, 'member' => 'artist/member/delete/' 'member' => 'artist/member/delete/',
); );
$editionLinkActionArray = $editionLinkActionArray = array(
array('artist' => 'artist/edit/' 'artist' => 'artist/edit/',
, 'alias' => 'artist/alias/edit/' 'alias' => 'artist/alias/edit/',
, 'member' => 'artist/member/edit/' 'member' => 'artist/member/edit/',
); );
$typeTextArray = $typeTextArray = array(
array('artist' => 'Artist' 'artist' => 'Artist',
, 'alias' => 'Alias' 'alias' => 'Alias',
, 'member' => 'Member' 'member' => 'Member',
); );
foreach ($artists as $artist) { foreach ($artists as $artist) {
@ -236,12 +227,11 @@ class ArtistsTheme extends Themelet {
$this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages); $this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages);
} }
public function show_new_alias_composer($artistID) public function show_new_alias_composer($artistID) {
{
global $user; global $user;
$html = $html = '
'<form method="POST" action='.make_link("artist/alias/add").'> <form method="POST" action='.make_link("artist/alias/add").'>
'.$user->get_auth_html().' '.$user->get_auth_html().'
<table> <table>
<tr><td>Alias:</td><td><input type="text" name="aliases" /> <tr><td>Alias:</td><td><input type="text" name="aliases" />
@ -254,12 +244,12 @@ class ArtistsTheme extends Themelet {
global $page; global $page;
$page->add_block(new Block("Artist Aliases", $html, "main", 20)); $page->add_block(new Block("Artist Aliases", $html, "main", 20));
} }
public function show_new_member_composer($artistID)
{ public function show_new_member_composer($artistID) {
global $user; global $user;
$html = $html = '
' <form method="POST" action='.make_link("artist/member/add").'> <form method="POST" action='.make_link("artist/member/add").'>
'.$user->get_auth_html().' '.$user->get_auth_html().'
<table> <table>
<tr><td>Members:</td><td><input type="text" name="members" /> <tr><td>Members:</td><td><input type="text" name="members" />
@ -273,12 +263,11 @@ class ArtistsTheme extends Themelet {
$page->add_block(new Block("Artist members", $html, "main", 30)); $page->add_block(new Block("Artist members", $html, "main", 30));
} }
public function show_new_url_composer($artistID) public function show_new_url_composer($artistID) {
{
global $user; global $user;
$html = $html = '
' <form method="POST" action='.make_link("artist/url/add").'> <form method="POST" action='.make_link("artist/url/add").'>
'.$user->get_auth_html().' '.$user->get_auth_html().'
<table> <table>
<tr><td>URL:</td><td><textarea name="urls"></textarea> <tr><td>URL:</td><td><textarea name="urls"></textarea>
@ -292,12 +281,10 @@ class ArtistsTheme extends Themelet {
$page->add_block(new Block("Artist URLs", $html, "main", 40)); $page->add_block(new Block("Artist URLs", $html, "main", 40));
} }
public function show_alias_editor($alias) public function show_alias_editor($alias) {
{
global $user; global $user;
$html = $html = '
'
<form method="POST" action="'.make_link("artist/alias/edited/".$alias['id']).'"> <form method="POST" action="'.make_link("artist/alias/edited/".$alias['id']).'">
'.$user->get_auth_html().' '.$user->get_auth_html().'
<label for="alias">Alias:</label> <label for="alias">Alias:</label>
@ -311,12 +298,10 @@ class ArtistsTheme extends Themelet {
$page->add_block(new Block("Edit Alias", $html, "main", 10)); $page->add_block(new Block("Edit Alias", $html, "main", 10));
} }
public function show_url_editor($url) public function show_url_editor($url) {
{
global $user; global $user;
$html = $html = '
'
<form method="POST" action="'.make_link("artist/url/edited/".$url['id']).'"> <form method="POST" action="'.make_link("artist/url/edited/".$url['id']).'">
'.$user->get_auth_html().' '.$user->get_auth_html().'
<label for="url">URL:</label> <label for="url">URL:</label>
@ -330,12 +315,10 @@ class ArtistsTheme extends Themelet {
$page->add_block(new Block("Edit URL", $html, "main", 10)); $page->add_block(new Block("Edit URL", $html, "main", 10));
} }
public function show_member_editor($member) public function show_member_editor($member) {
{
global $user; global $user;
$html = $html = '
'
<form method="POST" action="'.make_link("artist/member/edited/".$member['id']).'"> <form method="POST" action="'.make_link("artist/member/edited/".$member['id']).'">
'.$user->get_auth_html().' '.$user->get_auth_html().'
<label for="member">Member name:</label> <label for="member">Member name:</label>
@ -349,8 +332,7 @@ class ArtistsTheme extends Themelet {
$page->add_block(new Block("Edit Member", $html, "main", 10)); $page->add_block(new Block("Edit Member", $html, "main", 10));
} }
public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin) public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin) {
{
global $page; global $page;
$artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>"; $artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>";
@ -361,11 +343,8 @@ class ArtistsTheme extends Themelet {
<th></th> <th></th>
<th></th>"; <th></th>";
if ($userIsLogged) if ($userIsLogged) $html .= "<th></th>";
$html .= "<th></th>"; if ($userIsAdmin) $html .= "<th></th>";
if ($userIsAdmin)
$html .= "<th></th>";
$html .= " <tr> $html .= " <tr>
</thead> </thead>
@ -377,124 +356,11 @@ class ArtistsTheme extends Themelet {
if ($userIsAdmin) $html .= "<td></td>"; if ($userIsAdmin) $html .= "<td></td>";
$html .= "</tr>"; $html .= "</tr>";
if (count($aliases) > 0) $html .= $this->render_aliases($aliases, $userIsLogged, $userIsAdmin);
{ $html .= $this->render_members($members, $userIsLogged, $userIsAdmin);
$aliasViewLink = str_replace("_", " ", $aliases[0]['alias_name']); // no link anymore $html .= $this->render_urls($urls, $userIsLogged, $userIsAdmin);
$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 ($userIsAdmin)
$html .= "<td class='left'>".$aliasDeleteLink."</td>";
$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>";
$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>";
$html .= "</tr>";
}
}
}
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>
<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>";
$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>";
$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>";
$html .= "</tr>";
}
}
}
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>
<td class='left'>URLs:</td>
<td class='left'>".$urlViewLink."</td>";
if ($userIsLogged)
$html .= "<td class='left'>".$urlEditLink."</td>";
if ($userIsAdmin)
$html .= "<td class='left'>".$urlDeleteLink."</td>";
$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>";
$html .= "<tr>
<td class='left'>&nbsp;</td>
<td class='left'>".$urlViewLink."</td>";
if ($userIsLogged)
$html .= "<td class='left'>".$urlEditLink."</td>";
if ($userIsAdmin)
$html .= "<td class='left'>".$urlDeleteLink."</td>";
$html .= "</tr>";
}
}
}
$html .=
"<tr>
<td class='left'>Notes:</td> <td class='left'>Notes:</td>
<td class='left'>".$artist["notes"]."</td>"; <td class='left'>".$artist["notes"]."</td>";
if ($userIsLogged) $html .= "<td></td>"; if ($userIsLogged) $html .= "<td></td>";
@ -511,7 +377,6 @@ class ArtistsTheme extends Themelet {
//we show the images for the artist //we show the images for the artist
$artist_images = ""; $artist_images = "";
foreach($images as $image) { foreach($images as $image) {
$thumb_html = $this->build_thumb_html($image); $thumb_html = $this->build_thumb_html($image);
$artist_images .= '<span class="thumb">'. $artist_images .= '<span class="thumb">'.
@ -522,5 +387,143 @@ class ArtistsTheme extends Themelet {
$page->add_block(new Block("Artist Images", $artist_images, "main", 20)); $page->add_block(new Block("Artist Images", $artist_images, "main", 20));
} }
/**
* @param $aliases
* @param $userIsLogged
* @param $userIsAdmin
* @return string
*/
private function render_aliases($aliases, $userIsLogged, $userIsAdmin) {
$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>
<td class='left'>Aliases:</td>
<td class='left'>" . $aliasViewLink . "</td>";
if ($userIsLogged)
$html .= "<td class='left'>" . $aliasEditLink . "</td>";
if ($userIsAdmin)
$html .= "<td class='left'>" . $aliasDeleteLink . "</td>";
$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>";
$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>";
$html .= "</tr>";
}
}
}
return $html;
}
/**
* @param $members
* @param $userIsLogged
* @param $userIsAdmin
* @return string
*/
private function render_members($members, $userIsLogged, $userIsAdmin) {
$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>
<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>";
$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>";
$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>";
$html .= "</tr>";
}
}
}
return $html;
}
/**
* @param $urls
* @param $userIsLogged
* @param $userIsAdmin
* @return string
*/
private function render_urls($urls, $userIsLogged, $userIsAdmin) {
$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>
<td class='left'>URLs:</td>
<td class='left'>" . $urlViewLink . "</td>";
if ($userIsLogged)
$html .= "<td class='left'>" . $urlEditLink . "</td>";
if ($userIsAdmin)
$html .= "<td class='left'>" . $urlDeleteLink . "</td>";
$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>";
$html .= "<tr>
<td class='left'>&nbsp;</td>
<td class='left'>" . $urlViewLink . "</td>";
if ($userIsLogged)
$html .= "<td class='left'>" . $urlEditLink . "</td>";
if ($userIsAdmin)
$html .= "<td class='left'>" . $urlDeleteLink . "</td>";
$html .= "</tr>";
}
return $html;
}
}
return $html;
}
} }

View file

@ -1,45 +1,33 @@
<?php <?php
class BanWordsTest extends ShimmieWebTestCase { 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() { public function testWordBan() {
$this->log_in_as_admin(); global $config;
$this->get_page("setup"); $config->set_string("banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
$this->set_field("_config_banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
$this->click("Save Settings");
$this->log_out();
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$this->get_page("post/view/$image_id"); $this->check_blocked($image_id, "kittens and viagra");
$this->set_field('comment', "kittens and viagra"); $this->check_blocked($image_id, "kittens and ViagrA");
$this->click("Post Comment"); $this->check_blocked($image_id, "kittens and viagra!");
$this->assert_title("Comment Blocked"); $this->check_blocked($image_id, "some link to http://something.cn/");
$this->get_page("post/view/$image_id");
$this->set_field('comment', "kittens and ViagrA");
$this->click("Post Comment");
$this->assert_title("Comment Blocked");
$this->get_page("post/view/$image_id");
$this->set_field('comment', "kittens and viagra!");
$this->click("Post Comment");
$this->assert_title("Comment Blocked");
$this->get_page("post/view/$image_id");
$this->set_field('comment', "some link to http://something.cn/");
$this->click("Post Comment");
$this->assert_title("Comment Blocked");
$this->get_page('comment/list'); $this->get_page('comment/list');
$this->assert_title('Comments'); $this->assert_title('Comments');
$this->assert_no_text('viagra'); $this->assert_no_text('viagra');
$this->assert_no_text('ViagrA'); $this->assert_no_text('ViagrA');
$this->assert_no_text('http://something.cn/'); $this->assert_no_text('http://something.cn/');
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
} }
} }

View file

@ -93,7 +93,7 @@ class BBCode extends FormatterExtension {
/** /**
* @param string $text * @param string $text
* @return mixed * @return string
*/ */
private function filter_spoiler(/*string*/ $text) { private function filter_spoiler(/*string*/ $text) {
return str_replace( return str_replace(

View file

@ -1,78 +1,73 @@
<?php <?php
# FIXME: web test class BBCodeTest extends ShimmiePHPUnitTestCase {
class BBCodeTest extends ShimmieWebTestCase {}
if(!defined(VERSION)) return;
class BBCodeUnitTest extends UnitTestCase {
public function testBasics() { public function testBasics() {
$this->assertEqual( $this->assertEquals(
$this->filter("[b]bold[/b][i]italic[/i]"), $this->filter("[b]bold[/b][i]italic[/i]"),
"<b>bold</b><i>italic</i>"); "<b>bold</b><i>italic</i>");
} }
public function testStacking() { public function testStacking() {
$this->assertEqual( $this->assertEquals(
$this->filter("[b]B[/b][i]I[/i][b]B[/b]"), $this->filter("[b]B[/b][i]I[/i][b]B[/b]"),
"<b>B</b><i>I</i><b>B</b>"); "<b>B</b><i>I</i><b>B</b>");
$this->assertEqual( $this->assertEquals(
$this->filter("[b]bold[i]bolditalic[/i]bold[/b]"), $this->filter("[b]bold[i]bolditalic[/i]bold[/b]"),
"<b>bold<i>bolditalic</i>bold</b>"); "<b>bold<i>bolditalic</i>bold</b>");
} }
public function testFailure() { public function testFailure() {
$this->assertEqual( $this->assertEquals(
$this->filter("[b]bold[i]italic"), $this->filter("[b]bold[i]italic"),
"[b]bold[i]italic"); "[b]bold[i]italic");
} }
public function testCode() { public function testCode() {
$this->assertEqual( $this->assertEquals(
$this->filter("[code][b]bold[/b][/code]"), $this->filter("[code][b]bold[/b][/code]"),
"<pre>[b]bold[/b]</pre>"); "<pre>[b]bold[/b]</pre>");
} }
public function testNestedList() { public function testNestedList() {
$this->assertEqual( $this->assertEquals(
$this->filter("[list][*]a[list][*]a[*]b[/list][*]b[/list]"), $this->filter("[list][*]a[list][*]a[*]b[/list][*]b[/list]"),
"<ul><li>a<ul><li>a<li>b</ul><li>b</ul>"); "<ul><li>a<ul><li>a<li>b</ul><li>b</ul>");
$this->assertEqual( $this->assertEquals(
$this->filter("[ul][*]a[ol][*]a[*]b[/ol][*]b[/ul]"), $this->filter("[ul][*]a[ol][*]a[*]b[/ol][*]b[/ul]"),
"<ul><li>a<ol><li>a<li>b</ol><li>b</ul>"); "<ul><li>a<ol><li>a<li>b</ol><li>b</ul>");
} }
public function testSpoiler() { public function testSpoiler() {
$this->assertEqual( $this->assertEquals(
$this->filter("[spoiler]ShishNet[/spoiler]"), $this->filter("[spoiler]ShishNet[/spoiler]"),
"<span style=\"background-color:#000; color:#000;\">ShishNet</span>"); "<span style=\"background-color:#000; color:#000;\">ShishNet</span>");
$this->assertEqual( $this->assertEquals(
$this->strip("[spoiler]ShishNet[/spoiler]"), $this->strip("[spoiler]ShishNet[/spoiler]"),
"FuvfuArg"); "FuvfuArg");
#$this->assertEqual( #$this->assertEquals(
# $this->filter("[spoiler]ShishNet"), # $this->filter("[spoiler]ShishNet"),
# "[spoiler]ShishNet"); # "[spoiler]ShishNet");
} }
public function testURL() { public function testURL() {
$this->assertEqual( $this->assertEquals(
$this->filter("[url]http://shishnet.org[/url]"), $this->filter("[url]http://shishnet.org[/url]"),
"<a href=\"http://shishnet.org\">http://shishnet.org</a>"); "<a href=\"http://shishnet.org\">http://shishnet.org</a>");
$this->assertEqual( $this->assertEquals(
$this->filter("[url=http://shishnet.org]ShishNet[/url]"), $this->filter("[url=http://shishnet.org]ShishNet[/url]"),
"<a href=\"http://shishnet.org\">ShishNet</a>"); "<a href=\"http://shishnet.org\">ShishNet</a>");
$this->assertEqual( $this->assertEquals(
$this->filter("[url=javascript:alert(\"owned\")]click to fail[/url]"), $this->filter("[url=javascript:alert(\"owned\")]click to fail[/url]"),
"[url=javascript:alert(\"owned\")]click to fail[/url]"); "[url=javascript:alert(\"owned\")]click to fail[/url]");
} }
public function testEmailURL() { public function testEmailURL() {
$this->assertEqual( $this->assertEquals(
$this->filter("[email]spam@shishnet.org[/email]"), $this->filter("[email]spam@shishnet.org[/email]"),
"<a href=\"mailto:spam@shishnet.org\">spam@shishnet.org</a>"); "<a href=\"mailto:spam@shishnet.org\">spam@shishnet.org</a>");
} }
public function testAnchor() { public function testAnchor() {
$this->assertEqual( $this->assertEquals(
$this->filter("[anchor=rules]Rules[/anchor]"), $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>'); '<span class="anchor">Rules <a class="alink" href="#bb-rules" name="bb-rules" title="link to this anchor"> ¶ </a></span>');
} }

View file

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

View file

@ -1,7 +1,7 @@
<?php <?php
class BlocksTheme extends Themelet { class BlocksTheme extends Themelet {
public function display_blocks($blocks) { public function display_blocks($blocks) {
global $page, $user; global $page;
$html = "<table class='form' style='width: 100%;'>"; $html = "<table class='form' style='width: 100%;'>";
foreach($blocks as $block) { foreach($blocks as $block) {

View file

@ -32,8 +32,8 @@ class Blotter extends Extension {
important SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N important SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N
"); ");
// Insert sample data: // Insert sample data:
$database->execute("INSERT INTO blotter (id, entry_date, entry_text, important) VALUES (?, now(), ?, ?)", $database->execute("INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), ?, ?)",
array(NULL, "Installed the blotter extension!", "Y")); array("Installed the blotter extension!", "Y"));
log_info("blotter", "Installed tables for blotter extension."); log_info("blotter", "Installed tables for blotter extension.");
$config->set_int("blotter_version", 1); $config->set_int("blotter_version", 1);
} }
@ -41,11 +41,9 @@ class Blotter extends Extension {
$config->set_default_int("blotter_recent", 5); $config->set_default_int("blotter_recent", 5);
$config->set_default_string("blotter_color", "FF0000"); $config->set_default_string("blotter_color", "FF0000");
$config->set_default_string("blotter_position", "subheading"); $config->set_default_string("blotter_position", "subheading");
} }
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
global $config;
$sb = new SetupBlock("Blotter"); $sb = new SetupBlock("Blotter");
$sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: "); $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_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) ");

View file

@ -1,10 +1,10 @@
<?php <?php
class BlotterTest extends SCoreWebTestCase { class BlotterTest extends ShimmiePHPUnitTestCase {
public function testLogin() { public function testLogin() {
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->assert_text("Blotter Editor"); //$this->assert_text("Blotter Editor");
$this->click("Blotter Editor"); //$this->click("Blotter Editor");
$this->log_out(); //$this->log_out();
} }
public function testDenial() { public function testDenial() {
@ -20,18 +20,15 @@ class BlotterTest extends SCoreWebTestCase {
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("blotter/editor"); $this->get_page("blotter/editor");
$this->set_field("entry_text", "blotter testing"); //$this->set_field("entry_text", "blotter testing");
$this->click("Add"); //$this->click("Add");
$this->assert_text("blotter testing"); //$this->assert_text("blotter testing");
$this->get_page("blotter"); $this->get_page("blotter");
$this->assert_text("blotter testing"); //$this->assert_text("blotter testing");
$this->get_page("blotter/editor"); $this->get_page("blotter/editor");
$this->click("Remove"); //$this->click("Remove");
$this->assert_no_text("blotter testing"); //$this->assert_no_text("blotter testing");
$this->log_out();
} }
} }

View file

@ -1,5 +1,5 @@
<?php <?php
class BookmarksTest extends ShimmieWebTestCase { class BookmarksTest extends ShimmiePHPUnitTestCase {
public function testBookmarks() { public function testBookmarks() {
$this->get_page("bookmark/add"); $this->get_page("bookmark/add");
$this->get_page("bookmark/remove"); $this->get_page("bookmark/remove");

View file

@ -1,5 +1,5 @@
<?php <?php
class BrowserSearchTest extends SCoreWebTestCase { class BrowserSearchTest extends ShimmiePHPUnitTestCase {
public function testBasic() { 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/please_dont_use_this_tag_as_it_would_break_stuff__search.xml");
$this->get_page("browser_search/test"); $this->get_page("browser_search/test");

View file

@ -15,13 +15,26 @@
* <p><b>Note:</b> requires the "admin" extension to be enabled * <p><b>Note:</b> requires the "admin" extension to be enabled
*/ */
class BulkAddEvent extends Event {
public $dir, $results;
public function __construct($dir) {
$this->dir = $dir;
$this->results = array();
}
}
class BulkAdd extends Extension { class BulkAdd extends Extension {
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event) {
global $page, $user; global $page, $user;
if($event->page_matches("bulk_add")) { if($event->page_matches("bulk_add")) {
if($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) { if($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) {
set_time_limit(0); set_time_limit(0);
$this->add_dir($_POST['dir']); $bae = new BulkAddEvent($_POST['dir']);
send_event($bae);
if(strlen($bae->results) > 0) {
$this->theme->add_status("Adding files", $bae->results);
}
$this->theme->display_upload_results($page); $this->theme->display_upload_results($page);
} }
} }
@ -29,12 +42,14 @@ class BulkAdd extends Extension {
public function onCommand(CommandEvent $event) { public function onCommand(CommandEvent $event) {
if($event->cmd == "help") { if($event->cmd == "help") {
print " bulk-add [directory]\n"; print "\tbulk-add [directory]\n";
print " Import this directory\n\n"; print "\t\tImport this directory\n\n";
} }
if($event->cmd == "bulk-add") { if($event->cmd == "bulk-add") {
if(count($event->args) == 1) { if(count($event->args) == 1) {
$this->add_dir($event->args[0]); $bae = new BulkAddEvent($event->args[0]);
send_event($bae);
print(implode("\n", $bae->results));
} }
} }
} }
@ -43,72 +58,13 @@ class BulkAdd extends Extension {
$this->theme->display_admin_block(); $this->theme->display_admin_block();
} }
/** public function onBulkAdd(BulkAddEvent $event) {
* Generate the necessary DataUploadEvent for a given image and tags. if(is_dir($event->dir) && is_readable($event->dir)) {
*/ $event->results = add_dir($event->dir);
private function add_image($tmpname, $filename, $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'] = $tags;
$metadata['source'] = null;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
if($event->image_id == -1) {
throw new UploadException("File type not recognised");
}
}
private function add_dir(/*string*/ $base, $subdir="") {
if(!is_dir($base)) {
$this->theme->add_status("Error", "$base is not a directory");
return;
}
$list = "";
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->add_dir($base, str_replace($base, "", $fullpath));
} }
else { else {
$pathinfo = pathinfo($fullpath); $h_dir = html_escape($event->dir);
$matches = array(); $event->results[] = "Error, $h_dir is not a readable directory";
if(preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", $pathinfo["basename"], $matches)) {
$tags = $matches[1];
}
else {
$tags = $subdir;
$tags = str_replace("/", " ", $tags);
$tags = str_replace("__", " ", $tags);
$tags = trim($tags);
}
$list .= "<br>".html_escape("$shortpath (".str_replace(" ", ", ", $tags).")... ");
try{
$this->add_image($fullpath, $pathinfo["basename"], $tags);
$list .= "ok\n";
}
catch(Exception $ex) {
$list .= "failed:<br>". $ex->getMessage();
} }
} }
} }
if(strlen($list) > 0) {
$this->theme->add_status("Adding $subdir", $list);
}
}
}

View file

@ -1,18 +1,22 @@
<?php <?php
class BulkAddTest extends ShimmieWebTestCase { class BulkAddTest extends ShimmiePHPUnitTestCase {
public function testBulkAdd() { public function testBulkAdd() {
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page('admin'); $this->get_page('admin');
$this->assert_title("Admin Tools"); $this->assert_title("Admin Tools");
$this->set_field('dir', "asdf");
$this->click("Add"); $bae = new BulkAddEvent('asdf');
$this->assert_text("is not a directory"); 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();
$this->get_page('admin'); $this->get_page('admin');
$this->assert_title("Admin Tools"); $this->assert_title("Admin Tools");
$this->set_field('dir', "contrib/simpletest"); send_event(new BulkAddEvent('tests'));
$this->click("Add");
# 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"
@ -31,4 +35,3 @@ class BulkAddTest extends ShimmieWebTestCase {
$this->log_out(); $this->log_out();
} }
} }

View file

@ -10,9 +10,11 @@ class BulkAddTheme extends Themelet {
$page->set_title("Adding folder"); $page->set_title("Adding folder");
$page->set_heading("Adding folder"); $page->set_heading("Adding folder");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
$html = "";
foreach($this->messages as $block) { foreach($this->messages as $block) {
$page->add_block($block); $html .= "<br/>" . html_escape($html);
} }
$page->add_block(new Block("Results", $block));
} }
/* /*
@ -21,7 +23,7 @@ class BulkAddTheme extends Themelet {
* directory full of images * directory full of images
*/ */
public function display_admin_block() { public function display_admin_block() {
global $page, $user; global $page;
$html = " $html = "
Add a folder full of images; any subfolders will have their names Add a folder full of images; any subfolders will have their names
used as tags for the images within. used as tags for the images within.
@ -42,4 +44,3 @@ class BulkAddTheme extends Themelet {
$this->messages[] = new Block($title, $body); $this->messages[] = new Block($title, $body);
} }
} }

View file

@ -54,6 +54,14 @@ class BulkAddCSV extends Extension {
/** /**
* Generate the necessary DataUploadEvent for a given image and tags. * Generate the necessary DataUploadEvent for a given image and tags.
*
* @param string $tmpname
* @param string $filename
* @param string $tags
* @param string $source
* @param string $rating
* @param string $thumbfile
* @throws UploadException
*/ */
private function add_image($tmpname, $filename, $tags, $source, $rating, $thumbfile) { private function add_image($tmpname, $filename, $tags, $source, $rating, $thumbfile) {
assert(file_exists($tmpname)); assert(file_exists($tmpname));

View file

@ -21,7 +21,7 @@ class BulkAddCSVTheme extends Themelet {
* csv file * csv file
*/ */
public function display_admin_block() { public function display_admin_block() {
global $page, $user; global $page;
$html = " $html = "
Add images from a csv. Images will be tagged and have their Add images from a csv. Images will be tagged and have their
source and rating set (if \"Image Ratings\" is enabled) source and rating set (if \"Image Ratings\" is enabled)

View file

@ -25,6 +25,7 @@ class CommentPostingEvent extends Event {
* @param string $comment * @param string $comment
*/ */
public function __construct($image_id, $user, $comment) { public function __construct($image_id, $user, $comment) {
assert('is_numeric($image_id)');
$this->image_id = $image_id; $this->image_id = $image_id;
$this->user = $user; $this->user = $user;
$this->comment = $comment; $this->comment = $comment;
@ -44,6 +45,7 @@ class CommentDeletionEvent extends Event {
* @param int $comment_id * @param int $comment_id
*/ */
public function __construct($comment_id) { public function __construct($comment_id) {
assert('is_numeric($comment_id)');
$this->comment_id = $comment_id; $this->comment_id = $comment_id;
} }
} }
@ -69,12 +71,16 @@ class Comment {
} }
/** /**
* @param \User $user * @param User $user
* @return mixed * @return mixed
*/ */
public static function count_comments_by_user($user) { public static function count_comments_by_user($user) {
global $database; global $database;
return $database->get_one("SELECT COUNT(*) AS count FROM comments WHERE owner_id=:owner_id", array("owner_id"=>$user->id)); return $database->get_one("
SELECT COUNT(*) AS count
FROM comments
WHERE owner_id=:owner_id
", array("owner_id"=>$user->id));
} }
/** /**
@ -87,6 +93,9 @@ class Comment {
} }
class CommentList extends Extension { class CommentList extends Extension {
/** @var CommentListTheme $theme */
var $theme;
public function onInitExt(InitExtEvent $event) { public function onInitExt(InitExtEvent $event) {
global $config, $database; global $config, $database;
$config->set_default_int('comment_window', 5); $config->set_default_int('comment_window', 5);
@ -145,9 +154,19 @@ class CommentList extends Extension {
} }
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event) {
global $page, $user, $database;
if($event->page_matches("comment")) { if($event->page_matches("comment")) {
if($event->get_arg(0) === "add") { switch($event->get_arg(0)) {
case "add": $this->onPageRequest_add(); break;
case "delete": $this->onPageRequest_delete($event); break;
case "bulk_delete": $this->onPageRequest_bulk_delete(); break;
case "list": $this->onPageRequest_list($event); break;
case "beta-search": $this->onPageRequest_beta_search($event); break;
}
}
}
private function onPageRequest_add() {
global $user, $page;
if (isset($_POST['image_id']) && isset($_POST['comment'])) { if (isset($_POST['image_id']) && isset($_POST['comment'])) {
try { try {
$i_iid = int_escape($_POST['image_id']); $i_iid = int_escape($_POST['image_id']);
@ -155,13 +174,14 @@ class CommentList extends Extension {
send_event($cpe); send_event($cpe);
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid")); $page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
} } catch (CommentPostingException $ex) {
catch(CommentPostingException $ex) {
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage()); $this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
} }
} }
} }
else if($event->get_arg(0) === "delete") {
private function onPageRequest_delete(PageRequestEvent $event) {
global $user, $page;
if ($user->can("delete_comment")) { if ($user->can("delete_comment")) {
// FIXME: post, not args // FIXME: post, not args
if ($event->count_args() === 3) { if ($event->count_args() === 3) {
@ -170,52 +190,55 @@ class CommentList extends Extension {
$page->set_mode("redirect"); $page->set_mode("redirect");
if (!empty($_SERVER['HTTP_REFERER'])) { if (!empty($_SERVER['HTTP_REFERER'])) {
$page->set_redirect($_SERVER['HTTP_REFERER']); $page->set_redirect($_SERVER['HTTP_REFERER']);
} } else {
else {
$page->set_redirect(make_link("post/view/" . $event->get_arg(2))); $page->set_redirect(make_link("post/view/" . $event->get_arg(2)));
} }
} }
} } else {
else {
$this->theme->display_permission_denied(); $this->theme->display_permission_denied();
} }
} }
else if($event->get_arg(0) === "bulk_delete") {
private function onPageRequest_bulk_delete() {
global $user, $database, $page;
if ($user->can("delete_comment") && !empty($_POST["ip"])) { if ($user->can("delete_comment") && !empty($_POST["ip"])) {
$ip = $_POST['ip']; $ip = $_POST['ip'];
$cids = $database->get_col("SELECT id FROM comments WHERE owner_ip=:ip", array("ip"=>$ip)); $comment_ids = $database->get_col("
$num = count($cids); SELECT id
FROM comments
WHERE owner_ip=:ip
", array("ip" => $ip));
$num = count($comment_ids);
log_warning("comment", "Deleting $num comments from $ip"); log_warning("comment", "Deleting $num comments from $ip");
foreach($cids as $cid) { foreach($comment_ids as $cid) {
send_event(new CommentDeletionEvent($cid)); send_event(new CommentDeletionEvent($cid));
} }
flash_message("Deleted $num comments"); flash_message("Deleted $num comments");
$page->set_mode("redirect"); $page->set_mode("redirect");
$page->set_redirect(make_link("admin")); $page->set_redirect(make_link("admin"));
} } else {
else {
$this->theme->display_permission_denied(); $this->theme->display_permission_denied();
} }
} }
else if($event->get_arg(0) === "list") {
private function onPageRequest_list(PageRequestEvent $event) {
$page_num = int_escape($event->get_arg(1)); $page_num = int_escape($event->get_arg(1));
$this->build_page($page_num); $this->build_page($page_num);
} }
else if($event->get_arg(0) === "beta-search") {
private function onPageRequest_beta_search(PageRequestEvent $event) {
$search = $event->get_arg(1); $search = $event->get_arg(1);
$page_num = int_escape($event->get_arg(2)); $page_num = int_escape($event->get_arg(2));
$duser = User::by_name($search); $duser = User::by_name($search);
$i_comment_count = Comment::count_comments_by_user($duser); $i_comment_count = Comment::count_comments_by_user($duser);
$com_per_page = 50; $com_per_page = 50;
$total_pages = ceil($i_comment_count / $com_per_page); $total_pages = ceil($i_comment_count / $com_per_page);
$page_num = $this->sanity_check_pagenumber($page_num, $total_pages); $page_num = clamp($page_num, 1, $total_pages);
$comments = $this->get_user_comments($duser->id, $com_per_page, ($page_num - 1) * $com_per_page); $comments = $this->get_user_comments($duser->id, $com_per_page, ($page_num - 1) * $com_per_page);
$this->theme->display_all_user_comments($comments, $page_num, $total_pages, $duser); $this->theme->display_all_user_comments($comments, $page_num, $total_pages, $duser);
} }
}
}
public function onAdminBuilding(AdminBuildingEvent $event) { public function onAdminBuilding(AdminBuildingEvent $event) {
$this->theme->display_admin_block(); $this->theme->display_admin_block();
@ -262,7 +285,10 @@ class CommentList extends Extension {
public function onCommentDeletion(CommentDeletionEvent $event) { public function onCommentDeletion(CommentDeletionEvent $event) {
global $database; global $database;
$database->Execute("DELETE FROM comments WHERE id=:comment_id", array("comment_id"=>$event->comment_id)); $database->Execute("
DELETE FROM comments
WHERE id=:comment_id
", array("comment_id"=>$event->comment_id));
log_info("comment", "Deleting Comment #{$event->comment_id}"); log_info("comment", "Deleting Comment #{$event->comment_id}");
} }
@ -286,8 +312,6 @@ class CommentList extends Extension {
} }
public function onSearchTermParse(SearchTermParseEvent $event) { public function onSearchTermParse(SearchTermParseEvent $event) {
global $database;
$matches = array(); $matches = array();
if(preg_match("/^comments([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) { if(preg_match("/^comments([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
@ -318,48 +342,46 @@ class CommentList extends Extension {
private function build_page(/*int*/ $current_page) { private function build_page(/*int*/ $current_page) {
global $database, $user; global $database, $user;
if(class_exists("Ratings")) {
$user_ratings = Ratings::get_user_privs($user);
} else {
$user_ratings = "";
}
$where = SPEED_HAX ? "WHERE posted > now() - interval '24 hours'" : ""; $where = SPEED_HAX ? "WHERE posted > now() - interval '24 hours'" : "";
$total_pages = $database->cache->get("comment_pages"); $total_pages = $database->cache->get("comment_pages");
if(empty($total_pages)) { if(empty($total_pages)) {
$total_pages = (int)($database->get_one("SELECT COUNT(c1) FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1") / 10); $total_pages = (int)($database->get_one("
SELECT COUNT(c1)
FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
") / 10);
$database->cache->set("comment_pages", $total_pages, 600); $database->cache->set("comment_pages", $total_pages, 600);
} }
$total_pages = max($total_pages, 1);
if(is_null($current_page) || $current_page <= 0) { $current_page = clamp($current_page, 1, $total_pages);
$current_page = 1;
}
$current_page = $this->sanity_check_pagenumber($current_page, $total_pages);
$threads_per_page = 10; $threads_per_page = 10;
$start = $threads_per_page * ($current_page - 1); $start = $threads_per_page * ($current_page - 1);
$get_threads = " $result = $database->Execute("
SELECT image_id,MAX(posted) AS latest SELECT image_id,MAX(posted) AS latest
FROM comments $where FROM comments
$where
GROUP BY image_id GROUP BY image_id
ORDER BY latest DESC ORDER BY latest DESC
LIMIT :limit OFFSET :offset LIMIT :limit OFFSET :offset
"; ", array("limit"=>$threads_per_page, "offset"=>$start));
$result = $database->Execute($get_threads, array("limit"=>$threads_per_page, "offset"=>$start));
$user_ratings = ext_is_live("Ratings") ? Ratings::get_user_privs($user) : "";
$images = array(); $images = array();
while($row = $result->fetch()) { while($row = $result->fetch()) {
$image = Image::by_id($row["image_id"]); $image = Image::by_id($row["image_id"]);
if (!is_null($image)) { if(
$comments = $this->get_comments($image->id); ext_is_live("Ratings") && !is_null($image) &&
if(class_exists("Ratings")) { strpos($user_ratings, $image->rating) === FALSE
if(strpos($user_ratings, $image->rating) === FALSE) { ) {
$image = null; // this is "clever", I may live to regret it $image = null; // this is "clever", I may live to regret it
} }
} if(!is_null($image)) {
if(!is_null($image)) $images[] = array($image, $comments); $comments = $this->get_comments($image->id);
$images[] = array($image, $comments);
} }
} }
@ -368,13 +390,27 @@ class CommentList extends Extension {
// }}} // }}}
// get comments {{{ // get comments {{{
/**
* @param string $query
* @param array $args
* @return Comment[]
*/
private function get_generic_comments($query, $args) {
global $database;
$rows = $database->get_all($query, $args);
$comments = array();
foreach($rows as $row) {
$comments[] = new Comment($row);
}
return $comments;
}
/** /**
* @param int $count * @param int $count
* @return array * @return Comment[]
*/ */
private function get_recent_comments($count) { private function get_recent_comments($count) {
global $database; return $this->get_generic_comments("
$rows = $database->get_all("
SELECT SELECT
users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class, users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class,
comments.comment as comment, comments.id as comment_id, comments.comment as comment, comments.id as comment_id,
@ -385,22 +421,16 @@ class CommentList extends Extension {
ORDER BY comments.id DESC ORDER BY comments.id DESC
LIMIT :limit LIMIT :limit
", array("limit"=>$count)); ", array("limit"=>$count));
$comments = array();
foreach($rows as $row) {
$comments[] = new Comment($row);
}
return $comments;
} }
/** /**
* @param int $user_id * @param int $user_id
* @param int $count * @param int $count
* @param int $offset * @param int $offset
* @return array * @return Comment[]
*/ */
private function get_user_comments(/*int*/ $user_id, /*int*/ $count, /*int*/ $offset=0) { private function get_user_comments(/*int*/ $user_id, /*int*/ $count, /*int*/ $offset=0) {
global $database; return $this->get_generic_comments("
$rows = $database->get_all("
SELECT SELECT
users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class, users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class,
comments.comment as comment, comments.id as comment_id, comments.comment as comment, comments.id as comment_id,
@ -412,21 +442,14 @@ class CommentList extends Extension {
ORDER BY comments.id DESC ORDER BY comments.id DESC
LIMIT :limit OFFSET :offset LIMIT :limit OFFSET :offset
", array("user_id"=>$user_id, "offset"=>$offset, "limit"=>$count)); ", array("user_id"=>$user_id, "offset"=>$offset, "limit"=>$count));
$comments = array();
foreach($rows as $row) {
$comments[] = new Comment($row);
}
return $comments;
} }
/** /**
* @param int $image_id * @param int $image_id
* @return array * @return Comment[]
*/ */
private function get_comments(/*int*/ $image_id) { private function get_comments(/*int*/ $image_id) {
global $database; return $this->get_generic_comments("
$i_image_id = int_escape($image_id);
$rows = $database->get_all("
SELECT SELECT
users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class, users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class,
comments.comment as comment, comments.id as comment_id, comments.comment as comment, comments.id as comment_id,
@ -436,18 +459,16 @@ class CommentList extends Extension {
LEFT JOIN users ON comments.owner_id=users.id LEFT JOIN users ON comments.owner_id=users.id
WHERE comments.image_id=:image_id WHERE comments.image_id=:image_id
ORDER BY comments.id ASC ORDER BY comments.id ASC
", array("image_id"=>$i_image_id)); ", array("image_id"=>$image_id));
$comments = array();
foreach($rows as $row) {
$comments[] = new Comment($row);
}
return $comments;
} }
// }}} // }}}
// add / remove / edit comments {{{ // add / remove / edit comments {{{
/**
* @return bool
*/
private function is_comment_limit_hit() { private function is_comment_limit_hit() {
global $user, $config, $database; global $config, $database;
// sqlite fails at intervals // sqlite fails at intervals
if($database->get_driver_name() === "sqlite") return false; if($database->get_driver_name() === "sqlite") return false;
@ -459,9 +480,11 @@ class CommentList extends Extension {
else $window_sql = "interval '$window minute'"; else $window_sql = "interval '$window minute'";
// window doesn't work as an SQL param because it's inside quotes >_< // window doesn't work as an SQL param because it's inside quotes >_<
$result = $database->get_all("SELECT * FROM comments WHERE owner_ip = :remote_ip ". $result = $database->get_all("
"AND posted > now() - $window_sql", SELECT *
Array("remote_ip"=>$_SERVER['REMOTE_ADDR'])); FROM comments
WHERE owner_ip = :remote_ip AND posted > now() - $window_sql
", array("remote_ip"=>$_SERVER['REMOTE_ADDR']));
return (count($result) >= $max); return (count($result) >= $max);
} }
@ -479,6 +502,8 @@ class CommentList extends Extension {
* many times. * many times.
* *
* FIXME: assumes comments are posted via HTTP... * FIXME: assumes comments are posted via HTTP...
*
* @return string
*/ */
public static function get_hash() { public static function get_hash() {
return md5($_SERVER['REMOTE_ADDR'] . date("%Y%m%d")); return md5($_SERVER['REMOTE_ADDR'] . date("%Y%m%d"));
@ -533,28 +558,14 @@ class CommentList extends Extension {
*/ */
private function is_dupe(/*int*/ $image_id, /*string*/ $comment) { private function is_dupe(/*int*/ $image_id, /*string*/ $comment) {
global $database; global $database;
return ($database->get_row("SELECT * FROM comments WHERE image_id=:image_id AND comment=:comment", array("image_id"=>$image_id, "comment"=>$comment))); return $database->get_row("
SELECT *
FROM comments
WHERE image_id=:image_id AND comment=:comment
", array("image_id"=>$image_id, "comment"=>$comment));
} }
// do some checks // do some checks
/**
* @param int $pagenum
* @param int $maxpage
* @return int
*/
private function sanity_check_pagenumber(/*int*/ $pagenum, /*int*/ $maxpage){
if (!is_numeric($pagenum)){
$pagenum=1;
}
if ($pagenum>$maxpage){
$pagenum=$maxpage;
}
if ($pagenum<=0){
$pagenum=1;
}
return $pagenum;
}
/** /**
* @param int $image_id * @param int $image_id
* @param User $user * @param User $user
@ -562,7 +573,7 @@ class CommentList extends Extension {
* @throws CommentPostingException * @throws CommentPostingException
*/ */
private function add_comment_wrapper(/*int*/ $image_id, User $user, /*string*/ $comment) { private function add_comment_wrapper(/*int*/ $image_id, User $user, /*string*/ $comment) {
global $database, $config; global $database, $page;
if(!$user->can("bypass_comment_checks")) { if(!$user->can("bypass_comment_checks")) {
// will raise an exception if anything is wrong // will raise an exception if anything is wrong
@ -571,7 +582,7 @@ class CommentList extends Extension {
// all checks passed // all checks passed
if($user->is_anonymous()) { if($user->is_anonymous()) {
set_prefixed_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/"); $page->add_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/");
} }
$database->Execute( $database->Execute(
"INSERT INTO comments(image_id, owner_id, owner_ip, posted, comment) ". "INSERT INTO comments(image_id, owner_id, owner_ip, posted, comment) ".
@ -584,8 +595,14 @@ class CommentList extends Extension {
log_info("comment", "Comment #$cid added to Image #$image_id: $snippet", false, array("image_id"=>$image_id, "comment_id"=>$cid)); log_info("comment", "Comment #$cid added to Image #$image_id: $snippet", false, array("image_id"=>$image_id, "comment_id"=>$cid));
} }
/**
* @param int $image_id
* @param User $user
* @param string $comment
* @throws CommentPostingException
*/
private function comment_checks(/*int*/ $image_id, User $user, /*string*/ $comment) { private function comment_checks(/*int*/ $image_id, User $user, /*string*/ $comment) {
global $config; global $config, $page;
// basic sanity checks // basic sanity checks
if(!$user->can("create_comment")) { if(!$user->can("create_comment")) {
@ -606,7 +623,7 @@ class CommentList extends Extension {
throw new CommentPostingException("Comment too repetitive~"); throw new CommentPostingException("Comment too repetitive~");
} }
else if($user->is_anonymous() && !$this->hash_match()) { else if($user->is_anonymous() && !$this->hash_match()) {
set_prefixed_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/"); $page->add_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/");
throw new CommentPostingException( throw new CommentPostingException(
"Comment submission form is out of date; refresh the ". "Comment submission form is out of date; refresh the ".
"comment form to show you aren't a spammer~"); "comment form to show you aren't a spammer~");

View file

@ -15,7 +15,8 @@
border: 1px solid #CCC; border: 1px solid #CCC;
position: absolute; position: absolute;
top: 0px; top: 0px;
right: 0px; left: -195px;
width: 180px;
z-index: 1; z-index: 1;
box-shadow: 0px 0px 4px #000; box-shadow: 0px 0px 4px #000;
border-radius: 4px; border-radius: 4px;
@ -24,6 +25,13 @@
visibility: visible; visibility: visible;
} }
.comment_add TEXTAREA {
width: 100%;
}
.comment_add INPUT {
width: 100%;
}
#comment-list-list .blockbody, #comment-list-list .blockbody,
#comment-list-recent .blockbody, #comment-list-recent .blockbody,
#comment-list-image .blockbody, #comment-list-image .blockbody,

View file

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

View file

@ -66,8 +66,7 @@ class CommentListTheme extends Themelet {
$comment_count = count($comments); $comment_count = count($comments);
if($comment_limit > 0 && $comment_count > $comment_limit) { if($comment_limit > 0 && $comment_count > $comment_limit) {
$hidden = $comment_count - $comment_limit; $comment_html .= "<p>showing $comment_limit of $comment_count comments</p>";
$comment_html .= '<p>showing '.$comment_limit.' of '.$comment_count.' comments</p>';
$comments = array_slice($comments, -$comment_limit); $comments = array_slice($comments, -$comment_limit);
$this->show_anon_id = false; $this->show_anon_id = false;
} }
@ -88,7 +87,8 @@ class CommentListTheme extends Themelet {
$comment_html .= $this->build_postbox($image->id); $comment_html .= $this->build_postbox($image->id);
} }
else { else {
$comment_html .= "<a href='".make_link("post/view/".$image->id)."'>Add Comment</a>"; $link = make_link("post/view/".$image->id);
$comment_html .= "<a href='$link'>Add Comment</a>";
} }
} }
} }
@ -210,7 +210,6 @@ class CommentListTheme extends Themelet {
//$u_tags = url_escape(implode(" ", $search_terms)); //$u_tags = url_escape(implode(" ", $search_terms));
//$query = empty($u_tags) ? "" : '/'.$u_tags; //$query = empty($u_tags) ? "" : '/'.$u_tags;
$h_prev = ($page_number <= 1) ? "Prev" : "<a href='$prev'>Prev</a>"; $h_prev = ($page_number <= 1) ? "Prev" : "<a href='$prev'>Prev</a>";
$h_index = "<a href='".make_link("post/list")."'>Index</a>"; $h_index = "<a href='".make_link("post/list")."'>Index</a>";
$h_next = ($page_number >= $total_pages) ? "Next" : "<a href='$next'>Next</a>"; $h_next = ($page_number >= $total_pages) ? "Next" : "<a href='$next'>Next</a>";
@ -233,7 +232,6 @@ class CommentListTheme extends Themelet {
$i_uid = int_escape($comment->owner_id); $i_uid = int_escape($comment->owner_id);
$h_name = html_escape($comment->owner_name); $h_name = html_escape($comment->owner_name);
$h_poster_ip = html_escape($comment->poster_ip);
$h_timestamp = autodate($comment->posted); $h_timestamp = autodate($comment->posted);
$h_comment = ($trim ? truncate($tfe->stripped, 50) : $tfe->formatted); $h_comment = ($trim ? truncate($tfe->stripped, 50) : $tfe->formatted);
$i_comment_id = int_escape($comment->comment_id); $i_comment_id = int_escape($comment->comment_id);
@ -310,7 +308,7 @@ class CommentListTheme extends Themelet {
$h_captcha = $config->get_bool("comment_captcha") ? captcha_get_html() : ""; $h_captcha = $config->get_bool("comment_captcha") ? captcha_get_html() : "";
return ' return '
<div class="comment"> <div class="comment comment_add">
'.make_form(make_link("comment/add")).' '.make_form(make_link("comment/add")).'
<input type="hidden" name="image_id" value="'.$i_image_id.'" /> <input type="hidden" name="image_id" value="'.$i_image_id.'" />
<input type="hidden" name="hash" value="'.$hash.'" /> <input type="hidden" name="hash" value="'.$hash.'" />

View file

@ -61,7 +61,7 @@ class CronUploader extends Extension {
} }
private function display_documentation() { private function display_documentation() {
global $config, $page; global $page;
$this->set_dir(); // Determines path to cron_uploader_dir $this->set_dir(); // Determines path to cron_uploader_dir
@ -151,7 +151,6 @@ class CronUploader extends Extension {
} }
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
global $config;
$this->set_dir(); $this->set_dir();
$cron_url = make_http(make_link("/cron_upload/" . $this->upload_key)); $cron_url = make_http(make_link("/cron_upload/" . $this->upload_key));
@ -196,7 +195,7 @@ class CronUploader extends Extension {
// Sets new default dir if not in config yet/anymore // Sets new default dir if not in config yet/anymore
if ($dir == "") { if ($dir == "") {
$dir = $_SERVER ['DOCUMENT_ROOT'] . "/data/cron_uploader"; $dir = data_path("cron_uploader");
$config->set_string ('cron_uploader_dir', $dir); $config->set_string ('cron_uploader_dir', $dir);
} }
@ -344,7 +343,7 @@ class CronUploader extends Extension {
foreach ( glob ( "$base/$subdir/*" ) as $fullpath ) { foreach ( glob ( "$base/$subdir/*" ) as $fullpath ) {
$fullpath = str_replace ( "//", "/", $fullpath ); $fullpath = str_replace ( "//", "/", $fullpath );
$shortpath = str_replace ( $base, "", $fullpath ); //$shortpath = str_replace ( $base, "", $fullpath );
if (is_link ( $fullpath )) { if (is_link ( $fullpath )) {
// ignore // ignore

View file

@ -17,8 +17,6 @@
class custom_html_headers extends Extension { class custom_html_headers extends Extension {
# Adds setup block for custom <head> content # Adds setup block for custom <head> content
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
global $config;
$sb = new SetupBlock("Custom HTML Headers"); $sb = new SetupBlock("Custom HTML Headers");
// custom headers // custom headers

View file

@ -55,232 +55,162 @@ class DanbooruApi extends Extension {
} }
// Danbooru API // Danbooru API
private function api_danbooru(PageRequestEvent $event) private function api_danbooru(PageRequestEvent $event) {
{
global $page; global $page;
global $config;
global $database;
global $user;
$page->set_mode("data"); $page->set_mode("data");
$page->set_type("application/xml");
//debug
//$page->set_type("text/plain");
$results = array(); if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) {
$danboorup_kludge=1; // danboorup for firefox makes broken links out of location: /path
/*
add_post()
Adds a post to the database.
Parameters
* login: login
* password: password
* file: file as a multipart form
* source: source url
* title: title **IGNORED**
* 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.
*/
if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml')))
{
// No XML data is returned from this function // No XML data is returned from this function
$page->set_type("text/plain"); $page->set_type("text/plain");
// Check first if a login was supplied, if it wasn't check if the user is logged in via cookie $this->api_add_post();
// 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"))
{
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
$url = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
$source = $url;
$tmp_filename = tempnam("/tmp", "shimmie_transload");
// Are we using fopen wrappers or curl?
if($config->get_string("transload_engine") == "fopen")
{
$fp = fopen($url, "r");
if(!$fp) {
$page->add_http_header("HTTP/1.0 409 Conflict");
$page->add_http_header("X-Danbooru-Errors: fopen read error");
} }
$data = ""; elseif(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) {
$length = 0; $page->set_type("application/xml");
while(!feof($fp) && $length <= $config->get_int('upload_size')) $page->set_data($this->api_find_posts());
{
$data .= fread($fp, 8192);
$length = strlen($data);
}
fclose($fp);
$fp = fopen($tmp_filename, "w");
fwrite($fp, $data);
fclose($fp);
} }
if($config->get_string("transload_engine") == "curl") elseif($event->get_arg(1) == 'find_tags') {
{ $page->set_type("application/xml");
$ch = curl_init($url); $page->set_data($this->api_find_tags());
$fp = fopen($tmp_filename, "w");
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
fclose($fp);
}
$file = $tmp_filename;
$filename = basename($url);
} else
{ // Nothing was specified at all
$page->add_http_header("HTTP/1.0 409 Conflict");
$page->add_http_header("X-Danbooru-Errors: no input files");
return;
} }
// Get tags out of url // Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
$posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']); // Shimmie view page
$hash = md5_file($file); // Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123
// Was an md5 supplied? Does it match the file hash? // This redirects that to http://shimmie/post/view/123
if(isset($_REQUEST['md5'])) elseif(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) {
{ $fixedlocation = make_link("post/view/" . $event->get_arg(3));
if(strtolower($_REQUEST['md5']) != $hash) $page->set_mode("redirect");
{ $page->set_redirect($fixedlocation);
$page->add_http_header("HTTP/1.0 409 Conflict");
$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->add_http_header("HTTP/1.0 409 Conflict");
$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; // wut!
}
// 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).")...");
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->add_http_header("HTTP/1.0 409 Conflict");
$page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage());
return;
}
} else
{
$page->add_http_header("HTTP/1.0 409 Conflict");
$page->add_http_header("X-Danbooru-Errors: authentication error");
return;
} }
} }
/* /**
find_posts() * Turns out I use this a couple times so let's make it a utility function
Find all posts that match the search criteria. Posts will be ordered by id descending. * Authenticates a user based on the contents of the login and password parameters
Parameters * or makes them anonymous. Does not set any cookies or anything permanent.
* 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
*/ */
if(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) 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.
*
* 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']);
}
}
// 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;
}
/**
* 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
* @throws SCoreException
*/
private function api_find_posts() {
$results = array();
$this->authenticate_user(); $this->authenticate_user();
$start = 0; $start = 0;
if(isset($_GET['md5'])) if(isset($_GET['md5'])) {
{
$md5list = explode(",", $_GET['md5']); $md5list = explode(",", $_GET['md5']);
foreach($md5list as $md5) foreach ($md5list as $md5) {
{
$results[] = Image::by_hash($md5); $results[] = Image::by_hash($md5);
} }
$count = count($results); $count = count($results);
} elseif(isset($_GET['id'])) }
{ elseif(isset($_GET['id'])) {
$idlist = explode(",", $_GET['id']); $idlist = explode(",", $_GET['id']);
foreach($idlist as $id) foreach ($idlist as $id) {
{
$results[] = Image::by_id($id); $results[] = Image::by_id($id);
} }
$count = count($results); $count = count($results);
} else }
{ else {
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100; $limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
// Calculate start offset. // Calculate start offset.
@ -299,8 +229,7 @@ class DanbooruApi extends Extension {
// Now we have the array $results filled with Image objects // Now we have the array $results filled with Image objects
// Let's display them // Let's display them
$xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n"; $xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n";
foreach($results as $img) foreach ($results as $img) {
{
// Sanity check to see if $img is really an image object // Sanity check to see if $img is really an image object
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this // If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
if (!is_object($img)) if (!is_object($img))
@ -328,114 +257,140 @@ class DanbooruApi extends Extension {
)); ));
} }
$xml .= "</posts>"; $xml .= "</posts>";
$page->set_data($xml); return $xml;
} }
/* /**
find_tags() Find all tags that match the search criteria. * add_post()
Parameters * Adds a post to the database.
* id: A comma delimited list of tag id numbers. *
* name: A comma delimited list of tag names. * Parameters:
* tags: any typical tag query. See Tag#parse_query for details. * - login: login
* after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh * - password: password
* - file: file as a multipart form
* - source: source url
* - title: title **IGNORED**
* - 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.
*/ */
if($event->get_arg(1) == 'find_tags') private function api_add_post() {
{ global $user, $config, $page;
if(isset($_GET['id'])) $danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path
{
$idlist = explode(",",$_GET['id']);
foreach($idlist as $id)
{
$sqlresult = $database->execute("SELECT id,tag,count FROM tags WHERE id = ?", array($id));
if(!$sqlresult->EOF)
{
$results[] = array($sqlresult->fields['count'], $sqlresult->fields['tag'], $sqlresult->fields['id']);
}
}
} elseif(isset($_GET['name']))
{
$namelist = explode(",",$_GET['name']);
foreach($namelist as $name)
{
$sqlresult = $database->execute("SELECT id,tag,count FROM tags WHERE tag = ?", array($name));
if(!$sqlresult->EOF)
{
$results[] = array($sqlresult->fields['count'], $sqlresult->fields['tag'], $sqlresult->fields['id']);
}
}
}
/* Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags
elseif(isset($_GET['tags']))
{
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$tags = Tag::explode($_GET['tags']);
} // 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
else $this->authenticate_user();
{ // Now we check if a file was uploaded or a url was provided to transload
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0; // Much of this code is borrowed from /ext/upload
$sqlresult = $database->execute("SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC",array($start));
while(!$sqlresult->EOF) if (!$user->can("create_image")) {
{ $page->set_code(409);
$results[] = array($sqlresult->fields['count'], $sqlresult->fields['tag'], $sqlresult->fields['id']); $page->add_http_header("X-Danbooru-Errors: authentication error");
$sqlresult->MoveNext(); return;
}
} }
// Tag results collected, build XML output if (isset($_FILES['file'])) { // A file was POST'd in
$xml = "<tags>\n"; $file = $_FILES['file']['tmp_name'];
foreach($results as $tag) $filename = $_FILES['file']['name'];
{ // If both a file is posted and a source provided, I'm assuming source is the source of the file
$xml .= "<tag type=\"0\" count=\"$tag[0]\" name=\"" . $this->xmlspecialchars($tag[1]) . "\" id=\"$tag[2]\"/>\n"; if (isset($_REQUEST['source']) && !empty($_REQUEST['source'])) {
$source = $_REQUEST['source'];
} else {
$source = null;
} }
$xml .= "</tags>"; } elseif (isset($_FILES['post'])) {
$page->set_data($xml); $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;
} }
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper // Get tags out of url
// Shimmie view page $posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']);
// Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123
// This redirects that to http://shimmie/post/view/123 // Was an md5 supplied? Does it match the file hash?
if(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) $hash = md5_file($file);
{ if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) {
$fixedlocation = make_link("post/view/" . $event->get_arg(3)); $page->set_code(409);
$page->set_mode("redirect"); $page->add_http_header("X-Danbooru-Errors: md5 mismatch");
$page->set_redirect($fixedlocation); 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;
} }
// Turns out I use this a couple times so let's make it a utility function // Fire off an event which should process the new file and add it to the db
// Authenticates a user based on the contents of the login and password parameters $fileinfo = pathinfo($filename);
// or makes them anonymous. Does not set any cookies or anything permanent. $metadata = array();
private function authenticate_user() $metadata['filename'] = $fileinfo['basename'];
{ $metadata['extension'] = $fileinfo['extension'];
global $config; $metadata['tags'] = $posttags;
global $database; $metadata['source'] = $source;
global $user; //log_debug("danbooru_api","========== NEW($filename) =========");
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
if(isset($_REQUEST['login']) && isset($_REQUEST['password'])) try {
{ $nevent = new DataUploadEvent($file, $metadata);
// Get this user from the db, if it fails the user becomes anonymous //log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
// Code borrowed from /ext/user send_event($nevent);
$name = $_REQUEST['login']; // If it went ok, grab the id for the newly uploaded image and pass it in the header
$pass = $_REQUEST['password']; $newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error?
$duser = User::by_name_and_pass($name, $pass); $newid = make_link("post/view/" . $newimg->id);
if(!is_null($duser)) { if ($danboorup_kludge) $newid = make_http($newid);
$user = $duser;
} else // Did we POST or GET this call?
{ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$user = User::by_id($config->get_int("anon_id", 0)); $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());
} }
} }
} }
// From htmlspecialchars man page on php.net comments
// If tags contain quotes they need to be htmlified
private function xmlspecialchars($text)
{
return str_replace('&#039;', '&apos;', htmlspecialchars($text, ENT_QUOTES));
}
}

View file

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

View file

@ -29,8 +29,12 @@ class Downtime extends Extension {
if(!$user->can("ignore_downtime") && !$this->is_safe_page($event)) { if(!$user->can("ignore_downtime") && !$this->is_safe_page($event)) {
$msg = $config->get_string("downtime_message"); $msg = $config->get_string("downtime_message");
$this->theme->display_message($msg); $this->theme->display_message($msg);
if(!defined("UNITTEST")) { // hax D:
header("HTTP/1.0 {$page->code} Downtime");
print($page->data);
exit; exit;
} }
}
$this->theme->display_notification($page); $this->theme->display_notification($page);
} }
} }
@ -40,4 +44,3 @@ class Downtime extends Extension {
else return false; else return false;
} }
} }

View file

@ -1,23 +1,39 @@
<?php <?php
class DowntimeTest extends SCoreWebTestCase { class DowntimeTest extends ShimmiePHPUnitTestCase {
public function tearDown() {
global $config;
$config->set_bool("downtime", false);
}
public function testDowntime() { public function testDowntime() {
$this->log_in_as_admin(); global $config;
$this->get_page("setup");
$this->set_field("_config_downtime", true);
$this->set_field("_config_downtime_message", "brb, unit testing");
$this->click("Save Settings");
$this->assert_text("DOWNTIME MODE IS ON!");
$this->log_out();
$config->set_string("downtime_message", "brb, unit testing");
// downtime on
$config->set_bool("downtime", true);
$this->log_in_as_admin();
$this->get_page("post/list"); $this->get_page("post/list");
$this->assert_text("brb, unit testing"); $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);
// downtime off
$config->set_bool("downtime", false);
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("setup"); $this->get_page("post/list");
$this->set_field("_config_downtime", false);
$this->click("Save Settings");
$this->assert_no_text("DOWNTIME MODE IS ON!"); $this->assert_no_text("DOWNTIME MODE IS ON!");
$this->log_out(); $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

@ -3,6 +3,8 @@
class DowntimeTheme extends Themelet { class DowntimeTheme extends Themelet {
/** /**
* Show the admin that downtime mode is enabled * Show the admin that downtime mode is enabled
*
* @param Page $page
*/ */
public function display_notification(Page $page) { public function display_notification(Page $page) {
$page->add_block(new Block("Downtime", $page->add_block(new Block("Downtime",
@ -11,16 +13,19 @@ class DowntimeTheme extends Themelet {
/** /**
* Display $message and exit * Display $message and exit
*
* @param string $message
*/ */
public function display_message(/*string*/ $message) { public function display_message(/*string*/ $message) {
global $config, $user; global $config, $user, $page;
$theme_name = $config->get_string('theme'); $theme_name = $config->get_string('theme');
$data_href = get_base_href(); $data_href = get_base_href();
$login_link = make_link("user_admin/login"); $login_link = make_link("user_admin/login");
header("HTTP/1.0 503 Service Temporarily Unavailable");
$auth = $user->get_auth_html(); $auth = $user->get_auth_html();
print <<<EOD
$page->set_mode('data');
$page->set_code(503);
$page->set_data(<<<EOD
<html> <html>
<head> <head>
<title>Downtime</title> <title>Downtime</title>
@ -56,7 +61,7 @@ class DowntimeTheme extends Themelet {
</div> </div>
</body> </body>
</html> </html>
EOD; EOD
);
} }
} }

View file

@ -1,22 +1,19 @@
<?php <?php
class EmoticonTest extends ShimmieWebTestCase { class EmoticonTest extends ShimmiePHPUnitTestCase {
public function testEmoticons() { public function testEmoticons() {
$this->log_in_as_user(); global $user;
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->get_page("post/view/$image_id");
$this->set_field('comment', ":cool: :beans:"); $this->log_in_as_user();
$this->click("Post Comment"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
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_no_text(":cool:"); # FIXME: test for working image link
#$this->assert_text(":beans:"); # FIXME: this should be left as-is //$this->assert_text(":beans:"); # FIXME: this should be left as-is
$this->get_page("emote/list"); $this->get_page("emote/list");
$this->assert_text(":arrow:"); //$this->assert_text(":arrow:");
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
} }
} }

View file

@ -34,7 +34,6 @@ class ET extends Extension {
*/ */
private function get_info() { private function get_info() {
global $config, $database; global $config, $database;
global $_event_listeners; // yay for using secret globals \o/
$info = array(); $info = array();
$info['site_title'] = $config->get_string("title"); $info['site_title'] = $config->get_string("title");
@ -48,7 +47,7 @@ class ET extends Extension {
$info['sys_os'] = php_uname(); $info['sys_os'] = php_uname();
$info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " . $info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " .
to_shorthand_int(disk_total_space("./")); to_shorthand_int(disk_total_space("./"));
$info['sys_server'] = $_SERVER["SERVER_SOFTWARE"]; $info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown';
$info['thumb_engine'] = $config->get_string("thumb_engine"); $info['thumb_engine'] = $config->get_string("thumb_engine");
$info['thumb_quality'] = $config->get_int('thumb_quality'); $info['thumb_quality'] = $config->get_int('thumb_quality');

View file

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

View file

@ -23,7 +23,7 @@ class ExtensionInfo {
var $description, $documentation, $version, $visibility; var $description, $documentation, $version, $visibility;
var $enabled; var $enabled;
function __construct($main) { public function __construct($main) {
$matches = array(); $matches = array();
$lines = file($main); $lines = file($main);
$number_of_lines = count($lines); $number_of_lines = count($lines);
@ -37,26 +37,26 @@ class ExtensionInfo {
if(preg_match("/Name: (.*)/", $line, $matches)) { if(preg_match("/Name: (.*)/", $line, $matches)) {
$this->name = $matches[1]; $this->name = $matches[1];
} }
if(preg_match("/Visibility: (.*)/", $line, $matches)) { else if(preg_match("/Visibility: (.*)/", $line, $matches)) {
$this->visibility = $matches[1]; $this->visibility = $matches[1];
} }
if(preg_match("/Link: (.*)/", $line, $matches)) { else if(preg_match("/Link: (.*)/", $line, $matches)) {
$this->link = $matches[1]; $this->link = $matches[1];
if($this->link[0] == "/") { if($this->link[0] == "/") {
$this->link = make_link(substr($this->link, 1)); $this->link = make_link(substr($this->link, 1));
} }
} }
if(preg_match("/Version: (.*)/", $line, $matches)) { else if(preg_match("/Version: (.*)/", $line, $matches)) {
$this->version = $matches[1]; $this->version = $matches[1];
} }
if(preg_match("/Author: (.*) [<\(](.*@.*)[>\)]/", $line, $matches)) { else if(preg_match("/Author: (.*) [<\(](.*@.*)[>\)]/", $line, $matches)) {
$this->author = $matches[1]; $this->author = $matches[1];
$this->email = $matches[2]; $this->email = $matches[2];
} }
else if(preg_match("/Author: (.*)/", $line, $matches)) { else if(preg_match("/Author: (.*)/", $line, $matches)) {
$this->author = $matches[1]; $this->author = $matches[1];
} }
if(preg_match("/(.*)Description: ?(.*)/", $line, $matches)) { else if(preg_match("/(.*)Description: ?(.*)/", $line, $matches)) {
$this->description = $matches[2]; $this->description = $matches[2];
$start = $matches[1]." "; $start = $matches[1]." ";
$start_len = strlen($start); $start_len = strlen($start);
@ -65,7 +65,7 @@ class ExtensionInfo {
$i++; $i++;
} }
} }
if(preg_match("/(.*)Documentation: ?(.*)/", $line, $matches)) { else if(preg_match("/(.*)Documentation: ?(.*)/", $line, $matches)) {
$this->documentation = $matches[2]; $this->documentation = $matches[2];
$start = $matches[1]." "; $start = $matches[1]." ";
$start_len = strlen($start); $start_len = strlen($start);
@ -75,7 +75,7 @@ class ExtensionInfo {
} }
$this->documentation = str_replace('$site', make_http(get_base_href()), $this->documentation); $this->documentation = str_replace('$site', make_http(get_base_href()), $this->documentation);
} }
if(preg_match("/\*\//", $line, $matches)) { else if(preg_match("/\*\//", $line, $matches)) {
break; break;
} }
} }
@ -135,8 +135,8 @@ class ExtManager extends Extension {
public function onCommand(CommandEvent $event) { public function onCommand(CommandEvent $event) {
if($event->cmd == "help") { if($event->cmd == "help") {
print " disable-all-ext\n"; print "\tdisable-all-ext\n";
print " disable all extensions\n\n"; print "\t\tdisable all extensions\n\n";
} }
if($event->cmd == "disable-all-ext") { if($event->cmd == "disable-all-ext") {
$this->write_config(array()); $this->write_config(array());
@ -156,7 +156,7 @@ class ExtManager extends Extension {
/** /**
* @param bool $all * @param bool $all
* @return array * @return ExtensionInfo[]
*/ */
private function get_extensions(/*bool*/ $all) { private function get_extensions(/*bool*/ $all) {
$extensions = array(); $extensions = array();
@ -206,4 +206,3 @@ class ExtManager extends Extension {
} }
} }
} }

View file

@ -1,5 +1,5 @@
<?php <?php
class ExtManagerTest extends SCoreWebTestCase { class ExtManagerTest extends ShimmiePHPUnitTestCase {
public function testAuth() { public function testAuth() {
$this->get_page('ext_manager'); $this->get_page('ext_manager');
$this->assert_title("Extensions"); $this->assert_title("Extensions");
@ -17,10 +17,9 @@ class ExtManagerTest extends SCoreWebTestCase {
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page('ext_manager'); $this->get_page('ext_manager');
$this->assert_title("Extensions"); $this->assert_title("Extensions");
$this->assert_text("SimpleTest integration"); //$this->assert_text("SimpleTest integration"); // FIXME: something which still exists
$this->log_out(); $this->log_out();
# FIXME: test that some extensions can be added and removed? :S # FIXME: test that some extensions can be added and removed? :S
} }
} }

View file

@ -2,7 +2,6 @@
class ExtManagerTheme extends Themelet { class ExtManagerTheme extends Themelet {
public function display_table(Page $page, /*array*/ $extensions, /*bool*/ $editable) { public function display_table(Page $page, /*array*/ $extensions, /*bool*/ $editable) {
global $user;
$h_en = $editable ? "<th>Enabled</th>" : ""; $h_en = $editable ? "<th>Enabled</th>" : "";
$html = " $html = "
".make_form(make_link("ext_manager/set"))." ".make_form(make_link("ext_manager/set"))."

View file

@ -133,7 +133,6 @@ class Favorites extends Extension {
$event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE favorites $cmp $favorites)")); $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)) { else if(preg_match("/^favorited_by[=|:](.*)$/i", $event->term, $matches)) {
global $database;
$user = User::by_name($matches[1]); $user = User::by_name($matches[1]);
if(!is_null($user)) { if(!is_null($user)) {
$user_id = $user->id; $user_id = $user->id;
@ -167,7 +166,7 @@ class Favorites extends Extension {
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE
"); ");
$database->execute("CREATE INDEX user_favorites_image_id_idx ON user_favorites(image_id)", array()); $database->execute("CREATE INDEX user_favorites_image_id_idx ON user_favorites(image_id)", array());
$config->set_int("ext_favorites_version", 1); $config->set_int("ext_favorites_version", 2);
} }
if($config->get_int("ext_favorites_version") < 2) { if($config->get_int("ext_favorites_version") < 2) {
@ -205,7 +204,7 @@ class Favorites extends Extension {
/** /**
* @param Image $image * @param Image $image
* @return array * @return string[]
*/ */
private function list_persons_who_have_favorited(Image $image) { private function list_persons_who_have_favorited(Image $image) {
global $database; global $database;

View file

@ -1,13 +1,15 @@
<?php <?php
class FavoritesTest extends ShimmieWebTestCase { class FavoritesTest extends ShimmiePHPUnitTestCase {
public function testFavorites() { public function testFavorites() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: test"); $this->assert_title("Image $image_id: test");
$this->assert_no_text("Favorited By"); $this->assert_no_text("Favorited By");
$this->markTestIncomplete();
$this->click("Favorite"); $this->click("Favorite");
$this->assert_text("Favorited By"); $this->assert_text("Favorited By");
@ -22,12 +24,6 @@ class FavoritesTest extends ShimmieWebTestCase {
$this->click("Un-Favorite"); $this->click("Un-Favorite");
$this->assert_no_text("Favorited By"); $this->assert_no_text("Favorited By");
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
} }
} }

View file

@ -2,8 +2,6 @@
class FavoritesTheme extends Themelet { class FavoritesTheme extends Themelet {
public function get_voter_html(Image $image, $is_favorited) { public function get_voter_html(Image $image, $is_favorited) {
global $page, $user;
$i_image_id = int_escape($image->id); $i_image_id = int_escape($image->id);
$name = $is_favorited ? "unset" : "set"; $name = $is_favorited ? "unset" : "set";
$label = $is_favorited ? "Un-Favorite" : "Favorite"; $label = $is_favorited ? "Un-Favorite" : "Favorite";

View file

@ -69,7 +69,7 @@ class Featured extends Extension {
$database->cache->set("featured_image_object:$fid", $image, 600); $database->cache->set("featured_image_object:$fid", $image, 600);
} }
if(!is_null($image)) { if(!is_null($image)) {
if(class_exists("Ratings")) { if(ext_is_live("Ratings")) {
if(strpos(Ratings::get_user_privs($user), $image->rating) === FALSE) { if(strpos(Ratings::get_user_privs($user), $image->rating) === FALSE) {
return; return;
} }

View file

@ -1,15 +1,17 @@
<?php <?php
class FeaturedTest extends ShimmieWebTestCase { class FeaturedTest extends ShimmiePHPUnitTestCase {
public function testFeatured() { public function testFeatured() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
$this->log_out();
# FIXME: test that regular users can't feature things # FIXME: test that regular users can't feature things
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: pbx"); $this->assert_title("Image $image_id: pbx");
$this->markTestIncomplete();
$this->click("Feature This"); $this->click("Feature This");
$this->get_page("post/list"); $this->get_page("post/list");
$this->assert_text("Featured Image"); $this->assert_text("Featured Image");

View file

@ -71,7 +71,7 @@ class Forum extends Extension {
} }
public function onUserPageBuilding(UserPageBuildingEvent $event) { public function onUserPageBuilding(UserPageBuildingEvent $event) {
global $page, $user, $database; global $database;
$threads_count = $database->get_one("SELECT COUNT(*) FROM forum_threads WHERE user_id=?", array($event->display_user->id)); $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)); $posts_count = $database->get_one("SELECT COUNT(*) FROM forum_posts WHERE user_id=?", array($event->display_user->id));
@ -250,7 +250,6 @@ class Forum extends Extension {
} }
private function sanity_check_viewed_thread($threadID) private function sanity_check_viewed_thread($threadID)
{ {
global $database;
$errors = null; $errors = null;
if (!$this->threadExists($threadID)) if (!$this->threadExists($threadID))
{ {
@ -298,7 +297,7 @@ class Forum extends Extension {
private function show_posts($event, $showAdminOptions = false) private function show_posts($event, $showAdminOptions = false)
{ {
global $config, $database, $user; global $config, $database;
$threadID = $event->get_arg(1); $threadID = $event->get_arg(1);
$pageNumber = $event->get_arg(2); $pageNumber = $event->get_arg(2);
$postsPerPage = $config->get_int('forumPostsPerPage', 15); $postsPerPage = $config->get_int('forumPostsPerPage', 15);

View file

@ -21,8 +21,8 @@ class Handle404 extends Extension {
$filename = file_exists("themes/$theme_name/$f_pagename") ? $filename = file_exists("themes/$theme_name/$f_pagename") ?
"themes/$theme_name/$f_pagename" : "lib/static/$f_pagename"; "themes/$theme_name/$f_pagename" : "lib/static/$f_pagename";
header("Cache-control: public, max-age=600"); $page->add_http_header("Cache-control: public, max-age=600");
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT'); $page->add_http_header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT');
$page->set_mode("data"); $page->set_mode("data");
$page->set_data(file_get_contents($filename)); $page->set_data(file_get_contents($filename));
if(endsWith($filename, ".ico")) $page->set_type("image/x-icon"); if(endsWith($filename, ".ico")) $page->set_type("image/x-icon");
@ -31,7 +31,7 @@ class Handle404 extends Extension {
} }
else { else {
log_debug("handle_404", "Hit 404: $h_pagename"); log_debug("handle_404", "Hit 404: $h_pagename");
$page->add_http_header("HTTP/1.0 404 Page Not Found",5); $page->set_code(404);
$page->set_title("404"); $page->set_title("404");
$page->set_heading("404 - No Handler Found"); $page->set_heading("404 - No Handler Found");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
@ -45,6 +45,9 @@ class Handle404 extends Extension {
foreach($blocks as $block) { foreach($blocks as $block) {
if($block->section == "main") $n++; // more hax. if($block->section == "main") $n++; // more hax.
} }
if(ext_is_live("Chatbox")) {
$n--; // even more hax.
}
return $n; return $n;
} }

View file

@ -1,10 +1,11 @@
<?php <?php
class Handle404Test extends SCoreWebTestCase { class Handle404Test extends ShimmiePHPUnitTestCase {
public function test404Handler() { public function test404Handler() {
$this->get_page('not/a/page'); $this->get_page('not/a/page');
$this->assert_response(404); // most descriptive error first
$this->assert_title('404');
$this->assert_text("No handler could be found for the page 'not/a/page'"); $this->assert_text("No handler could be found for the page 'not/a/page'");
$this->assert_title('404');
$this->assert_response(404);
$this->get_page('favicon.ico'); $this->get_page('favicon.ico');
$this->assert_response(200); $this->assert_response(200);

View file

@ -33,84 +33,22 @@ class ArchiveFileHandler extends Extension {
$cmd = str_replace('%f', $event->tmpname, $cmd); $cmd = str_replace('%f', $event->tmpname, $cmd);
$cmd = str_replace('%d', $tmpdir, $cmd); $cmd = str_replace('%d', $tmpdir, $cmd);
exec($cmd); exec($cmd);
$this->add_dir($tmpdir); $results = add_dir($tmpdir);
if(count($results) > 0) {
// FIXME no theme?
$this->theme->add_status("Adding files", $results);
}
deltree($tmpdir); deltree($tmpdir);
$event->image_id = -2; // default -1 = upload wasn't handled $event->image_id = -2; // default -1 = upload wasn't handled
} }
} }
/** /**
* @param $ext * @param string $ext
* @return bool * @return bool
*/ */
private function supported_ext($ext) { private function supported_ext($ext) {
$exts = array("zip"); $exts = array("zip");
return in_array(strtolower($ext), $exts); return in_array(strtolower($ext), $exts);
} }
// copied from bulk add extension
private function add_image($tmpname, $filename, $tags) {
assert(file_exists($tmpname));
try {
$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'] = $tags;
$metadata['source'] = null;
$event = new DataUploadEvent($tmpname, $metadata);
send_event($event);
}
catch(UploadException $ex) {
return $ex->getMessage();
}
}
// copied from bulk add extension
private function add_dir($base, $subdir="") {
$list = "";
$dir = opendir("$base/$subdir");
$files = array();
while($f = readdir($dir)) {
$files[] = $f;
}
sort($files);
foreach($files as $filename) {
$fullpath = "$base/$subdir/$filename";
if(is_link($fullpath)) {
// ignore
}
else if(is_dir($fullpath)) {
if($filename[0] != ".") {
$this->add_dir($base, "$subdir/$filename");
}
}
else {
$tmpfile = $fullpath;
$tags = $subdir;
$tags = str_replace("/", " ", $tags);
$tags = str_replace("__", " ", $tags);
$list .= "<br>".html_escape("$subdir/$filename (".str_replace(" ", ",", $tags).")...");
$error = $this->add_image($tmpfile, $filename, $tags);
if(is_null($error)) {
$list .= "ok\n";
}
else {
$list .= "failed: $error\n";
}
}
}
closedir($dir);
// $this->theme->add_status("Adding $subdir", $list);
}
}

View file

@ -50,7 +50,7 @@ class FlashFileHandler extends DataHandlerExtension {
} }
/** /**
* @param $file * @param string $file
* @return bool * @return bool
*/ */
protected function check_contents(/*string*/ $file) { protected function check_contents(/*string*/ $file) {

View file

@ -50,7 +50,7 @@ class IcoFileHandler extends Extension {
} }
/** /**
* @param $ext * @param string $ext
* @return bool * @return bool
*/ */
private function supported_ext($ext) { private function supported_ext($ext) {
@ -59,8 +59,8 @@ class IcoFileHandler extends Extension {
} }
/** /**
* @param $filename * @param string $filename
* @param $metadata * @param mixed[] $metadata
* @return Image * @return Image
*/ */
private function create_image_from_data($filename, $metadata) { private function create_image_from_data($filename, $metadata) {

View file

@ -1,18 +1,11 @@
<?php <?php
class IcoHandlerTest extends ShimmieWebTestCase { class IcoHandlerTest extends ShimmiePHPUnitTestCase {
public function testPixelHander() { public function testIcoHander() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("lib/static/favicon.ico", "shimmie favicon"); $image_id = $this->post_image("lib/static/favicon.ico", "shimmie favicon");
$this->assert_response(302);
$this->log_out();
$this->get_page("post/view/$image_id"); // test for no crash $this->get_page("post/view/$image_id"); // test for no crash
$this->get_page("get_ico/$image_id"); // test for no crash $this->get_page("get_ico/$image_id"); // test for no crash
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
# FIXME: test that the thumb works # FIXME: test that the thumb works
# FIXME: test that it gets displayed properly # FIXME: test that it gets displayed properly
} }

View file

@ -26,7 +26,7 @@ class MP3FileHandler extends DataHandlerExtension {
/** /**
* @param string $filename * @param string $filename
* @param array $metadata * @param mixed[] $metadata
* @return Image|null * @return Image|null
*/ */
protected function create_image_from_data($filename, $metadata) { protected function create_image_from_data($filename, $metadata) {

View file

@ -67,7 +67,7 @@ class PixelFileHandler extends DataHandlerExtension {
} }
/** /**
* @param $hash * @param string $hash
* @return bool * @return bool
*/ */
protected function create_thumb_force(/*string*/ $hash) { protected function create_thumb_force(/*string*/ $hash) {
@ -91,9 +91,6 @@ class PixelFileHandler extends DataHandlerExtension {
return $ok; return $ok;
} }
/**
* @param ImageAdminBlockBuildingEvent $event
*/
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) {
$event->add_part(" $event->add_part("
<form> <form>

View file

@ -1,22 +1,7 @@
$(function() { $(function() {
$(".shm-zoomer").change(function(e) {
zoom(this.options[this.selectedIndex].value);
});
$(".shm-main-image").click(function(e) {
switch($.cookie("ui-image-zoom")) {
case "full": zoom("width"); break;
default: zoom("full"); break;
}
});
if($.cookie("ui-image-zoom")) {
zoom($.cookie("ui-image-zoom"));
}
});
function zoom(zoom_type) { function zoom(zoom_type) {
var img = $('.shm-main-image'); var img = $('.shm-main-image');
if(zoom_type == "full") { if(zoom_type == "full") {
img.css('max-width', img.data('width') + 'px'); img.css('max-width', img.data('width') + 'px');
img.css('max-height', img.data('height') + 'px'); img.css('max-height', img.data('height') + 'px');
@ -38,3 +23,19 @@ function zoom(zoom_type) {
$.cookie("ui-image-zoom", zoom_type, {path: '/', expires: 365}); $.cookie("ui-image-zoom", zoom_type, {path: '/', expires: 365});
} }
$(".shm-zoomer").change(function(e) {
zoom(this.options[this.selectedIndex].value);
});
$(".shm-main-image").click(function(e) {
switch($.cookie("ui-image-zoom")) {
case "full": zoom("width"); break;
default: zoom("full"); break;
}
});
if($.cookie("ui-image-zoom")) {
zoom($.cookie("ui-image-zoom"));
}
});

View file

@ -1,14 +1,9 @@
<?php <?php
class PixelHandlerTest extends ShimmieWebTestCase { class PixelHandlerTest extends ShimmiePHPUnitTestCase {
public function testPixelHander() { public function testPixelHander() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$this->assert_response(302); //$this->assert_response(302);
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
# FIXME: test that the thumb works # FIXME: test that the thumb works
# FIXME: test that it gets displayed properly # FIXME: test that it gets displayed properly

View file

@ -27,7 +27,8 @@ class PixelFileHandlerTheme extends Themelet {
} }
} }
$html = "<img alt='main image' class='shm-main-image' id='main_image' src='$u_ilink' data-width='{$image->width}' data-height='{$image->height}'>"; $html = "<img alt='main image' class='shm-main-image' id='main_image' src='$u_ilink' ".
"data-width='{$image->width}' data-height='{$image->height}'>";
$page->add_block(new Block("Image", $html, "main", 10)); $page->add_block(new Block("Image", $html, "main", 10));
} }
} }

View file

@ -23,7 +23,6 @@ class SVGFileHandler extends Extension {
} }
public function onThumbnailGeneration(ThumbnailGenerationEvent $event) { public function onThumbnailGeneration(ThumbnailGenerationEvent $event) {
global $config;
if($this->supported_ext($event->type)) { if($this->supported_ext($event->type)) {
$hash = $event->hash; $hash = $event->hash;
@ -39,7 +38,7 @@ class SVGFileHandler extends Extension {
} }
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event) {
global $config, $database, $page; global $page;
if($event->page_matches("get_svg")) { if($event->page_matches("get_svg")) {
$id = int_escape($event->get_arg(0)); $id = int_escape($event->get_arg(0));
$image = Image::by_id($id); $image = Image::by_id($id);
@ -52,7 +51,7 @@ class SVGFileHandler extends Extension {
} }
/** /**
* @param $ext * @param string $ext
* @return bool * @return bool
*/ */
private function supported_ext($ext) { private function supported_ext($ext) {
@ -61,13 +60,11 @@ class SVGFileHandler extends Extension {
} }
/** /**
* @param $filename * @param string $filename
* @param $metadata * @param mixed[] $metadata
* @return Image * @return Image
*/ */
private function create_image_from_data($filename, $metadata) { private function create_image_from_data($filename, $metadata) {
global $config;
$image = new Image(); $image = new Image();
$msp = new MiniSVGParser($filename); $msp = new MiniSVGParser($filename);
@ -85,7 +82,7 @@ class SVGFileHandler extends Extension {
} }
/** /**
* @param $file * @param string $file
* @return bool * @return bool
*/ */
private function check_contents($file) { private function check_contents($file) {

View file

@ -1,34 +1,11 @@
<?php <?php
class SVGHandlerTest extends ShimmieWebTestCase { class SVGHandlerTest extends ShimmiePHPUnitTestCase {
public function testSVGHander() { public function testSVGHander() {
file_put_contents("test.svg", '<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="128"
height="128"
id="svg2"
version="1.0">
<g id="layer1">
<path
style="fill:#0000ff;stroke:#213847;stroke-opacity:1"
id="path2383"
d="M 120.07832,64.983688 A 55.573441,53.092484 0 1 1 8.9314423,64.983688 A 55.573441,53.092484 0 1 1 120.07832,64.983688 z" />
</g>
</svg>');
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("test.svg", "something"); $image_id = $this->post_image("tests/test.svg", "something");
$this->assert_response(302); $this->get_page("post/view/$image_id"); // test for no crash
$this->log_out(); $this->get_page("get_svg/$image_id"); // test for no crash
$this->assert_content("www.w3.org");
$raw = $this->get_page("get_svg/$image_id");
$this->assertTrue(strpos($raw, "www.w3.org") > 0);
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
unlink("test.svg");
# FIXME: test that the thumb works # FIXME: test that the thumb works
# FIXME: test that it gets displayed properly # FIXME: test that it gets displayed properly

View file

@ -118,7 +118,7 @@ class VideoFileHandler extends DataHandlerExtension {
/** /**
* @param string $filename * @param string $filename
* @param array $metadata * @param mixed[] $metadata
* @return Image|null * @return Image|null
*/ */
protected function create_image_from_data($filename, $metadata) { protected function create_image_from_data($filename, $metadata) {
@ -162,7 +162,7 @@ class VideoFileHandler extends DataHandlerExtension {
} }
/** /**
* @param $file * @param string $file
* @return bool * @return bool
*/ */
protected function check_contents($file) { protected function check_contents($file) {

View file

@ -13,7 +13,6 @@ class Holiday extends Extension {
} }
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
global $config;
$sb = new SetupBlock("Holiday Theme"); $sb = new SetupBlock("Holiday Theme");
$sb->add_bool_option("holiday_aprilfools", "Enable April Fools"); $sb->add_bool_option("holiday_aprilfools", "Enable April Fools");
$event->panel->add_block($sb); $event->panel->add_block($sb);

View file

@ -46,7 +46,6 @@ class Home extends Extension {
private function get_body() { private function get_body() {
// returns just the contents of the body // returns just the contents of the body
global $database;
global $config; global $config;
$base_href = get_base_href(); $base_href = get_base_href();
$sitename = $config->get_string('title'); $sitename = $config->get_string('title');

View file

@ -1,11 +1,12 @@
<?php <?php
class HomeTest extends ShimmieWebTestCase { class HomeTest extends ShimmiePHPUnitTestCase {
public function testHomePage() { public function testHomePage() {
$this->get_page('home'); $this->get_page('home');
$this->assert_title('Shimmie');
$this->assert_text('Shimmie'); // FIXME: this page doesn't use blocks; need assert_data_contains
//$this->assert_title('Shimmie');
//$this->assert_text('Shimmie');
# FIXME: test search box # FIXME: test search box
} }
} }

View file

@ -3,13 +3,16 @@
class HomeTheme extends Themelet { class HomeTheme extends Themelet {
public function display_page(Page $page, $sitename, $base_href, $theme_name, $body) { public function display_page(Page $page, $sitename, $base_href, $theme_name, $body) {
$page->set_mode("data"); $page->set_mode("data");
$hh = "";
$page->add_auto_html_headers();
foreach($page->html_headers as $h) {$hh .= $h;}
$page->set_data(<<<EOD $page->set_data(<<<EOD
<html> <html>
<head> <head>
<title>$sitename</title> <title>$sitename</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='$base_href/themes/$theme_name/style.css' type='text/css'> $hh
</head> </head>
<style> <style>
div#front-page h1 {font-size: 4em; margin-top: 2em; margin-bottom: 0px; text-align: center; border: none; background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;} div#front-page h1 {font-size: 4em; margin-top: 2em; margin-bottom: 0px; text-align: center; border: none; background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;}
@ -38,7 +41,7 @@ EOD
$search_html = " $search_html = "
<div class='space' id='search'> <div class='space' id='search'>
<form action='".make_link("post/list")."' method='GET'> <form action='".make_link("post/list")."' method='GET'>
<input name='search' size='30' type='text' value='' class='autocomplete_tags' autofocus='autofocus'/> <input name='search' size='30' type='search' value='' class='autocomplete_tags' autofocus='autofocus' autocomplete='off' />
<input type='hidden' name='q' value='/post/list'> <input type='hidden' name='q' value='/post/list'>
<input type='submit' value='Search'/> <input type='submit' value='Search'/>
</form> </form>

View file

@ -32,6 +32,9 @@ class ImageAdditionEvent extends Event {
class ImageAdditionException extends SCoreException { class ImageAdditionException extends SCoreException {
var $error; var $error;
/**
* @param string $error
*/
public function __construct($error) { public function __construct($error) {
$this->error = $error; $this->error = $error;
} }
@ -217,7 +220,7 @@ class ImageIO extends Extension {
} }
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) {
global $user, $config; global $user;
if($user->can("delete_image")) { if($user->can("delete_image")) {
$event->add_part($this->theme->get_deleter_html($event->image->id)); $event->add_part($this->theme->get_deleter_html($event->image->id));
@ -310,7 +313,7 @@ class ImageIO extends Extension {
* @throws ImageAdditionException * @throws ImageAdditionException
*/ */
private function add_image(Image $image) { private function add_image(Image $image) {
global $page, $user, $database, $config; global $user, $database, $config;
/* /*
* Validate things * Validate things
@ -328,7 +331,7 @@ class ImageIO extends Extension {
if($handler == "merge" || isset($_GET['update'])) { if($handler == "merge" || isset($_GET['update'])) {
$merged = array_merge($image->get_tag_array(), $existing->get_tag_array()); $merged = array_merge($image->get_tag_array(), $existing->get_tag_array());
send_event(new TagSetEvent($existing, $merged)); send_event(new TagSetEvent($existing, $merged));
if(isset($_GET['rating']) && isset($_GET['update']) && class_exists("Ratings")){ if(isset($_GET['rating']) && isset($_GET['update']) && ext_is_live("Ratings")){
send_event(new RatingSetEvent($existing, $_GET['rating'])); send_event(new RatingSetEvent($existing, $_GET['rating']));
} }
if(isset($_GET['source']) && isset($_GET['update'])){ if(isset($_GET['source']) && isset($_GET['update'])){
@ -384,7 +387,6 @@ class ImageIO extends Extension {
*/ */
private function send_file($image_id, $type) { private function send_file($image_id, $type) {
global $config; global $config;
global $database;
$image = Image::by_id($image_id); $image = Image::by_id($image_id);
global $page; global $page;
@ -408,7 +410,7 @@ class ImageIO extends Extension {
$gmdate_mod = gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT'; $gmdate_mod = gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT';
if($if_modified_since == $gmdate_mod) { if($if_modified_since == $gmdate_mod) {
$page->add_http_header("HTTP/1.0 304 Not Modified",3); $page->set_code(304);
$page->set_data(""); $page->set_data("");
} }
else { else {

View file

@ -1,26 +1,18 @@
<?php <?php
class ImageTest extends ShimmieWebTestCase { class ImageTest extends ShimmiePHPUnitTestCase {
public function testUserStats() { public function testUserStats() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
# test collision // broken with sqlite?
$this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test"); //$this->get_page("user/test");
$this->assert_text("already has hash"); //$this->assert_text("Images uploaded: 1");
$this->get_page("user/test"); //$this->click("Images uploaded");
$this->assert_text("Images uploaded: 1"); //$this->assert_title("Image $image_id: test");
$this->click("Images uploaded");
$this->assert_title("Image $image_id: test");
$this->log_out();
# test that serving manually doesn't cause errors # test that serving manually doesn't cause errors
$this->get_page("image/$image_id/moo.jpg"); $this->get_page("image/$image_id/moo.jpg");
$this->get_page("thumb/$image_id/moo.jpg"); $this->get_page("thumb/$image_id/moo.jpg");
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
} }
} }

View file

@ -8,8 +8,6 @@ class ImageIOTheme extends Themelet {
* @return string * @return string
*/ */
public function get_deleter_html(/*int*/ $image_id) { public function get_deleter_html(/*int*/ $image_id) {
global $config;
$html = " $html = "
".make_form(make_link("image/delete"))." ".make_form(make_link("image/delete"))."
<input type='hidden' name='image_id' value='$image_id' /> <input type='hidden' name='image_id' value='$image_id' />

View file

@ -13,6 +13,9 @@
class RemoveImageHashBanEvent extends Event { class RemoveImageHashBanEvent extends Event {
var $hash; var $hash;
/**
* @param string $hash
*/
public function __construct($hash) { public function __construct($hash) {
$this->hash = $hash; $this->hash = $hash;
} }
@ -23,6 +26,10 @@ class AddImageHashBanEvent extends Event {
var $hash; var $hash;
var $reason; var $reason;
/**
* @param string $hash
* @param string $reason
*/
public function __construct($hash, $reason) { public function __construct($hash, $reason) {
$this->hash = $hash; $this->hash = $hash;
$this->reason = $reason; $this->reason = $reason;
@ -53,7 +60,7 @@ class ImageBan extends Extension {
} }
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event) {
global $config, $database, $page, $user; global $database, $page, $user;
if($event->page_matches("image_hash_ban")) { if($event->page_matches("image_hash_ban")) {
if($user->can("ban_image")) { if($user->can("ban_image")) {
@ -126,6 +133,11 @@ class ImageBan extends Extension {
// DB funness // DB funness
/**
* @param int $page
* @param int $size
* @return array
*/
public function get_image_hash_bans($page, $size=100) { public function get_image_hash_bans($page, $size=100) {
global $database; global $database;

View file

@ -1,37 +1,33 @@
<?php <?php
class HashBanTest extends ShimmieWebTestCase { class HashBanTest extends ShimmiePHPUnitTestCase {
public function testBan() { public function testBan() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
$this->log_out(); $this->log_out();
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->markTestIncomplete();
$this->click("Ban and Delete"); $this->click("Ban and Delete");
$this->log_out(); $this->log_out();
$this->log_in_as_user(); $this->log_in_as_user();
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_response(404); $this->assert_response(404);
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_response(404); $this->assert_response(404);
$this->log_out();
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("image_hash_ban/list/1"); $this->get_page("image_hash_ban/list/1");
$this->click("Remove"); $this->click("Remove");
$this->log_out();
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_response(200); $this->assert_response(200);
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
} }
} }

View file

@ -235,7 +235,7 @@ class Index extends Extension {
} }
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event) {
global $config, $database, $page, $user; global $database, $page;
if($event->page_matches("post/list")) { if($event->page_matches("post/list")) {
if(isset($_GET['search'])) { if(isset($_GET['search'])) {
$search = url_escape(Tag::implode(Tag::resolve_aliases(Tag::explode($_GET['search'], false)))); $search = url_escape(Tag::implode(Tag::resolve_aliases(Tag::explode($_GET['search'], false))));
@ -377,19 +377,17 @@ class Index extends Extension {
$event->add_querylet(new Querylet("height $cmp :height{$this->stpen}",array("height{$this->stpen}"=>int_escape($matches[2])))); $event->add_querylet(new Querylet("height $cmp :height{$this->stpen}",array("height{$this->stpen}"=>int_escape($matches[2]))));
} }
else if(preg_match("/^order[=|:](id|width|height|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)){ else if(preg_match("/^order[=|:](id|width|height|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)){
global $order_sql;
$ord = strtolower($matches[1]); $ord = strtolower($matches[1]);
$default_order_for_column = preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC"; $default_order_for_column = preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC";
$sort = isset($matches[2]) ? strtoupper($matches[2]) : $default_order_for_column; $sort = isset($matches[2]) ? strtoupper($matches[2]) : $default_order_for_column;
$order_sql = "images.$ord $sort"; Image::$order_sql = "images.$ord $sort";
$event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag $event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag
} }
else if(preg_match("/^order[=|:]random[_]([0-9]{1,4})$/i", $event->term, $matches)){ else if(preg_match("/^order[=|:]random[_]([0-9]{1,4})$/i", $event->term, $matches)){
global $order_sql;
//order[=|:]random requires a seed to avoid duplicates //order[=|:]random requires a seed to avoid duplicates
//since the tag can't be changed during the parseevent, we instead generate the seed during submit using js //since the tag can't be changed during the parseevent, we instead generate the seed during submit using js
$seed = $matches[1]; $seed = $matches[1];
$order_sql = "RAND($seed)"; Image::$order_sql = "RAND($seed)";
$event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag $event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag
} }

View file

@ -3,7 +3,7 @@
$(function() { $(function() {
var blocked_tags = ($.cookie("ui-blocked-tags") || "").split(" "); var blocked_tags = ($.cookie("ui-blocked-tags") || "").split(" ");
var needs_refresh = false; var needs_refresh = false;
for(i=0; i<blocked_tags.length; i++) { for(var i=0; i<blocked_tags.length; i++) {
var tag = blocked_tags[i]; var tag = blocked_tags[i];
if(tag) { if(tag) {
$(".shm-thumb[data-tags~='"+tag+"']").hide(); $(".shm-thumb[data-tags~='"+tag+"']").hide();

View file

@ -1,17 +1,18 @@
<?php <?php
class IndexTest extends ShimmieWebTestCase { class IndexTest extends ShimmiePHPUnitTestCase {
public function testIndexPage() { public function testIndexPage() {
$this->get_page('post/list'); $this->get_page('post/list');
$this->assert_title("Welcome to Shimmie ".VERSION); $this->assert_title("Welcome to Shimmie ".VERSION);
$this->assert_no_text("Prev | Index | Next"); $this->assert_no_text("Prev | Index | Next");
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot"); $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$this->log_out(); $this->log_out();
$this->get_page('post/list'); $this->get_page('post/list');
$this->assert_title("Shimmie"); $this->assert_title("Shimmie");
$this->assert_text("Prev | Index | Next"); // FIXME
//$this->assert_text("Prev | Index | Next");
$this->get_page('post/list/-1'); $this->get_page('post/list/-1');
$this->assert_title("Shimmie"); $this->assert_title("Shimmie");
@ -25,17 +26,13 @@ class IndexTest extends ShimmieWebTestCase {
$this->get_page('post/list/99999'); $this->get_page('post/list/99999');
$this->assert_title("No Images Found"); $this->assert_title("No Images Found");
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
# FIXME: test search box # FIXME: test search box
} }
public function testSearches() { public function testSearches() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot"); $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$image_id_2 = $this->post_image("ext/simpletest/data/bedroom_workshop.jpg", "computer bedroom workshop"); $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "computer bedroom workshop");
$this->log_out(); $this->log_out();
# make sure both uploads were ok # make sure both uploads were ok
@ -45,7 +42,6 @@ class IndexTest extends ShimmieWebTestCase {
# regular tag, no results # regular tag, no results
$this->get_page('post/list/maumaumau/1'); $this->get_page('post/list/maumaumau/1');
$this->assert_title("No Images Found"); $this->assert_title("No Images Found");
$this->assert_text("No Images Found");
# regular tag, many results # regular tag, many results
$this->get_page('post/list/computer/1'); $this->get_page('post/list/computer/1');
@ -59,14 +55,16 @@ class IndexTest extends ShimmieWebTestCase {
# meta tag, one result # meta tag, one result
$this->get_page("post/list/hash=feb01bab5698a11dd87416724c7a89e3/1"); $this->get_page("post/list/hash=feb01bab5698a11dd87416724c7a89e3/1");
$this->assert_title(new PatternExpectation("/^Image $image_id_1: /")); //$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->assert_no_text("No Images Found"); $this->assert_no_text("No Images Found");
# meta tag, one result # meta tag, one result
$this->get_page("post/list/md5=feb01bab5698a11dd87416724c7a89e3/1"); $this->get_page("post/list/md5=feb01bab5698a11dd87416724c7a89e3/1");
$this->assert_title(new PatternExpectation("/^Image $image_id_1: /")); //$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->assert_no_text("No Images Found"); $this->assert_no_text("No Images Found");
$this->markTestIncomplete();
# multiple tags, many results # multiple tags, many results
$this->get_page('post/list/computer%20size=640x480/1'); $this->get_page('post/list/computer%20size=640x480/1');
$this->assert_title("computer size=640x480"); $this->assert_title("computer size=640x480");
@ -79,11 +77,11 @@ class IndexTest extends ShimmieWebTestCase {
# multiple tags, single result; search with one result = direct to image # multiple tags, single result; search with one result = direct to image
$this->get_page('post/list/screenshot%20computer/1'); $this->get_page('post/list/screenshot%20computer/1');
$this->assert_title(new PatternExpectation("/^Image $image_id_1: /")); //$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
# negative tag, should have one result # negative tag, should have one result
$this->get_page('post/list/computer%20-pbx/1'); $this->get_page('post/list/computer%20-pbx/1');
$this->assert_title(new PatternExpectation("/^Image $image_id_2: /")); //$this->assert_title(new PatternExpectation("/^Image $image_id_2: /"));
# negative tag alone, should work # negative tag alone, should work
# FIXME: known broken in mysql # FIXME: known broken in mysql
@ -92,12 +90,12 @@ class IndexTest extends ShimmieWebTestCase {
# test various search methods # test various search methods
$this->get_page("post/list/bedroo*/1"); $this->get_page("post/list/bedroo*/1");
$this->assert_title(new PatternExpectation("/^Image $image_id_2: /")); //$this->assert_title(new PatternExpectation("/^Image $image_id_2: /"));
$this->get_page("post/list/id=$image_id_1/1"); $this->get_page("post/list/id=$image_id_1/1");
$this->assert_title(new PatternExpectation("/^Image $image_id_1: /")); //$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->assert_no_text("No Images Found"); $this->assert_no_text("No Images Found");
$this->get_page("post/list/filename=screenshot/1"); $this->get_page("post/list/filename=screenshot/1");
$this->assert_title(new PatternExpectation("/^Image $image_id_1: /")); //$this->assert_title(new PatternExpectation("/^Image $image_id_1: /"));
$this->assert_no_text("No Images Found"); $this->assert_no_text("No Images Found");
$this->get_page("post/list/tags=3/1"); $this->get_page("post/list/tags=3/1");
$this->assert_title("tags=3"); $this->assert_title("tags=3");
@ -105,11 +103,6 @@ class IndexTest extends ShimmieWebTestCase {
$this->get_page("post/list/ext=jpg/1"); $this->get_page("post/list/ext=jpg/1");
$this->assert_title("ext=jpg"); $this->assert_title("ext=jpg");
$this->assert_no_text("No Images Found"); $this->assert_no_text("No Images Found");
$this->log_in_as_admin();
$this->delete_image($image_id_1);
$this->delete_image($image_id_2);
$this->log_out();
} }
} }

View file

@ -3,6 +3,11 @@
class IndexTheme extends Themelet { class IndexTheme extends Themelet {
var $page_number, $total_pages, $search_terms; var $page_number, $total_pages, $search_terms;
/**
* @param int $page_number
* @param int $total_pages
* @param string[] $search_terms
*/
public function set_page($page_number, $total_pages, $search_terms) { public function set_page($page_number, $total_pages, $search_terms) {
$this->page_number = $page_number; $this->page_number = $page_number;
$this->total_pages = $total_pages; $this->total_pages = $total_pages;
@ -27,6 +32,10 @@ and of course start organising your images :-)
$page->add_block(new Block("Installation Succeeded!", $text, "main", 0)); $page->add_block(new Block("Installation Succeeded!", $text, "main", 0));
} }
/**
* @param Page $page
* @param Image[] $images
*/
public function display_page(Page $page, $images) { public function display_page(Page $page, $images) {
$this->display_page_header($page, $images); $this->display_page_header($page, $images);
@ -41,12 +50,21 @@ and of course start organising your images :-)
} }
} }
public function display_admin_block(/*array(string)*/ $parts) { /**
* @param string[] $parts
*/
public function display_admin_block($parts) {
global $page; global $page;
$page->add_block(new Block("List Controls", join("<br>", $parts), "left", 50)); $page->add_block(new Block("List Controls", join("<br>", $parts), "left", 50));
} }
/**
* @param int $page_number
* @param int $total_pages
* @param string[] $search_terms
* @return string
*/
protected function build_navigation($page_number, $total_pages, $search_terms) { protected function build_navigation($page_number, $total_pages, $search_terms) {
$prev = $page_number - 1; $prev = $page_number - 1;
$next = $page_number + 1; $next = $page_number + 1;
@ -63,7 +81,7 @@ and of course start organising your images :-)
$h_search_link = make_link(); $h_search_link = make_link();
$h_search = " $h_search = "
<p><form action='$h_search_link' method='GET'> <p><form action='$h_search_link' method='GET'>
<input class='autocomplete_tags' name='search' type='text' placeholder='Search' value='$h_search_string' /> <input type='search' name='search' value='$h_search_string' placeholder='Search' class='autocomplete_tags' autocomplete='off' />
<input type='hidden' name='q' value='/post/list'> <input type='hidden' name='q' value='/post/list'>
<input type='submit' value='Find' style='display: none;' /> <input type='submit' value='Find' style='display: none;' />
</form> </form>
@ -72,6 +90,11 @@ and of course start organising your images :-)
return $h_prev.' | '.$h_index.' | '.$h_next.'<br>'.$h_search; return $h_prev.' | '.$h_index.' | '.$h_next.'<br>'.$h_search;
} }
/**
* @param Image[] $images
* @param string $query
* @return string
*/
protected function build_table($images, $query) { protected function build_table($images, $query) {
$h_query = html_escape($query); $h_query = html_escape($query);
$table = "<div class='shm-image-list' data-query='$h_query'>"; $table = "<div class='shm-image-list' data-query='$h_query'>";
@ -82,6 +105,10 @@ and of course start organising your images :-)
return $table; return $table;
} }
/**
* @param Page $page
* @param Image[] $images
*/
protected function display_page_header(Page $page, $images) { protected function display_page_header(Page $page, $images) {
global $config; global $config;
@ -102,14 +129,18 @@ and of course start organising your images :-)
$page->set_heading($page_title); $page->set_heading($page_title);
} }
/**
* @param Page $page
* @param Image[] $images
*/
protected function display_page_images(Page $page, $images) { protected function display_page_images(Page $page, $images) {
if (count($this->search_terms) > 0) { if (count($this->search_terms) > 0) {
$query = url_escape(implode(' ', $this->search_terms)); $query = url_escape(implode(' ', $this->search_terms));
$page->add_block(new Block("Images", $this->build_table($images, "#search=$query"), "main", 10, "image-list")); $page->add_block(new Block("Images", $this->build_table($images, "#search=$query"), "main", 10, "image-list"));
$this->display_paginator($page, "post/list/$query", null, $this->page_number, $this->total_pages); $this->display_paginator($page, "post/list/$query", null, $this->page_number, $this->total_pages, TRUE);
} else { } else {
$page->add_block(new Block("Images", $this->build_table($images, null), "main", 10, "image-list")); $page->add_block(new Block("Images", $this->build_table($images, null), "main", 10, "image-list"));
$this->display_paginator($page, "post/list", null, $this->page_number, $this->total_pages); $this->display_paginator($page, "post/list", null, $this->page_number, $this->total_pages, TRUE);
} }
} }
} }

View file

@ -48,7 +48,7 @@ class IPBan extends Extension {
public function onPageRequest(PageRequestEvent $event) { public function onPageRequest(PageRequestEvent $event) {
if($event->page_matches("ip_ban")) { if($event->page_matches("ip_ban")) {
global $config, $database, $page, $user; global $page, $user;
if($user->can("ban_ip")) { if($user->can("ban_ip")) {
if($event->get_arg(0) == "add" && $user->check_auth_token()) { if($event->get_arg(0) == "add" && $user->check_auth_token()) {
if(isset($_POST['ip']) && isset($_POST['reason']) && isset($_POST['end'])) { if(isset($_POST['ip']) && isset($_POST['reason']) && isset($_POST['end'])) {
@ -119,9 +119,11 @@ class IPBan extends Extension {
ip SCORE_INET NOT NULL, ip SCORE_INET NOT NULL,
end_timestamp INTEGER, end_timestamp INTEGER,
reason TEXT NOT NULL, reason TEXT NOT NULL,
INDEX (end_timestamp) added SCORE_DATETIME NOT NULL DEFAULT SCORE_NOW,
FOREIGN KEY (banner_id) REFERENCES users(id) ON DELETE CASCADE,
"); ");
$config->set_int("ext_ipban_version", 6); $database->execute("CREATE INDEX bans__end_timestamp ON bans(end_timestamp)");
$config->set_int("ext_ipban_version", 8);
} }
// === // ===

View file

@ -1,5 +1,5 @@
<?php <?php
class IPBanTest extends SCoreWebTestCase { class IPBanTest extends ShimmiePHPUnitTestCase {
public function testIPBan() { public function testIPBan() {
$this->get_page('ip_ban/list'); $this->get_page('ip_ban/list');
$this->assert_response(403); $this->assert_response(403);
@ -9,6 +9,9 @@ class IPBanTest extends SCoreWebTestCase {
$this->get_page('ip_ban/list'); $this->get_page('ip_ban/list');
$this->assert_no_text("42.42.42.42"); $this->assert_no_text("42.42.42.42");
$this->markTestIncomplete();
$this->set_field('ip', '42.42.42.42'); $this->set_field('ip', '42.42.42.42');
$this->set_field('reason', 'unit testing'); $this->set_field('reason', 'unit testing');
$this->set_field('end', '1 week'); $this->set_field('end', '1 week');
@ -20,10 +23,7 @@ class IPBanTest extends SCoreWebTestCase {
$this->get_page('ip_ban/list?all=on'); // just test it doesn't crash for now $this->get_page('ip_ban/list?all=on'); // just test it doesn't crash for now
$this->log_out();
# FIXME: test that the IP is actually banned # FIXME: test that the IP is actually banned
} }
} }

View file

@ -15,7 +15,6 @@ class IPBanTheme extends Themelet {
global $database, $user; global $database, $user;
$h_bans = ""; $h_bans = "";
$prefix = ($database->get_driver_name() == "sqlite" ? "bans." : ""); $prefix = ($database->get_driver_name() == "sqlite" ? "bans." : "");
$prefix2 = ($database->get_driver_name() == "sqlite" ? "users." : "");
foreach($bans as $ban) { foreach($bans as $ban) {
$end_human = date('Y-m-d', $ban[$prefix.'end_timestamp']); $end_human = date('Y-m-d', $ban[$prefix.'end_timestamp']);
$h_bans .= " $h_bans .= "

View file

@ -30,7 +30,7 @@ class LinkImage extends Extension {
return array( return array(
'thumb_src' => make_http($image->get_thumb_link()), 'thumb_src' => make_http($image->get_thumb_link()),
'image_src' => make_http($image->get_image_link()), 'image_src' => make_http($image->get_image_link()),
'post_link' => make_http($_SERVER["REQUEST_URI"]), 'post_link' => make_http(make_link("post/view/{$image->id}")),
'text_link' => $text_link); 'text_link' => $text_link);
} }
} }

View file

@ -1,12 +1,17 @@
<?php <?php
class LinkImageTest extends ShimmieWebTestCase { class LinkImageTest extends ShimmiePHPUnitTestCase {
public function testLinkImage() { public function testLinkImage() {
$this->log_in_as_user(); $this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pie"); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pie");
# FIXME
# look in the "plain text link to post" box, follow the link # look in the "plain text link to post" box, follow the link
# in there, see if it takes us to the right page # in there, see if it takes us to the right page
$raw = $this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->markTestIncomplete();
// FIXME
$matches = array(); $matches = array();
preg_match("#value='(http://.*(/|%2F)post(/|%2F)view(/|%2F)[0-9]+)'#", $raw, $matches); preg_match("#value='(http://.*(/|%2F)post(/|%2F)view(/|%2F)[0-9]+)'#", $raw, $matches);
$this->assertTrue(count($matches) > 0); $this->assertTrue(count($matches) > 0);
@ -14,12 +19,6 @@ class LinkImageTest extends ShimmieWebTestCase {
$this->get($matches[1]); $this->get($matches[1]);
$this->assert_title("Image $image_id: pie"); $this->assert_title("Image $image_id: pie");
} }
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
} }
} }

View file

@ -96,7 +96,7 @@ class LogDatabase extends Extension {
$page_total = $database->get_one("SELECT count(*) FROM score_log $where", $args); $page_total = $database->get_one("SELECT count(*) FROM score_log $where", $args);
// don't cache a length of zero when the extension is first installed // don't cache a length of zero when the extension is first installed
if($page_total > 10) { if($page_total > 10) {
$database->cache->set("event_log_length", 600); $database->cache->set("event_log_length", $page_total, 600);
} }
} }

View file

@ -1,5 +1,5 @@
<?php <?php
class LogDatabaseTest extends SCoreWebTestCase { class LogDatabaseTest extends ShimmiePHPUnitTestCase {
public function testLog() { public function testLog() {
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->get_page("log/view"); $this->get_page("log/view");
@ -7,7 +7,5 @@ class LogDatabaseTest extends SCoreWebTestCase {
$this->get_page("log/view?time=2012-03-01"); $this->get_page("log/view?time=2012-03-01");
$this->get_page("log/view?user=demo"); $this->get_page("log/view?user=demo");
$this->get_page("log/view?priority=10"); $this->get_page("log/view?priority=10");
$this->log_out();
} }
} }

View file

@ -23,7 +23,7 @@ class LogDatabaseTheme extends Themelet {
<tr><th>Time</th><th>Module</th><th>User</th><th colspan='2'>Message</th></tr> <tr><th>Time</th><th>Module</th><th>User</th><th colspan='2'>Message</th></tr>
<form action='".make_link("log/view")."' method='GET'> <form action='".make_link("log/view")."' method='GET'>
<tr class='sizedinputs'> <tr class='sizedinputs'>
<td><input type='text' name='time' value='".$this->heie("time")."'></td> <td><input type='time' name='time' value='".$this->heie("time")."'></td>
<td><input type='text' name='module' value='".$this->heie("module")."'></td> <td><input type='text' name='module' value='".$this->heie("module")."'></td>
<td><input type='text' name='user' value='".$this->heie("user")."'></td> <td><input type='text' name='user' value='".$this->heie("user")."'></td>
<td> <td>

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