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
thumbs
!lib/images
*.phar
*.sqlite
# Created by http://www.gitignore.io

View file

@ -1,6 +1,9 @@
imports:
- javascript
- php
- javascript
- php
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
sudo: false
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.5
- 5.4
- 5.5
- 5.6
- nightly
# optionally specify a list of environments, for example to test different RDBMS
env:
matrix:
- DB=mysql
- DB=pgsql
before_install:
- sudo apt-get update > /dev/null
- sudo chmod u+x tests/setup_test_env.sh
- DB=sqlite
install:
# Install nginx, php5-fpm and configure them
- 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 "CREATE DATABASE shimmie;" -U postgres; 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
- mkdir -p data/config
- 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 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 "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:
- php tests/test_install.php -d $DB -h "http://127.0.0.1/"
- php tests/test_all.php -h "http://127.0.0.1/"
- php install.php
- 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:
- sudo ls -al
- sudo ls -al data/config/
- sudo cat data/config/shimmie.conf.php
- sudo cat data/config/extensions.conf.php
- sudo cat /etc/nginx/sites-enabled/default
- sudo cat /var/log/nginx/error.log
- sudo cat /var/log/php5-fpm.log
- sudo ls /var/run/mysql*
- sudo ls /var/log/*mysql*
- 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*
- head -n 100 data/config/*
- ls /var/run/mysql*
- ls /var/log/*mysql*
- cat /var/log/mysql.err
- cat /var/log/mysql.log
- cat /var/log/mysql/error.log
- cat /var/log/mysql/slow.log
- ls /var/log/postgresql
- cat /var/log/postgresql/postgresql*
# configure notifications (email, IRC, campfire etc)
#notifications:
# irc: "irc.freenode.org#shimmie"
#
after_script:
- php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover

View file

@ -20,8 +20,8 @@ check out one of the versioned branches.
# Requirements
- MySQL/MariaDB 5.1+ (with experimental support for PostgreSQL 8+ and SQLite 3)
- PHP 5.3.7+
- MySQL/MariaDB 5.1+ (with experimental support for PostgreSQL 9+ and SQLite 3)
- PHP 5.4.8+
- GD or ImageMagick
# 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) {
global $page;
$page->add_http_header("HTTP/1.0 $code $title");
$page->set_code($code);
$page->set_title($title);
$page->set_heading($title);
$has_nav = false;
@ -66,8 +66,8 @@ class BaseThemelet {
$custom_classes = "";
if(class_exists("Relationships")){
if(property_exists('Image', 'parent_id') && $image->parent_id !== NULL){ $custom_classes .= "shm-thumb-has_parent "; }
if(property_exists('Image', 'has_children') && $image->has_children == TRUE){ $custom_classes .= "shm-thumb-has_child "; }
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 "; }
}
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 int $page_number
* @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;
$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"));
}
@ -95,7 +96,7 @@ class BaseThemelet {
*
* @param string $base_url
* @param string $query
* @param int|string $page
* @param string $page
* @param string $name
* @return string
*/
@ -107,8 +108,8 @@ class BaseThemelet {
/**
* @param string $base_url
* @param string $query
* @param int|string $page
* @param int|string $current_page
* @param string $page
* @param int $current_page
* @param string $name
* @return string
*/
@ -127,19 +128,25 @@ class BaseThemelet {
* @param int $total_pages
* @param string $base_url
* @param string $query
* @param bool $show_random
* @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;
$prev = $current_page - 1;
$rand = mt_rand(1, $total_pages);
$at_start = ($current_page <= 1 || $total_pages <= 1);
$at_end = ($current_page >= $total_pages);
$first_html = $at_start ? "First" : $this->gen_page_link($base_url, $query, 1, "First");
$prev_html = $at_start ? "Prev" : $this->gen_page_link($base_url, $query, $prev, "Prev");
$random_html = $this->gen_page_link($base_url, $query, $rand, "Random");
$random_html = "-";
if($show_random) {
$rand = mt_rand(1, $total_pages);
$random_html = $this->gen_page_link($base_url, $query, $rand, "Random");
}
$next_html = $at_end ? "Next" : $this->gen_page_link($base_url, $query, $next, "Next");
$last_html = $at_end ? "Last" : $this->gen_page_link($base_url, $query, $total_pages, "Last");

View file

@ -88,10 +88,10 @@ interface Config {
* parameter won't show up.
*
* @param string $name
* @param bool|string|null $value
* @param bool $value
* @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.
@ -218,10 +218,10 @@ abstract class BaseConfig implements Config {
/**
* @param string $name
* @param bool|null|string $value
* @param bool $value
* @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))) {
$this->values[$name] = (($value == 'on' || $value === true) ? 'Y' : 'N');
}
@ -328,8 +328,9 @@ class StaticConfig extends BaseConfig {
*/
public function __construct($filename) {
if(file_exists($filename)) {
$config = array();
require_once $filename;
if(isset($config)) {
if(!empty($config)) {
$this->values = $config;
}
else {

View file

@ -20,7 +20,7 @@ class Querylet {
* @param \Querylet $querylet
*/
public function append($querylet) {
assert(!is_null($querylet));
assert('!is_null($querylet)');
$this->sql .= $querylet->sql;
$this->variables = array_merge($this->variables, $querylet->variables);
}
@ -146,7 +146,12 @@ class PostgreSQL extends DBEngine {
* @param \PDO $db
*/
public function init($db) {
$db->exec("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';");
if(array_key_exists('REMOTE_ADDR', $_SERVER)) {
$db->exec("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';");
}
else {
$db->exec("SET application_name TO 'shimmie [local]';");
}
$db->exec("SET statement_timeout TO 10000;");
}
@ -174,7 +179,7 @@ class PostgreSQL extends DBEngine {
*/
public function create_table_sql($name, $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 _concat($a, $b) { return $a . $b; }
function _lower($a) { return strtolower($a); }
function _rand() { return rand(); }
function _ln($n) { return log($n); }
class SQLite extends DBEngine {
/** @var string */
@ -209,6 +216,8 @@ class SQLite extends DBEngine {
$db->sqliteCreateFunction('md5', '_md5', 1);
$db->sqliteCreateFunction('concat', '_concat', 2);
$db->sqliteCreateFunction('lower', '_lower', 1);
$db->sqliteCreateFunction('rand', '_rand', 0);
$db->sqliteCreateFunction('ln', '_ln', 1);
}
/**
@ -238,9 +247,10 @@ class SQLite extends DBEngine {
$extras = "";
foreach(explode(",", $data) as $bit) {
$matches = array();
if(preg_match("/INDEX\s*\((.*)\)/", $bit, $matches)) {
$col = $matches[1];
$extras .= "CREATE INDEX {$name}_{$col} on {$name}({$col});";
if(preg_match("/(UNIQUE)? ?INDEX\s*\((.*)\)/", $bit, $matches)) {
$uni = $matches[1];
$col = $matches[2];
$extras .= "CREATE $uni INDEX {$name}_{$col} ON {$name}({$col});";
}
else {
$cols[] = $bit;
@ -295,7 +305,7 @@ class MemcacheCache implements CacheEngine {
* @return array|bool|string
*/
public function get($key) {
assert(!is_null($key));
assert('!is_null($key)');
$val = $this->memcache->get($key);
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
$hit = $val === false ? "miss" : "hit";
@ -317,7 +327,7 @@ class MemcacheCache implements CacheEngine {
* @param int $time
*/
public function set($key, $val, $time=0) {
assert(!is_null($key));
assert('!is_null($key)');
$this->memcache->set($key, $val, false, $time);
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
file_put_contents("data/cache.log", "Cache set: $key ($time)\n", FILE_APPEND);
@ -328,7 +338,7 @@ class MemcacheCache implements CacheEngine {
* @param string $key
*/
public function delete($key) {
assert(!is_null($key));
assert('!is_null($key)');
$this->memcache->delete($key);
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
file_put_contents("data/cache.log", "Cache delete: $key\n", FILE_APPEND);
@ -354,7 +364,7 @@ class APCCache implements CacheEngine {
}
public function get($key) {
assert(!is_null($key));
assert('!is_null($key)');
$val = apc_fetch($key);
if($val) {
$this->hits++;
@ -367,12 +377,12 @@ class APCCache implements CacheEngine {
}
public function set($key, $val, $time=0) {
assert(!is_null($key));
assert('!is_null($key)');
apc_store($key, $val, $time);
}
public function delete($key) {
assert(!is_null($key));
assert('!is_null($key)');
apc_delete($key);
}
@ -413,6 +423,11 @@ class Database {
*/
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
* 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("/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(
PDO::ATTR_PERSISTENT => DATABASE_KA,
PDO::ATTR_PERSISTENT => $ka,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
$this->db = new PDO(DATABASE_DSN, $db_user, $db_pass, $db_params);
@ -545,6 +566,26 @@ class Database {
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.
*
@ -556,7 +597,7 @@ class Database {
public function execute($query, $args=array()) {
try {
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);
if (!array_key_exists(0, $args)) {
foreach($args as $name=>$value) {
@ -598,7 +639,7 @@ class Database {
*
* @param string $query
* @param array $args
* @return mixed|null
* @return array|null
*/
public function get_row($query, $args=array()) {
$_start = microtime(true);
@ -661,7 +702,7 @@ class Database {
* Get the ID of the last inserted row.
*
* @param string|null $seq
* @return string
* @return int
*/
public function get_last_insert_id($seq) {
if($this->engine->name == "pgsql") {
@ -690,6 +731,7 @@ class Database {
* @return int|null
*/
public function count_tables() {
if(is_null($this->db) || is_null($this->engine)) $this->connect_db();
if($this->engine->name === "mysql") {
@ -702,7 +744,7 @@ class Database {
);
} else if ($this->engine->name === "sqlite") {
return count(
$this->get_all(".tables")
$this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'")
);
} else {
// 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
* 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.
*
* This event is sent before $user is set to anything
*/
class InitExtEvent extends Event {}

View file

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

View file

@ -47,7 +47,7 @@
* }
*
* // ext/hello/test.php
* public class HelloTest extends SCoreWebTestCase {
* public class HelloTest extends SCorePHPUnitTestCase {
* public function testHello() {
* $this->get_page("post/list"); // View a page, any 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 >_<
*/
abstract class Extension {
/** @var array which DBs this ext supports (blank for 'all') */
protected $db_support = [];
/** this theme's Themelet object */
public $theme;
@ -97,6 +100,15 @@ abstract class Extension {
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.
*
@ -270,7 +282,7 @@ abstract class DataHandlerExtension extends Extension {
abstract protected function supported_ext($ext);
/**
* @param $tmpname
* @param string $tmpname
* @return bool
*/
abstract protected function check_contents($tmpname);

View file

@ -23,10 +23,6 @@
* Classes *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
$tag_n = 0; // temp hack
$_flexihash = null;
$_fh_last_opts = null;
$order_sql = null; // this feels ugly
require_once "lib/flexihash.php";
@ -40,6 +36,9 @@ require_once "lib/flexihash.php";
* other supported upload type.
*/
class Image {
private static $tag_n = 0; // temp hack
public static $order_sql = null; // this feels ugly
/** @var null|int */
public $id = null;
@ -64,7 +63,7 @@ class Image {
public $tag_array;
public $owner_id, $owner_ip;
public $posted, $posted_timestamp;
public $posted;
public $source;
public $locked;
@ -75,13 +74,14 @@ class Image {
* @param null|mixed $row
*/
public function __construct($row=null) {
assert('is_null($row) || is_array($row)');
if(!is_null($row)) {
foreach($row as $name => $value) {
// some databases use table.name rather than name
$name = str_replace("images.", "", $name);
$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);
assert(is_numeric($this->id));
@ -97,7 +97,7 @@ class Image {
* @return Image
*/
public static function by_id(/*int*/ $id) {
assert(is_numeric($id));
assert('is_numeric($id)');
global $database;
$row = $database->get_row("SELECT * FROM images WHERE images.id=:id", array("id"=>$id));
return ($row ? new Image($row) : null);
@ -110,7 +110,7 @@ class Image {
* @return Image
*/
public static function by_hash(/*string*/ $hash) {
assert(is_string($hash));
assert('is_string($hash)');
global $database;
$row = $database->get_row("SELECT images.* FROM images WHERE hash=:hash", array("hash"=>$hash));
return ($row ? new Image($row) : null);
@ -123,7 +123,7 @@ class Image {
* @return Image
*/
public static function by_random($tags=array()) {
assert(is_array($tags));
assert('is_array($tags)');
$max = Image::count_images($tags);
if ($max < 1) return null; // From Issue #22 - opened by HungryFeline on May 30, 2011.
$rand = mt_rand(0, $max-1);
@ -142,10 +142,10 @@ class Image {
* @return Image[]
*/
public static function find_images(/*int*/ $start, /*int*/ $limit, $tags=array()) {
assert(is_numeric($start));
assert(is_numeric($limit));
assert(is_array($tags));
global $database, $user, $config, $order_sql;
assert('is_numeric($start)');
assert('is_numeric($limit)');
assert('is_array($tags)');
global $database, $user, $config;
$images = array();
@ -158,19 +158,90 @@ class Image {
}
}
$querylet = Image::build_search_querylet($tags);
$querylet->append(new Querylet(" ORDER BY ".($order_sql ?: "images.".$config->get_string("index_order"))));
$querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start)));
#var_dump($querylet->sql); var_dump($querylet->variables);
$result = $database->execute($querylet->sql, $querylet->variables);
$result = null;
if(SEARCH_ACCEL) {
$result = Image::get_accelerated_result($tags, $start, $limit);
}
if(!$result) {
$querylet = Image::build_search_querylet($tags);
$querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order"))));
$querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start)));
#var_dump($querylet->sql); var_dump($querylet->variables);
$result = $database->execute($querylet->sql, $querylet->variables);
}
while($row = $result->fetch()) {
$images[] = new Image($row);
}
$order_sql = null;
Image::$order_sql = null;
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
*/
@ -179,10 +250,10 @@ class Image {
* Count the number of image results for a given search
*
* @param string[] $tags
* @return mixed
* @return int
*/
public static function count_images($tags=array()) {
assert(is_array($tags));
assert('is_array($tags)');
global $database;
$tag_count = count($tags);
@ -214,12 +285,11 @@ class Image {
* @return float
*/
public static function count_pages($tags=array()) {
assert(is_array($tags));
global $config, $database;
assert('is_array($tags)');
global $config;
return ceil(Image::count_images($tags) / $config->get_int('index_images'));
}
/*
* Accessors & mutators
*/
@ -235,8 +305,8 @@ class Image {
* @return Image
*/
public function get_next($tags=array(), $next=true) {
assert(is_array($tags));
assert(is_bool($next));
assert('is_array($tags)');
assert('is_bool($next)');
global $database;
if($next) {
@ -321,33 +391,7 @@ class Image {
* @return string
*/
public function get_image_link() {
global $config;
$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'));
return $this->get_link('image_ilink', '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.jpg');
}
/**
@ -356,21 +400,33 @@ class Image {
* @return string
*/
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;
$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(!startsWith($image_tlink, "http://") && !startsWith($image_tlink, "/")) {
$image_tlink = make_link($image_tlink);
if(!empty($image_link)) {
if(!(strpos($image_link, "://") > 0) && !startsWith($image_link, "/")) {
$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)) {
return $this->parse_link_template(make_link('_thumbs/$hash/thumb.jpg'));
return $this->parse_link_template(make_link($nice));
}
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;
}
/**
* @param bool $tf
* @throws SCoreException
*/
public function set_locked($tf) {
global $database;
$ln = $tf ? "Y" : "N";
@ -510,20 +570,20 @@ class Image {
* Set the tags for this image.
*
* @param string[] $tags
* @throws Exception
*/
public function set_tags($tags) {
assert('is_array($tags) && count($tags) > 0', var_export($tags, true));
global $database;
assert(is_array($tags));
$tags = array_map(array('Tag', 'sanitise'), $tags);
$tags = Tag::resolve_aliases($tags);
assert(is_array($tags));
assert(count($tags) > 0);
$new_tags = implode(" ", $tags);
if(count($tags) <= 0) {
throw new SCoreException('Tried to set zero tags');
}
if($new_tags != $this->get_tag_list()) {
if(implode(" ", $tags) != $this->get_tag_list()) {
// delete old
$this->delete_tags_from_image();
// insert each new tags
@ -637,16 +697,17 @@ class Image {
$tmpl = $plte->link;
}
global $_flexihash, $_fh_last_opts;
static $flexihash = null;
static $fh_last_opts = null;
$matches = array();
if(preg_match("/(.*){(.*)}(.*)/", $tmpl, $matches)) {
$pre = $matches[1];
$opts = $matches[2];
$post = $matches[3];
if($opts != $_fh_last_opts) {
$_fh_last_opts = $opts;
$_flexihash = new Flexihash();
if($opts != $fh_last_opts) {
$fh_last_opts = $opts;
$flexihash = new Flexihash();
foreach(explode(",", $opts) as $opt) {
$parts = explode("=", $opt);
$parts_count = count($parts);
@ -660,11 +721,11 @@ class Image {
$opt_val = $parts[0];
$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;
}
@ -676,7 +737,7 @@ class Image {
* @return \Querylet
*/
private static function build_search_querylet($terms) {
assert(is_array($terms));
assert('is_array($terms)');
global $database;
if($database->get_driver_name() === "mysql")
return Image::build_ugly_search_querylet($terms);
@ -684,9 +745,58 @@ class Image {
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
* 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
*
@ -700,7 +810,7 @@ class Image {
* A) Incredibly simple:
* Each search term maps to a list of image IDs
* 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:
* All the subqueries are executed every time for every row in the
* images table. Yes, MySQL does suck this much.
@ -712,21 +822,12 @@ class Image {
global $database;
$tag_querylets = array();
$img_querylets = array();
$img_querylets = self::parse_meta_terms($terms);
$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
// various types of querylet
$terms = Tag::resolve_aliases($terms);
foreach($terms as $term) {
$positive = true;
if(is_string($term) && !empty($term) && ($term[0] == '-')) {
@ -752,41 +853,25 @@ class Image {
}
}
}
// 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);
$img_search = self::build_img_search($img_querylets);
// How many tag querylets are there?
$count_tag_querylets = count($tag_querylets);
// no tags, do a simple search (+image metadata if we have any)
if($count_tag_querylets === 0) {
$query = new Querylet("SELECT images.* FROM images ");
if(!empty($img_search->sql)) {
$query->append_sql(" WHERE ");
$query->append($img_search);
}
$query = self::build_simple_query($img_search);
}
// one positive tag (a common case), do an optimised search
else if($count_tag_querylets === 1 && $tag_querylets[0]->positive) {
$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 tags ON image_tags.tag_id=tags.id
WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)
"), array("tag"=>$tag_querylets[0]->tag));
"), array("tag"=>$tag_querylets[0]->tag));
if(!empty($img_search->sql)) {
$query->append_sql(" AND ");
@ -802,10 +887,12 @@ class Image {
foreach($tag_querylets as $tq) {
$tag_ids = $database->get_col(
$database->scoreql_to_sql(
"SELECT id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)"
),
array("tag"=>$tq->tag));
$database->scoreql_to_sql("
SELECT id
FROM tags
WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)
"), array("tag"=>$tq->tag)
);
if($tq->positive) {
$positive_tag_id_array = array_merge($positive_tag_id_array, $tag_ids);
$tags_ok = count($tag_ids) > 0;
@ -820,7 +907,7 @@ class Image {
$have_pos = count($positive_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) {
$positive_tag_id_list = join(', ', $positive_tag_id_array);
$sql .= "
@ -842,8 +929,11 @@ class Image {
WHERE tag_id IN ($negative_tag_id_list)
";
}
$sql .= ")";
$query = new Querylet($sql);
$query = new Querylet("
SELECT images.*
FROM images
WHERE images.id IN ($sql)
");
if(strlen($img_search->sql) > 0) {
$query->append_sql(" AND ");
@ -867,23 +957,18 @@ class Image {
/**
* this function exists because mysql is a turd, see the docs for
* build_accurate_search_querylet() for a full explanation
*
* @param array $terms
* @return Querylet
*/
private static function build_ugly_search_querylet($terms) {
global $database;
$tag_querylets = array();
$img_querylets = array();
$img_querylets = self::parse_meta_terms($terms);
$positive_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);
reset($terms); // rewind to first element in array.
@ -916,56 +1001,33 @@ class Image {
$sql = "0";
$terms = array();
foreach($tag_querylets as $tq) {
global $tag_n;
$sign = $tq->positive ? "+" : "-";
//$sql .= " $sign (tag LIKE :tag$tag_n)";
$sql .= ' '.$sign.' (tag LIKE :tag'.$tag_n.')';
//$terms["tag$tag_n"] = $tq->tag;
$terms['tag'.$tag_n] = $tq->tag;
$tag_n++;
$sql .= ' '.$sign.' (tag LIKE :tag'.Image::$tag_n.')';
$terms['tag'.Image::$tag_n] = $tq->tag;
Image::$tag_n++;
if($sign === "+") $positive_tag_count++;
else $negative_tag_count++;
}
$tag_search = new Querylet($sql, $terms);
// 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);
$img_search = self::build_img_search($img_querylets);
// no tags, do a simple search (+image metadata if we have any)
if($positive_tag_count + $negative_tag_count == 0) {
$query = new Querylet("SELECT images.*,UNIX_TIMESTAMP(posted) AS posted_timestamp FROM images ");
if(!empty($img_search->sql)) {
$query->append_sql(" WHERE ");
$query->append($img_search);
}
$query = self::build_simple_query($img_search);
}
// one positive tag (a common case), do an optimised search
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 -_-
// "{$this->get_images} WHERE images.id IN (SELECT image_id FROM tags WHERE tag LIKE ?) ",
"
SELECT images.*, UNIX_TIMESTAMP(posted) AS posted_timestamp
FROM tags, image_tags, images
WHERE
tag LIKE :tag0
AND tags.id = image_tags.tag_id
AND image_tags.image_id = images.id
",
$tag_search->variables);
// 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 ?) ",
$query = new Querylet("
SELECT images.*
FROM images
JOIN image_tags ON images.id=image_tags.image_id
JOIN tags ON image_tags.tag_id=tags.id
WHERE tag LIKE :tag0
", $tag_search->variables);
if(!empty($img_search->sql)) {
$query->append_sql(" AND ");
@ -980,7 +1042,10 @@ class Image {
$x = 0;
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);
$tags_ok = count($tag_ids) > 0 || !$tag_querylets[$x]->positive;
@ -1006,7 +1071,7 @@ class Image {
)
);
$query = new Querylet('
SELECT *, UNIX_TIMESTAMP(posted) AS posted_timestamp
SELECT *
FROM ('.$subquery->sql.') AS images ', $subquery->variables);
if(!empty($img_search->sql)) {
@ -1033,7 +1098,7 @@ class Image {
WHERE 1=0
");
}
$tag_n = 0;
Image::$tag_n = 0;
return $query;
}
}
@ -1051,13 +1116,14 @@ class Tag {
* Remove any excess fluff from a user-input tag
*
* @param string $tag
* @return mixed
* @return string
*/
public static function sanitise($tag) {
assert(is_string($tag));
$tag = preg_replace("/[\s?*]/", "", $tag);
$tag = preg_replace("/\.+/", ".", $tag);
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag);
assert('is_string($tag)');
$tag = preg_replace("/[\s?*]/", "", $tag); # whitespace
$tag = preg_replace('/\x20(\x0e|\x0f)/', '', $tag); # unicode RTL
$tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
return $tag;
}
@ -1066,10 +1132,10 @@ class Tag {
*
* @param string|string[] $tags
* @param bool $tagme
* @return array
* @return string[]
*/
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)) {
$tags = explode(' ', trim($tags));
@ -1100,7 +1166,7 @@ class Tag {
* @return string
*/
public static function implode($tags) {
assert(is_string($tags) || is_array($tags));
assert('is_string($tags) || is_array($tags)');
if(is_array($tags)) {
sort($tags);
@ -1118,7 +1184,8 @@ class Tag {
* @return string
*/
public static function resolve_alias($tag) {
assert(is_string($tag));
assert('is_string($tag)');
global $database;
$negative = false;
if(!empty($tag) && ($tag[0] == '-')) {
@ -1126,14 +1193,18 @@ class Tag {
$tag = substr($tag, 1);
}
global $database;
$newtag = $database->get_one(
$database->scoreql_to_sql("SELECT newtag FROM aliases WHERE SCORE_STRNORM(oldtag)=SCORE_STRNORM(:tag)"),
array("tag"=>$tag));
array("tag"=>$tag)
);
if(empty($newtag)) {
//tag has no alias, use old tag
$newtag = $tag;
}
return $negative ? "-$newtag" : $newtag;
return !$negative ? $newtag : preg_replace("/(\S+)/", "-$1", $newtag);
}
/**
@ -1156,7 +1227,10 @@ class Tag {
global $database;
$db_wild_tag = str_replace("%", "\%", $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) {
$resolved = $newtags;
} else {
@ -1173,7 +1247,7 @@ class Tag {
* @return array
*/
public static function resolve_aliases($tags) {
assert(is_array($tags));
assert('is_array($tags)');
$new = array();
@ -1221,13 +1295,66 @@ function move_upload_to_archive(DataUploadEvent $event) {
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
* into the configured thumbnail square, with ratio intact
*
* @param int $orig_width
* @param int $orig_height
* @return array
* @return int[]
*/
function get_thumbnail_size(/*int*/ $orig_width, /*int*/ $orig_height) {
global $config;
@ -1253,4 +1380,3 @@ function get_thumbnail_size(/*int*/ $orig_width, /*int*/ $orig_height) {
}
}

View file

@ -1,13 +1,13 @@
<?php
/**
* \page themes Themes
*
*
* Each extension has a theme with a specific name -- eg. the extension Setup
* which is stored in ext/setup/main.php will have a theme called SetupTheme
* stored in ext/setup/theme.php. If you want to customise it, create a class
* in the file themes/mytheme/setup.theme.php called CustomSetupTheme which
* extends SetupTheme and overrides some of its methods.
*
*
* Generally an extension should only deal with processing data; whenever it
* wants to display something, it should pass the data to be displayed to the
* theme object, and the theme will add the data into the global $page
@ -65,11 +65,11 @@ class Page {
/** @name "data" mode */
//@{
/** @var string */
private $data = "";
/** @var string; public only for unit test */
public $data = "";
/** @var string */
private $filename = null;
/** @var string; public only for unit test */
public $filename = null;
/**
* Set the raw data to be sent.
@ -111,6 +111,9 @@ class Page {
/** @name "page" mode */
//@{
/** @var int */
public $code = 200;
/** @var string */
public $title = "";
@ -129,9 +132,19 @@ class Page {
/** @var string[] */
public $http_headers = array();
/** @var string[][] */
public $cookies = array();
/** @var Block[] */
public $blocks = array();
/**
* Set the HTTP status code
* @param int $code
*/
public function set_code($code) {
$this->code = $code;
}
/**
* Set the window title.
@ -177,6 +190,35 @@ class Page {
$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.
* @return string
@ -188,7 +230,7 @@ class Page {
}
return $data;
}
/**
* Removes all currently set HTML headers (Be careful..).
*/
@ -213,12 +255,18 @@ class Page {
*/
public function display() {
global $page, $user;
header("HTTP/1.0 {$this->code} Shimmie");
header("Content-type: ".$this->type);
header("X-Powered-By: SCore-".SCORE_VERSION);
if (!headers_sent()) {
foreach($this->http_headers as $head){ header($head); }
foreach($this->http_headers as $head) {
header($head);
}
foreach($this->cookies as $c) {
setcookie($c[0], $c[1], $c[2], $c[3]);
}
} else {
print "Error: Headers have already been sent to the client.";
}
@ -241,6 +289,9 @@ class Page {
# header("Cache-control: no-cache");
# 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");
$this->add_auto_html_headers();
$layout = new Layout();
@ -262,19 +313,19 @@ class Page {
break;
}
}
/**
* This function grabs all the CSS and JavaScript files sprinkled throughout Shimmie's folders,
* concatenates them together into two large files (one for CSS and one for JS) and then stores
* them in the /cache/ directory for serving to the user.
*
*
* Why do this? Two reasons:
* 1. Reduces the number of files the user's browser needs to download.
* 2. Allows these cached files to be compressed/minified by the admin.
*
*
* TODO: This should really be configurable somehow...
*/
protected function add_auto_html_headers() {
public function add_auto_html_headers() {
global $config;
$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='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_latest = 0;
$css_latest = $config_latest;
foreach(array_merge(zglob("lib/*.css"), zglob("ext/*/style.css"), zglob("themes/$theme_name/style.css")) as $css) {
$css_files[] = $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);
$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) {
$js_files[] = $js;
$js_latest = max($js_latest, filemtime($js));
@ -326,4 +382,3 @@ class Page {
class MockPage extends Page {
}

View file

@ -18,6 +18,8 @@
* define("SPEED_HAX", true);
*
*/
/** @private */
function _d($name, $value) {if(!defined($name)) define($name, $value);}
_d("DATABASE_DSN", null); // string PDO database connection details
_d("DATABASE_KA", true); // string Keep database connection alive
@ -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("COMPILE_ELS", false); // boolean pre-build the list of event listeners
_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("VERSION", '2.5.4'); // string shimmie version
_d("VERSION", '2.5.5'); // string shimmie version
_d("TIMEZONE", null); // string timezone
_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable
_d("EXTRA_EXTS", ""); // optional extra extensions

View file

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

View file

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

View file

@ -82,7 +82,7 @@ function sql_escape($input) {
* Turn all manner of HTML / INI / JS / DB booleans into a PHP one
*
* @param $input
* @return boolean
* @return bool
*/
function bool_escape($input) {
/*
@ -124,6 +124,25 @@ function no_escape($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 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 )
*
* @param $dateTime
* @return boolean
* @return bool
*/
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)) {
@ -250,7 +269,7 @@ function isValidDateTime($dateTime) {
* Check if a given string is a valid date. ( Format: yyyy-mm-dd )
*
* @param $date
* @return boolean
* @return bool
*/
function isValidDate($date) {
if (preg_match("/^(\d{4})-(\d{2})-(\d{2})$/", $date, $matches)) {
@ -263,6 +282,94 @@ function isValidDate($date) {
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),
* 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(NICE_URLS || $config->get_bool('nice_urls', false)) {
#$full = "http://" . $_SERVER["SERVER_NAME"] . $_SERVER["PHP_SELF"];
$full = $_SERVER["PHP_SELF"];
$base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $full);
$base = str_replace('/'.basename($_SERVER["SCRIPT_FILENAME"]), "", $_SERVER["PHP_SELF"]);
}
else {
$base = "./".basename($_SERVER["SCRIPT_FILENAME"])."?q=";
@ -379,7 +484,7 @@ function modify_url($url, $changes) {
unset($changes['q']);
}
else {
$base = $_GET['q'];
$base = _get_query();
}
if(isset($params['q'])) {
@ -402,7 +507,7 @@ function modify_url($url, $changes) {
* @return string
*/
function make_http(/*string*/ $link) {
if(strpos($link, "ttp://") > 0) {
if(strpos($link, "://") > 0) {
return $link;
}
@ -573,7 +678,6 @@ function is_https_enabled() {
* from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Schönknecht
* and released under the 'Simplified BSD License'.
*
* @internal Used to get mime types
* @param string &$file File path
* @param string $ext
* @param bool $list
@ -643,66 +747,6 @@ function getExtension ($mime_type){
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
*
@ -781,37 +825,6 @@ function get_session_ip(Config $config) {
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.
@ -826,13 +839,14 @@ function set_prefixed_cookie($name, $value, $time, $path) {
* @param string $type
*/
function flash_message(/*string*/ $text, /*string*/ $type="info") {
$current = get_prefixed_cookie("flash_message");
global $page;
$current = $page->get_cookie("flash_message");
if($current) {
$text = $current . "\n" . $text;
}
# the message should be viewed pretty much immediately,
# so 60s timeout should be more than enough
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
*/
function get_base_href() {
if(defined("BASE_HREF")) return BASE_HREF;
$possible_vars = array('SCRIPT_NAME', 'PHP_SELF', 'PATH_INFO', 'ORIG_PATH_INFO');
$ok_var = null;
foreach($possible_vars as $var) {
if(substr($_SERVER[$var], -4) === '.php') {
if(isset($_SERVER[$var]) && substr($_SERVER[$var], -4) === '.php') {
$ok_var = $_SERVER[$var];
break;
}
@ -944,21 +959,19 @@ function transload($url, $mfile) {
}
if($config->get_string("transload_engine") === "fopen") {
$fp = @fopen($url, "r");
if(!$fp) {
$fp_in = @fopen($url, "r");
$fp_out = fopen($mfile, "w");
if(!$fp_in || !$fp_out) {
return false;
}
$data = "";
$length = 0;
while(!feof($fp) && $length <= $config->get_int('upload_size')) {
$data .= fread($fp, 8192);
$length = strlen($data);
while(!feof($fp_in) && $length <= $config->get_int('upload_size')) {
$data = fread($fp_in, 8192);
$length += strlen($data);
fwrite($fp_out, $data);
}
fclose($fp);
$fp = fopen($mfile, "w");
fwrite($fp, $data);
fclose($fp);
fclose($fp_in);
fclose($fp_out);
$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
}
}
function findHeader ($headers, $name){
//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.
$header = FALSE;
/**
* 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) {
if (!is_array($headers)) {
return false;
}
$header = false;
if(array_key_exists($name, $headers)){
if(array_key_exists($name, $headers)) {
$header = $headers[$name];
}else{
$headers = array_change_key_case($headers);
if(array_key_exists(strtolower($name), $headers)){
$header = $headers[strtolower($name)];
} else {
$headers = array_change_key_case($headers); // convert all to lower case.
$lc_name = strtolower($name);
if(array_key_exists($lc_name, $headers)) {
$header = $headers[$lc_name];
}
}
return $header;
}
$_included = array();
/**
* Get the active contents of a .php file
*
@ -1016,13 +1040,13 @@ $_included = array();
* @return string|null
*/
function manual_include($fname) {
global $_included;
static $included = array();
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";
@ -1039,10 +1063,6 @@ function manual_include($fname) {
// @include_once is used for user-creatable config files
$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;
}
@ -1072,49 +1092,49 @@ define("SCORE_LOG_NOTSET", 0);
* @param string $section
* @param int $priority
* @param string $message
* @param null|bool|string $flash
* @param bool|string $flash
* @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));
$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";
}
if($flash === True) {
if($flash === true) {
flash_message($message);
}
else if(!is_null($flash)) {
else if(is_string($flash)) {
flash_message($flash);
}
}
// 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_info( /*string*/ $section, /*string*/ $message, $flash=null, $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_error( /*string*/ $section, /*string*/ $message, $flash=null, $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_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=false, $args=array()) {log_msg($section, SCORE_LOG_INFO, $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=false, $args=array()) {log_msg($section, SCORE_LOG_ERROR, $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.
*
* @return null|string
*/
function get_request_id() {
global $_request_id;
if(!$_request_id) {
static $request_id = null;
if(!$request_id) {
// not completely trustworthy, as a user can spoof this
if(@$_SERVER['HTTP_X_VARNISH']) {
$_request_id = $_SERVER['HTTP_X_VARNISH'];
$request_id = $_SERVER['HTTP_X_VARNISH'];
}
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
* "extension manager" extension, but it seems to fit better here
*
* @param string $f
*/
function deltree($f) {
//Because Windows (I know, bad excuse)
@ -1249,6 +1271,9 @@ function deltree($f) {
* Copy an entire file hierarchy
*
* from a comment on http://uk.php.net/copy
*
* @param string $source
* @param string $target
*/
function full_copy($source, $target) {
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 *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/** @private */
$_event_listeners = array();
global $_shm_event_listeners;
$_shm_event_listeners = array();
/**
* Register an Extension.
*
* @param Extension $extension
* @param int $pos
* @param array $events
*/
function add_event_listener(Extension $extension, $pos=50, $events=array()) {
global $_event_listeners;
$pos *= 100;
foreach($events as $event) {
while(isset($_event_listeners[$event][$pos])) {
$pos += 1;
function _load_event_listeners() {
global $_shm_event_listeners;
ctx_log_start("Loading extensions");
$cache_path = data_path("cache/shm_event_listeners.php");
if(COMPILE_ELS && file_exists($cache_path)) {
require_once($cache_path);
}
else {
_set_event_listeners();
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;
}
$_shm_event_listeners[$event][$pos] = $extension;
}
}
}
$_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 */
$_event_count = 0;
global $_shm_event_count;
$_shm_event_count = 0;
/**
* Send an event to all registered Extensions.
@ -1310,23 +1469,27 @@ $_event_count = 0;
* @param Event $event
*/
function send_event(Event $event) {
global $_event_listeners, $_event_count;
if(!isset($_event_listeners[get_class($event)])) return;
global $_shm_event_listeners, $_shm_event_count;
if(!isset($_shm_event_listeners[get_class($event)])) return;
$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
$my_event_listeners = $_event_listeners[get_class($event)];
$my_event_listeners = $_shm_event_listeners[get_class($event)];
ksort($my_event_listeners);
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)) {
$listener->$method_name($event);
}
ctx_log_endok();
if($ctx) ctx_log_endok();
}
$_event_count++;
ctx_log_endok();
$_shm_event_count++;
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
// string representation of a number, it's two numbers separated by a space.
// 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)
@ -1346,7 +1509,7 @@ $_load_start = microtime(true);
* @return string debug info to add to the page.
*/
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);
@ -1356,21 +1519,31 @@ function get_debug_info() {
else {
$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);
$i_files = count(get_included_files());
$hits = $database->cache->get_hits();
$miss = $database->cache->get_misses();
$debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM";
$debug .= "; Used $i_files files and $_execs queries";
$debug .= "; Sent $_event_count events";
$debug .= "; Used $i_files files and {$database->query_count} queries";
$debug .= "; Sent $_shm_event_count events";
$debug .= "; $hits cache hits and $miss misses";
$debug .= "; Shimmie version ". VERSION . $commit; // .", SCore Version ". SCORE_VERSION;
return $debug;
}
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 *
@ -1378,12 +1551,16 @@ function get_debug_info() {
/** @privatesection */
/**
* @param array|string $arr
* @return array|string
*/
function _stripslashes_r($arr) {
return is_array($arr) ? array_map('_stripslashes_r', $arr) : stripslashes($arr);
function _version_check() {
$min_version = "5.4.8";
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;
}
}
function _sanitise_environment() {
@ -1393,11 +1570,15 @@ function _sanitise_environment() {
if(DEBUG) {
error_reporting(E_ALL);
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_BAIL, 1);
assert_options(ASSERT_WARNING, 0);
assert_options(ASSERT_QUIET_EVAL, 1);
assert_options(ASSERT_CALLBACK, 'score_assert_handler');
}
if(CONTEXT) {
ctx_set_log(CONTEXT);
ctx_log_start(@$_SERVER["REQUEST_URI"], true, true);
}
if(COVERAGE) {
@ -1405,18 +1586,9 @@ function _sanitise_environment() {
register_shutdown_function("_end_coverage");
}
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_BAIL, 1);
ob_start();
if(get_magic_quotes_gpc()) {
$_GET = _stripslashes_r($_GET);
$_POST = _stripslashes_r($_POST);
$_COOKIE = _stripslashes_r($_COOKIE);
}
if(is_cli()) {
if(PHP_SAPI === 'cli') {
if(isset($_SERVER['REMOTE_ADDR'])) {
die("CLI with remote addr? Confused, not taking the risk.");
}
@ -1425,6 +1597,7 @@ function _sanitise_environment() {
}
}
/**
* @param string $_theme
* @return array
@ -1441,73 +1614,6 @@ function _get_themelet_files($_theme) {
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.
@ -1532,7 +1638,7 @@ function _fatal_error(Exception $e) {
<body>
<h1>Internal Error</h1>
<p><b>Message:</b> '.$message.'
<p><b>Version:</b> '.$version.'
<p><b>Version:</b> '.$version.' (on '.phpversion().')
</body>
</html>
';
@ -1568,10 +1674,10 @@ function _decaret($str) {
* @return User
*/
function _get_user() {
global $config;
global $config, $page;
$user = null;
if(get_prefixed_cookie("user") && get_prefixed_cookie("session")) {
$tmp_user = User::by_session(get_prefixed_cookie("user"), get_prefixed_cookie("session"));
if($page->get_cookie("user") && $page->get_cookie("session")) {
$tmp_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session"));
if(!is_null($tmp_user)) {
$user = $tmp_user;
}
@ -1584,6 +1690,10 @@ function _get_user() {
return $user;
}
function _get_query() {
return @$_POST["q"]?:@$_GET["q"];
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Code coverage *

View file

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

View file

@ -1,5 +1,5 @@
<?php
class AdminPageTest extends ShimmieWebTestCase {
class AdminPageTest extends ShimmiePHPUnitTestCase {
public function testAuth() {
$this->get_page('admin');
$this->assert_response(403);
@ -9,27 +9,31 @@ class AdminPageTest extends ShimmieWebTestCase {
$this->get_page('admin');
$this->assert_response(403);
$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() {
$ts = time(); // we need a tag that hasn't been used before
$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->assert_title("Image $image_id_1: TeStCase$ts");
$this->get_page('admin');
$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->assert_title("Image $image_id_1: testcase$ts");
$this->delete_image($image_id_1);
$this->log_out();
}
# FIXME: make sure the admin tools actually work
@ -37,35 +41,33 @@ class AdminPageTest extends ShimmieWebTestCase {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->click("Recount tag use");
$this->log_out();
}
public function testPurge() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->click("Purge unused tags");
$this->log_out();
//$this->click("Recount tag use");
send_event(new AdminActionEvent('recount_tag_use'));
}
public function testDump() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->click("Download database contents");
$this->assert_response(200);
$this->log_out();
// this calls mysqldump which jams up travis prompting for a password
//$this->click("Download database contents");
//send_event(new AdminActionEvent('database_dump'));
//$this->assert_response(200);
}
public function testDBQ() {
$this->log_in_as_user();
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test");
$image_id_2 = $this->post_image("ext/simpletest/data/bedroom_workshop.jpg", "test2");
$image_id_3 = $this->post_image("ext/simpletest/data/favicon.png", "test");
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "test");
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "test2");
$image_id_3 = $this->post_image("tests/favicon.png", "test");
$this->get_page("post/list/test/1");
$this->click("Delete All These Images");
//$this->click("Delete All These Images");
$_POST['query'] = 'test';
//$_POST['reason'] = 'reason'; // non-null-reason = add a hash ban
send_event(new AdminActionEvent('delete_by_query'));
$this->get_page("post/view/$image_id_1");
$this->assert_response(404);
@ -77,7 +79,6 @@ class AdminPageTest extends ShimmieWebTestCase {
$this->delete_image($image_id_1);
$this->delete_image($image_id_2);
$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) {
$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) {
$html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>";
$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:
* 'lowercase all tags'
* 'recount tag use'
* 'purge unused tags'
* etc
*/
public function display_form() {
@ -53,7 +52,7 @@ class AdminPageTheme extends Themelet {
$page->add_block(new Block("Misc Admin Tools", $html));
$html = make_form(make_link("admin/set_tag_case"), "POST");
$html .= "<input type='text' name='tag' placeholder='Enter tag with correct case'>";
$html .= "<input type='text' name='tag' placeholder='Enter tag with correct case' class='autocomplete_tags' autocomplete='off'>";
$html .= "<input type='submit' value='Set Tag Case'>";
$html .= "</form>\n";
$page->add_block(new Block("Set Tag Case", $html));

View file

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

View file

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

View file

@ -19,8 +19,8 @@ class AliasEditorTheme extends Themelet {
$h_add = "
<tr>
".make_form(make_link("alias/add"))."
<td><input type='text' name='oldtag'></td>
<td><input type='text' name='newtag'></td>
<td><input type='text' name='oldtag' class='autocomplete_tags' autocomplete='off'></td>
<td><input type='text' name='newtag' class='autocomplete_tags' autocomplete='off'></td>
<td><input type='submit' value='Add'></td>
</form>
</tr>
@ -55,7 +55,7 @@ class AliasEditorTheme extends Themelet {
<tbody>$h_aliases</tbody>
<tfoot>$h_add</tfoot>
</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 = "

View file

@ -67,8 +67,8 @@ class UploadS3 extends Extension {
if(!empty($bucket)) {
log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3");
$s3 = new S3($access, $secret);
$s3->deleteObject($bucket, "images/"+$event->image->hash);
$s3->deleteObject($bucket, "thumbs/"+$event->image->hash);
$s3->deleteObject($bucket, "images/" . $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');
// if there are no tags, use default
if ($event->get_arg(1) == null){
if (is_null($event->get_arg(1))){
$prefix = "";
$page_number = int_escape($event->get_arg(0));
$total_pages = ceil($database->get_one(

File diff suppressed because it is too large Load diff

View file

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

View file

@ -23,7 +23,7 @@ class ArtistsTheme extends Themelet {
* @param null|int $artistID
* @param bool $is_admin
*/
public function sidebar_options(/*string*/ $mode, $artistID=NULL, $is_admin=FALSE){
public function sidebar_options(/*string*/ $mode, $artistID=NULL, $is_admin=FALSE) {
global $page, $user;
$html = "";
@ -77,49 +77,44 @@ class ArtistsTheme extends Themelet {
if($html) $page->add_block(new Block("Manage Artists", $html, "left", 10));
}
public function show_artist_editor($artist, $aliases, $members, $urls)
{
global $user;
public function show_artist_editor($artist, $aliases, $members, $urls) {
global $user;
$artistName = $artist['name'];
$artistNotes = $artist['notes'];
$artistID = $artist['id'];
$artistName = $artist['name'];
$artistNotes = $artist['notes'];
$artistID = $artist['id'];
// aliases
$aliasesString = "";
$aliasesIDsString = "";
foreach ($aliases as $alias)
{
$aliasesString .= $alias["alias_name"]." ";
$aliasesIDsString .= $alias["alias_id"]." ";
}
$aliasesString = rtrim($aliasesString);
$aliasesIDsString = rtrim($aliasesIDsString);
// aliases
$aliasesString = "";
$aliasesIDsString = "";
foreach ($aliases as $alias) {
$aliasesString .= $alias["alias_name"]." ";
$aliasesIDsString .= $alias["alias_id"]." ";
}
$aliasesString = rtrim($aliasesString);
$aliasesIDsString = rtrim($aliasesIDsString);
// members
$membersString = "";
$membersIDsString = "";
foreach ($members as $member)
{
$membersString .= $member["name"]." ";
$membersIDsString .= $member["id"]." ";
}
$membersString = rtrim($membersString);
$membersIDsString = rtrim($membersIDsString);
// members
$membersString = "";
$membersIDsString = "";
foreach ($members as $member) {
$membersString .= $member["name"]." ";
$membersIDsString .= $member["id"]." ";
}
$membersString = rtrim($membersString);
$membersIDsString = rtrim($membersIDsString);
// urls
$urlsString = "";
$urlsIDsString = "";
foreach ($urls as $url)
{
$urlsString .= $url["url"]."\n";
$urlsIDsString .= $url["id"]." ";
}
$urlsString = substr($urlsString, 0, strlen($urlsString) -1);
$urlsIDsString = rtrim($urlsIDsString);
// urls
$urlsString = "";
$urlsIDsString = "";
foreach ($urls as $url) {
$urlsString .= $url["url"]."\n";
$urlsIDsString .= $url["id"]." ";
}
$urlsString = substr($urlsString, 0, strlen($urlsString) -1);
$urlsIDsString = rtrim($urlsIDsString);
$html =
'
$html = '
<form method="POST" action="'.make_link("artist/edited/".$artist['id']).'">
'.$user->get_auth_html().'
<table>
@ -135,113 +130,108 @@ class ArtistsTheme extends Themelet {
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
';
global $page;
$page->add_block(new Block("Edit artist", $html, "main", 10));
}
public function new_artist_composer()
{
global $page, $user;
$html = "<form action=".make_link("artist/create")." method='POST'>
".$user->get_auth_html()."
<table>
<tr><td>Name:</td><td><input type='text' name='name' /></td></tr>
<tr><td>Aliases:</td><td><input type='text' name='aliases' /></td></tr>
<tr><td>Members:</td><td><input type='text' name='members' /></td></tr>
<tr><td>URLs:</td><td><textarea name='urls'></textarea></td></tr>
<tr><td>Notes:</td><td><textarea name='notes'></textarea></td></tr>
<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
</table>
";
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
global $page;
$page->add_block(new Block("Edit artist", $html, "main", 10));
}
public function list_artists($artists, $pageNumber, $totalPages)
{
global $user, $page;
public function new_artist_composer() {
global $page, $user;
$html = "<table id='poolsList' class='zebra'>".
"<thead><tr>".
"<th>Name</th>".
"<th>Type</th>".
"<th>Last updater</th>".
"<th>Posts</th>";
$html = "<form action=".make_link("artist/create")." method='POST'>
".$user->get_auth_html()."
<table>
<tr><td>Name:</td><td><input type='text' name='name' /></td></tr>
<tr><td>Aliases:</td><td><input type='text' name='aliases' /></td></tr>
<tr><td>Members:</td><td><input type='text' name='members' /></td></tr>
<tr><td>URLs:</td><td><textarea name='urls'></textarea></td></tr>
<tr><td>Notes:</td><td><textarea name='notes'></textarea></td></tr>
<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
</table>
";
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
}
public function list_artists($artists, $pageNumber, $totalPages) {
global $user, $page;
if(!$user->is_anonymous()) $html .= "<th colspan='2'>Action</th>"; // space for edit link
$html = "<table id='poolsList' class='zebra'>".
"<thead><tr>".
"<th>Name</th>".
"<th>Type</th>".
"<th>Last updater</th>".
"<th>Posts</th>";
if(!$user->is_anonymous()) $html .= "<th colspan='2'>Action</th>"; // space for edit link
$html .= "</tr></thead>";
$html .= "</tr></thead>";
$deletionLinkActionArray =
array('artist' => 'artist/nuke/'
, 'alias' => 'artist/alias/delete/'
, 'member' => 'artist/member/delete/'
);
$deletionLinkActionArray = array(
'artist' => 'artist/nuke/',
'alias' => 'artist/alias/delete/',
'member' => 'artist/member/delete/',
);
$editionLinkActionArray =
array('artist' => 'artist/edit/'
, 'alias' => 'artist/alias/edit/'
, 'member' => 'artist/member/edit/'
);
$editionLinkActionArray = array(
'artist' => 'artist/edit/',
'alias' => 'artist/alias/edit/',
'member' => 'artist/member/edit/',
);
$typeTextArray =
array('artist' => 'Artist'
, 'alias' => 'Alias'
, 'member' => 'Member'
);
$typeTextArray = array(
'artist' => 'Artist',
'alias' => 'Alias',
'member' => 'Member',
);
foreach ($artists as $artist) {
if ($artist['type'] != 'artist')
$artist['name'] = str_replace("_", " ", $artist['name']);
foreach ($artists as $artist) {
if ($artist['type'] != 'artist')
$artist['name'] = str_replace("_", " ", $artist['name']);
$elementLink = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['name'])."</a>";
//$artist_link = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['artist_name'])."</a>";
$user_link = "<a href='".make_link("user/".$artist['user_name'])."'>".$artist['user_name']."</a>";
$edit_link = "<a href='".make_link($editionLinkActionArray[$artist['type']].$artist['id'])."'>Edit</a>";
$del_link = "<a href='".make_link($deletionLinkActionArray[$artist['type']].$artist['id'])."'>Delete</a>";
$elementLink = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['name'])."</a>";
//$artist_link = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['artist_name'])."</a>";
$user_link = "<a href='".make_link("user/".$artist['user_name'])."'>".$artist['user_name']."</a>";
$edit_link = "<a href='".make_link($editionLinkActionArray[$artist['type']].$artist['id'])."'>Edit</a>";
$del_link = "<a href='".make_link($deletionLinkActionArray[$artist['type']].$artist['id'])."'>Delete</a>";
$html .= "<tr>".
"<td class='left'>".$elementLink;
$html .= "<tr>".
"<td class='left'>".$elementLink;
//if ($artist['type'] == 'member')
// $html .= " (member of ".$artist_link.")";
//if ($artist['type'] == 'member')
// $html .= " (member of ".$artist_link.")";
//if ($artist['type'] == 'alias')
// $html .= " (alias for ".$artist_link.")";
//if ($artist['type'] == 'alias')
// $html .= " (alias for ".$artist_link.")";
$html .= "</td>".
"<td>".$typeTextArray[$artist['type']]."</td>".
"<td>".$user_link."</td>".
"<td>".$artist['posts']."</td>";
$html .= "</td>".
"<td>".$typeTextArray[$artist['type']]."</td>".
"<td>".$user_link."</td>".
"<td>".$artist['posts']."</td>";
if(!$user->is_anonymous()) $html .= "<td>".$edit_link."</td>";
if($user->is_admin()) $html .= "<td>".$del_link."</td>";
if(!$user->is_anonymous()) $html .= "<td>".$edit_link."</td>";
if($user->is_admin()) $html .= "<td>".$del_link."</td>";
$html .= "</tr>";
}
$html .= "</tr>";
}
$html .= "</tbody></table>";
$html .= "</tbody></table>";
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
$this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages);
$this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages);
}
public function show_new_alias_composer($artistID)
{
global $user;
public function show_new_alias_composer($artistID) {
global $user;
$html =
'<form method="POST" action='.make_link("artist/alias/add").'>
$html = '
<form method="POST" action='.make_link("artist/alias/add").'>
'.$user->get_auth_html().'
<table>
<tr><td>Alias:</td><td><input type="text" name="aliases" />
@ -249,277 +239,290 @@ class ArtistsTheme extends Themelet {
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
';
global $page;
$page->add_block(new Block("Artist Aliases", $html, "main", 20));
}
public function show_new_member_composer($artistID)
{
global $user;
global $page;
$page->add_block(new Block("Artist Aliases", $html, "main", 20));
}
$html =
' <form method="POST" action='.make_link("artist/member/add").'>
public function show_new_member_composer($artistID) {
global $user;
$html = '
<form method="POST" action='.make_link("artist/member/add").'>
'.$user->get_auth_html().'
<table>
<tr><td>Members:</td><td><input type="text" name="members" />
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
<table>
<tr><td>Members:</td><td><input type="text" name="members" />
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
global $page;
$page->add_block(new Block("Artist members", $html, "main", 30));
}
global $page;
$page->add_block(new Block("Artist members", $html, "main", 30));
}
public function show_new_url_composer($artistID)
{
global $user;
public function show_new_url_composer($artistID) {
global $user;
$html =
' <form method="POST" action='.make_link("artist/url/add").'>
$html = '
<form method="POST" action='.make_link("artist/url/add").'>
'.$user->get_auth_html().'
<table>
<tr><td>URL:</td><td><textarea name="urls"></textarea>
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
<table>
<tr><td>URL:</td><td><textarea name="urls"></textarea>
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
global $page;
$page->add_block(new Block("Artist URLs", $html, "main", 40));
}
global $page;
$page->add_block(new Block("Artist URLs", $html, "main", 40));
}
public function show_alias_editor($alias)
{
global $user;
public function show_alias_editor($alias) {
global $user;
$html =
'
<form method="POST" action="'.make_link("artist/alias/edited/".$alias['id']).'">
'.$user->get_auth_html().'
<label for="alias">Alias:</label>
<input type="text" name="alias" value="'.$alias['alias'].'" />
<input type="hidden" name="aliasID" value="'.$alias['id'].'" />
<input type="submit" value="Submit" />
</form>
';
$html = '
<form method="POST" action="'.make_link("artist/alias/edited/".$alias['id']).'">
'.$user->get_auth_html().'
<label for="alias">Alias:</label>
<input type="text" name="alias" value="'.$alias['alias'].'" />
<input type="hidden" name="aliasID" value="'.$alias['id'].'" />
<input type="submit" value="Submit" />
</form>
';
global $page;
$page->add_block(new Block("Edit Alias", $html, "main", 10));
}
global $page;
$page->add_block(new Block("Edit Alias", $html, "main", 10));
}
public function show_url_editor($url)
{
global $user;
public function show_url_editor($url) {
global $user;
$html =
'
<form method="POST" action="'.make_link("artist/url/edited/".$url['id']).'">
'.$user->get_auth_html().'
<label for="url">URL:</label>
<input type="text" name="url" value="'.$url['url'].'" />
<input type="hidden" name="urlID" value="'.$url['id'].'" />
<input type="submit" value="Submit" />
</form>
';
$html = '
<form method="POST" action="'.make_link("artist/url/edited/".$url['id']).'">
'.$user->get_auth_html().'
<label for="url">URL:</label>
<input type="text" name="url" value="'.$url['url'].'" />
<input type="hidden" name="urlID" value="'.$url['id'].'" />
<input type="submit" value="Submit" />
</form>
';
global $page;
$page->add_block(new Block("Edit URL", $html, "main", 10));
}
global $page;
$page->add_block(new Block("Edit URL", $html, "main", 10));
}
public function show_member_editor($member)
{
global $user;
public function show_member_editor($member) {
global $user;
$html =
'
<form method="POST" action="'.make_link("artist/member/edited/".$member['id']).'">
'.$user->get_auth_html().'
<label for="member">Member name:</label>
<input type="text" name="name" value="'.$member['name'].'" />
<input type="hidden" name="memberID" value="'.$member['id'].'" />
<input type="submit" value="Submit" />
</form>
';
$html = '
<form method="POST" action="'.make_link("artist/member/edited/".$member['id']).'">
'.$user->get_auth_html().'
<label for="member">Member name:</label>
<input type="text" name="name" value="'.$member['name'].'" />
<input type="hidden" name="memberID" value="'.$member['id'].'" />
<input type="submit" value="Submit" />
</form>
';
global $page;
$page->add_block(new Block("Edit Member", $html, "main", 10));
}
global $page;
$page->add_block(new Block("Edit Member", $html, "main", 10));
}
public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin)
{
global $page;
public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin) {
global $page;
$artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>";
$artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>";
$html = "<table id='poolsList' class='zebra'>
$html = "<table id='poolsList' class='zebra'>
<thead>
<tr>
<th></th>
<th></th>";
if ($userIsLogged)
$html .= "<th></th>";
if ($userIsLogged) $html .= "<th></th>";
if ($userIsAdmin) $html .= "<th></th>";
if ($userIsAdmin)
$html .= "<th></th>";
$html .= " <tr>
$html .= " <tr>
</thead>
<tr>
<td class='left'>Name:</td>
<td class='left'>".$artist_link."</td>";
if ($userIsLogged) $html .= "<td></td>";
if ($userIsAdmin) $html .= "<td></td>";
$html .= "</tr>";
if ($userIsLogged) $html .= "<td></td>";
if ($userIsAdmin) $html .= "<td></td>";
$html .= "</tr>";
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>";
$html .= $this->render_aliases($aliases, $userIsLogged, $userIsAdmin);
$html .= $this->render_members($members, $userIsLogged, $userIsAdmin);
$html .= $this->render_urls($urls, $userIsLogged, $userIsAdmin);
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>
$html .= "<tr>
<td class='left'>Notes:</td>
<td class='left'>".$artist["notes"]."</td>";
if ($userIsLogged) $html .= "<td></td>";
if ($userIsAdmin) $html .= "<td></td>";
//TODO how will notes be edited? On edit artist? (should there be an editartist?) or on a editnotes?
//same question for deletion
$html .= "</tr>
</table>";
if ($userIsLogged) $html .= "<td></td>";
if ($userIsAdmin) $html .= "<td></td>";
//TODO how will notes be edited? On edit artist? (should there be an editartist?) or on a editnotes?
//same question for deletion
$html .= "</tr>
</table>";
$page->set_title("Artist");
$page->set_heading("Artist");
$page->add_block(new Block("Artist", $html, "main", 10));
$page->set_title("Artist");
$page->set_heading("Artist");
$page->add_block(new Block("Artist", $html, "main", 10));
//we show the images for the artist
$artist_images = "";
foreach($images as $image) {
$thumb_html = $this->build_thumb_html($image);
//we show the images for the artist
$artist_images = "";
foreach($images as $image) {
$thumb_html = $this->build_thumb_html($image);
$artist_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
}
$artist_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
}
$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
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() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
$this->click("Save Settings");
$this->log_out();
global $config;
$config->set_string("banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
$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->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', "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->check_blocked($image_id, "kittens and viagra");
$this->check_blocked($image_id, "kittens and ViagrA");
$this->check_blocked($image_id, "kittens and viagra!");
$this->check_blocked($image_id, "some link to http://something.cn/");
$this->get_page('comment/list');
$this->assert_title('Comments');
$this->assert_no_text('viagra');
$this->assert_no_text('ViagrA');
$this->assert_no_text('http://something.cn/');
$this->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
* @return mixed
* @return string
*/
private function filter_spoiler(/*string*/ $text) {
return str_replace(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,13 +15,26 @@
* <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 {
public function onPageRequest(PageRequestEvent $event) {
global $page, $user;
if($event->page_matches("bulk_add")) {
if($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) {
set_time_limit(0);
$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);
}
}
@ -29,12 +42,14 @@ class BulkAdd extends Extension {
public function onCommand(CommandEvent $event) {
if($event->cmd == "help") {
print " bulk-add [directory]\n";
print " Import this directory\n\n";
print "\tbulk-add [directory]\n";
print "\t\tImport this directory\n\n";
}
if($event->cmd == "bulk-add") {
if(count($event->args) == 1) {
$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();
}
/**
* Generate the necessary DataUploadEvent for a given image and tags.
*/
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");
public function onBulkAdd(BulkAddEvent $event) {
if(is_dir($event->dir) && is_readable($event->dir)) {
$event->results = add_dir($event->dir);
}
$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 {
$pathinfo = pathinfo($fullpath);
$matches = array();
if(preg_match("/\d+ - (.*)\.([a-zA-Z]+)/", $pathinfo["basename"], $matches)) {
$tags = $matches[1];
}
else {
$tags = $subdir;
$tags = str_replace("/", " ", $tags);
$tags = str_replace("__", " ", $tags);
$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);
else {
$h_dir = html_escape($event->dir);
$event->results[] = "Error, $h_dir is not a readable directory";
}
}
}

View file

@ -1,18 +1,22 @@
<?php
class BulkAddTest extends ShimmieWebTestCase {
class BulkAddTest extends ShimmiePHPUnitTestCase {
public function testBulkAdd() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->set_field('dir', "asdf");
$this->click("Add");
$this->assert_text("is not a directory");
$bae = new BulkAddEvent('asdf');
send_event($bae);
$this->assertContains("Error, asdf is not a readable directory",
$bae->results, implode("\n", $bae->results));
// FIXME: have BAE return a list of successes as well as errors?
$this->markTestIncomplete();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->set_field('dir', "contrib/simpletest");
$this->click("Add");
send_event(new BulkAddEvent('tests'));
# FIXME: test that the output here makes sense, no "adding foo.php ... ok"
@ -31,4 +35,3 @@ class BulkAddTest extends ShimmieWebTestCase {
$this->log_out();
}
}

View file

@ -10,9 +10,11 @@ class BulkAddTheme extends Themelet {
$page->set_title("Adding folder");
$page->set_heading("Adding folder");
$page->add_block(new NavBlock());
$html = "";
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
*/
public function display_admin_block() {
global $page, $user;
global $page;
$html = "
Add a folder full of images; any subfolders will have their names
used as tags for the images within.
@ -42,4 +44,3 @@ class BulkAddTheme extends Themelet {
$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.
*
* @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) {
assert(file_exists($tmpname));

View file

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

View file

@ -25,6 +25,7 @@ class CommentPostingEvent extends Event {
* @param string $comment
*/
public function __construct($image_id, $user, $comment) {
assert('is_numeric($image_id)');
$this->image_id = $image_id;
$this->user = $user;
$this->comment = $comment;
@ -44,6 +45,7 @@ class CommentDeletionEvent extends Event {
* @param int $comment_id
*/
public function __construct($comment_id) {
assert('is_numeric($comment_id)');
$this->comment_id = $comment_id;
}
}
@ -69,12 +71,16 @@ class Comment {
}
/**
* @param \User $user
* @param User $user
* @return mixed
*/
public static function count_comments_by_user($user) {
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 {
/** @var CommentListTheme $theme */
var $theme;
public function onInitExt(InitExtEvent $event) {
global $config, $database;
$config->set_default_int('comment_window', 5);
@ -145,78 +154,92 @@ class CommentList extends Extension {
}
public function onPageRequest(PageRequestEvent $event) {
global $page, $user, $database;
if($event->page_matches("comment")) {
if($event->get_arg(0) === "add") {
if(isset($_POST['image_id']) && isset($_POST['comment'])) {
try {
$i_iid = int_escape($_POST['image_id']);
$cpe = new CommentPostingEvent($_POST['image_id'], $user, $_POST['comment']);
send_event($cpe);
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
}
catch(CommentPostingException $ex) {
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
}
}
}
else if($event->get_arg(0) === "delete") {
if($user->can("delete_comment")) {
// FIXME: post, not args
if($event->count_args() === 3) {
send_event(new CommentDeletionEvent($event->get_arg(1)));
flash_message("Deleted comment");
$page->set_mode("redirect");
if(!empty($_SERVER['HTTP_REFERER'])) {
$page->set_redirect($_SERVER['HTTP_REFERER']);
}
else {
$page->set_redirect(make_link("post/view/".$event->get_arg(2)));
}
}
}
else {
$this->theme->display_permission_denied();
}
}
else if($event->get_arg(0) === "bulk_delete") {
if($user->can("delete_comment") && !empty($_POST["ip"])) {
$ip = $_POST['ip'];
$cids = $database->get_col("SELECT id FROM comments WHERE owner_ip=:ip", array("ip"=>$ip));
$num = count($cids);
log_warning("comment", "Deleting $num comments from $ip");
foreach($cids as $cid) {
send_event(new CommentDeletionEvent($cid));
}
flash_message("Deleted $num comments");
$page->set_mode("redirect");
$page->set_redirect(make_link("admin"));
}
else {
$this->theme->display_permission_denied();
}
}
else if($event->get_arg(0) === "list") {
$page_num = int_escape($event->get_arg(1));
$this->build_page($page_num);
}
else if($event->get_arg(0) === "beta-search") {
$search = $event->get_arg(1);
$page_num = int_escape($event->get_arg(2));
$duser = User::by_name($search);
$i_comment_count = Comment::count_comments_by_user($duser);
$com_per_page = 50;
$total_pages = ceil($i_comment_count/$com_per_page);
$page_num = $this->sanity_check_pagenumber($page_num, $total_pages);
$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);
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'])) {
try {
$i_iid = int_escape($_POST['image_id']);
$cpe = new CommentPostingEvent($_POST['image_id'], $user, $_POST['comment']);
send_event($cpe);
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
} catch (CommentPostingException $ex) {
$this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
}
}
}
private function onPageRequest_delete(PageRequestEvent $event) {
global $user, $page;
if ($user->can("delete_comment")) {
// FIXME: post, not args
if ($event->count_args() === 3) {
send_event(new CommentDeletionEvent($event->get_arg(1)));
flash_message("Deleted comment");
$page->set_mode("redirect");
if (!empty($_SERVER['HTTP_REFERER'])) {
$page->set_redirect($_SERVER['HTTP_REFERER']);
} else {
$page->set_redirect(make_link("post/view/" . $event->get_arg(2)));
}
}
} else {
$this->theme->display_permission_denied();
}
}
private function onPageRequest_bulk_delete() {
global $user, $database, $page;
if ($user->can("delete_comment") && !empty($_POST["ip"])) {
$ip = $_POST['ip'];
$comment_ids = $database->get_col("
SELECT id
FROM comments
WHERE owner_ip=:ip
", array("ip" => $ip));
$num = count($comment_ids);
log_warning("comment", "Deleting $num comments from $ip");
foreach($comment_ids as $cid) {
send_event(new CommentDeletionEvent($cid));
}
flash_message("Deleted $num comments");
$page->set_mode("redirect");
$page->set_redirect(make_link("admin"));
} else {
$this->theme->display_permission_denied();
}
}
private function onPageRequest_list(PageRequestEvent $event) {
$page_num = int_escape($event->get_arg(1));
$this->build_page($page_num);
}
private function onPageRequest_beta_search(PageRequestEvent $event) {
$search = $event->get_arg(1);
$page_num = int_escape($event->get_arg(2));
$duser = User::by_name($search);
$i_comment_count = Comment::count_comments_by_user($duser);
$com_per_page = 50;
$total_pages = ceil($i_comment_count / $com_per_page);
$page_num = clamp($page_num, 1, $total_pages);
$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);
}
public function onAdminBuilding(AdminBuildingEvent $event) {
$this->theme->display_admin_block();
}
@ -262,7 +285,10 @@ class CommentList extends Extension {
public function onCommentDeletion(CommentDeletionEvent $event) {
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}");
}
@ -286,8 +312,6 @@ class CommentList extends Extension {
}
public function onSearchTermParse(SearchTermParseEvent $event) {
global $database;
$matches = array();
if(preg_match("/^comments([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
@ -318,48 +342,46 @@ class CommentList extends Extension {
private function build_page(/*int*/ $current_page) {
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'" : "";
$total_pages = $database->cache->get("comment_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);
}
if(is_null($current_page) || $current_page <= 0) {
$current_page = 1;
}
$current_page = $this->sanity_check_pagenumber($current_page, $total_pages);
$total_pages = max($total_pages, 1);
$current_page = clamp($current_page, 1, $total_pages);
$threads_per_page = 10;
$start = $threads_per_page * ($current_page - 1);
$get_threads = "
$result = $database->Execute("
SELECT image_id,MAX(posted) AS latest
FROM comments $where
FROM comments
$where
GROUP BY image_id
ORDER BY latest DESC
LIMIT :limit OFFSET :offset
";
$result = $database->Execute($get_threads, array("limit"=>$threads_per_page, "offset"=>$start));
", array("limit"=>$threads_per_page, "offset"=>$start));
$user_ratings = ext_is_live("Ratings") ? Ratings::get_user_privs($user) : "";
$images = array();
while($row = $result->fetch()) {
$image = Image::by_id($row["image_id"]);
if (!is_null($image)) {
if(
ext_is_live("Ratings") && !is_null($image) &&
strpos($user_ratings, $image->rating) === FALSE
) {
$image = null; // this is "clever", I may live to regret it
}
if(!is_null($image)) {
$comments = $this->get_comments($image->id);
if(class_exists("Ratings")) {
if(strpos($user_ratings, $image->rating) === FALSE) {
$image = null; // this is "clever", I may live to regret it
}
}
if(!is_null($image)) $images[] = array($image, $comments);
$images[] = array($image, $comments);
}
}
@ -369,22 +391,13 @@ class CommentList extends Extension {
// get comments {{{
/**
* @param int $count
* @return array
* @param string $query
* @param array $args
* @return Comment[]
*/
private function get_recent_comments($count) {
private function get_generic_comments($query, $args) {
global $database;
$rows = $database->get_all("
SELECT
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.image_id as image_id, comments.owner_ip as poster_ip,
comments.posted as posted
FROM comments
LEFT JOIN users ON comments.owner_id=users.id
ORDER BY comments.id DESC
LIMIT :limit
", array("limit"=>$count));
$rows = $database->get_all($query, $args);
$comments = array();
foreach($rows as $row) {
$comments[] = new Comment($row);
@ -392,62 +405,70 @@ class CommentList extends Extension {
return $comments;
}
/**
* @param int $count
* @return Comment[]
*/
private function get_recent_comments($count) {
return $this->get_generic_comments("
SELECT
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.image_id as image_id, comments.owner_ip as poster_ip,
comments.posted as posted
FROM comments
LEFT JOIN users ON comments.owner_id=users.id
ORDER BY comments.id DESC
LIMIT :limit
", array("limit"=>$count));
}
/**
* @param int $user_id
* @param int $count
* @param int $offset
* @return array
* @return Comment[]
*/
private function get_user_comments(/*int*/ $user_id, /*int*/ $count, /*int*/ $offset=0) {
global $database;
$rows = $database->get_all("
SELECT
return $this->get_generic_comments("
SELECT
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.image_id as image_id, comments.owner_ip as poster_ip,
comments.posted as posted
FROM comments
LEFT JOIN users ON comments.owner_id=users.id
WHERE users.id = :user_id
ORDER BY comments.id DESC
LIMIT :limit OFFSET :offset
", array("user_id"=>$user_id, "offset"=>$offset, "limit"=>$count));
$comments = array();
foreach($rows as $row) {
$comments[] = new Comment($row);
}
return $comments;
FROM comments
LEFT JOIN users ON comments.owner_id=users.id
WHERE users.id = :user_id
ORDER BY comments.id DESC
LIMIT :limit OFFSET :offset
", array("user_id"=>$user_id, "offset"=>$offset, "limit"=>$count));
}
/**
* @param int $image_id
* @return array
* @return Comment[]
*/
private function get_comments(/*int*/ $image_id) {
global $database;
$i_image_id = int_escape($image_id);
$rows = $database->get_all("
SELECT
return $this->get_generic_comments("
SELECT
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.image_id as image_id, comments.owner_ip as poster_ip,
comments.posted as posted
FROM comments
LEFT JOIN users ON comments.owner_id=users.id
WHERE comments.image_id=:image_id
ORDER BY comments.id ASC
", array("image_id"=>$i_image_id));
$comments = array();
foreach($rows as $row) {
$comments[] = new Comment($row);
}
return $comments;
FROM comments
LEFT JOIN users ON comments.owner_id=users.id
WHERE comments.image_id=:image_id
ORDER BY comments.id ASC
", array("image_id"=>$image_id));
}
// }}}
// add / remove / edit comments {{{
/**
* @return bool
*/
private function is_comment_limit_hit() {
global $user, $config, $database;
global $config, $database;
// sqlite fails at intervals
if($database->get_driver_name() === "sqlite") return false;
@ -459,9 +480,11 @@ class CommentList extends Extension {
else $window_sql = "interval '$window minute'";
// 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 ".
"AND posted > now() - $window_sql",
Array("remote_ip"=>$_SERVER['REMOTE_ADDR']));
$result = $database->get_all("
SELECT *
FROM comments
WHERE owner_ip = :remote_ip AND posted > now() - $window_sql
", array("remote_ip"=>$_SERVER['REMOTE_ADDR']));
return (count($result) >= $max);
}
@ -479,6 +502,8 @@ class CommentList extends Extension {
* many times.
*
* FIXME: assumes comments are posted via HTTP...
*
* @return string
*/
public static function get_hash() {
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) {
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
/**
* @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 User $user
@ -562,7 +573,7 @@ class CommentList extends Extension {
* @throws CommentPostingException
*/
private function add_comment_wrapper(/*int*/ $image_id, User $user, /*string*/ $comment) {
global $database, $config;
global $database, $page;
if(!$user->can("bypass_comment_checks")) {
// will raise an exception if anything is wrong
@ -571,7 +582,7 @@ class CommentList extends Extension {
// all checks passed
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(
"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));
}
/**
* @param int $image_id
* @param User $user
* @param string $comment
* @throws CommentPostingException
*/
private function comment_checks(/*int*/ $image_id, User $user, /*string*/ $comment) {
global $config;
global $config, $page;
// basic sanity checks
if(!$user->can("create_comment")) {
@ -606,7 +623,7 @@ class CommentList extends Extension {
throw new CommentPostingException("Comment too repetitive~");
}
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(
"Comment submission form is out of date; refresh the ".
"comment form to show you aren't a spammer~");

View file

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

View file

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

View file

@ -66,8 +66,7 @@ class CommentListTheme extends Themelet {
$comment_count = count($comments);
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);
$this->show_anon_id = false;
}
@ -88,7 +87,8 @@ class CommentListTheme extends Themelet {
$comment_html .= $this->build_postbox($image->id);
}
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));
//$query = empty($u_tags) ? "" : '/'.$u_tags;
$h_prev = ($page_number <= 1) ? "Prev" : "<a href='$prev'>Prev</a>";
$h_index = "<a href='".make_link("post/list")."'>Index</a>";
$h_next = ($page_number >= $total_pages) ? "Next" : "<a href='$next'>Next</a>";
@ -233,7 +232,6 @@ class CommentListTheme extends Themelet {
$i_uid = int_escape($comment->owner_id);
$h_name = html_escape($comment->owner_name);
$h_poster_ip = html_escape($comment->poster_ip);
$h_timestamp = autodate($comment->posted);
$h_comment = ($trim ? truncate($tfe->stripped, 50) : $tfe->formatted);
$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() : "";
return '
<div class="comment">
<div class="comment comment_add">
'.make_form(make_link("comment/add")).'
<input type="hidden" name="image_id" value="'.$i_image_id.'" />
<input type="hidden" name="hash" value="'.$hash.'" />

View file

@ -61,7 +61,7 @@ class CronUploader extends Extension {
}
private function display_documentation() {
global $config, $page;
global $page;
$this->set_dir(); // Determines path to cron_uploader_dir
@ -151,7 +151,6 @@ class CronUploader extends Extension {
}
public function onSetupBuilding(SetupBuildingEvent $event) {
global $config;
$this->set_dir();
$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
if ($dir == "") {
$dir = $_SERVER ['DOCUMENT_ROOT'] . "/data/cron_uploader";
$dir = data_path("cron_uploader");
$config->set_string ('cron_uploader_dir', $dir);
}
@ -344,7 +343,7 @@ class CronUploader extends Extension {
foreach ( glob ( "$base/$subdir/*" ) as $fullpath ) {
$fullpath = str_replace ( "//", "/", $fullpath );
$shortpath = str_replace ( $base, "", $fullpath );
//$shortpath = str_replace ( $base, "", $fullpath );
if (is_link ( $fullpath )) {
// ignore

View file

@ -15,23 +15,21 @@
* You can also add your website name as prefix or suffix to the title of each page on your website.
*/
class custom_html_headers extends Extension {
# Adds setup block for custom <head> content
public function onSetupBuilding(SetupBuildingEvent $event) {
global $config;
# Adds setup block for custom <head> content
public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Custom HTML Headers");
// custom headers
// custom headers
$sb->add_longtext_option("custom_html_headers",
"HTML Code to place within &lt;head&gt;&lt;/head&gt; on all pages<br>");
// modified title
$sb->add_choice_option("sitename_in_title", array(
"none" => 0,
"as prefix" => 1,
"as suffix" => 2
), "<br>Add website name in title");
"HTML Code to place within &lt;head&gt;&lt;/head&gt; on all pages<br>");
// modified title
$sb->add_choice_option("sitename_in_title", array(
"none" => 0,
"as prefix" => 1,
"as suffix" => 2
), "<br>Add website name in title");
$event->panel->add_block($sb);
}

View file

@ -55,367 +55,46 @@ class DanbooruApi extends Extension {
}
// Danbooru API
private function api_danbooru(PageRequestEvent $event)
{
private function api_danbooru(PageRequestEvent $event) {
global $page;
global $config;
global $database;
global $user;
$page->set_mode("data");
$page->set_type("application/xml");
//debug
//$page->set_type("text/plain");
$results = array();
$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')))
{
if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) {
// No XML data is returned from this function
$page->set_type("text/plain");
// Check first if a login was supplied, if it wasn't check if the user is logged in via cookie
// If all that fails, it's an anonymous upload
$this->authenticate_user();
// Now we check if a file was uploaded or a url was provided to transload
// Much of this code is borrowed from /ext/upload
if($user->can("create_image"))
{
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 = "";
$length = 0;
while(!feof($fp) && $length <= $config->get_int('upload_size'))
{
$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")
{
$ch = curl_init($url);
$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
$posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']);
$hash = md5_file($file);
// Was an md5 supplied? Does it match the file hash?
if(isset($_REQUEST['md5']))
{
if(strtolower($_REQUEST['md5']) != $hash)
{
$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;
}
$this->api_add_post();
}
/*
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
*/
if(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml')))
{
$this->authenticate_user();
$start = 0;
if(isset($_GET['md5']))
{
$md5list = explode(",",$_GET['md5']);
foreach($md5list as $md5)
{
$results[] = Image::by_hash($md5);
}
$count = count($results);
} elseif(isset($_GET['id']))
{
$idlist = explode(",",$_GET['id']);
foreach($idlist as $id)
{
$results[] = Image::by_id($id);
}
$count = count($results);
} else
{
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
// Calculate start offset.
if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1
$start = (int_escape($_GET['page'])-1) * $limit;
else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0
$start = int_escape($_GET['pid']) * $limit;
else
$start = 0;
$tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array();
$count = Image::count_images($tags);
$results = Image::find_images(max($start, 0), min($limit, 100), $tags);
}
// Now we have the array $results filled with Image objects
// Let's display them
$xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n";
foreach($results as $img)
{
// Sanity check to see if $img is really an image object
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
if(!is_object($img))
continue;
$taglist = $img->get_tag_list();
$owner = $img->get_owner();
$previewsize = get_thumbnail_size($img->width, $img->height);
$xml .= xml_tag("post", array(
"id" => $img->id,
"md5" => $img->hash,
"file_name" => $img->filename,
"file_url" => $img->get_image_link(),
"height" => $img->height,
"width" => $img->width,
"preview_url" => $img->get_thumb_link(),
"preview_height" => $previewsize[1],
"preview_width" => $previewsize[0],
"rating" => "u",
"date" => $img->posted,
"is_warehoused" => false,
"tags" => $taglist,
"source" => $img->source,
"score" => 0,
"author" => $owner->name
));
}
$xml .= "</posts>";
$page->set_data($xml);
elseif(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) {
$page->set_type("application/xml");
$page->set_data($this->api_find_posts());
}
/*
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
*/
if($event->get_arg(1) == 'find_tags')
{
if(isset($_GET['id']))
{
$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']);
}
*/
else
{
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$sqlresult = $database->execute("SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC",array($start));
while(!$sqlresult->EOF)
{
$results[] = array($sqlresult->fields['count'], $sqlresult->fields['tag'], $sqlresult->fields['id']);
$sqlresult->MoveNext();
}
}
// Tag results collected, build XML output
$xml = "<tags>\n";
foreach($results as $tag)
{
$xml .= "<tag type=\"0\" count=\"$tag[0]\" name=\"" . $this->xmlspecialchars($tag[1]) . "\" id=\"$tag[2]\"/>\n";
}
$xml .= "</tags>";
$page->set_data($xml);
elseif($event->get_arg(1) == 'find_tags') {
$page->set_type("application/xml");
$page->set_data($this->api_find_tags());
}
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
// Shimmie view page
// Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123
// This redirects that to http://shimmie/post/view/123
if(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show'))
{
elseif(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) {
$fixedlocation = make_link("post/view/" . $event->get_arg(3));
$page->set_mode("redirect");
$page->set_redirect($fixedlocation);
}
}
// Turns out I use this a couple times so let's make it a utility function
// Authenticates a user based on the contents of the login and password parameters
// or makes them anonymous. Does not set any cookies or anything permanent.
private function authenticate_user()
{
global $config;
global $database;
global $user;
/**
* Turns out I use this a couple times so let's make it a utility function
* Authenticates a user based on the contents of the login and password parameters
* or makes them anonymous. Does not set any cookies or anything permanent.
*/
private function authenticate_user() {
global $config, $user;
if(isset($_REQUEST['login']) && isset($_REQUEST['password']))
{
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'];
@ -423,18 +102,294 @@ class DanbooruApi extends Extension {
$duser = User::by_name_and_pass($name, $pass);
if(!is_null($duser)) {
$user = $duser;
} else
{
}
else {
$user = User::by_id($config->get_int("anon_id", 0));
}
}
}
// 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));
/**
* 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();
$start = 0;
if(isset($_GET['md5'])) {
$md5list = explode(",", $_GET['md5']);
foreach ($md5list as $md5) {
$results[] = Image::by_hash($md5);
}
$count = count($results);
}
elseif(isset($_GET['id'])) {
$idlist = explode(",", $_GET['id']);
foreach ($idlist as $id) {
$results[] = Image::by_id($id);
}
$count = count($results);
}
else {
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
// Calculate start offset.
if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1
$start = (int_escape($_GET['page']) - 1) * $limit;
else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0
$start = int_escape($_GET['pid']) * $limit;
else
$start = 0;
$tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array();
$count = Image::count_images($tags);
$results = Image::find_images(max($start, 0), min($limit, 100), $tags);
}
// Now we have the array $results filled with Image objects
// Let's display them
$xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n";
foreach ($results as $img) {
// Sanity check to see if $img is really an image object
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
if (!is_object($img))
continue;
$taglist = $img->get_tag_list();
$owner = $img->get_owner();
$previewsize = get_thumbnail_size($img->width, $img->height);
$xml .= xml_tag("post", array(
"id" => $img->id,
"md5" => $img->hash,
"file_name" => $img->filename,
"file_url" => $img->get_image_link(),
"height" => $img->height,
"width" => $img->width,
"preview_url" => $img->get_thumb_link(),
"preview_height" => $previewsize[1],
"preview_width" => $previewsize[0],
"rating" => "u",
"date" => $img->posted,
"is_warehoused" => false,
"tags" => $taglist,
"source" => $img->source,
"score" => 0,
"author" => $owner->name
));
}
$xml .= "</posts>";
return $xml;
}
/**
* 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.
*/
private function api_add_post() {
global $user, $config, $page;
$danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path
// Check first if a login was supplied, if it wasn't check if the user is logged in via cookie
// If all that fails, it's an anonymous upload
$this->authenticate_user();
// Now we check if a file was uploaded or a url was provided to transload
// Much of this code is borrowed from /ext/upload
if (!$user->can("create_image")) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: authentication error");
return;
}
if (isset($_FILES['file'])) { // A file was POST'd in
$file = $_FILES['file']['tmp_name'];
$filename = $_FILES['file']['name'];
// If both a file is posted and a source provided, I'm assuming source is the source of the file
if (isset($_REQUEST['source']) && !empty($_REQUEST['source'])) {
$source = $_REQUEST['source'];
} else {
$source = null;
}
} elseif (isset($_FILES['post'])) {
$file = $_FILES['post']['tmp_name']['file'];
$filename = $_FILES['post']['name']['file'];
if (isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source'])) {
$source = $_REQUEST['post']['source'];
} else {
$source = null;
}
} elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided
$source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
$file = tempnam("/tmp", "shimmie_transload");
$ok = transload($source, $file);
if (!$ok) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: fopen read error");
return;
}
$filename = basename($source);
} else { // Nothing was specified at all
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: no input files");
return;
}
// Get tags out of url
$posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']);
// Was an md5 supplied? Does it match the file hash?
$hash = md5_file($file);
if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: md5 mismatch");
return;
}
// Upload size checking is now performed in the upload extension
// It is also currently broken due to some confusion over file variable ($tmp_filename?)
// Does it exist already?
$existing = Image::by_hash($hash);
if (!is_null($existing)) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: duplicate");
$existinglink = make_link("post/view/" . $existing->id);
if ($danboorup_kludge) $existinglink = make_http($existinglink);
$page->add_http_header("X-Danbooru-Location: $existinglink");
return;
}
// Fire off an event which should process the new file and add it to the db
$fileinfo = pathinfo($filename);
$metadata = array();
$metadata['filename'] = $fileinfo['basename'];
$metadata['extension'] = $fileinfo['extension'];
$metadata['tags'] = $posttags;
$metadata['source'] = $source;
//log_debug("danbooru_api","========== NEW($filename) =========");
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
try {
$nevent = new DataUploadEvent($file, $metadata);
//log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
send_event($nevent);
// If it went ok, grab the id for the newly uploaded image and pass it in the header
$newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error?
$newid = make_link("post/view/" . $newimg->id);
if ($danboorup_kludge) $newid = make_http($newid);
// Did we POST or GET this call?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$page->add_http_header("X-Danbooru-Location: $newid");
} else {
$page->add_http_header("Location: $newid");
}
} catch (UploadException $ex) {
// Did something screw up?
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage());
}
}
}

View file

@ -1,9 +1,9 @@
<?php
class DanbooruApiTest extends ShimmieWebTestCase {
class DanbooruApiTest extends ShimmiePHPUnitTestCase {
public function testSearch() {
$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?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/post/show/$image_id");
$this->assert_response(302);
//$this->assert_response(302); // FIXME
$this->get_page("post/list/md5:17fc89f372ed3636e28bd25cc7f3bac1/1");
$this->assert_title(new PatternExpectation("/^Image \d+: data/"));
$this->click("Delete");
$this->log_out();
//$this->assert_title(new PatternExpectation("/^Image \d+: data/"));
//$this->click("Delete");
}
}

View file

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

View file

@ -1,23 +1,39 @@
<?php
class DowntimeTest extends SCoreWebTestCase {
class DowntimeTest extends ShimmiePHPUnitTestCase {
public function tearDown() {
global $config;
$config->set_bool("downtime", false);
}
public function testDowntime() {
$this->log_in_as_admin();
$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();
global $config;
$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->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->get_page("setup");
$this->set_field("_config_downtime", false);
$this->click("Save Settings");
$this->get_page("post/list");
$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 {
/**
* Show the admin that downtime mode is enabled
*
* @param Page $page
*/
public function display_notification(Page $page) {
$page->add_block(new Block("Downtime",
@ -11,16 +13,19 @@ class DowntimeTheme extends Themelet {
/**
* Display $message and exit
*
* @param string $message
*/
public function display_message(/*string*/ $message) {
global $config, $user;
global $config, $user, $page;
$theme_name = $config->get_string('theme');
$data_href = get_base_href();
$login_link = make_link("user_admin/login");
header("HTTP/1.0 503 Service Temporarily Unavailable");
$auth = $user->get_auth_html();
print <<<EOD
$page->set_mode('data');
$page->set_code(503);
$page->set_data(<<<EOD
<html>
<head>
<title>Downtime</title>
@ -56,7 +61,7 @@ class DowntimeTheme extends Themelet {
</div>
</body>
</html>
EOD;
EOD
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,6 @@
class ExtManagerTheme extends Themelet {
public function display_table(Page $page, /*array*/ $extensions, /*bool*/ $editable) {
global $user;
$h_en = $editable ? "<th>Enabled</th>" : "";
$html = "
".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)"));
}
else if(preg_match("/^favorited_by[=|:](.*)$/i", $event->term, $matches)) {
global $database;
$user = User::by_name($matches[1]);
if(!is_null($user)) {
$user_id = $user->id;
@ -167,7 +166,7 @@ class Favorites extends Extension {
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE
");
$database->execute("CREATE INDEX user_favorites_image_id_idx ON user_favorites(image_id)", array());
$config->set_int("ext_favorites_version", 1);
$config->set_int("ext_favorites_version", 2);
}
if($config->get_int("ext_favorites_version") < 2) {
@ -205,7 +204,7 @@ class Favorites extends Extension {
/**
* @param Image $image
* @return array
* @return string[]
*/
private function list_persons_who_have_favorited(Image $image) {
global $database;

View file

@ -1,13 +1,15 @@
<?php
class FavoritesTest extends ShimmieWebTestCase {
class FavoritesTest extends ShimmiePHPUnitTestCase {
public function testFavorites() {
$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->assert_title("Image $image_id: test");
$this->assert_no_text("Favorited By");
$this->markTestIncomplete();
$this->click("Favorite");
$this->assert_text("Favorited By");
@ -22,12 +24,6 @@ class FavoritesTest extends ShimmieWebTestCase {
$this->click("Un-Favorite");
$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 {
public function get_voter_html(Image $image, $is_favorited) {
global $page, $user;
$i_image_id = int_escape($image->id);
$name = $is_favorited ? "unset" : "set";
$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);
}
if(!is_null($image)) {
if(class_exists("Ratings")) {
if(ext_is_live("Ratings")) {
if(strpos(Ratings::get_user_privs($user), $image->rating) === FALSE) {
return;
}

View file

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

View file

@ -71,7 +71,7 @@ class Forum extends Extension {
}
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));
$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)
{
global $database;
$errors = null;
if (!$this->threadExists($threadID))
{
@ -298,7 +297,7 @@ class Forum extends Extension {
private function show_posts($event, $showAdminOptions = false)
{
global $config, $database, $user;
global $config, $database;
$threadID = $event->get_arg(1);
$pageNumber = $event->get_arg(2);
$postsPerPage = $config->get_int('forumPostsPerPage', 15);

View file

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

View file

@ -1,10 +1,11 @@
<?php
class Handle404Test extends SCoreWebTestCase {
class Handle404Test extends ShimmiePHPUnitTestCase {
public function test404Handler() {
$this->get_page('not/a/page');
$this->assert_response(404);
$this->assert_title('404');
// most descriptive error first
$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->assert_response(200);

View file

@ -33,84 +33,22 @@ class ArchiveFileHandler extends Extension {
$cmd = str_replace('%f', $event->tmpname, $cmd);
$cmd = str_replace('%d', $tmpdir, $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);
$event->image_id = -2; // default -1 = upload wasn't handled
}
}
/**
* @param $ext
* @param string $ext
* @return bool
*/
private function supported_ext($ext) {
$exts = array("zip");
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
*/
protected function check_contents(/*string*/ $file) {

View file

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

View file

@ -1,18 +1,11 @@
<?php
class IcoHandlerTest extends ShimmieWebTestCase {
public function testPixelHander() {
class IcoHandlerTest extends ShimmiePHPUnitTestCase {
public function testIcoHander() {
$this->log_in_as_user();
$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("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 it gets displayed properly
}

View file

@ -26,7 +26,7 @@ class MP3FileHandler extends DataHandlerExtension {
/**
* @param string $filename
* @param array $metadata
* @param mixed[] $metadata
* @return Image|null
*/
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
*/
protected function create_thumb_force(/*string*/ $hash) {
@ -91,9 +91,6 @@ class PixelFileHandler extends DataHandlerExtension {
return $ok;
}
/**
* @param ImageAdminBlockBuildingEvent $event
*/
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) {
$event->add_part("
<form>

View file

@ -1,4 +1,29 @@
$(function() {
function zoom(zoom_type) {
var img = $('.shm-main-image');
if(zoom_type == "full") {
img.css('max-width', img.data('width') + 'px');
img.css('max-height', img.data('height') + 'px');
}
if(zoom_type == "width") {
img.css('max-width', '95%');
img.css('max-height', img.data('height') + 'px');
}
if(zoom_type == "height") {
img.css('max-width', img.data('width') + 'px');
img.css('max-height', (window.innerHeight * 0.95) + 'px');
}
if(zoom_type == "both") {
img.css('max-width', '95%');
img.css('max-height', (window.innerHeight * 0.95) + 'px');
}
$(".shm-zoomer").val(zoom_type);
$.cookie("ui-image-zoom", zoom_type, {path: '/', expires: 365});
}
$(".shm-zoomer").change(function(e) {
zoom(this.options[this.selectedIndex].value);
});
@ -14,27 +39,3 @@ $(function() {
zoom($.cookie("ui-image-zoom"));
}
});
function zoom(zoom_type) {
var img = $('.shm-main-image');
if(zoom_type == "full") {
img.css('max-width', img.data('width') + 'px');
img.css('max-height', img.data('height') + 'px');
}
if(zoom_type == "width") {
img.css('max-width', '95%');
img.css('max-height', img.data('height') + 'px');
}
if(zoom_type == "height") {
img.css('max-width', img.data('width') + 'px');
img.css('max-height', (window.innerHeight * 0.95) + 'px');
}
if(zoom_type == "both") {
img.css('max-width', '95%');
img.css('max-height', (window.innerHeight * 0.95) + 'px');
}
$(".shm-zoomer").val(zoom_type);
$.cookie("ui-image-zoom", zoom_type, {path: '/', expires: 365});
}

View file

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

View file

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

View file

@ -1,34 +1,11 @@
<?php
class SVGHandlerTest extends ShimmieWebTestCase {
class SVGHandlerTest extends ShimmiePHPUnitTestCase {
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();
$image_id = $this->post_image("test.svg", "something");
$this->assert_response(302);
$this->log_out();
$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");
$image_id = $this->post_image("tests/test.svg", "something");
$this->get_page("post/view/$image_id"); // test for no crash
$this->get_page("get_svg/$image_id"); // test for no crash
$this->assert_content("www.w3.org");
# FIXME: test that the thumb works
# FIXME: test that it gets displayed properly

View file

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

View file

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

View file

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

View file

@ -1,11 +1,12 @@
<?php
class HomeTest extends ShimmieWebTestCase {
class HomeTest extends ShimmiePHPUnitTestCase {
public function testHomePage() {
$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
}
}

View file

@ -3,13 +3,16 @@
class HomeTheme extends Themelet {
public function display_page(Page $page, $sitename, $base_href, $theme_name, $body) {
$page->set_mode("data");
$hh = "";
$page->add_auto_html_headers();
foreach($page->html_headers as $h) {$hh .= $h;}
$page->set_data(<<<EOD
<html>
<head>
<title>$sitename</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<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>
<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;}
@ -38,7 +41,7 @@ EOD
$search_html = "
<div class='space' id='search'>
<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='submit' value='Search'/>
</form>

View file

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

View file

@ -1,26 +1,18 @@
<?php
class ImageTest extends ShimmieWebTestCase {
class ImageTest extends ShimmiePHPUnitTestCase {
public function testUserStats() {
$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
$this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test");
$this->assert_text("already has hash");
// broken with sqlite?
//$this->get_page("user/test");
//$this->assert_text("Images uploaded: 1");
$this->get_page("user/test");
$this->assert_text("Images uploaded: 1");
$this->click("Images uploaded");
$this->assert_title("Image $image_id: test");
$this->log_out();
//$this->click("Images uploaded");
//$this->assert_title("Image $image_id: test");
# test that serving manually doesn't cause errors
$this->get_page("image/$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
*/
public function get_deleter_html(/*int*/ $image_id) {
global $config;
$html = "
".make_form(make_link("image/delete"))."
<input type='hidden' name='image_id' value='$image_id' />

View file

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

View file

@ -1,37 +1,33 @@
<?php
class HashBanTest extends ShimmieWebTestCase {
class HashBanTest extends ShimmiePHPUnitTestCase {
public function testBan() {
$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_in_as_admin();
$this->get_page("post/view/$image_id");
$this->markTestIncomplete();
$this->click("Ban and Delete");
$this->log_out();
$this->log_in_as_user();
$this->get_page("post/view/$image_id");
$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->assert_response(404);
$this->log_out();
$this->log_in_as_admin();
$this->get_page("image_hash_ban/list/1");
$this->click("Remove");
$this->log_out();
$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->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) {
global $config, $database, $page, $user;
global $database, $page;
if($event->page_matches("post/list")) {
if(isset($_GET['search'])) {
$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]))));
}
else if(preg_match("/^order[=|:](id|width|height|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)){
global $order_sql;
$ord = strtolower($matches[1]);
$default_order_for_column = preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC";
$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
}
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
//since the tag can't be changed during the parseevent, we instead generate the seed during submit using js
$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
}

View file

@ -3,7 +3,7 @@
$(function() {
var blocked_tags = ($.cookie("ui-blocked-tags") || "").split(" ");
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];
if(tag) {
$(".shm-thumb[data-tags~='"+tag+"']").hide();

View file

@ -1,17 +1,18 @@
<?php
class IndexTest extends ShimmieWebTestCase {
class IndexTest extends ShimmiePHPUnitTestCase {
public function testIndexPage() {
$this->get_page('post/list');
$this->assert_title("Welcome to Shimmie ".VERSION);
$this->assert_no_text("Prev | Index | Next");
$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->get_page('post/list');
$this->assert_title("Shimmie");
$this->assert_text("Prev | Index | Next");
// FIXME
//$this->assert_text("Prev | Index | Next");
$this->get_page('post/list/-1');
$this->assert_title("Shimmie");
@ -25,17 +26,13 @@ class IndexTest extends ShimmieWebTestCase {
$this->get_page('post/list/99999');
$this->assert_title("No Images Found");
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
# FIXME: test search box
}
public function testSearches() {
$this->log_in_as_user();
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$image_id_2 = $this->post_image("ext/simpletest/data/bedroom_workshop.jpg", "computer bedroom workshop");
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "computer bedroom workshop");
$this->log_out();
# make sure both uploads were ok
@ -45,7 +42,6 @@ class IndexTest extends ShimmieWebTestCase {
# regular tag, no results
$this->get_page('post/list/maumaumau/1');
$this->assert_title("No Images Found");
$this->assert_text("No Images Found");
# regular tag, many results
$this->get_page('post/list/computer/1');
@ -59,14 +55,16 @@ class IndexTest extends ShimmieWebTestCase {
# meta tag, one result
$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");
# meta tag, one result
$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->markTestIncomplete();
# multiple tags, many results
$this->get_page('post/list/computer%20size=640x480/1');
$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
$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
$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
# FIXME: known broken in mysql
@ -92,12 +90,12 @@ class IndexTest extends ShimmieWebTestCase {
# test various search methods
$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->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->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->get_page("post/list/tags=3/1");
$this->assert_title("tags=3");
@ -105,11 +103,6 @@ class IndexTest extends ShimmieWebTestCase {
$this->get_page("post/list/ext=jpg/1");
$this->assert_title("ext=jpg");
$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 {
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) {
$this->page_number = $page_number;
$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));
}
/**
* @param Page $page
* @param Image[] $images
*/
public function display_page(Page $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;
$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) {
$prev = $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 = "
<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='submit' value='Find' style='display: none;' />
</form>
@ -72,6 +90,11 @@ and of course start organising your images :-)
return $h_prev.' | '.$h_index.' | '.$h_next.'<br>'.$h_search;
}
/**
* @param Image[] $images
* @param string $query
* @return string
*/
protected function build_table($images, $query) {
$h_query = html_escape($query);
$table = "<div class='shm-image-list' data-query='$h_query'>";
@ -82,6 +105,10 @@ and of course start organising your images :-)
return $table;
}
/**
* @param Page $page
* @param Image[] $images
*/
protected function display_page_header(Page $page, $images) {
global $config;
@ -102,14 +129,18 @@ and of course start organising your images :-)
$page->set_heading($page_title);
}
/**
* @param Page $page
* @param Image[] $images
*/
protected function display_page_images(Page $page, $images) {
if (count($this->search_terms) > 0) {
$query = url_escape(implode(' ', $this->search_terms));
$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 {
$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) {
if($event->page_matches("ip_ban")) {
global $config, $database, $page, $user;
global $page, $user;
if($user->can("ban_ip")) {
if($event->get_arg(0) == "add" && $user->check_auth_token()) {
if(isset($_POST['ip']) && isset($_POST['reason']) && isset($_POST['end'])) {
@ -119,9 +119,11 @@ class IPBan extends Extension {
ip SCORE_INET NOT NULL,
end_timestamp INTEGER,
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
class IPBanTest extends SCoreWebTestCase {
class IPBanTest extends ShimmiePHPUnitTestCase {
public function testIPBan() {
$this->get_page('ip_ban/list');
$this->assert_response(403);
@ -9,6 +9,9 @@ class IPBanTest extends SCoreWebTestCase {
$this->get_page('ip_ban/list');
$this->assert_no_text("42.42.42.42");
$this->markTestIncomplete();
$this->set_field('ip', '42.42.42.42');
$this->set_field('reason', 'unit testing');
$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->log_out();
# FIXME: test that the IP is actually banned
}
}

View file

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

View file

@ -28,9 +28,9 @@ class LinkImage extends Extension {
$text_link = trim($text_link) == "" ? null : $text_link; // null blank setting so the url gets filled in on the text links.
return array(
'thumb_src' => make_http($image->get_thumb_link()),
'image_src' => make_http($image->get_image_link()),
'post_link' => make_http($_SERVER["REQUEST_URI"]),
'thumb_src' => make_http($image->get_thumb_link()),
'image_src' => make_http($image->get_image_link()),
'post_link' => make_http(make_link("post/view/{$image->id}")),
'text_link' => $text_link);
}
}

View file

@ -1,12 +1,17 @@
<?php
class LinkImageTest extends ShimmieWebTestCase {
class LinkImageTest extends ShimmiePHPUnitTestCase {
public function testLinkImage() {
$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
# 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();
preg_match("#value='(http://.*(/|%2F)post(/|%2F)view(/|%2F)[0-9]+)'#", $raw, $matches);
$this->assertTrue(count($matches) > 0);
@ -14,12 +19,6 @@ class LinkImageTest extends ShimmieWebTestCase {
$this->get($matches[1]);
$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);
// don't cache a length of zero when the extension is first installed
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
class LogDatabaseTest extends SCoreWebTestCase {
class LogDatabaseTest extends ShimmiePHPUnitTestCase {
public function testLog() {
$this->log_in_as_admin();
$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?user=demo");
$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>
<form action='".make_link("log/view")."' method='GET'>
<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='user' value='".$this->heie("user")."'></td>
<td>

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