Merge branch 'master' of git://github.com/shish/shimmie2
@ -31,7 +31,9 @@ DefaultType image/jpeg
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
<FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|css|js))$">
|
||||
Header set Cache-Control "public, max-age=2629743"
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "public, max-age=2629743"
|
||||
</IfModule>
|
||||
ExpiresDefault "access plus 1 month"
|
||||
</FilesMatch>
|
||||
#ExpiresByType text/html "now"
|
||||
|
@ -37,10 +37,10 @@ class BaseThemelet {
|
||||
* Generic thumbnail code; returns HTML rather than adding
|
||||
* a block since thumbs tend to go inside blocks...
|
||||
*/
|
||||
public function build_thumb_html(Image $image, $query=null) {
|
||||
public function build_thumb_html(Image $image) {
|
||||
global $config;
|
||||
$i_id = (int) $image->id;
|
||||
$h_view_link = make_link('post/view/'.$i_id, $query);
|
||||
$h_view_link = make_link('post/view/'.$i_id);
|
||||
$h_thumb_link = $image->get_thumb_link();
|
||||
$h_tip = html_escape($image->get_tooltip());
|
||||
$h_tags = strtolower($image->get_tag_list());
|
||||
@ -55,9 +55,8 @@ class BaseThemelet {
|
||||
$tsize = get_thumbnail_size($image->width, $image->height);
|
||||
}
|
||||
|
||||
return "<a href='$h_view_link' class='thumb shm-thumb' data-tags='$h_tags' data-post-id='$i_id'>".
|
||||
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' class='lazy' data-original='$h_thumb_link' src='$base/lib/static/grey.gif'>".
|
||||
"<noscript><img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'></noscript>".
|
||||
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link' data-tags='$h_tags' data-post-id='$i_id'>".
|
||||
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>".
|
||||
"</a>\n";
|
||||
}
|
||||
|
||||
|
@ -211,7 +211,9 @@ class DatabaseConfig extends BaseConfig {
|
||||
$this->database->Execute("DELETE FROM config WHERE name = :name", array("name"=>$name));
|
||||
$this->database->Execute("INSERT INTO config VALUES (:name, :value)", array("name"=>$name, "value"=>$this->values[$name]));
|
||||
}
|
||||
$this->database->cache->delete("config");
|
||||
// rather than deleting and having some other request(s) do a thundering
|
||||
// herd of race-conditioned updates, just save the updated version once here
|
||||
$this->database->cache->set("config", $this->values);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ class SQLite extends DBEngine {
|
||||
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
|
||||
$data = str_replace("SCORE_BOOL", "CHAR(1)", $data);
|
||||
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
|
||||
$data = str_replace("SCORE_STRNORM", "", $data);
|
||||
$data = str_replace("SCORE_STRNORM", "lower", $data);
|
||||
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
|
||||
return $data;
|
||||
}
|
||||
@ -199,6 +199,9 @@ class MemcacheCache implements CacheEngine {
|
||||
|
||||
public function get($key) {
|
||||
assert(!is_null($key));
|
||||
if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) {
|
||||
file_put_contents("data/cache.log", "Cache lookup: $key\n", FILE_APPEND);
|
||||
}
|
||||
$val = $this->memcache->get($key);
|
||||
if($val !== false) {
|
||||
$this->hits++;
|
||||
|
@ -34,9 +34,7 @@ class PageRequestEvent extends Event {
|
||||
global $config;
|
||||
|
||||
// trim starting slashes
|
||||
while(strlen($path) > 0 && $path[0] == '/') {
|
||||
$path = substr($path, 1);
|
||||
}
|
||||
$path = ltrim($path, "/");
|
||||
|
||||
// if path is not specified, use the default front page
|
||||
if(empty($path)) { /* empty is faster than strlen */
|
||||
@ -254,10 +252,16 @@ class LogEvent extends Event {
|
||||
*/
|
||||
var $time;
|
||||
|
||||
public function __construct($section, $priority, $message) {
|
||||
/**
|
||||
* Extra data to be held separate
|
||||
*/
|
||||
var $args;
|
||||
|
||||
public function __construct($section, $priority, $message, $args) {
|
||||
$this->section = $section;
|
||||
$this->priority = $priority;
|
||||
$this->message = $message;
|
||||
$this->args = $args;
|
||||
$this->time = time();
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ class Image {
|
||||
global $database;
|
||||
if($owner->id != $this->owner_id) {
|
||||
$database->execute("UPDATE images SET owner_id=:owner_id WHERE id=:id", array("owner_id"=>$owner->id, "id"=>$this->id));
|
||||
log_info("core-image", "Owner for Image #{$this->id} set to {$owner->name}");
|
||||
log_info("core_image", "Owner for Image #{$this->id} set to {$owner->name}", false, array("image_id" => $this->id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -419,7 +419,7 @@ class Image {
|
||||
if(empty($new_source)) $new_source = null;
|
||||
if($new_source != $old_source) {
|
||||
$database->execute("UPDATE images SET source=:source WHERE id=:id", array("source"=>$new_source, "id"=>$this->id));
|
||||
log_info("core-image", "Source for Image #{$this->id} set to: $new_source (was $old_source)");
|
||||
log_info("core_image", "Source for Image #{$this->id} set to: $new_source (was $old_source)", false, array("image_id" => $this->id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -439,7 +439,7 @@ class Image {
|
||||
$sln = str_replace('"', "", $sln);
|
||||
if(bool_escape($sln) !== $this->locked) {
|
||||
$database->execute("UPDATE images SET locked=:yn WHERE id=:id", array("yn"=>$sln, "id"=>$this->id));
|
||||
log_info("core-image", "Setting Image #{$this->id} lock to: $ln");
|
||||
log_info("core_image", "Setting Image #{$this->id} lock to: $ln", false, array("image_id" => $this->id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -462,7 +462,10 @@ class Image {
|
||||
public function set_tags($tags) {
|
||||
global $database;
|
||||
|
||||
$tags = Tag::resolve_list($tags);
|
||||
assert(is_array($tags));
|
||||
|
||||
$tags = array_map(array('Tag', 'sanitise'), $tags);
|
||||
$tags = Tag::resolve_aliases($tags);
|
||||
|
||||
assert(is_array($tags));
|
||||
assert(count($tags) > 0);
|
||||
@ -501,7 +504,7 @@ class Image {
|
||||
array("tag"=>$tag));
|
||||
}
|
||||
|
||||
log_info("core-image", "Tags for Image #{$this->id} set to: ".implode(" ", $tags));
|
||||
log_info("core_image", "Tags for Image #{$this->id} set to: ".implode(" ", $tags), false, array("image_id" => $this->id));
|
||||
$database->cache->delete("image-{$this->id}-tags");
|
||||
}
|
||||
}
|
||||
@ -513,7 +516,7 @@ class Image {
|
||||
global $database;
|
||||
$this->delete_tags_from_image();
|
||||
$database->execute("DELETE FROM images WHERE id=:id", array("id"=>$this->id));
|
||||
log_info("core-image", 'Deleted Image #'.$this->id.' ('.$this->hash.')');
|
||||
log_info("core_image", 'Deleted Image #'.$this->id.' ('.$this->hash.')', false, array("image_id" => $this->id));
|
||||
|
||||
unlink($this->get_image_filename());
|
||||
unlink($this->get_thumb_filename());
|
||||
@ -524,7 +527,7 @@ class Image {
|
||||
* It DOES NOT remove anything from the database.
|
||||
*/
|
||||
public function remove_image_only() {
|
||||
log_info("core-image", 'Removed Image File ('.$this->hash.')');
|
||||
log_info("core_image", 'Removed Image File ('.$this->hash.')', false, array("image_id" => $this->id));
|
||||
@unlink($this->get_image_filename());
|
||||
@unlink($this->get_thumb_filename());
|
||||
}
|
||||
@ -968,11 +971,11 @@ class Tag {
|
||||
/**
|
||||
* Turn any string or array into a valid tag array
|
||||
*/
|
||||
public static function explode($tags) {
|
||||
public static function explode($tags, $tagme=true) {
|
||||
assert(is_string($tags) || is_array($tags));
|
||||
|
||||
if(is_string($tags)) {
|
||||
$tags = explode(' ', $tags);
|
||||
$tags = explode(' ', trim($tags));
|
||||
}
|
||||
//else if(is_array($tags)) {
|
||||
// do nothing
|
||||
@ -986,7 +989,7 @@ class Tag {
|
||||
}
|
||||
}
|
||||
|
||||
if(count($tag_array) == 0) {
|
||||
if(count($tag_array) == 0 && $tagme) {
|
||||
$tag_array = array("tagme");
|
||||
}
|
||||
|
||||
@ -1012,15 +1015,20 @@ class Tag {
|
||||
public static function resolve_alias($tag) {
|
||||
assert(is_string($tag));
|
||||
|
||||
$negative = false;
|
||||
if(!empty($tag) && ($tag[0] == '-')) {
|
||||
$negative = true;
|
||||
$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));
|
||||
if(!empty($newtag)) {
|
||||
return $newtag;
|
||||
} else {
|
||||
return $tag;
|
||||
if(empty($newtag)) {
|
||||
$newtag = $tag;
|
||||
}
|
||||
return $negative ? "-$newtag" : $newtag;
|
||||
}
|
||||
|
||||
public static function resolve_wildcard($tag) {
|
||||
@ -1055,9 +1063,9 @@ class Tag {
|
||||
* @param $tags Array of tags
|
||||
* @return Array of tags
|
||||
*/
|
||||
public static function resolve_list($tags) {
|
||||
$tags = Tag::explode($tags);
|
||||
reset($tags); // rewind array to the first element.
|
||||
public static function resolve_aliases($tags) {
|
||||
assert(is_array($tags));
|
||||
|
||||
$new = array();
|
||||
foreach($tags as $tag) {
|
||||
$new_set = explode(' ', Tag::resolve_alias($tag));
|
||||
@ -1065,7 +1073,7 @@ class Tag {
|
||||
$new[] = $new_one;
|
||||
}
|
||||
}
|
||||
$new = array_map(array('Tag', 'sanitise'), $new);
|
||||
|
||||
$new = array_iunique($new); // remove any duplicate tags
|
||||
return $new;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ _d("DATABASE_KA", true); // string Keep database connection alive
|
||||
_d("CACHE_DSN", null); // string cache connection details
|
||||
_d("DEBUG", false); // boolean print various debugging details
|
||||
_d("DEBUG_SQL", false); // boolean dump SQL queries to data/sql.log
|
||||
_d("DEBUG_CACHE", false); // boolean dump cache queries to data/cache.log
|
||||
_d("COVERAGE", false); // boolean activate xdebug coverage monitor
|
||||
_d("CONTEXT", null); // string file to log performance data into
|
||||
_d("CACHE_HTTP", false); // boolean output explicit HTTP caching headers
|
||||
@ -34,7 +35,8 @@ _d("NICE_URLS", false); // boolean force niceurl mode
|
||||
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
|
||||
_d("VERSION", 'trunk'); // 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("MIN_FREE_SPACE",100*1024*1024); // int disable uploading if there's less than MIN_FREE_SPACE bytes free space
|
||||
_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,hellban"); // extensions to always enable
|
||||
_d("EXTRA_EXTS", ""); // optional extra extensions
|
||||
|
||||
|
||||
|
@ -99,6 +99,9 @@ new UserClass("base", null, array(
|
||||
"edit_other_vote" => False,
|
||||
"view_sysinfo" => False,
|
||||
|
||||
"hellbanned" => False,
|
||||
"view_hellbanned" => False,
|
||||
|
||||
"protected" => False, # only admins can modify protected users (stops a moderator changing an admin's password)
|
||||
));
|
||||
|
||||
@ -152,8 +155,13 @@ new UserClass("admin", "base", array(
|
||||
"bulk_edit_vote" => True,
|
||||
"edit_other_vote" => True,
|
||||
"view_sysinfo" => True,
|
||||
"view_hellbanned" => True,
|
||||
"protected" => True,
|
||||
));
|
||||
|
||||
new UserClass("hellbanned", "user", array(
|
||||
"hellbanned" => True,
|
||||
));
|
||||
|
||||
@include_once "data/config/user-classes.conf.php";
|
||||
?>
|
||||
|
@ -713,7 +713,7 @@ function get_prefixed_cookie(/*string*/ $name) {
|
||||
*/
|
||||
function set_prefixed_cookie($name, $value, $time, $path) {
|
||||
global $config;
|
||||
$full_name = $config->get_string('cookie_prefix','shm')."_".$name;
|
||||
$full_name = COOKIE_PREFIX."_".$name;
|
||||
setcookie($full_name, $value, $time, $path);
|
||||
}
|
||||
|
||||
@ -805,6 +805,7 @@ function transload($url, $mfile) {
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_REFERER, $url);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, "Shimmie-".VERSION);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
@ -816,7 +817,7 @@ function transload($url, $mfile) {
|
||||
if($config->get_string("transload_engine") == "wget") {
|
||||
$s_url = escapeshellarg($url);
|
||||
$s_mfile = escapeshellarg($mfile);
|
||||
system("wget $s_url --output-document=$s_mfile");
|
||||
system("wget --no-check-certificate $s_url --output-document=$s_mfile");
|
||||
|
||||
return file_exists($mfile);
|
||||
}
|
||||
@ -901,8 +902,8 @@ define("SCORE_LOG_NOTSET", 0);
|
||||
* $flash = true - show the message to the user as well
|
||||
* $flash = "some string" - log the message, flash the string
|
||||
*/
|
||||
function log_msg(/*string*/ $section, /*int*/ $priority, /*string*/ $message, $flash=null) {
|
||||
send_event(new LogEvent($section, $priority, $message));
|
||||
function log_msg(/*string*/ $section, /*int*/ $priority, /*string*/ $message, $flash=null, $args=array()) {
|
||||
send_event(new LogEvent($section, $priority, $message, $args));
|
||||
$threshold = defined("CLI_LOG_LEVEL") ? CLI_LOG_LEVEL : 0;
|
||||
if(is_cli() && ($priority >= $threshold)) {
|
||||
print date("c")." $section: $message\n";
|
||||
@ -916,11 +917,29 @@ function log_msg(/*string*/ $section, /*int*/ $priority, /*string*/ $message, $f
|
||||
}
|
||||
|
||||
// More shorthand ways of logging
|
||||
function log_debug( /*string*/ $section, /*string*/ $message, $flash=null) {log_msg($section, SCORE_LOG_DEBUG, $message, $flash);}
|
||||
function log_info( /*string*/ $section, /*string*/ $message, $flash=null) {log_msg($section, SCORE_LOG_INFO, $message, $flash);}
|
||||
function log_warning( /*string*/ $section, /*string*/ $message, $flash=null) {log_msg($section, SCORE_LOG_WARNING, $message, $flash);}
|
||||
function log_error( /*string*/ $section, /*string*/ $message, $flash=null) {log_msg($section, SCORE_LOG_ERROR, $message, $flash);}
|
||||
function log_critical(/*string*/ $section, /*string*/ $message, $flash=null) {log_msg($section, SCORE_LOG_CRITICAL, $message, $flash);}
|
||||
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);}
|
||||
|
||||
/**
|
||||
* Get a unique ID for this request, useful for grouping log messages
|
||||
*/
|
||||
$_request_id = null;
|
||||
function get_request_id() {
|
||||
global $_request_id;
|
||||
if(!$_request_id) {
|
||||
// not completely trustworthy, as a user can spoof this
|
||||
if(@$_SERVER['HTTP_X_VARNISH']) {
|
||||
$_request_id = $_SERVER['HTTP_X_VARNISH'];
|
||||
}
|
||||
else {
|
||||
$_request_id = "P" . uniqid();
|
||||
}
|
||||
}
|
||||
return $_request_id;
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
|
@ -110,11 +110,15 @@ class AdminPage extends Extension {
|
||||
private function delete_by_query() {
|
||||
global $page, $user;
|
||||
$query = $_POST['query'];
|
||||
$reason = @$_POST['reason'];
|
||||
assert(strlen($query) > 1);
|
||||
|
||||
log_warning("admin", "Mass deleting: $query");
|
||||
$count = 0;
|
||||
foreach(Image::find_images(0, 1000000, Tag::explode($query)) as $image) {
|
||||
if($reason && class_exists("ImageBan")) {
|
||||
send_event(new AddImageHashBanEvent($image->hash, $reason));
|
||||
}
|
||||
send_event(new ImageDeletionEvent($image));
|
||||
$count++;
|
||||
}
|
||||
@ -213,5 +217,46 @@ class AdminPage extends Extension {
|
||||
//TODO: Delete file after downloaded?
|
||||
return false; // we do want a redirect, but a manual one
|
||||
}
|
||||
|
||||
private function reset_image_ids() {
|
||||
global $database;
|
||||
|
||||
//This might be a bit laggy on boards with lots of images (?)
|
||||
//Seems to work fine with 1.2k~ images though.
|
||||
$i = 0;
|
||||
$image = $database->get_all("SELECT * FROM images ORDER BY images.id ASC");
|
||||
/*$score_log = $database->get_all("SELECT message FROM score_log");*/
|
||||
foreach($image as $img){
|
||||
$xid = $img[0];
|
||||
$i = $i + 1;
|
||||
$table = array( //Might be missing some tables?
|
||||
"image_tags", "tag_histories", "image_reports", "comments", "user_favorites", "tag_histories",
|
||||
"numeric_score_votes", "pool_images", "slext_progress_cache", "notes");
|
||||
|
||||
$sql =
|
||||
"SET FOREIGN_KEY_CHECKS=0;
|
||||
UPDATE images
|
||||
SET id=".$i.
|
||||
" WHERE id=".$xid.";"; //id for images
|
||||
|
||||
foreach($table as $tbl){
|
||||
$sql .= "
|
||||
UPDATE ".$tbl."
|
||||
SET image_id=".$i."
|
||||
WHERE image_id=".$xid.";";
|
||||
}
|
||||
|
||||
/*foreach($score_log as $sl){
|
||||
//This seems like a bad idea.
|
||||
//TODO: Might be better for log_info to have an $id option (which would then affix the id to the table?)
|
||||
preg_replace(".Image \\#[0-9]+.", "Image #".$i, $sl);
|
||||
}*/
|
||||
$sql .= " SET FOREIGN_KEY_CHECKS=1;";
|
||||
$database->execute($sql);
|
||||
}
|
||||
$count = (count($image)) + 1;
|
||||
$database->execute("ALTER TABLE images AUTO_INCREMENT=".$count);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -16,8 +16,8 @@ class AdminPageTheme extends Themelet {
|
||||
$c_protected = $protected ? " protected" : "";
|
||||
$html = make_form(make_link("admin/$action"), "POST", false, false, false, "admin$c_protected");
|
||||
if($protected) {
|
||||
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
|
||||
$html .= "<input type='submit' id='$action' value='$name' disabled='true'>";
|
||||
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
|
||||
}
|
||||
else {
|
||||
$html .= "<input type='submit' id='$action' value='$name'>";
|
||||
@ -40,7 +40,9 @@ class AdminPageTheme extends Themelet {
|
||||
$html .= $this->button("All tags to lowercase", "lowercase_all_tags", true);
|
||||
$html .= $this->button("Recount tag use", "recount_tag_user", false);
|
||||
$html .= $this->button("Download all images", "image_dump", false);
|
||||
$html .= $this->button("Download database contents", "database_dump", false);
|
||||
$html .= $this->button("Download database contents", "database_dump", false);
|
||||
if($database->get_driver_name() == "mysql")
|
||||
$html .= $this->button("Reset image IDs", "reset_image_ids", true);
|
||||
$page->add_block(new Block("Misc Admin Tools", $html));
|
||||
|
||||
$html = make_form(make_link("admin/set_tag_case"), "POST");
|
||||
@ -52,9 +54,14 @@ class AdminPageTheme extends Themelet {
|
||||
|
||||
public function dbq_html($terms) {
|
||||
$h_terms = html_escape($terms);
|
||||
$h_reason = "";
|
||||
if(class_exists("ImageBan")) {
|
||||
$h_reason = "<input type='text' name='reason' placeholder='Ban reason (leave blank to not ban)'>";
|
||||
}
|
||||
$html = make_form(make_link("admin/delete_by_query"), "POST") . "
|
||||
<input type='button' class='shm-unlocker' data-unlock-sel='#dbqsubmit' value='Unlock'>
|
||||
<input type='hidden' name='query' value='$h_terms'>
|
||||
$h_reason
|
||||
<input type='submit' id='dbqsubmit' disabled='true' value='Delete All These Images'>
|
||||
</form>
|
||||
";
|
||||
|
@ -15,8 +15,8 @@ class AddAliasEvent extends Event {
|
||||
var $newtag;
|
||||
|
||||
public function AddAliasEvent($oldtag, $newtag) {
|
||||
$this->oldtag = $oldtag;
|
||||
$this->newtag = $newtag;
|
||||
$this->oldtag = trim($oldtag);
|
||||
$this->newtag = trim($newtag);
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +138,12 @@ class AliasEditor extends Extension {
|
||||
foreach(explode("\n", $csv) as $line) {
|
||||
$parts = explode(",", $line);
|
||||
if(count($parts) == 2) {
|
||||
$database->execute("INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)", array("oldtag" => $parts[0], "newtag" => $parts[1]));
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class AliasEditorTheme extends Themelet {
|
||||
";
|
||||
|
||||
$bulk_html = "
|
||||
".make_form(make_link("alias/import"), $multipart=True)."
|
||||
".make_form(make_link("alias/import"), 'post', true)."
|
||||
<input type='file' name='alias_file'>
|
||||
<input type='submit' value='Upload List'>
|
||||
</form>
|
||||
|
@ -8,28 +8,22 @@
|
||||
* Documentation:
|
||||
* Simply enable this extention in the extention manager to enable arrow key navigation.
|
||||
*/
|
||||
class arrowkey_navigation extends Extension {
|
||||
class ArrowkeyNavigation extends Extension {
|
||||
# Adds functionality for post/view on images
|
||||
public function onDisplayingImage(DisplayingImageEvent $event) {
|
||||
$prev_url = make_http(make_link("post/prev/".$event->image->id));
|
||||
$next_url = make_http(make_link("post/next/".$event->image->id));
|
||||
$this->add_arrowkeys_code($prev_url, $next_url);
|
||||
}
|
||||
|
||||
# Adds functionality for post/list
|
||||
public function onPageRequest(PageRequestEvent $event) {
|
||||
if ($event->page_matches("post/view")) {
|
||||
$pageinfo = $this->get_view_pageinfo($event);
|
||||
$prev_url = make_http(make_link("post/prev/".$pageinfo));
|
||||
$next_url = make_http(make_link("post/next/".$pageinfo));
|
||||
$this->add_arrowkeys_code($prev_url, $next_url);
|
||||
}
|
||||
|
||||
else if ($event->page_matches("post/list")) {
|
||||
if($event->page_matches("post/list")) {
|
||||
$pageinfo = $this->get_list_pageinfo($event);
|
||||
$prev_url = make_http(make_link("post/list/".$pageinfo["prev"]));
|
||||
$next_url = make_http(make_link("post/list/".$pageinfo["next"]));
|
||||
$this->add_arrowkeys_code($prev_url, $next_url);
|
||||
}
|
||||
|
||||
// for random_list extension
|
||||
else if ($event->page_matches("random")) {
|
||||
$randomurl = make_http(make_link("random"));
|
||||
$this->add_arrowkeys_code($randomurl, $randomurl);
|
||||
}
|
||||
}
|
||||
|
||||
# adds the javascript to the page with the given urls
|
||||
@ -46,8 +40,8 @@ class arrowkey_navigation extends Extension {
|
||||
|
||||
if (e.srcElement.tagName != \"INPUT\")
|
||||
{
|
||||
if(keycode==\"37\") window.location.href='$prev_url' + window.location.hash;
|
||||
else if(keycode==\"39\") window.location.href='$next_url' + window.location.hash;
|
||||
if(keycode==\"37\") window.location.href='$prev_url';
|
||||
else if(keycode==\"39\") window.location.href='$next_url';
|
||||
}
|
||||
}
|
||||
</script>");
|
||||
@ -60,16 +54,8 @@ class arrowkey_navigation extends Extension {
|
||||
// get the amount of images per page
|
||||
$images_per_page = $config->get_int('index_images');
|
||||
|
||||
// this occurs when viewing post/list without page number
|
||||
if ($event->get_arg(0) == null) {// no page listed
|
||||
$prefix = "";
|
||||
$page_number = 1;
|
||||
$total_pages = ceil($database->get_one(
|
||||
"SELECT COUNT(*) FROM images") / $images_per_page);
|
||||
}
|
||||
|
||||
// if there are no tags, use default
|
||||
else if ($event->get_arg(1) == null){
|
||||
if ($event->get_arg(1) == null){
|
||||
$prefix = "";
|
||||
$page_number = (int)$event->get_arg(0);
|
||||
$total_pages = ceil($database->get_one(
|
||||
@ -93,28 +79,11 @@ class arrowkey_navigation extends Extension {
|
||||
|
||||
// Create return array
|
||||
$pageinfo = array(
|
||||
"prev" => $prefix.$prev.$after,
|
||||
"next" => $prefix.$next.$after,
|
||||
"prev" => $prefix.$prev,
|
||||
"next" => $prefix.$next,
|
||||
);
|
||||
|
||||
return $pageinfo;
|
||||
}
|
||||
|
||||
# returns url ext with any tags
|
||||
private function get_view_pageinfo($event) {
|
||||
// if there are no tags, use default
|
||||
if ($event->get_arg(1) == null){
|
||||
$prefix = "";
|
||||
$image_id = (int)$event->get_arg(0);
|
||||
}
|
||||
|
||||
else { // if there are tags, use pages with tags
|
||||
$prefix = $event->get_arg(0)."/";
|
||||
$image_id = (int)$event->get_arg(1);
|
||||
}
|
||||
|
||||
// returns result
|
||||
return $prefix.$image_id;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
132
ext/bulk_remove/main.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Bulk Remove (Beta)
|
||||
* Author: Drudex Software <support@drudexsoftware.com>
|
||||
* Link: http://www.drudexsoftware.com/
|
||||
* License: GPLv2
|
||||
* Description: Allows admin to delete many images at once through Board Admin.
|
||||
* Documentation:
|
||||
*
|
||||
*/
|
||||
//todo: removal by tag returns 1 less image in test for some reason, actually a combined search doesn't seem to work for shit either
|
||||
|
||||
class BulkRemove extends Extension {
|
||||
public function onPageRequest(PageRequestEvent $event) {
|
||||
global $page, $user;
|
||||
if($event->page_matches("bulk_remove") && $user->is_admin() && $user->check_auth_token()) {
|
||||
if ($event->get_arg(0) == "confirm") $this->do_bulk_remove();
|
||||
else $this->show_confirm();
|
||||
}
|
||||
}
|
||||
|
||||
public function onAdminBuilding(AdminBuildingEvent $event) {
|
||||
global $page, $user;
|
||||
$html = "<b>Be extremely careful when using this!</b><br>
|
||||
Once an image is removed there is no way to recover it so it is recommended that
|
||||
you first take when removing a large amount of images.<br>
|
||||
<b>Note:</b> Entering both an ID range and tags will only remove images between the given ID's that have the given tags.
|
||||
|
||||
<p>".make_form(make_link("bulk_remove"))."
|
||||
<table class='form'>
|
||||
<tr><td colspan='2'><b>Remove images by ID</b></td></tr>
|
||||
<tr><th>From</th><td> <input type='text' name='remove_id_min' size='2'></td></tr>
|
||||
<tr><th>Until</th><td> <input type='text' name='remove_id_max' size='2'></td></tr>
|
||||
|
||||
<tr><td colspan='2'><b>Where tags are</b></td></tr>
|
||||
<tr><td colspan='2'>
|
||||
<input type='text' name='remove_tags' size='10'>
|
||||
</td> </tr>
|
||||
<tr><td colspan='2'><input type='submit' value='Remove'></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
";
|
||||
$page->add_block(new Block("Bulk Remove", $html));
|
||||
}
|
||||
|
||||
// returns a list of images to be removed
|
||||
private function determine_images()
|
||||
{
|
||||
// set vars
|
||||
$images_for_removal = array();
|
||||
$error = "";
|
||||
|
||||
$min_id = $_POST['remove_id_min'];
|
||||
$max_id = $_POST['remove_id_max'];
|
||||
$tags = $_POST['remove_tags'];
|
||||
|
||||
|
||||
// if using id range to remove (comined removal with tags)
|
||||
if ($min_id != "" && $max_id != "")
|
||||
{
|
||||
// error if values are not correctly entered
|
||||
if (!is_numeric($min_id) || !is_numeric($max_id) ||
|
||||
intval($max_id) < intval($min_id))
|
||||
$error = "Values not correctly entered for removal between id.";
|
||||
|
||||
else { // if min & max id are valid
|
||||
|
||||
// Grab the list of images & place it in the removing array
|
||||
foreach (Image::find_images(intval($min_id), intval($max_id)) as $image)
|
||||
array_push($images_for_removal, $image);
|
||||
}
|
||||
}
|
||||
|
||||
// refine previous results or create results from tags
|
||||
if ($tags != "")
|
||||
{
|
||||
$tags_arr = explode(" ", $_POST['remove_tags']);
|
||||
|
||||
// Search all images with the specified tags & add to list
|
||||
foreach (Image::find_images(1, 2147483647, $tags_arr) as $image)
|
||||
array_push($images_for_removal, $image);
|
||||
}
|
||||
|
||||
|
||||
// if no images were found with the given info
|
||||
if (count($images_for_removal) == 0 && $html == "")
|
||||
$error = "No images selected for removal";
|
||||
|
||||
var_dump($tags_arr);
|
||||
return array(
|
||||
"error" => $error,
|
||||
"images_for_removal" => $images_for_removal);
|
||||
}
|
||||
|
||||
// displays confirmation to admin before removal
|
||||
private function show_confirm()
|
||||
{
|
||||
global $page;
|
||||
|
||||
// set vars
|
||||
$determined_imgs = $this->determine_images();
|
||||
$error = $determined_imgs["error"];
|
||||
$images_for_removal = $determined_imgs["images_for_removal"];
|
||||
|
||||
// if there was an error in determine_images()
|
||||
if ($error != "") {
|
||||
$page->add_block(new Block("Cannot remove images", $error));
|
||||
return;
|
||||
}
|
||||
// generates the image array & places it in $_POST["bulk_remove_images"]
|
||||
$_POST["bulk_remove_images"] = $images_for_removal;
|
||||
|
||||
// Display confirmation message
|
||||
$html = make_form(make_link("bulk_remove")).
|
||||
"Are you sure you want to PERMANENTLY remove ".
|
||||
count($images_for_removal) ." images?<br></form>";
|
||||
$page->add_block(new Block("Confirm Removal", $html));
|
||||
}
|
||||
|
||||
private function do_bulk_remove()
|
||||
{
|
||||
// display error if user didn't go through admin board
|
||||
if (!isset($_POST["bulk_remove_images"])) {
|
||||
$page->add_block(new Block("Bulk Remove Error",
|
||||
"Please use Board Admin to use bulk remove."));
|
||||
}
|
||||
|
||||
//
|
||||
$image_arr = $_POST["bulk_remove_images"];
|
||||
}
|
||||
}
|
||||
?>
|
459
ext/chatbox/cp/ajax.php
Normal file
@ -0,0 +1,459 @@
|
||||
<?
|
||||
error_reporting(E_ALL);
|
||||
$kioskMode = false;
|
||||
|
||||
include '../php/filestorage.class.php';
|
||||
include '../preferences.php';
|
||||
include '../php/json.class.php';
|
||||
include '../php/functions.php';
|
||||
include '../php/yshout.class.php';
|
||||
include '../php/ajaxcall.class.php';
|
||||
|
||||
if (isset($_POST['mode']))
|
||||
switch($_POST['mode']) {
|
||||
case 'login':
|
||||
doLogin();
|
||||
break;
|
||||
case 'logout':
|
||||
doLogout();
|
||||
break;
|
||||
case 'unban':
|
||||
doUnban();
|
||||
break;
|
||||
case 'unbanall':
|
||||
doUnbanAll();
|
||||
break;
|
||||
case 'setpreference':
|
||||
doSetPreference();
|
||||
break;
|
||||
case 'resetpreferences':
|
||||
doResetPreferences();
|
||||
break;
|
||||
}
|
||||
|
||||
function doLogin() {
|
||||
global $kioskMode;
|
||||
|
||||
if ($kioskMode) {
|
||||
logout();
|
||||
$result = array(
|
||||
'error' => false,
|
||||
'html' => cp()
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
login(md5($_POST['password']));
|
||||
$result = array();
|
||||
if (loggedIn()) {
|
||||
$result['error'] = false;
|
||||
$result['html'] = cp();
|
||||
} else
|
||||
$result['error'] = 'invalid';
|
||||
|
||||
echo jsonEncode($result);
|
||||
}
|
||||
|
||||
function doLogout() {
|
||||
logout();
|
||||
|
||||
$result = array(
|
||||
'error' => false
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
}
|
||||
|
||||
function doUnban() {
|
||||
global $kioskMode;
|
||||
|
||||
if ($kioskMode) {
|
||||
$result = array(
|
||||
'error' => false
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loggedIn()) return;
|
||||
|
||||
$ys = ys();
|
||||
$result = array();
|
||||
|
||||
$ip = $_POST['ip'];
|
||||
|
||||
if ($ys->banned($ip)) {
|
||||
$ys->unban($ip);
|
||||
$result['error'] = false;
|
||||
} else
|
||||
$result['error'] = 'notbanned';
|
||||
|
||||
|
||||
echo jsonEncode($result);
|
||||
}
|
||||
|
||||
function doUnbanAll() {
|
||||
global $kioskMode;
|
||||
|
||||
if ($kioskMode) {
|
||||
$result = array(
|
||||
'error' => false
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loggedIn()) return;
|
||||
|
||||
$ys = ys();
|
||||
$ys->unbanAll();
|
||||
|
||||
$result = array(
|
||||
'error' => false
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
}
|
||||
|
||||
|
||||
function doSetPreference() {
|
||||
global $prefs, $kioskMode;
|
||||
|
||||
if ($kioskMode) {
|
||||
$result = array(
|
||||
'error' => false
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loggedIn()) return;
|
||||
|
||||
$pref = $_POST['preference'];
|
||||
$value = magic($_POST['value']);
|
||||
|
||||
if ($value === 'true') $value = true;
|
||||
if ($value === 'false') $value = false;
|
||||
|
||||
$prefs[$pref] = $value;
|
||||
|
||||
savePrefs($prefs);
|
||||
|
||||
if ($pref == 'password') login(md5($value));
|
||||
|
||||
$result = array(
|
||||
'error' => false
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
}
|
||||
|
||||
|
||||
function doResetPreferences() {
|
||||
global $prefs, $kioskMode;
|
||||
|
||||
if ($kioskMode) {
|
||||
$result = array(
|
||||
'error' => false
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loggedIn()) return;
|
||||
|
||||
resetPrefs();
|
||||
login(md5($prefs['password']));
|
||||
|
||||
// $prefs['password'] = 'lol no';
|
||||
$result = array(
|
||||
'error' => false,
|
||||
'prefs' => $prefs
|
||||
);
|
||||
|
||||
echo jsonEncode($result);
|
||||
}
|
||||
|
||||
/* CP Display */
|
||||
|
||||
function cp() {
|
||||
global $kioskMode;
|
||||
|
||||
if (!loggedIn() && !$kioskMode) return 'You\'re not logged in!';
|
||||
|
||||
return '
|
||||
|
||||
<div class="section" id="preferences">
|
||||
<span style="display: none;" id="cp-loaded">true</span>
|
||||
<div class="header">
|
||||
<h1>YShout.Preferences</h1>
|
||||
<a href="#" class="logout">Logout</a>
|
||||
</div>
|
||||
|
||||
<ul class="subnav">
|
||||
<li id="sn-administration"><a href="#">Administration</a></li>
|
||||
<li id="sn-display"><a href="#">Display</a></li>
|
||||
<li id="sn-resetall"><a href="#">Reset All</a></li>
|
||||
<span class="sn-loading">Loading...</span>
|
||||
</ul>
|
||||
|
||||
' . preferencesForm() . '
|
||||
</div>
|
||||
|
||||
<div class="section" id="about">
|
||||
<div class="header">
|
||||
<h1>YShout.About</h1>
|
||||
<a href="#" class="logout">Logout</a>
|
||||
</div>
|
||||
|
||||
<ul class="subnav">
|
||||
<li id="sn-about"><a href="#">About</a></li>
|
||||
<li id="sn-contact"><a href="#">Contact</a></li>
|
||||
<span class="sn-loading">Loading...</span>
|
||||
</ul>
|
||||
|
||||
' . about() . '
|
||||
</div>
|
||||
|
||||
<div class="section" id="bans">
|
||||
<div class="header">
|
||||
<h1>YShout.Bans</h1>
|
||||
<a href="#" class="logout">Logout</a>
|
||||
</div>
|
||||
|
||||
<ul class="subnav">
|
||||
<li id="sn-unbanall"><a href="#">Unban All</a></li>
|
||||
<span class="sn-loading">Loading...</span>
|
||||
</ul>
|
||||
|
||||
' . bansList() . '
|
||||
|
||||
</div>';
|
||||
}
|
||||
|
||||
function bansList() {
|
||||
global $kioskMode;
|
||||
|
||||
$ys = ys();
|
||||
$bans = $ys->bans();
|
||||
|
||||
$html = '<ul id="bans-list">';
|
||||
|
||||
$hasBans = false;
|
||||
foreach($bans as $ban) {
|
||||
$hasBans = true;
|
||||
$html .= '
|
||||
<li>
|
||||
<span class="nickname">' . $ban['nickname']. '</span>
|
||||
(<span class="ip">' . ($kioskMode ? '[No IP in Kiosk Mode]' : $ban['ip']) . '</span>)
|
||||
<a title="Unban" class="unban-link" href="#" rel="' . $ban['timestamp'] . '">Unban</a>
|
||||
</li>
|
||||
';
|
||||
}
|
||||
|
||||
if (!$hasBans)
|
||||
$html = '<p id="no-bans">No one is banned.</p>';
|
||||
else
|
||||
$html .= '</ul>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
function preferencesForm() {
|
||||
global $prefs, $kioskMode;
|
||||
|
||||
return '
|
||||
<form id="preferences-form">
|
||||
<div id="cp-pane-administration" class="cp-pane">
|
||||
<fieldset id="prefs-cat-cp">
|
||||
<div class="legend">Control Panel</div class="legend">
|
||||
<ol>
|
||||
<li>
|
||||
<label for="pref-password">Password</label>
|
||||
<input rel="password" type="text" id="pref-password" value="' . ($kioskMode ? 'No password in Kiosk Mode.' : $prefs['password']) . '" />
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="prefs-cat-flood">
|
||||
<div class="legend">Flood Control</div class="legend">
|
||||
<ol>
|
||||
<li>
|
||||
<label for="pref-flood">Use flood control</label>
|
||||
<select rel="flood" id="pref-flood">
|
||||
<option' . ($prefs['flood'] == true ? ' selected' : '') . ' rel="true">Yes</option>
|
||||
<option' . ($prefs['flood'] == false ? ' selected' : '') . ' rel="false">No</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-flood-timeout">Flood timeout</label>
|
||||
<input rel="floodTimeout" type="text" id="pref-flood-timeout" value="' . $prefs['floodTimeout'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-flood-messages">Flood messages</label>
|
||||
<input rel="floodMessages" type="text" id="pref-flood-messages" value="' . $prefs['floodMessages'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-flood-length">Flood length</label>
|
||||
<input rel="floodDisable" type="text" id="pref-flood-length" value="' . $prefs['floodDisable'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-flood-autoban">Automatically ban after</label>
|
||||
<select rel="autobanFlood" id="pref-flood-autoban">
|
||||
<option' . ($prefs['autobanFlood'] == 1 ? ' selected' : '') . ' rel="1">One activation</option>
|
||||
<option' . ($prefs['autobanFlood'] == 2 ? ' selected' : '') . ' rel="2">Two activations</option>
|
||||
<option' . ($prefs['autobanFlood'] == 3 ? ' selected' : '') . ' rel="3">Three activations</option>
|
||||
<option' . ($prefs['autobanFlood'] == 4 ? ' selected' : '') . ' rel="4">Four activations</option>
|
||||
<option' . ($prefs['autobanFlood'] == 5 ? ' selected' : '') . ' rel="5">Five activations</option>
|
||||
<option' . ($prefs['autobanFlood'] == 0 ? ' selected' : '') . ' rel="false">Never</option>
|
||||
</select>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="prefs-cat-history">
|
||||
<div class="legend">History</div class="legend">
|
||||
<ol>
|
||||
<li>
|
||||
<label for="pref-max-logs">Max. amount of logs</label>
|
||||
<input rel="logs" type="text" id="pref-max-logs" value="' . $prefs['logs'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-history-shouts">Shouts to keep in history</label>
|
||||
<input rel="history" type="text" id="pref-history-shouts" value="' . $prefs['history'] . '" />
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="prefs-cat-misc">
|
||||
<div class="legend">Miscellaneous</div class="legend">
|
||||
<ol>
|
||||
<li>
|
||||
<label for="pref-refresh-rate">Refresh rate</label>
|
||||
<input rel="refresh" type="text" id="pref-refresh-rate" value="' . $prefs['refresh'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-censor-words">Censor words</label>
|
||||
<input rel="censorWords" type="text" id="pref-censor-words" value="' . $prefs['censorWords'] . '" />
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div id="cp-pane-display" class="cp-pane">
|
||||
<fieldset id="prefs-cat-form">
|
||||
<div class="legend">Form</div class="legend">
|
||||
<ol>
|
||||
<li>
|
||||
<label for="pref-form-position">Form position</label>
|
||||
<select rel="inverse" id="pref-form-position">
|
||||
<option' . ($prefs['inverse'] == true ? ' selected' : '') . ' rel="true">Top</option>
|
||||
<option' . ($prefs['inverse'] == false ? ' selected' : '') . ' rel="false">Bottom</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-nickname-text">Default nickname text</label>
|
||||
<input rel="defaultNickname" type="text" id="pref-nickname-text" value="' . $prefs['defaultNickname'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-message-text">Default message text</label>
|
||||
<input rel="defaultMessage" type="text" id="pref-message-text" value="' . $prefs['defaultMessage'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-submit-text">Default submit text</label>
|
||||
<input rel="defaultSubmit" type="text" id="pref-submit-text" value="' . $prefs['defaultSubmit'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-nickname-length">Max. nickname length</label>
|
||||
<input rel="nicknameLength" type="text" id="pref-nickname-length" value="' . $prefs['nicknameLength'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-message-length">Max. message length</label>
|
||||
<input rel="messageLength" type="text" id="pref-message-length" value="' . $prefs['messageLength'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-show-submit">Show submit button</label>
|
||||
<select rel="showSubmit" id="pref-show-submit">
|
||||
<option' . ($prefs['showSubmit'] == true ? ' selected' : '') . ' rel="true">Yes</option>
|
||||
<option' . ($prefs['showSubmit'] == false ? ' selected' : '') . ' rel="false">No</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-post-form-link">Show link</label>
|
||||
<select rel="postFormLink" id="pref-post-form-link">
|
||||
<option' . ($prefs['postFormLink'] == 'none' ? ' selected' : '') . ' rel="none">None</option>
|
||||
<option' . ($prefs['postFormLink'] == 'history' ? ' selected' : '') . ' rel="history">History</option>
|
||||
<option' . ($prefs['postFormLink'] == 'cp' ? ' selected' : '') . ' rel="cp">Control Panel</option>
|
||||
</select>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="prefs-cat-shouts">
|
||||
<div class="legend">Shouts</div class="legend">
|
||||
<ol>
|
||||
<li>
|
||||
<label for="pref-timestamp-format">Timestamp format</label>
|
||||
<select rel="timestamp" id="pref-timestamp-format">
|
||||
<option' . ($prefs['timestamp'] == 12 ? ' selected' : '') . ' rel="12">12-hour</option>
|
||||
<option' . ($prefs['timestamp'] == 24 ? ' selected' : '') . ' rel="24">24-hour</option>
|
||||
<option' . ($prefs['timestamp'] == 0 ? ' selected' : '') . ' rel="false">No timestamps</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-truncate">Messages to show</label>
|
||||
<input rel="truncate" type="text" id="pref-truncate" value="' . $prefs['truncate'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-do-truncate">Truncate messages</label>
|
||||
<select rel="doTruncate" id="pref-do-truncate">
|
||||
<option' . ($prefs['doTruncate'] == true ? ' selected' : '') . ' rel="true">Yes</option>
|
||||
<option' . ($prefs['doTruncate'] == false ? ' selected' : '') . ' rel="false">No</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-nickname-suffix">Nickname suffix</label>
|
||||
<input rel="nicknameSeparator" type="text" id="pref-nickname-suffix" value="' . $prefs['nicknameSeparator'] . '" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="pref-info-view">Info view</label>
|
||||
<select rel="info" id="pref-info-view">
|
||||
<option' . ($prefs['info'] == 'inline' ? ' selected' : '') . ' rel="inline">Inline</option>
|
||||
<option' . ($prefs['info'] == 'overlay' ? ' selected' : '') . ' rel="overlay">Overlay</option>
|
||||
</select>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
';
|
||||
}
|
||||
|
||||
function about() {
|
||||
global $prefs;
|
||||
|
||||
$html = '
|
||||
<div id="cp-pane-about" class="cp-pane">
|
||||
<h2>About YShout</h2>
|
||||
<p>YShout was created and developed by Yuri Vishnevsky. Version 5 is the first one with an about page, so you\'ll have to excuse the lack of appropriate information — I\'m not quite sure what it is that goes on "About" pages anyway.</p>
|
||||
<p>Other than that obviously important tidbit of information, there\'s really nothing else that I can think of putting here... If anyone knows what a good and proper about page should contain, please contact me!
|
||||
</div>
|
||||
|
||||
<div id="cp-pane-contact" class="cp-pane">
|
||||
<h2>Contact Yuri</h2>
|
||||
<p>If you have any questions or comments, you can contact me by email at <a href="mailto:yurivish@gmail.com">yurivish@gmail.com</a>, or on AIM at <a href="aim:goim?screnname=yurivish42">yurivish42</a>.</p>
|
||||
<p>I hope you\'ve enjoyed using YShout!</p>
|
||||
</div>
|
||||
';
|
||||
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
?>
|
386
ext/chatbox/cp/css/style.css
Normal file
@ -0,0 +1,386 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {height: 100%;}
|
||||
|
||||
body {
|
||||
background: #1a1a1a url(../images/bg.gif) center center no-repeat;
|
||||
color: #a7a7a7;
|
||||
font: 11px/1 Tahoma, Arial, sans-serif;
|
||||
text-shadow: 0 0 0 #273541;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
outline: none;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover{
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 11px;
|
||||
background: #e5e5e5;
|
||||
border: 1px solid #f5f5f5;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
select {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#cp {
|
||||
height: 440px;
|
||||
width: 620px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -220px;
|
||||
margin-left: -310px;
|
||||
}
|
||||
|
||||
#nav {
|
||||
height: 65px;
|
||||
width: 100%;
|
||||
background: url(../images/bg-nav.gif) repeat-x;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#nav ul {
|
||||
display: none;
|
||||
width: 240px;
|
||||
height: 65px;
|
||||
margin: 0 auto;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#nav li {
|
||||
width: 80px;
|
||||
float: left;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#nav a {
|
||||
display: block;
|
||||
height: 65px;
|
||||
text-indent: -4200px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#nav a:active {
|
||||
background-position: 0 -65px;
|
||||
}
|
||||
|
||||
#n-prefs a { background: 0 0 url("../images/n-prefs.gif") no-repeat; }
|
||||
#n-bans a { background: 0 0 url("../images/n-bans.gif") no-repeat; }
|
||||
#n-about a { background: 0 0 url("../images/n-about.gif") no-repeat; }
|
||||
|
||||
.subnav {
|
||||
height: 25px;
|
||||
background: url(../images/bg-subnav.gif) repeat-x;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.subnav input {
|
||||
float: left;
|
||||
margin-top: 2px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.subnav li {
|
||||
width: 85px;
|
||||
float: left;
|
||||
text-indent: -4200px;
|
||||
}
|
||||
|
||||
.subnav a {
|
||||
display: block;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.subnav a:hover {
|
||||
background-position: bottom left !important;
|
||||
}
|
||||
|
||||
#sn-administration a { background: url(../images/sn-administration.gif) no-repeat; }
|
||||
#sn-display a { background: url(../images/sn-display.gif) no-repeat; }
|
||||
#sn-form a { background: url(../images/sn-form.gif) no-repeat; }
|
||||
#sn-resetall a { background: url(../images/sn-resetall.gif) no-repeat; }
|
||||
#sn-ban a { background: url(../images/sn-ban.gif) no-repeat; }
|
||||
#sn-unbanall a { background: url(../images/sn-unbanall.gif) no-repeat; }
|
||||
#sn-deleteall a { background: url(../images/sn-deleteall.gif) no-repeat; }
|
||||
#sn-about a { background: url(../images/sn-about.gif) no-repeat; }
|
||||
#sn-contact a { background: url(../images/sn-contact.gif) no-repeat; }
|
||||
|
||||
|
||||
|
||||
.sn-loading {
|
||||
display: block;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
float: right;
|
||||
text-indent: -4200px;
|
||||
background: url(../images/sn-spinny.gif) no-repeat;
|
||||
_position: absolute;
|
||||
_right: 20px;
|
||||
_top: 50px;
|
||||
}
|
||||
|
||||
@media { .sn-loading {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 41px;
|
||||
}}
|
||||
|
||||
#content {
|
||||
position: relative;
|
||||
height: 375px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 33px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
#login .header { border-bottom: 1px solid #4c657b; }
|
||||
|
||||
h1 {
|
||||
float: left;
|
||||
height: 32px;
|
||||
width: 185px;
|
||||
text-indent: -4200px;
|
||||
}
|
||||
|
||||
#login h1 { background: url(../images/h-login.gif) no-repeat; }
|
||||
#preferences h1 { background: url(../images/h-preferences.gif) no-repeat; }
|
||||
#bans h1 { background: url(../images/h-bans.gif) no-repeat; }
|
||||
#about h1 { background: url(../images/h-about.gif) no-repeat; }
|
||||
|
||||
.logout {
|
||||
display: block;
|
||||
height: 32px;
|
||||
width: 45px;
|
||||
float: right;
|
||||
text-indent: -4200px;
|
||||
background: url(../images/a-logout.gif) no-repeat;
|
||||
}
|
||||
|
||||
.logout:hover {
|
||||
background-position: bottom left;
|
||||
}
|
||||
|
||||
.section {
|
||||
clear: both;
|
||||
width: 590px;
|
||||
height: 355px;
|
||||
padding: 15px;
|
||||
padding-top: 5px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#login {
|
||||
left: 0;
|
||||
background: url(../images/bg-login.gif) repeat-x;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#login-form {
|
||||
height: 45px;
|
||||
width: 300px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -45px;
|
||||
margin-left: -150px;
|
||||
background: url(../images/bg-login-form.gif) no-repeat;
|
||||
}
|
||||
|
||||
#login-form label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#login-form input {
|
||||
position: absolute;
|
||||
left: 127px;
|
||||
top: 13px;
|
||||
width: 153px;
|
||||
z-index: 2;
|
||||
border: 1px solid #d4e7fa;
|
||||
background: #e7eef6;
|
||||
}
|
||||
|
||||
#login-loading {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 8px;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
text-indent: -4200px;
|
||||
background: url(../images/login-spinny.gif) no-repeat;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#preferences {
|
||||
left: 0;
|
||||
background: url(../images/bg-prefs.gif) repeat-x;
|
||||
}
|
||||
|
||||
#preferences-form { }
|
||||
|
||||
#preferences-form fieldset {
|
||||
margin-top: 10px;
|
||||
width: 295px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#preferences-form fieldset.odd {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#preferences-form fieldset.even {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#preferences-form .legend {
|
||||
display: block;
|
||||
width: 265px;
|
||||
color: #fff;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid #80a147;
|
||||
}
|
||||
|
||||
/* IE7 */
|
||||
@media {#preferences-form legend {
|
||||
margin-left: -7px;
|
||||
}}
|
||||
|
||||
#preferences-form ol {
|
||||
list-style: none;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#preferences-form li {
|
||||
width: 295px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#preferences-form label {
|
||||
display: block;
|
||||
width: 130px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#preferences-form input {
|
||||
width: 129px;
|
||||
}
|
||||
|
||||
#preferences-form select {
|
||||
width: 135px;
|
||||
}
|
||||
|
||||
.cp-pane {
|
||||
position: absolute;
|
||||
width: 590px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#cp-pane-administration {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#bans {
|
||||
left: 0;
|
||||
background: url(../images/bg-bans.gif) repeat-x;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
#cp #bans-list a {
|
||||
color: #d9d9d9;
|
||||
border-bottom: 1px solid transparent;
|
||||
_border-bottom: none;
|
||||
}
|
||||
|
||||
#cp #bans-list a:hover {
|
||||
color: #fff;
|
||||
border-bottom: 1px solid #de4147;
|
||||
}
|
||||
|
||||
#bans-list {
|
||||
padding-top: 10px;
|
||||
list-style: none;
|
||||
height: 280px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#bans-list li {
|
||||
clear: both;
|
||||
padding: 3px 5px;
|
||||
|
||||
}
|
||||
|
||||
#bans-list .nickname {
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#bans-list .unban-link {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
|
||||
}
|
||||
|
||||
#no-bans {
|
||||
margin-top: 100px;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
#about {
|
||||
left: 0;
|
||||
background: url(../images/bg-about.gif) repeat-x;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#about h2 {
|
||||
color: #fff;
|
||||
font: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#about p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
#cp-pane-about {
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#cp-pane-contact {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#cp-pane-about a,
|
||||
#cp-pane-contact a {
|
||||
color: #d9d9d9;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
#cp-pane-about a:hover,
|
||||
#cp-pane-contact a:hover {
|
||||
color: #fff;
|
||||
border-bottom: 1px solid #f3982d;
|
||||
}
|
42
ext/chatbox/cp/index.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?
|
||||
include 'ajax.php';
|
||||
?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>YShout: Admin CP</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
||||
<script type="text/javascript" src="../js/jquery.js"></script>
|
||||
<script type="text/javascript" src="js/admincp.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cp">
|
||||
<div id="nav">
|
||||
<ul>
|
||||
<li id="n-prefs"><a href="#">Preferences</a></li>
|
||||
<li id="n-bans"><a href="#">Bans</a></li>
|
||||
<li id="n-about"><a href="#">About</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<div class="section" id="login">
|
||||
<div class="header">
|
||||
<h1>YShout.Preferences</h1>
|
||||
</div>
|
||||
|
||||
<form id="login-form" action="index.php" method="post">
|
||||
<label for="login-password">Password:</label>
|
||||
<input type="password" id="login-password" name="loginPassword">
|
||||
<span id="login-loading">Loading...</span>
|
||||
</form>
|
||||
</div>
|
||||
<?
|
||||
if (loggedIn()) echo cp();
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
373
ext/chatbox/cp/js/admincp.js
Normal file
@ -0,0 +1,373 @@
|
||||
Array.prototype.inArray = function (value) {
|
||||
for (var i = 0; i < this.length; i++)
|
||||
if (this[i] === value)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
var AdminCP = function() {
|
||||
var self = this;
|
||||
var args = arguments;
|
||||
$(function(){
|
||||
self.init.apply(self, args);
|
||||
});
|
||||
};
|
||||
|
||||
AdminCP.prototype = {
|
||||
z: 5,
|
||||
animSpeed: 'normal',
|
||||
curSection: 'login',
|
||||
curPrefPane: 'administration',
|
||||
curAboutPane: 'about',
|
||||
|
||||
init: function(options) {
|
||||
this.initializing = true;
|
||||
this.loginForm();
|
||||
this.initEvents();
|
||||
if (this.loaded()) this.afterLogin();
|
||||
else {
|
||||
$('#login-password')[0].focus();
|
||||
}
|
||||
|
||||
this.initializing = false;
|
||||
},
|
||||
|
||||
loginForm: function() {
|
||||
$('#login-loading').fadeTo(1, 0);
|
||||
},
|
||||
|
||||
initEvents: function() {
|
||||
var self = this;
|
||||
|
||||
$('#login-form').submit(function() { self.login(); return false; });
|
||||
$('#n-prefs').click(function() { self.show('preferences'); return false; });
|
||||
$('#n-bans').click(function() { self.show('bans'); return false; });
|
||||
$('#n-about').click(function() { self.show('about'); return false; });
|
||||
},
|
||||
|
||||
afterLogin: function() {
|
||||
var self = this;
|
||||
|
||||
// Login and logout
|
||||
$('#login-password')[0].blur();
|
||||
$('.logout').click(function() { self.logout(); return false; });
|
||||
|
||||
// Show the nav
|
||||
if (this.initializing)
|
||||
$('#nav ul').css('display', 'block');
|
||||
else
|
||||
$('#nav ul').slideDown();
|
||||
|
||||
// Some css for betterlookingness
|
||||
$('#preferences-form fieldset:odd').addClass('odd');
|
||||
$('#preferences-form fieldset:even').addClass('even');
|
||||
|
||||
$('#bans-list li:odd').addClass('odd');
|
||||
$('#bans-list li:even').addClass('even');
|
||||
|
||||
// Hide the loading thingie
|
||||
$('.sn-loading').fadeTo(1, 0);
|
||||
|
||||
// Events after load
|
||||
this.initEventsAfter();
|
||||
|
||||
// If they want to go directly to a section
|
||||
var anchor = this.getAnchor();
|
||||
|
||||
if (anchor.length > 0 && ['preferences', 'bans', 'about'].inArray(anchor))
|
||||
self.show(anchor);
|
||||
else
|
||||
self.show('preferences');
|
||||
},
|
||||
|
||||
initEventsAfter: function() {
|
||||
var self = this;
|
||||
|
||||
// Navigation
|
||||
$('#sn-administration').click(function() { self.showPrefPane('administration'); return false; });
|
||||
$('#sn-display').click(function() { self.showPrefPane('display'); return false; });
|
||||
$('#sn-about').click(function() { self.showAboutPane('about'); return false; });
|
||||
$('#sn-contact').click(function() { self.showAboutPane('contact'); return false; });
|
||||
$('#sn-resetall').click(function() { self.resetPrefs(); return false; });
|
||||
$('#sn-unbanall').click(function() { self.unbanAll(); return false; });
|
||||
|
||||
// Bans
|
||||
$('.unban-link').click(function() {
|
||||
self.unban($(this).parent().find('.ip').html(), $(this).parent());
|
||||
return false;
|
||||
});
|
||||
|
||||
// Preferences
|
||||
$('#preferences-form input').keypress(function(e) {
|
||||
var key = window.event ? e.keyCode : e.which;
|
||||
if (key == 13 || key == 3) {
|
||||
self.changePref.apply(self, [$(this).attr('rel'), this.value]);
|
||||
return false;
|
||||
}
|
||||
}).focus(function() {
|
||||
this.name = this.value;
|
||||
}).blur(function() {
|
||||
if (this.name != this.value)
|
||||
self.changePref.apply(self, [$(this).attr('rel'), this.value]);
|
||||
});
|
||||
|
||||
$('#preferences-form select').change(function() {
|
||||
self.changePref.apply(self, [$(this).attr('rel'), $(this).find('option:selected').attr('rel')]);
|
||||
});
|
||||
},
|
||||
|
||||
changePref: function(pref, value) {
|
||||
this.loading();
|
||||
var pars = {
|
||||
mode: 'setpreference',
|
||||
preference: pref,
|
||||
'value': value
|
||||
};
|
||||
this.ajax(function(json) {
|
||||
if (!json.error)
|
||||
this.done();
|
||||
else
|
||||
alert(json.error);
|
||||
}, pars);
|
||||
},
|
||||
|
||||
resetPrefs: function() {
|
||||
this.loading();
|
||||
|
||||
var pars = {
|
||||
mode: 'resetpreferences'
|
||||
}
|
||||
|
||||
this.ajax(function(json) {
|
||||
this.done();
|
||||
if (json.prefs)
|
||||
for(pref in json.prefs) {
|
||||
var value = json.prefs[pref];
|
||||
var el = $('#preferences-form input[@rel=' + pref + '], select[@rel=' + pref + ']')[0];
|
||||
|
||||
if (el.type == 'text')
|
||||
el.value = value;
|
||||
else {
|
||||
if (value == true) value = 'true';
|
||||
if (value == false) value = 'false';
|
||||
|
||||
$('#preferences-form select[@rel=' + pref + ']')
|
||||
.find('option')
|
||||
.removeAttr('selected')
|
||||
.end()
|
||||
.find('option[@rel=' + value + ']')
|
||||
.attr('selected', 'yeah');
|
||||
|
||||
}
|
||||
}
|
||||
}, pars);
|
||||
|
||||
},
|
||||
|
||||
invalidPassword: function() {
|
||||
// Shake the login form
|
||||
$('#login-form')
|
||||
.animate({ marginLeft: -145 }, 100)
|
||||
.animate({ marginLeft: -155 }, 100)
|
||||
.animate({ marginLeft: -145 }, 100)
|
||||
.animate({ marginLeft: -155 }, 100)
|
||||
.animate({ marginLeft: -150 }, 50);
|
||||
|
||||
$('#login-password').val('').focus();
|
||||
},
|
||||
|
||||
login: function() {
|
||||
if (this.loaded()) {
|
||||
alert('Something _really_ weird has happened. Refresh and pretend nothing ever happened.');
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var pars = {
|
||||
mode: 'login',
|
||||
password: $('#login-password').val()
|
||||
};
|
||||
|
||||
this.loginLoading();
|
||||
|
||||
this.ajax(function() {
|
||||
this.ajax(function(json) {
|
||||
self.loginDone();
|
||||
if (json.error) {
|
||||
self.invalidPassword();
|
||||
return;
|
||||
}
|
||||
|
||||
$('#content').append(json.html);
|
||||
self.afterLogin.apply(self);
|
||||
}, pars);
|
||||
}, pars);
|
||||
|
||||
},
|
||||
|
||||
logout: function() {
|
||||
var self = this;
|
||||
var pars = {
|
||||
mode: 'logout'
|
||||
};
|
||||
|
||||
this.loading();
|
||||
|
||||
this.ajax(function() {
|
||||
$('#login-password').val('');
|
||||
$('#nav ul').slideUp();
|
||||
self.show('login', function() {
|
||||
$('#login-password')[0].focus();
|
||||
$('.section').not('#login').remove();
|
||||
self.done();
|
||||
});
|
||||
}, pars);
|
||||
},
|
||||
|
||||
show: function(section, callback) {
|
||||
// var sections = ['login', 'preferences', 'bans', 'about'];
|
||||
// if (!sections.inArray(section)) section = 'preferences';
|
||||
|
||||
if ($.browser.msie) {
|
||||
if (section == 'preferences')
|
||||
$('#preferences select').css('display', 'block');
|
||||
else
|
||||
$('#preferences select').css('display', 'none');
|
||||
}
|
||||
|
||||
if (section == this.curSection) return;
|
||||
this.curSection = section;
|
||||
|
||||
$('#' + section)[0].style.zIndex = ++this.z;
|
||||
if (this.initializing)
|
||||
$('#' + section).css('display', 'block');
|
||||
else
|
||||
$('#' + section).fadeIn(this.animSpeed, callback);
|
||||
},
|
||||
|
||||
showPrefPane: function(pane) {
|
||||
var self = this;
|
||||
|
||||
if (pane == this.curPrefPane) return;
|
||||
this.curPrefPane = pane;
|
||||
$('#preferences .cp-pane').css('display', 'none');
|
||||
$('#cp-pane-' + pane).css('display', 'block').fadeIn(this.animSpeed, function() {
|
||||
if (self.curPrefPane == pane)
|
||||
$('#preferences .cp-pane').not('#cp-pane-' + pane).css('display', 'none');
|
||||
else
|
||||
$('#cp-pane-' + pane).css('display', 'none');
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
showAboutPane: function(pane) {
|
||||
var self = this;
|
||||
|
||||
if (pane == this.curAboutPane) return;
|
||||
this.curAboutPane = pane;
|
||||
$('#about .cp-pane').css('display', 'none');
|
||||
$('#cp-pane-' + pane).css('display', 'block').fadeIn(this.animSpeed, function() {
|
||||
if (self.curAboutPane == pane)
|
||||
$('#about .cp-pane').not('#cp-pane-' + pane).css('display', 'none');
|
||||
else
|
||||
$('#cp-pane-' + pane).css('display', 'none');
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
ajax: function(callback, pars, html) {
|
||||
var self = this;
|
||||
|
||||
$.post('ajax.php', pars, function(parse) {
|
||||
// alert(parse);
|
||||
if (parse)
|
||||
if (html)
|
||||
callback.apply(self, [parse]);
|
||||
else
|
||||
callback.apply(self, [self.json(parse)]);
|
||||
else
|
||||
callback.apply(self);
|
||||
});
|
||||
},
|
||||
|
||||
json: function(parse) {
|
||||
var json = eval('(' + parse + ')');
|
||||
return json;
|
||||
},
|
||||
|
||||
loaded: function() {
|
||||
return ($('#cp-loaded').length == 1);
|
||||
},
|
||||
|
||||
loading: function() {
|
||||
$('#' + this.curSection + ' .sn-loading').fadeTo(this.animSpeed, 1);
|
||||
},
|
||||
|
||||
done: function() {
|
||||
$('#' + this.curSection + ' .sn-loading').fadeTo(this.animSpeed, 0);
|
||||
},
|
||||
|
||||
loginLoading: function() {
|
||||
$('#login-password').animate({
|
||||
width: 134
|
||||
});
|
||||
|
||||
$('#login-loading').fadeTo(this.animSpeed, 1);
|
||||
|
||||
},
|
||||
|
||||
loginDone: function() {
|
||||
$('#login-password').animate({
|
||||
width: 157
|
||||
});
|
||||
$('#login-loading').fadeTo(this.animSpeed, 0);
|
||||
},
|
||||
|
||||
getAnchor: function() {
|
||||
var href = window.location.href;
|
||||
if (href.indexOf('#') > -1 )
|
||||
return href.substr(href.indexOf('#') + 1).toLowerCase();
|
||||
return '';
|
||||
},
|
||||
|
||||
unban: function(ip, el) {
|
||||
var self = this;
|
||||
|
||||
this.loading();
|
||||
var pars = {
|
||||
mode: 'unban',
|
||||
'ip': ip
|
||||
};
|
||||
|
||||
this.ajax(function(json) {
|
||||
if (!json.error) {
|
||||
$(el).fadeOut(function() {
|
||||
$(this).remove();
|
||||
$('#bans-list li:odd').removeClass('even').addClass('odd');
|
||||
$('#bans-list li:even').removeClass('odd').addClass('even');
|
||||
}, this.animSpeed);
|
||||
}
|
||||
self.done();
|
||||
}, pars);
|
||||
},
|
||||
|
||||
unbanAll: function() {
|
||||
this.loading();
|
||||
|
||||
var pars = {
|
||||
mode: 'unbanall'
|
||||
}
|
||||
|
||||
this.ajax(function(json) {
|
||||
this.done();
|
||||
$('#bans-list').fadeOut(this.animSpeed, function() {
|
||||
$('#bans-list').children().remove();
|
||||
$('#bans-list').fadeIn();
|
||||
});
|
||||
}, pars);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var cp = new AdminCP();
|
389
ext/chatbox/css/dark.yshout.css
Normal file
@ -0,0 +1,389 @@
|
||||
/*
|
||||
|
||||
YShout HTML Structure:
|
||||
|
||||
<div id="yshout">
|
||||
<div id="ys-before-posts"></div>
|
||||
<div id="ys-posts>
|
||||
|
||||
<div id="ys-post-1" class="ys-post ys-first [ys-admin-post] [ys-banned-post]">
|
||||
<span class="ys-post-timestamp">12:56</span>
|
||||
<span class="ys-post-nickname">Yurivish:<span>
|
||||
<span class="ys-post-message">Hey!</span>
|
||||
<span class="ys-post-info ys-info-[inline|overlay]">
|
||||
<em>IP:</em> 127.0.0.1
|
||||
<em>Posted:</em> Sunday Apr. 29, 2007 at 12:56.
|
||||
</span>
|
||||
<span class="ys-post-actions">
|
||||
<a title="Show post information" class="ys-info-link" href="#">Info</a> |
|
||||
<a title="Delete post" class="ys-delete-link" href="#">Delete</a> |
|
||||
<a title="Ban Yurivish" class="ys-ban-link" href="#">Ban</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="ys-post-2" class="ys-post [ys-admin-post] [ys-banned-post]">
|
||||
<span class="ys-post-timestamp">12:57</span>
|
||||
<span class="ys-post-nickname>Travis:<span>
|
||||
<span class="ys-post-message>Hello.</span>
|
||||
<span class="ys-post-info ys-info-[inline|overlay]">
|
||||
<em>IP:</em> 127.0.0.2
|
||||
<em>Posted:</em> Sunday Apr. 29, 2007 at 12:57.
|
||||
</span>
|
||||
<span class="ys-post-actions">
|
||||
<a title="Show post information" class="ys-info-link" href="#">Info</a> |
|
||||
<a title="Delete post" class="ys-delete-link" href="#">Delete</a> |
|
||||
<a title="Ban Travis" class="ys-ban-link" href="#">Ban</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="ys-post-3" class="ys-post ys-last [ys-admin-post] [ys-banned-post]">
|
||||
<span class="ys-post-timestamp">12:57</span>
|
||||
<span class="ys-post-nickname>Yurivish:<span>
|
||||
<span class="ys-post-message>Yup...</span>
|
||||
<span class="ys-post-info ys-info-[inline|overlay]">
|
||||
<em>IP:</em> 127.0.0.1
|
||||
<em>Posted:</em> Sunday Apr. 29, 2007 at 12:57.
|
||||
</span>
|
||||
<span class="ys-post-actions">
|
||||
<a title="Show post information" class="ys-info-link" href="#">Info</a> |
|
||||
<a title="Delete post" class="ys-delete-link" href="#">Delete</a> |
|
||||
<a title="Ban Yurivish" class="ys-ban-link" href="#">Ban</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ys-after-posts"></div>
|
||||
|
||||
<div id="ys-before-post-form"></div>
|
||||
<div id="ys-post-form">
|
||||
<form id="ys-post-form>
|
||||
<fieldset>
|
||||
<input id="ys-input-nickname" value="Nickname" type="text" accesskey="N" maxlength="25" class="[ys-before-focus|ys-after-focus]" />
|
||||
<input id="ys-input-message" value="Message Text" type="text" accesskey="M" maxlength="175" class="[ys-before-focus|ys-after-focus]" />
|
||||
<input id="ys-input-submit" value="Shout!" accesskey="S" type="submit" />
|
||||
<a title="[View YShout History|Open Admin CP]" class="ys-post-form-link" id="[ys-history-link|ys-cp-link]" href="[history/|cp/]">[View History|Admin CP]</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<div id="ys-after-post-form"></div>
|
||||
</div>
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#yshout * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#yshout a {
|
||||
text-decoration: none;
|
||||
color: #989898;
|
||||
}
|
||||
|
||||
#yshout a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#yshout a:active {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
/* Adjust the width here
|
||||
-------------------------- */
|
||||
|
||||
#yshout {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font: 11px/1.4 Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* Posts
|
||||
------------------------------------- */
|
||||
|
||||
#yshout #ys-posts {
|
||||
position: relative;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
#yshout .ys-post {
|
||||
border-bottom: 1px solid #212121;
|
||||
margin: 0 5px;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
#yshout .ys-admin-post .ys-post-nickname {
|
||||
padding-left: 11px;
|
||||
background: url(../images/star-dark.gif) 0 2px no-repeat;
|
||||
}
|
||||
|
||||
|
||||
#yshout .ys-post-timestamp {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#yshout .ys-post-nickname {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
#yshout .ys-post-message {
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
|
||||
/* Banned
|
||||
------------------------------------- */
|
||||
|
||||
#yshout .ys-banned-post .ys-post-nickname,
|
||||
#yshout .ys-banned-post .ys-post-message,
|
||||
#yshout .ys-banned-post {
|
||||
color: #b3b3b3 !important;
|
||||
}
|
||||
|
||||
#yshout #ys-banned {
|
||||
position: absolute;
|
||||
z-index: 75;
|
||||
height: 100%;
|
||||
_height: 430px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 5px;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
#yshout #ys-banned span {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 20px;
|
||||
margin-top: -10px;
|
||||
top: 50%;
|
||||
padding: 0 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
#yshout #ys-banned a {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
#yshout #ys-banned a:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Hover Controls
|
||||
------------------------------------- */
|
||||
|
||||
#yshout .ys-post-actions {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 5px;
|
||||
font-size: 11px;
|
||||
z-index: 50;
|
||||
background: #1a1a1a;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#yshout .ys-post-actions a {
|
||||
color: #989898;
|
||||
}
|
||||
|
||||
#yshout .ys-post-actions a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#yshout .ys-post:hover .ys-post-actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#yshout .ys-post-info {
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
#yshout .ys-post-info em {
|
||||
font-style: normal;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
#yshout .ys-info-overlay {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 45;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #1a1a1a;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#yshout .ys-info-inline {
|
||||
display: none;
|
||||
margin-top: 2px;
|
||||
padding-top: 3px;
|
||||
border-top: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
/* Post Form
|
||||
------------------------------------- */
|
||||
|
||||
#yshout #ys-post-form {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background: #262626;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#yshout #ys-input-nickname,
|
||||
#yshout #ys-input-message {
|
||||
font-size: 11px;
|
||||
padding: 2px;
|
||||
background: #333;
|
||||
border: 1px solid #404040;
|
||||
}
|
||||
|
||||
#yshout #ys-post-form fieldset {
|
||||
_position: absolute;
|
||||
border: none;
|
||||
padding: 0 10px;
|
||||
_margin-top: 10px;
|
||||
}
|
||||
|
||||
#yshout #ys-input-nickname {
|
||||
width: 105px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#yshout #ys-input-message {
|
||||
margin-left: 5px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#yshout #ys-input-submit {
|
||||
font-size: 11px;
|
||||
width: 64px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#yshout #ys-input-submit:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#yshout .ys-before-focus {
|
||||
color: #4d4d4d;
|
||||
}
|
||||
|
||||
#yshout .ys-after-focus {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
#yshout .ys-input-invalid {
|
||||
|
||||
}
|
||||
|
||||
#yshout .ys-post-form-link {
|
||||
margin-left: 5px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Overlays - This should go in all YShout styles
|
||||
------------------------------------- */
|
||||
|
||||
#ys-overlay {
|
||||
position: fixed;
|
||||
_position: absolute;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #000;
|
||||
filter: alpha(opacity=60);
|
||||
-moz-opacity: 0.6;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
* html body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#ys-closeoverlay-link,
|
||||
#ys-switchoverlay-link {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
height: 13px;
|
||||
font: 11px/1 Arial, Helvetica, sans-serif;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
margin-bottom: 1px;
|
||||
outline: none;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#ys-switchoverlay-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.ys-window {
|
||||
z-index: 102;
|
||||
position: fixed;
|
||||
_position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
#ys-cp {
|
||||
margin-top: -220px;
|
||||
margin-left: -310px;
|
||||
width: 620px;
|
||||
}
|
||||
|
||||
#ys-yshout {
|
||||
margin-top: -250px;
|
||||
margin-left: -255px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
#ys-history {
|
||||
margin-top: -220px;
|
||||
margin-left: -270px;
|
||||
width: 540px;
|
||||
}
|
||||
|
||||
#yshout .ys-browser {
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
z-index: 102;
|
||||
overflow: auto;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
#yshout-browser {
|
||||
height: 580px;
|
||||
width: 510px;
|
||||
}
|
||||
|
||||
#cp-browser {
|
||||
height: 440px;
|
||||
width: 620px;
|
||||
_height: 450px;
|
||||
_width: 440px;
|
||||
}
|
||||
|
||||
#history-browser {
|
||||
height: 440px;
|
||||
width: 540px;
|
||||
border-top: 1px solid #545454;
|
||||
border-left: 1px solid #545454;
|
||||
border-bottom: 1px solid #444;
|
||||
border-right: 1px solid #444;
|
||||
}
|
93
ext/chatbox/css/overlay.css
Normal file
@ -0,0 +1,93 @@
|
||||
/* Overlays - Use this stylesheet if you want to only use yLink.
|
||||
------------------------------------- */
|
||||
|
||||
#ys-overlay {
|
||||
position: fixed;
|
||||
_position: absolute;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #000;
|
||||
filter: alpha(opacity=60);
|
||||
-moz-opacity: 0.6;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
* html body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#ys-closeoverlay-link,
|
||||
#ys-switchoverlay-link {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
height: 13px;
|
||||
font: 11px/1 Arial, Helvetica, sans-serif;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
margin-bottom: 1px;
|
||||
outline: none;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#ys-switchoverlay-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.ys-window {
|
||||
z-index: 102;
|
||||
position: fixed;
|
||||
_position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
#ys-cp {
|
||||
margin-top: -220px;
|
||||
margin-left: -310px;
|
||||
width: 620px;
|
||||
}
|
||||
|
||||
#ys-yshout {
|
||||
margin-top: -250px;
|
||||
margin-left: -255px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
#ys-history {
|
||||
margin-top: -220px;
|
||||
margin-left: -270px;
|
||||
width: 540px;
|
||||
}
|
||||
|
||||
#yshout .ys-browser {
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
z-index: 102;
|
||||
overflow: auto;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
#yshout-browser {
|
||||
height: 580px;
|
||||
width: 510px;
|
||||
}
|
||||
|
||||
#cp-browser {
|
||||
height: 440px;
|
||||
width: 620px;
|
||||
_height: 450px;
|
||||
_width: 440px;
|
||||
}
|
||||
|
||||
#history-browser {
|
||||
height: 440px;
|
||||
width: 540px;
|
||||
border-top: 1px solid #545454;
|
||||
border-left: 1px solid #545454;
|
||||
border-bottom: 1px solid #444;
|
||||
border-right: 1px solid #444;
|
||||
}
|
113
ext/chatbox/css/style.css
Normal file
@ -0,0 +1,113 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #182635 url(../images/bg.gif) fixed repeat-x;
|
||||
font: 11px/1.6 Arial, Helvetica, sans-serif;
|
||||
color: #92b5ce;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #d5edff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #fff !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 3px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
background: url(../images/bg-code.png);
|
||||
_background: none;
|
||||
color: #b4d4eb;
|
||||
}
|
||||
|
||||
code {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
pre code {
|
||||
padding: 0;
|
||||
color: #b4d4eb;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
em {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#container {
|
||||
width: 510px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#top {
|
||||
width: 510px;
|
||||
margin-top: 25px;
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #567083;
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-indent: -4200px;
|
||||
height: 13px;
|
||||
width: 120px;
|
||||
background: url(../images/h-welcome.gif) no-repeat;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#nav {
|
||||
color: #93b3ca;
|
||||
float: right;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#footer {
|
||||
width: 510px;
|
||||
margin: 20px auto 10px auto;
|
||||
padding-top: 5px;
|
||||
border-top: 1px solid #273e56;
|
||||
color: #384858;
|
||||
}
|
||||
|
||||
#footer:hover {
|
||||
color: #92b5ce;
|
||||
}
|
||||
|
||||
#footer:hover a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: #425d7a;
|
||||
}
|
85
ext/chatbox/history/css/style.css
Normal file
@ -0,0 +1,85 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #202020 url(../images/bg.gif) fixed repeat-x;
|
||||
color: #5c5c5c;
|
||||
font: 11px/1.6 Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
#top {
|
||||
height: 25px;
|
||||
width: 510px;
|
||||
margin: 0 auto;
|
||||
margin-top: 20px;
|
||||
border-bottom: 1px solid #444;
|
||||
overflow: none;
|
||||
line-height: 1.0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-indent: -4200px;
|
||||
background: url(../images/h-history.gif) no-repeat;
|
||||
width: 105px;
|
||||
height: 17px;
|
||||
margin-top: 5px;
|
||||
float: left;
|
||||
overflow: none;
|
||||
_position: absolute;
|
||||
}
|
||||
|
||||
#top a, #bottom a {
|
||||
color: #7d7d7d;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#top a {
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
#top a:hover, #bottom a:hover {
|
||||
color: #fff;
|
||||
border-bottom-color: #5e5e5e;
|
||||
}
|
||||
|
||||
|
||||
#log {
|
||||
font-size: 11px;
|
||||
margin-left: 10px;
|
||||
border: 1px solid #767676;
|
||||
border-right: none;
|
||||
width: 60px;
|
||||
|
||||
}
|
||||
|
||||
#controls {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
#yshout {
|
||||
margin: 0 auto;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#bottom {
|
||||
width:510px;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
#bottom #to-top {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Inane IE Compatibility PNG fixes
|
||||
------------------------------------- */
|
||||
|
||||
#yshout #ys-before-posts { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/ys-bg-posts-top.png',sizingMethod='crop'); }
|
||||
#yshout #ys-posts { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/bg-posts.png',sizingMethod='scale'); }
|
||||
#yshout #ys-after-posts { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/ys-bg-posts-bottom.png',sizingMethod='crop'); }
|
||||
#yshout #ys-banned { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/bg-banned.png',sizingMethod='scale'); }
|
||||
#yshout #ys-post-form { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/bg-form.png',sizingMethod='crop'); }
|
||||
#yshout .ys-post { _height: 1%; }
|
||||
|
133
ext/chatbox/history/index.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?
|
||||
error_reporting(E_ALL);
|
||||
|
||||
include '../php/filestorage.class.php';
|
||||
include '../preferences.php';
|
||||
include '../php/json.class.php';
|
||||
include '../php/functions.php';
|
||||
include '../php/yshout.class.php';
|
||||
|
||||
$html = '<div id="history-posts">';
|
||||
|
||||
$admin = loggedIn();
|
||||
|
||||
if (isset($_GET['log']))
|
||||
$log = $_GET['log'];
|
||||
|
||||
if (isset($_POST['log']))
|
||||
$log = $_POST['log'];
|
||||
|
||||
if (!isset($log))
|
||||
$log = 1;
|
||||
|
||||
$ys = ys($log);
|
||||
$posts = $ys->posts();
|
||||
|
||||
if (sizeof($posts) == 0)
|
||||
$html .= '
|
||||
<div id="ys-post-1" class="ys-post ys-first ys-admin-post">
|
||||
<span class="ys-post-timestamp">13:37</span>
|
||||
<span class="ys-post-nickname">Yurivish:<span>
|
||||
<span class="ys-post-message">Hey, there aren\'t any posts in this log.</span>
|
||||
</div>
|
||||
';
|
||||
|
||||
$id = 0;
|
||||
|
||||
foreach($posts as $post) {
|
||||
$id++;
|
||||
|
||||
$banned = $ys->banned($post['adminInfo']['ip']);
|
||||
$html .= '<div ' . ($admin ? 'rel="' . $post['adminInfo']['ip'] . '" ' : '') . 'id="ys-post-' . $id . '" class="ys-post' . ($post['admin'] ? ' ys-admin-post' : '') . ($banned ? ' ys-banned-post' : '') . '">' . "\n";
|
||||
switch($prefs['timestamp']) {
|
||||
case 12:
|
||||
$ts = date('h:i', $post['timestamp']);
|
||||
break;
|
||||
case 24:
|
||||
$ts = date('H:i', $post['timestamp']);
|
||||
break;
|
||||
case 0:
|
||||
$ts = '';
|
||||
break;
|
||||
}
|
||||
|
||||
$html .= ' <span class="ys-post-timestamp">' . $ts . '</span> ' . "\n";
|
||||
$html .= ' <span class="ys-post-nickname">' . $post['nickname'] . '</span>' . $prefs['nicknameSeparator'] . ' ' . "\n";
|
||||
$html .= ' <span class="ys-post-message">' . $post['message'] . '</span>' . "\n";
|
||||
$html .= ' <span class="ys-post-info' . ($prefs['info'] == 'overlay' ? ' ys-info-overlay' : ' ys-info-inline') . '">' . ($admin ? '<em>IP:</em> ' . $post['adminInfo']['ip'] . ', ' : '') . '<em>Posted:</em> ' . date('l M. j, Y \a\t ' . ($prefs['timestamp'] > 12 ? 'G:i' : 'g:i')) .'.</span>' . "\n";
|
||||
|
||||
$html .= ' <span class="ys-post-actions">' . "\n";
|
||||
$html .= ' <a title="Show post information" class="ys-info-link" href="#">Info</a>' . ($admin ? ' | <a title="Delete post" class="ys-delete-link" href="#">Delete</a> | ' . ($banned ? '<a title="Unban ' . $post['nickname'] . '" class="ys-ban-link" href="#">Unban</a>' : '<a title="Ban ' . $post['nickname'] . '" class="ys-ban-link" href="#">Ban</a>') : '') . "\n";
|
||||
$html .= ' </span>' . "\n";
|
||||
|
||||
if ($admin) {
|
||||
$html .= '<div class="ys-history" style="display: none;">';
|
||||
$html .= ' <span class="ys-h-ip">' . $post['adminInfo']['ip'] . '</span>';
|
||||
$html .= ' <span class="ys-h-nickname">' . $post['nickname'] . '</span>';
|
||||
$html .= ' <span class="ys-h-uid">' . $post['uid'] . '</span>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
$html .= '</div>' . "\n";
|
||||
}
|
||||
|
||||
$html .= '</div>' . "\n";
|
||||
|
||||
|
||||
if (isset($_POST['p'])) {
|
||||
echo $html;
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>YShout: History</title>
|
||||
<script type="text/javascript" src="../js/jquery.js"></script>
|
||||
<script type="text/javascript" src="js/history.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="../example/css/example.yshout.css" />
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
|
||||
<script type="text/javascript">
|
||||
new History({
|
||||
prefsInfo: '<?= $prefs['info'] ?>',
|
||||
log: <?= $log ?>
|
||||
});
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top">
|
||||
<h1>YShout.History</h1>
|
||||
<div id="controls">
|
||||
<? if($admin) : ?>
|
||||
<a id="clear-log" href="#">Clear this log</a>, or
|
||||
<a id="clear-logs" href="#">Clear all logs</a>.
|
||||
<? endif; ?>
|
||||
|
||||
<select id="log">
|
||||
<?
|
||||
for ($i = 1; $i <= $prefs['logs']; $i++)
|
||||
echo '<option' . ($log == $i ? ' selected' : '') . ' rel="' . $i . '">Log ' . $i . '</option>' . "\n";
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="yshout">
|
||||
<div id="ys-before-posts"></div>
|
||||
<div id="ys-posts">
|
||||
<?= $html ?>
|
||||
</div>
|
||||
<div id="ys-after-posts"></div>
|
||||
</div>
|
||||
|
||||
<div id="bottom">
|
||||
<a id="to-top" href="#top">Back to top</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
281
ext/chatbox/history/js/history.js
Normal file
@ -0,0 +1,281 @@
|
||||
var History = function() {
|
||||
var self = this;
|
||||
var args = arguments;
|
||||
$(function(){
|
||||
self.init.apply(self, args);
|
||||
});
|
||||
};
|
||||
|
||||
History.prototype = {
|
||||
animSpeed: 'normal',
|
||||
noPosts: '<div id="ys-post-1" class="ys-post ys-first ys-admin-post">\n<span class="ys-post-timestamp">13:37</span>\n<span class="ys-post-nickname">Yurivish:<span>\n<span class="ys-post-message">Hey, there aren\'t any posts in this log.</span>\n</div>',
|
||||
|
||||
init: function(options) {
|
||||
this.prefsInfo = options.prefsInfo;
|
||||
this.log = options.log;
|
||||
this.initEvents();
|
||||
$('body').ScrollToAnchors({ duration: 800 });
|
||||
},
|
||||
|
||||
|
||||
initEvents: function() {
|
||||
var self = this;
|
||||
|
||||
this.initLogEvents();
|
||||
|
||||
|
||||
// Select log
|
||||
$('#log').change(function() {
|
||||
var logIndex = $(this).find('option[@selected]').attr('rel');
|
||||
|
||||
var pars = {
|
||||
p: 'yes',
|
||||
log: logIndex
|
||||
}
|
||||
|
||||
self.ajax(function(html) {
|
||||
$('#ys-posts').html(html)
|
||||
$('#yshout').fadeIn();
|
||||
self.initLogEvents();
|
||||
}, pars, true, 'index.php');
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Clear the log
|
||||
$('#clear-log').click(function() {
|
||||
var el = this;
|
||||
var pars = {
|
||||
reqType: 'clearlog'
|
||||
};
|
||||
|
||||
self.ajax(function(json) {
|
||||
if (json.error) {
|
||||
switch(json.error) {
|
||||
case 'admin':
|
||||
self.error('You\'re not an admin. Log in through the admin CP to clear the log.');
|
||||
el.innerHTML = 'Clear this log';
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$('#ys-posts').html(self.noPosts);
|
||||
self.initLogEvents();
|
||||
el.innerHTML = 'Clear this log'
|
||||
}, pars);
|
||||
|
||||
this.innerHTML = 'Clearing...';
|
||||
return false;
|
||||
});
|
||||
|
||||
// Clear all logs
|
||||
$('#clear-logs').click(function() {
|
||||
var el = this;
|
||||
var pars = {
|
||||
reqType: 'clearlogs'
|
||||
};
|
||||
|
||||
self.ajax(function(json) {
|
||||
if (json.error) {
|
||||
switch(json.error) {
|
||||
case 'admin':
|
||||
el.innerHTML = 'Clear all logs'
|
||||
self.error('You\'re not an admin. Log in through the admin CP to clear logs.');
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$('#ys-posts').html(self.noPosts);
|
||||
self.initLogEvents();
|
||||
el.innerHTML = 'Clear all logs'
|
||||
}, pars);
|
||||
|
||||
this.innerHTML = 'Clearing...';
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
initLogEvents: function() {
|
||||
var self = this;
|
||||
|
||||
$('#yshout .ys-post')
|
||||
.find('.ys-info-link').toggle(
|
||||
function() { self.showInfo.apply(self, [$(this).parent().parent()[0].id, this]); return false; },
|
||||
function() { self.hideInfo.apply(self, [$(this).parent().parent()[0].id, this]); return false; })
|
||||
.end()
|
||||
.find('.ys-ban-link').click(
|
||||
function() { self.ban.apply(self, [$(this).parent().parent()[0]]); return false; })
|
||||
.end()
|
||||
.find('.ys-delete-link').click(
|
||||
function() { self.del.apply(self, [$(this).parent().parent()[0]]); return false; });
|
||||
},
|
||||
|
||||
showInfo: function(id, el) {
|
||||
var jEl = $('#' + id + ' .ys-post-info');
|
||||
|
||||
if (jEl.length == 0) return false;
|
||||
|
||||
if (this.prefsInfo == 'overlay')
|
||||
jEl.css('display', 'block').fadeIn(this.animSpeed);
|
||||
else
|
||||
jEl.slideDown(this.animSpeed);
|
||||
|
||||
el.innerHTML ='Close Info'
|
||||
return false;
|
||||
},
|
||||
|
||||
hideInfo: function(id, el) {
|
||||
var jEl = $('#' + id + ' .ys-post-info');
|
||||
|
||||
if (jEl.length == 0) return false;
|
||||
|
||||
if (this.prefsInfo == 'overlay')
|
||||
jEl.fadeOut(this.animSpeed);
|
||||
else
|
||||
jEl.slideUp(this.animSpeed);
|
||||
|
||||
el.innerHTML = 'Info';
|
||||
return false;
|
||||
},
|
||||
|
||||
ban: function(post) {
|
||||
var self = this;
|
||||
var link = $('#' + post.id).find('.ys-ban-link')[0];
|
||||
|
||||
switch(link.innerHTML) {
|
||||
case 'Ban':
|
||||
var pIP = $(post).find('.ys-h-ip').html();
|
||||
var pNickname = $(post).find('.ys-h-nickname').html();
|
||||
|
||||
var pars = {
|
||||
log: self.log,
|
||||
reqType: 'ban',
|
||||
ip: pIP,
|
||||
nickname: pNickname
|
||||
};
|
||||
|
||||
this.ajax(function(json) {
|
||||
if (json.error) {
|
||||
switch (json.error) {
|
||||
case 'admin':
|
||||
self.error('You\'re not an admin. Log in through the admin CP to ban people.');
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$('#yshout .ys-post[@rel="' + pars.ip + '"]')
|
||||
.addClass('ys-banned-post')
|
||||
.find('.ys-ban-link')
|
||||
.html('Unban');
|
||||
|
||||
}, pars);
|
||||
|
||||
link.innerHTML = 'Banning...';
|
||||
return false;
|
||||
break;
|
||||
|
||||
case 'Banning...':
|
||||
return false;
|
||||
break;
|
||||
|
||||
case 'Unban':
|
||||
var pIP = $(post).find('.ys-h-ip').html();
|
||||
var pars = {
|
||||
reqType: 'unban',
|
||||
ip: pIP
|
||||
};
|
||||
|
||||
this.ajax(function(json) {
|
||||
if (json.error) {
|
||||
switch(json.error) {
|
||||
case 'admin':
|
||||
self.error('You\'re not an admin. Log in through the admin CP to unban people.');
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$('#yshout .ys-post[@rel="' + pars.ip + '"]')
|
||||
.removeClass('ys-banned-post')
|
||||
.find('.ys-ban-link')
|
||||
.html('Ban');
|
||||
|
||||
}, pars);
|
||||
|
||||
link.innerHTML = 'Unbanning...';
|
||||
return false;
|
||||
break;
|
||||
|
||||
case 'Unbanning...':
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
del: function(post) {
|
||||
var self = this;
|
||||
|
||||
var link = $('#' + post.id).find('.ys-delete-link')[0];
|
||||
if (link.innerHTML == 'Deleting...') return;
|
||||
|
||||
var pUID = $(post).find('.ys-h-uid').html();
|
||||
|
||||
var pars = {
|
||||
reqType: 'delete',
|
||||
uid: pUID
|
||||
};
|
||||
|
||||
self.ajax(function(json) {
|
||||
if (json.error) {
|
||||
switch(json.error) {
|
||||
case 'admin':
|
||||
self.error('You\'re not an admin. Log in through the admin CP to ban people.');
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$(post).slideUp(self.animSpeed);
|
||||
|
||||
}, pars);
|
||||
|
||||
link.innerHTML = 'Deleting...';
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
json: function(parse) {
|
||||
var json = eval('(' + parse + ')');
|
||||
return json;
|
||||
},
|
||||
|
||||
ajax: function(callback, pars, html, page) {
|
||||
pars = jQuery.extend({
|
||||
reqFor: 'history',
|
||||
log: this.log
|
||||
}, pars);
|
||||
|
||||
var self = this;
|
||||
|
||||
if (page == null) page = '../yshout.php';
|
||||
|
||||
$.post(page, pars, function(parse) {
|
||||
if (parse)
|
||||
if (html)
|
||||
callback.apply(self, [parse]);
|
||||
else
|
||||
callback.apply(self, [self.json(parse)]);
|
||||
else
|
||||
callback.apply(self);
|
||||
});
|
||||
},
|
||||
|
||||
error: function(err) {
|
||||
alert(err);
|
||||
}
|
||||
|
||||
};
|
||||
|
10
ext/chatbox/include.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?
|
||||
$null = null;
|
||||
include 'php/filestorage.class.php';
|
||||
include 'preferences.php';
|
||||
include 'php/json.class.php';
|
||||
include 'php/functions.php';
|
||||
include 'php/yshout.class.php';
|
||||
include 'php/ajaxcall.class.php';
|
||||
|
||||
?>
|
154
ext/chatbox/js/jquery.js
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
/*!
|
||||
* jQuery JavaScript Library v1.4.2
|
||||
* http://jquery.com/
|
||||
*
|
||||
* Copyright 2010, John Resig
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Includes Sizzle.js
|
||||
* http://sizzlejs.com/
|
||||
* Copyright 2010, The Dojo Foundation
|
||||
* Released under the MIT, BSD, and GPL Licenses.
|
||||
*
|
||||
* Date: Sat Feb 13 22:33:48 2010 -0500
|
||||
*/
|
||||
(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
|
||||
e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
|
||||
j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
|
||||
"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
|
||||
true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
|
||||
Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
|
||||
(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
|
||||
a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
|
||||
"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
|
||||
function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
|
||||
c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
|
||||
L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
|
||||
"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
|
||||
a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
|
||||
d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
|
||||
a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
|
||||
!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
|
||||
true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
|
||||
var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
|
||||
parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
|
||||
false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
|
||||
s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
|
||||
applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
|
||||
else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
|
||||
a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
|
||||
w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
|
||||
cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
|
||||
i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
|
||||
" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
|
||||
this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
|
||||
e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
|
||||
c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
|
||||
a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
|
||||
function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
|
||||
k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
|
||||
C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
|
||||
null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
|
||||
e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
|
||||
f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
|
||||
if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
|
||||
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
|
||||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
|
||||
"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
|
||||
a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
|
||||
isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
|
||||
{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
|
||||
if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
|
||||
e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
|
||||
"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
|
||||
d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
|
||||
!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
|
||||
toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
|
||||
u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
|
||||
function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
|
||||
if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
|
||||
e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
|
||||
t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
|
||||
g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
|
||||
for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
|
||||
1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
|
||||
CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
|
||||
relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
|
||||
l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
|
||||
h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
|
||||
CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
|
||||
g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
|
||||
text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
|
||||
setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
|
||||
h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
|
||||
m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
|
||||
"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
|
||||
h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
|
||||
!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
|
||||
h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
|
||||
q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
|
||||
if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
|
||||
(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
|
||||
function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
|
||||
gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
|
||||
c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
|
||||
{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
|
||||
"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
|
||||
d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
|
||||
a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
|
||||
1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
|
||||
a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
|
||||
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
|
||||
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
|
||||
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
|
||||
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
|
||||
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
|
||||
""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
|
||||
this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
|
||||
u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
|
||||
1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
|
||||
return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
|
||||
""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
|
||||
c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
|
||||
c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
|
||||
function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
|
||||
Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
|
||||
"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
|
||||
a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
|
||||
a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
|
||||
"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
|
||||
serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
|
||||
function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
|
||||
global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
|
||||
e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
|
||||
"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
|
||||
false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
|
||||
false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
|
||||
c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
|
||||
d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
|
||||
g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
|
||||
1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
|
||||
"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
|
||||
if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
|
||||
this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
|
||||
"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
|
||||
animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
|
||||
j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
|
||||
this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
|
||||
"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
|
||||
c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
|
||||
this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
|
||||
this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
|
||||
e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
|
||||
c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
|
||||
function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
|
||||
this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
|
||||
k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
|
||||
f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
|
||||
a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
|
||||
c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
|
||||
d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
|
||||
f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
|
||||
"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
|
||||
e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
|
805
ext/chatbox/js/yshout.js
Normal file
@ -0,0 +1,805 @@
|
||||
String.prototype.sReplace = function(find, replace) {
|
||||
return this.split(find).join(replace);
|
||||
};
|
||||
|
||||
String.prototype.repeat = function(times) {
|
||||
var rep = new Array(times + 1);
|
||||
return rep.join(this);
|
||||
}
|
||||
|
||||
var YShout = function() {
|
||||
var self = this;
|
||||
var args = arguments;
|
||||
$(document).ready(function() {
|
||||
self.init.apply(self, args);
|
||||
});
|
||||
}
|
||||
|
||||
var yShout;
|
||||
|
||||
YShout.prototype = {
|
||||
animSpeed: 300,
|
||||
p: [],
|
||||
|
||||
init: function(options) {
|
||||
yShout = this;
|
||||
var self = this;
|
||||
|
||||
this.initializing = true;
|
||||
|
||||
var dOptions = {
|
||||
yPath: 'yshout/',
|
||||
log: 1
|
||||
};
|
||||
|
||||
this.options = jQuery.extend(dOptions, options);
|
||||
|
||||
this.postNum = 0;
|
||||
this.floodAttempt = 0;
|
||||
|
||||
// Correct for missing trailing /
|
||||
if ((this.options.yPath.length > 0) && (this.options.yPath.charAt(this.options.yPath.length - 1) != '/'))
|
||||
this.options.yPath += '/';
|
||||
|
||||
if (this.options.yLink) {
|
||||
if (this.options.yLink.charAt(0) != '#')
|
||||
this.options.yLink = '#' + this.options.yLink;
|
||||
|
||||
$(this.options.yLink).click(function() {
|
||||
self.openYShout.apply(self);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Load YShout from a link, in-page
|
||||
if (this.options.h_loadlink) {
|
||||
$(this.options.h_loadlink).click(function() {
|
||||
$('#yshout').css('display', 'block');
|
||||
$(this).unbind('click').click(function() { return false; });
|
||||
return false;
|
||||
});
|
||||
this.load(true);
|
||||
} else
|
||||
this.load();
|
||||
|
||||
|
||||
},
|
||||
|
||||
load: function(hidden) {
|
||||
if ($('#yshout').length == 0) return;
|
||||
|
||||
if (hidden) $('#yshout').css('display', 'none');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
this.ajax(this.initialLoad, {
|
||||
reqType: 'init',
|
||||
yPath: this.options.yPath,
|
||||
log: this.options.log
|
||||
});
|
||||
},
|
||||
|
||||
initialLoad: function(updates) {
|
||||
|
||||
|
||||
|
||||
|
||||
if (updates.yError) alert('There appears to be a problem: \n' + updates.yError + '\n\nIf you haven\'t already, try chmodding everything inside the YShout directory to 777.');
|
||||
|
||||
|
||||
|
||||
var self = this;
|
||||
|
||||
|
||||
|
||||
this.prefs = jQuery.extend(updates.prefs, this.options.prefs);
|
||||
this.initForm();
|
||||
this.initRefresh();
|
||||
this.initLinks();
|
||||
if (this.prefs.flood) this.initFlood();
|
||||
|
||||
if (updates.nickname)
|
||||
$('#ys-input-nickname')
|
||||
.removeClass('ys-before-focus')
|
||||
.addClass( 'ys-after-focus')
|
||||
.val(updates.nickname);
|
||||
|
||||
if (updates)
|
||||
this.updates(updates);
|
||||
|
||||
|
||||
if (!this.prefs.doTruncate) {
|
||||
$('#ys-posts').css('height', $('#ys-posts').height + 'px');
|
||||
}
|
||||
|
||||
if (!this.prefs.inverse) {
|
||||
var postsDiv = $('#ys-posts')[0];
|
||||
postsDiv.scrollTop = postsDiv.scrollHeight;
|
||||
}
|
||||
|
||||
this.markEnds();
|
||||
|
||||
this.initializing = false;
|
||||
},
|
||||
|
||||
initForm: function() {
|
||||
this.d('In initForm');
|
||||
|
||||
var postForm =
|
||||
'<form id="ys-post-form"' + (this.prefs.inverse ? 'class="ys-inverse"' : '' ) + '><fieldset>' +
|
||||
'<input id="ys-input-nickname" value="' + nickname + '" type="hidden" accesskey="N" maxlength="' + this.prefs.nicknameLength + '" class="ys-before-focus" />' +
|
||||
'<input id="ys-input-message" value="' + this.prefs.defaultMessage + '" type="text" accesskey="M" maxlength="' + this.prefs.messageLength + '" class="ys-before-focus" />' +
|
||||
(this.prefs.showSubmit ? '<input id="ys-input-submit" value="' + this.prefs.defaultSubmit + '" accesskey="S" type="submit" />' : '') +
|
||||
(this.prefs.postFormLink == 'cp' ? '<a title="View YShout Control Panel" class="ys-post-form-link" id="ys-cp-link" href="' + this.options.yPath + 'cp/index.php">Admin CP</a>' : '') +
|
||||
(this.prefs.postFormLink == 'history' ? '<a title="View YShout History" class="ys-post-form-link" id="ys-history-link" href="' + this.options.yPath + 'history/index.php?log=' + this.options.log + '">View History</a>' : '') +
|
||||
'</fieldset></form>';
|
||||
|
||||
var postsDiv = '<div id="ys-posts"></div>';
|
||||
|
||||
if (this.prefs.inverse) $('#yshout').html(postForm + postsDiv);
|
||||
else $('#yshout').html(postsDiv + postForm);
|
||||
|
||||
$('#ys-posts')
|
||||
.before('<div id="ys-before-posts"></div>')
|
||||
.after('<div id="ys-after-posts"></div>');
|
||||
|
||||
$('#ys-post-form')
|
||||
.before('<div id="ys-before-post-form"></div>')
|
||||
.after('<div id="ys-after-post-form"></div>');
|
||||
|
||||
var self = this;
|
||||
|
||||
var defaults = {
|
||||
'ys-input-nickname': self.prefs.defaultNickname,
|
||||
'ys-input-message': self.prefs.defaultMessage
|
||||
};
|
||||
|
||||
var keypress = function(e) {
|
||||
var key = window.event ? e.keyCode : e.which;
|
||||
if (key == 13 || key == 3) {
|
||||
self.send.apply(self);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var focus = function() {
|
||||
if (this.value == defaults[this.id])
|
||||
$(this).removeClass('ys-before-focus').addClass( 'ys-after-focus').val('');
|
||||
};
|
||||
|
||||
var blur = function() {
|
||||
if (this.value == '')
|
||||
$(this).removeClass('ys-after-focus').addClass('ys-before-focus').val(defaults[this.id]);
|
||||
};
|
||||
|
||||
$('#ys-input-message').keypress(keypress).focus(focus).blur(blur);
|
||||
$('#ys-input-nickname').keypress(keypress).focus(focus).blur(blur);
|
||||
|
||||
$('#ys-input-submit').click(function(){ self.send.apply(self) });
|
||||
$('#ys-post-form').submit(function(){ return false });
|
||||
},
|
||||
|
||||
initRefresh: function() {
|
||||
var self = this;
|
||||
if (this.refreshTimer) clearInterval(this.refreshTimer)
|
||||
this.refreshTimer = setInterval(function() {
|
||||
self.ajax(self.updates, { reqType: 'refresh' });
|
||||
}, this.prefs.refresh); // ! 3000..?
|
||||
},
|
||||
|
||||
initFlood: function() {
|
||||
this.d('in initFlood');
|
||||
var self = this;
|
||||
this.floodCount = 0;
|
||||
this.floodControl = false;
|
||||
|
||||
this.floodTimer = setInterval(function() {
|
||||
self.floodCount = 0;
|
||||
}, this.prefs.floodTimeout);
|
||||
},
|
||||
|
||||
initLinks: function() {
|
||||
if ($.browser.msie) return;
|
||||
|
||||
var self = this;
|
||||
|
||||
$('#ys-cp-link').click(function() {
|
||||
self.openCP.apply(self);
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#ys-history-link').click(function() {
|
||||
self.openHistory.apply(self);
|
||||
return false;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
openCP: function() {
|
||||
var self = this;
|
||||
if (this.cpOpen) return;
|
||||
this.cpOpen = true;
|
||||
|
||||
var url = this.options.yPath + 'cp/index.php';
|
||||
|
||||
$('body').append('<div id="ys-overlay"></div><div class="ys-window" id="ys-cp"><a title="Close Admin CP" href="#" id="ys-closeoverlay-link">Close</a><a title="View History" href="#" id="ys-switchoverlay-link">View History</a><object class="ys-browser" id="cp-browser" data="' + url +'" type="text/html">Something went horribly wrong.</object></div>');
|
||||
|
||||
$('#ys-overlay, #ys-closeoverlay-link').click(function() {
|
||||
self.reload.apply(self, [true]);
|
||||
self.closeCP.apply(self);
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#ys-switchoverlay-link').click(function() {
|
||||
self.closeCP.apply(self);
|
||||
self.openHistory.apply(self);
|
||||
return false;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
closeCP: function() {
|
||||
this.cpOpen = false;
|
||||
$('#ys-overlay, #ys-cp').remove();
|
||||
},
|
||||
|
||||
openHistory: function() {
|
||||
var self = this;
|
||||
if (this.hOpen) return;
|
||||
this.hOpen = true;
|
||||
var url = this.options.yPath + 'history/index.php?log='+ this.options.log;
|
||||
$('body').append('<div id="ys-overlay"></div><div class="ys-window" id="ys-history"><a title="Close history" href="#" id="ys-closeoverlay-link">Close</a><a title="View Admin CP" href="#" id="ys-switchoverlay-link">View Admin CP</a><object class="ys-browser" id="history-browser" data="' + url +'" type="text/html">Something went horribly wrong.</object></div>');
|
||||
|
||||
$('#ys-overlay, #ys-closeoverlay-link').click(function() {
|
||||
self.reload.apply(self, [true]);
|
||||
self.closeHistory.apply(self);
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#ys-switchoverlay-link').click(function() {
|
||||
self.closeHistory.apply(self);
|
||||
self.openCP.apply(self);
|
||||
return false;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
closeHistory: function() {
|
||||
this.hOpen = false;
|
||||
$('#ys-overlay, #ys-history').remove();
|
||||
},
|
||||
|
||||
openYShout: function() {
|
||||
var self = this;
|
||||
if (this.ysOpen) return;
|
||||
this.ysOpen = true;
|
||||
url = this.options.yPath + 'example/yshout.html';
|
||||
|
||||
$('body').append('<div id="ys-overlay"></div><div class="ys-window" id="ys-yshout"><a title="Close YShout" href="#" id="ys-closeoverlay-link">Close</a><object class="ys-browser" id="yshout-browser" data="' + url +'" type="text/html">Something went horribly wrong.</object></div>');
|
||||
|
||||
$('#ys-overlay, #ys-closeoverlay-link').click(function() {
|
||||
self.reload.apply(self, [true]);
|
||||
self.closeYShout.apply(self);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
closeYShout: function() {
|
||||
this.ysOpen = false;
|
||||
$('#ys-overlay, #ys-yshout').remove();
|
||||
},
|
||||
|
||||
send: function() {
|
||||
if (!this.validate()) return;
|
||||
if (this.prefs.flood && this.floodControl) return;
|
||||
|
||||
var postNickname = $('#ys-input-nickname').val(), postMessage = $('#ys-input-message').val();
|
||||
|
||||
if (postMessage == '/cp')
|
||||
this.openCP();
|
||||
else if (postMessage == '/history')
|
||||
this.openHistory();
|
||||
else
|
||||
this.ajax(this.updates, {
|
||||
reqType: 'post',
|
||||
nickname: postNickname,
|
||||
message: postMessage
|
||||
});
|
||||
|
||||
$('#ys-input-message').val('')
|
||||
|
||||
if (this.prefs.flood) this.flood();
|
||||
},
|
||||
|
||||
validate: function() {
|
||||
var nickname = $('#ys-input-nickname').val(),
|
||||
message = $('#ys-input-message').val(),
|
||||
error = false;
|
||||
|
||||
var showInvalid = function(input) {
|
||||
$(input).removeClass('ys-input-valid').addClass('ys-input-invalid')[0].focus();
|
||||
error = true;
|
||||
}
|
||||
|
||||
var showValid = function(input) {
|
||||
$(input).removeClass('ys-input-invalid').addClass('ys-input-valid');
|
||||
}
|
||||
|
||||
if (nickname == '' || nickname == this.prefs.defaultNickname)
|
||||
showInvalid('#ys-input-nickname');
|
||||
else
|
||||
showValid('#ys-input-nickname');
|
||||
|
||||
if (message == '' || message == this.prefs.defaultMessage)
|
||||
showInvalid('#ys-input-message');
|
||||
else
|
||||
showValid('#ys-input-message');
|
||||
|
||||
return !error;
|
||||
},
|
||||
|
||||
flood: function() {
|
||||
var self = this;
|
||||
this.d('in flood');
|
||||
if (this.floodCount < this.prefs.floodMessages) {
|
||||
this.floodCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
this.floodAttempt++;
|
||||
this.disable();
|
||||
|
||||
if (this.floodAttempt == this.prefs.autobanFlood)
|
||||
this.banSelf('You have been banned for flooding the shoutbox!');
|
||||
|
||||
setTimeout(function() {
|
||||
self.floodCount = 0;
|
||||
self.enable.apply(self);
|
||||
}, this.prefs.floodDisable);
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
$('#ys-input-submit')[0].disabled = true;
|
||||
this.floodControl = true;
|
||||
},
|
||||
|
||||
enable: function () {
|
||||
$('#ys-input-submit')[0].disabled = false;
|
||||
this.floodControl = false;
|
||||
},
|
||||
|
||||
findBySame: function(ip) {
|
||||
if (!$.browser.safari) return;
|
||||
|
||||
var same = [];
|
||||
for (var i = 0; i < this.p.length; i++)
|
||||
if (this.p[i].adminInfo.ip == ip)
|
||||
same.push(this.p[i]);
|
||||
|
||||
for (var i = 0; i < same.length; i++) {
|
||||
$('#' + same[i].id).fadeTo(this.animSpeed, .8).fadeTo(this.animSpeed, 1);
|
||||
}
|
||||
},
|
||||
|
||||
updates: function(updates) {
|
||||
if (!updates) return;
|
||||
if (updates.prefs) this.prefs = updates.prefs;
|
||||
if (updates.posts) this.posts(updates.posts);
|
||||
if (updates.banned) this.banned();
|
||||
},
|
||||
|
||||
banned: function() {
|
||||
var self = this;
|
||||
clearInterval(this.refreshTimer);
|
||||
clearInterval(this.floodTimer);
|
||||
if (this.initializing)
|
||||
$('#ys-post-form').css('display', 'none');
|
||||
else
|
||||
$('#ys-post-form').fadeOut(this.animSpeed);
|
||||
|
||||
if ($('#ys-banned').length == 0) {
|
||||
$('#ys-input-message')[0].blur();
|
||||
$('#ys-posts').append('<div id="ys-banned"><span>You\'re banned. Click <a href="#" id="ys-unban-self">here</a> to unban yourself if you\'re an admin. If you\'re not, go <a href="' + this.options.yPath + 'cp/index.php" id="ys-banned-cp-link">log in</a>!</span></div>');
|
||||
|
||||
$('#ys-banned-cp-link').click(function() {
|
||||
self.openCP.apply(self);
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#ys-unban-self').click(function() {
|
||||
self.ajax(function(json) {
|
||||
if (!json.error)
|
||||
self.unbanned();
|
||||
else if (json.error == 'admin')
|
||||
alert('You can only unban yourself if you\'re an admin.');
|
||||
}, { reqType: 'unbanself' });
|
||||
return false;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
unbanned: function() {
|
||||
var self = this;
|
||||
$('#ys-banned').fadeOut(function() { $(this).remove(); });
|
||||
this.initRefresh();
|
||||
$('#ys-post-form').css('display', 'block').fadeIn(this.animSpeed, function(){
|
||||
self.reload();
|
||||
});
|
||||
},
|
||||
|
||||
posts: function(p) {
|
||||
for (var i = 0; i < p.length; i++) {
|
||||
this.post(p[i]);
|
||||
}
|
||||
|
||||
this.truncate();
|
||||
|
||||
if (!this.prefs.inverse) {
|
||||
var postsDiv = $('#ys-posts')[0];
|
||||
postsDiv.scrollTop = postsDiv.scrollHeight;
|
||||
}
|
||||
},
|
||||
|
||||
post: function(post) {
|
||||
var self = this;
|
||||
|
||||
var pad = function(n) { return n > 9 ? n : '0' + n; };
|
||||
var date = function(ts) { return new Date(ts * 1000); };
|
||||
var time = function(ts) {
|
||||
var d = date(ts);
|
||||
var h = d.getHours(), m = d.getMinutes();
|
||||
|
||||
if (self.prefs.timestamp == 12) {
|
||||
h = (h > 12 ? h - 12 : h);
|
||||
if (h == 0) h = 12;
|
||||
}
|
||||
|
||||
return pad(h) + ':' + pad(m);
|
||||
};
|
||||
|
||||
var dateStr = function(ts) {
|
||||
var t = date(ts);
|
||||
|
||||
var Y = t.getFullYear();
|
||||
var M = t.getMonth();
|
||||
var D = t.getDay();
|
||||
var d = t.getDate();
|
||||
var day = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][D];
|
||||
var mon = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][M];
|
||||
|
||||
return day + ' ' + mon + '. ' + d + ', ' + Y;
|
||||
};
|
||||
|
||||
var self = this;
|
||||
|
||||
this.postNum++;
|
||||
var id = 'ys-post-' + this.postNum;
|
||||
post.id = id;
|
||||
|
||||
post.message = this.links(post.message);
|
||||
post.message = this.smileys(post.message);
|
||||
post.message = this.bbcode(post.message);
|
||||
var html =
|
||||
'<div id="' + id + '" class="ys-post' + (post.admin ? ' ys-admin-post' : '') + (post.banned ? ' ys-banned-post' : '') + '">' +
|
||||
(this.prefs.timestamp> 0 ? '<span class="ys-post-timestamp">' + time(post.timestamp) + '</span> ' : '') +
|
||||
'<span class="ys-post-nickname">' + post.nickname + this.prefs.nicknameSeparator + '</span> ' +
|
||||
'<span class="ys-post-message">' + post.message + '</span> ' +
|
||||
'<span class="ys-post-info' + (this.prefs.info == 'overlay' ? ' ys-info-overlay' : ' ys-info-inline') + '">' + (post.adminInfo ? '<em>IP:</em> ' + post.adminInfo.ip + ', ' : '') + '<em>Posted:</em> ' + dateStr(post.timestamp) + ' at ' + time(post.timestamp) + '.</span>' +
|
||||
'<span class="ys-post-actions"><a title="Show post information" class="ys-info-link" href="#">Info</a>' + (post.adminInfo ? ' | <a title="Delete post" class="ys-delete-link" href="#">Delete</a> | ' + (post.banned ? '<a title="Unban user" class="ys-ban-link" href="#">Unban</a>' : '<a title="Ban user" class="ys-ban-link" href="#">Ban</a>') : '') + '</span>' +
|
||||
'</div>';
|
||||
if (this.prefs.inverse) $('#ys-posts').prepend(html);
|
||||
else $('#ys-posts').append(html);
|
||||
|
||||
this.p.push(post);
|
||||
|
||||
$('#' + id)
|
||||
.find('.ys-post-nickname').click(function() {
|
||||
if (post.adminInfo)
|
||||
self.findBySame(post.adminInfo.ip);
|
||||
}).end()
|
||||
.find('.ys-info-link').toggle(
|
||||
function() { self.showInfo.apply(self, [id, this]); return false; },
|
||||
function() { self.hideInfo.apply(self, [id, this]); return false; })
|
||||
.end()
|
||||
.find('.ys-ban-link').click(
|
||||
function() { self.ban.apply(self, [post, id]); return false; })
|
||||
.end()
|
||||
.find('.ys-delete-link').click(
|
||||
function() { self.del.apply(self, [post, id]); return false; });
|
||||
|
||||
},
|
||||
|
||||
showInfo: function(id, el) {
|
||||
var jEl = $('#' + id + ' .ys-post-info');
|
||||
if (this.prefs.info == 'overlay')
|
||||
jEl.css('display', 'block').fadeIn(this.animSpeed);
|
||||
else
|
||||
jEl.slideDown(this.animSpeed);
|
||||
|
||||
el.innerHTML ='Close Info'
|
||||
return false;
|
||||
},
|
||||
|
||||
hideInfo: function(id, el) {
|
||||
var jEl = $('#' + id + ' .ys-post-info');
|
||||
if (this.prefs.info == 'overlay')
|
||||
jEl.fadeOut(this.animSpeed);
|
||||
else
|
||||
jEl.slideUp(this.animSpeed);
|
||||
|
||||
el.innerHTML = 'Info';
|
||||
return false;
|
||||
},
|
||||
|
||||
ban: function(post, id) {
|
||||
var self = this;
|
||||
|
||||
var link = $('#' + id).find('.ys-ban-link')[0];
|
||||
|
||||
switch(link.innerHTML) {
|
||||
case 'Ban':
|
||||
var pars = {
|
||||
reqType: 'ban',
|
||||
ip: post.adminInfo.ip,
|
||||
nickname: post.nickname
|
||||
};
|
||||
|
||||
this.ajax(function(json) {
|
||||
if (json.error) {
|
||||
switch (json.error) {
|
||||
case 'admin':
|
||||
self.error('You\'re not an admin. Log in through the Admin CP to ban people.');
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
//alert('p: ' + this.p + ' / ' + this.p.length);
|
||||
if (json.bannedSelf)
|
||||
self.banned(); // ?
|
||||
|
||||
else
|
||||
$.each(self.p, function(i) {
|
||||
if (this.adminInfo && this.adminInfo.ip == post.adminInfo.ip)
|
||||
$('#' + this.id)
|
||||
.addClass('ys-banned-post')
|
||||
.find('.ys-ban-link').html('Unban');
|
||||
});
|
||||
|
||||
}, pars);
|
||||
|
||||
link.innerHTML = 'Banning...';
|
||||
return false;
|
||||
break;
|
||||
|
||||
case 'Banning...':
|
||||
return false;
|
||||
break;
|
||||
|
||||
case 'Unban':
|
||||
var pars = {
|
||||
reqType: 'unban',
|
||||
ip: post.adminInfo.ip
|
||||
};
|
||||
|
||||
this.ajax(function(json) {
|
||||
if (json.error) {
|
||||
switch(json.error) {
|
||||
case 'admin':
|
||||
self.error('You\'re not an admin. Log in through the Admin CP to unban people.');
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$.each(self.p, function(i) {
|
||||
if (this.adminInfo && this.adminInfo.ip == post.adminInfo.ip)
|
||||
$('#' + this.id)
|
||||
.removeClass('ys-banned-post')
|
||||
.find('.ys-ban-link').html('Ban');
|
||||
});
|
||||
|
||||
}, pars);
|
||||
|
||||
link.innerHTML = 'Unbanning...';
|
||||
return false;
|
||||
break;
|
||||
|
||||
case 'Unbanning...':
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
del: function(post, id) {
|
||||
var self = this;
|
||||
var link = $('#' + id).find('.ys-delete-link')[0];
|
||||
|
||||
if (link.innerHTML == 'Deleting...') return;
|
||||
|
||||
var pars = {
|
||||
reqType: 'delete',
|
||||
uid: post.uid
|
||||
};
|
||||
|
||||
self.ajax(function(json) {
|
||||
if (json.error) {
|
||||
switch(json.error) {
|
||||
case 'admin':
|
||||
self.error('You\'re not an admin. Log in through the Admin CP to ban people.');
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.reload();
|
||||
}, pars);
|
||||
|
||||
link.innerHTML = 'Deleting...';
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
banSelf: function(reason) {
|
||||
var self = this;
|
||||
|
||||
this.ajax(function(json) {
|
||||
if (json.error == false)
|
||||
self.banned();
|
||||
}, {
|
||||
reqType: 'banself',
|
||||
nickname: $('#ys-input-nickname').val()
|
||||
});
|
||||
},
|
||||
|
||||
bbcode: function(s) {
|
||||
s = s.sReplace('[i]', '<i>');
|
||||
s = s.sReplace('[/i]', '</i>');
|
||||
s = s.sReplace('[I]', '<i>');
|
||||
s = s.sReplace('[/I]', '</i>');
|
||||
|
||||
s = s.sReplace('[b]', '<b>');
|
||||
s = s.sReplace('[/b]', '</b>');
|
||||
s = s.sReplace('[B]', '<b>');
|
||||
s = s.sReplace('[/B]', '</b>');
|
||||
|
||||
s = s.sReplace('[u]', '<u>');
|
||||
s = s.sReplace('[/u]', '</u>');
|
||||
s = s.sReplace('[U]', '<u>');
|
||||
s = s.sReplace('[/U]', '</u>');
|
||||
|
||||
return s;
|
||||
},
|
||||
|
||||
smileys: function(s) {
|
||||
var yp = this.options.yPath;
|
||||
|
||||
var smile = function(str, smiley, image) {
|
||||
return str.sReplace(smiley, '<img src="' + yp + 'smileys/' + image + '" />');
|
||||
};
|
||||
|
||||
s = smile(s, ':twisted:', 'twisted.gif');
|
||||
s = smile(s, ':cry:', 'cry.gif');
|
||||
s = smile(s, ':\'(', 'cry.gif');
|
||||
s = smile(s, ':shock:', 'eek.gif');
|
||||
s = smile(s, ':evil:', 'evil.gif');
|
||||
s = smile(s, ':lol:', 'lol.gif');
|
||||
s = smile(s, ':mrgreen:', 'mrgreen.gif');
|
||||
s = smile(s, ':oops:', 'redface.gif');
|
||||
s = smile(s, ':roll:', 'rolleyes.gif');
|
||||
|
||||
s = smile(s, ':?', 'confused.gif');
|
||||
s = smile(s, ':D', 'biggrin.gif');
|
||||
s = smile(s, '8)', 'cool.gif');
|
||||
s = smile(s, ':x', 'mad.gif');
|
||||
s = smile(s, ':|', 'neutral.gif');
|
||||
s = smile(s, ':P', 'razz.gif');
|
||||
s = smile(s, ':(', 'sad.gif');
|
||||
s = smile(s, ':)', 'smile.gif');
|
||||
s = smile(s, ':o', 'surprised.gif');
|
||||
s = smile(s, ';)', 'wink.gif');
|
||||
|
||||
return s;
|
||||
},
|
||||
|
||||
links: function(s) {
|
||||
return s.replace(/((https|http|ftp|ed2k):\/\/[\S]+)/gi, '<a href="$1" target="_blank">$1</a>');
|
||||
},
|
||||
|
||||
truncate: function(clearAll) {
|
||||
var truncateTo = clearAll ? 0 : this.prefs.truncate;
|
||||
var posts = $('#ys-posts .ys-post').length;
|
||||
if (posts <= truncateTo) return;
|
||||
//alert(this.initializing);
|
||||
if (this.prefs.doTruncate || this.initializing) {
|
||||
var diff = posts - truncateTo;
|
||||
for (var i = 0; i < diff; i++)
|
||||
this.p.shift();
|
||||
|
||||
// $('#ys-posts .ys-post:gt(' + truncateTo + ')').remove();
|
||||
|
||||
if (this.prefs.inverse)
|
||||
$('#ys-posts .ys-post:gt(' + (truncateTo - 1) + ')').remove();
|
||||
else
|
||||
$('#ys-posts .ys-post:lt(' + (posts - truncateTo) + ')').remove();
|
||||
}
|
||||
|
||||
this.markEnds();
|
||||
},
|
||||
|
||||
markEnds: function() {
|
||||
$('#ys-posts')
|
||||
.find('.ys-first').removeClass('ys-first').end()
|
||||
.find('.ys-last').removeClass('ys-last');
|
||||
|
||||
$('#ys-posts .ys-post:first-child').addClass('ys-first');
|
||||
$('#ys-posts .ys-post:last-child').addClass('ys-last');
|
||||
},
|
||||
|
||||
reload: function(everything) {
|
||||
var self = this;
|
||||
this.initializing = true;
|
||||
|
||||
if (everything) {
|
||||
this.ajax(function(json) {
|
||||
$('#yshout').html('');
|
||||
clearInterval(this.refreshTimer);
|
||||
clearInterval(this.floodTimer);
|
||||
this.initialLoad(json);
|
||||
}, {
|
||||
reqType: 'init',
|
||||
yPath: this.options.yPath,
|
||||
log: this.options.log
|
||||
});
|
||||
} else {
|
||||
this.ajax(function(json) { this.truncate(true); this.updates(json); this.initializing = false; }, {
|
||||
reqType: 'reload'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
error: function(str) {
|
||||
alert(str);
|
||||
},
|
||||
|
||||
json: function(parse) {
|
||||
this.d('In json: ' + parse);
|
||||
var json = eval('(' + parse + ')');
|
||||
if (!this.checkError(json)) return json;
|
||||
},
|
||||
|
||||
checkError: function(json) {
|
||||
if (!json.yError) return false;
|
||||
|
||||
this.d('Error: ' + json.yError);
|
||||
return true;
|
||||
},
|
||||
|
||||
ajax: function(callback, pars, html) {
|
||||
pars = jQuery.extend({
|
||||
reqFor: 'shout'
|
||||
}, pars);
|
||||
|
||||
var self = this;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.options.yPath + 'yshout.php',
|
||||
dataType: html ? 'text' : 'json',
|
||||
data: pars,
|
||||
success: function(parse) {
|
||||
var arr = [parse];
|
||||
|
||||
callback.apply(self, arr);
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
d: function(message) {
|
||||
// console.log(message);
|
||||
$('#debug').css('display', 'block').prepend('<p>' + message + '</p>');
|
||||
return message;
|
||||
}
|
||||
};
|
4
ext/chatbox/logs/.htaccess
Normal file
@ -0,0 +1,4 @@
|
||||
<Files *>
|
||||
order allow,deny
|
||||
deny from all
|
||||
</Files>
|
1
ext/chatbox/logs/log.1.txt
Normal file
@ -0,0 +1 @@
|
||||
a:2:{s:4:"info";a:1:{s:15:"latestTimestamp";d:1365655195.8733589649200439453125;}s:5:"posts";a:1:{i:0;a:6:{s:8:"nickname";s:7:"YaoiFox";s:7:"message";s:42:"I hope enjoy this chatbox based on YShout!";s:9:"timestamp";d:1365655195.8733589649200439453125;s:5:"admin";b:0;s:3:"uid";s:32:"ee9e9a7a01909be8065571655dad044d";s:9:"adminInfo";a:1:{s:2:"ip";s:11:"84.193.78.8";}}}}
|
1
ext/chatbox/logs/yshout.bans.txt
Normal file
@ -0,0 +1 @@
|
||||
a:0:{}
|
1
ext/chatbox/logs/yshout.prefs.txt
Normal file
@ -0,0 +1 @@
|
||||
a:23:{s:8:"password";s:8:"fortytwo";s:7:"refresh";i:6000;s:4:"logs";i:5;s:7:"history";i:200;s:7:"inverse";b:0;s:8:"truncate";i:15;s:10:"doTruncate";b:1;s:9:"timestamp";i:12;s:15:"defaultNickname";s:8:"Nickname";s:14:"defaultMessage";s:12:"Message Text";s:13:"defaultSubmit";s:6:"Shout!";s:10:"showSubmit";b:1;s:14:"nicknameLength";i:25;s:13:"messageLength";i:175;s:17:"nicknameSeparator";s:1:":";s:5:"flood";b:1;s:12:"floodTimeout";i:5000;s:13:"floodMessages";i:4;s:12:"floodDisable";i:8000;s:12:"autobanFlood";i:0;s:11:"censorWords";s:19:"fuck shit bitch ass";s:12:"postFormLink";s:7:"history";s:4:"info";s:6:"inline";}
|
36
ext/chatbox/main.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Name: Chatbox (Beta)
|
||||
* Author: Drudex Software <support@drudexsoftware.com>
|
||||
* Link: http://www.drudexsoftware.com
|
||||
* License: GPLv2
|
||||
* Description: Places an ajax chatbox at the bottom of each page
|
||||
* Documentation:
|
||||
* This chatbox uses YShout 5 as core.
|
||||
*/
|
||||
class Chatbox extends Extension {
|
||||
public function onPageRequest(PageRequestEvent $event) {
|
||||
global $page, $user;
|
||||
|
||||
// Adds header to enable chatbox
|
||||
$root = make_http();
|
||||
$yPath = "$root/ext/chatbox/";
|
||||
$page->add_html_header("
|
||||
<script src=\"$root/ext/chatbox/js/jquery.js\" type=\"text/javascript\"></script>
|
||||
<script src=\"$root/ext/chatbox/js/yshout.js\" type=\"text/javascript\"></script>
|
||||
|
||||
<link rel=\"stylesheet\" href=\"$root/ext/chatbox/css/dark.yshout.css\" />
|
||||
|
||||
<script type=\"text/javascript\">
|
||||
nickname = '{$user->name}';
|
||||
new YShout({ yPath: '$yPath' });
|
||||
</script>
|
||||
");
|
||||
|
||||
// loads the chatbox at the set location
|
||||
$html = "<div id=\"yshout\"></div>";
|
||||
$chatblock = new Block("Chatbox", $html, "main", 97);
|
||||
$page->add_block($chatblock);
|
||||
}
|
||||
}
|
||||
?>
|
279
ext/chatbox/php/ajaxcall.class.php
Normal file
@ -0,0 +1,279 @@
|
||||
<?
|
||||
class AjaxCall {
|
||||
function AjaxCall($log = null) {
|
||||
header('Content-type: application/json');
|
||||
session_start();
|
||||
|
||||
if (isset($log)) $_SESSION['yLog'] = $log;
|
||||
|
||||
$this->reqType = $_POST['reqType'];
|
||||
}
|
||||
|
||||
function process() {
|
||||
switch($this->reqType) {
|
||||
case 'init':
|
||||
|
||||
$this->initSession();
|
||||
$this->sendFirstUpdates();
|
||||
break;
|
||||
|
||||
case 'post':
|
||||
$nickname = $_POST['nickname'];
|
||||
$message = $_POST['message'];
|
||||
cookie('yNickname', $nickname);
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
|
||||
if ($ys->banned(ip())) { $this->sendBanned(); break; }
|
||||
if ($post = $ys->post($nickname, $message)) // To use $post somewheres later
|
||||
$this->sendUpdates();
|
||||
break;
|
||||
|
||||
case 'refresh':
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
if ($ys->banned(ip())) { $this->sendBanned(); break; }
|
||||
|
||||
$this->sendUpdates();
|
||||
break;
|
||||
|
||||
case 'reload':
|
||||
$this->reload();
|
||||
break;
|
||||
|
||||
case 'ban':
|
||||
$this->doBan();
|
||||
break;
|
||||
|
||||
case 'unban':
|
||||
$this->doUnban();
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$this->doDelete();
|
||||
break;
|
||||
|
||||
case 'banself':
|
||||
$this->banSelf();
|
||||
break;
|
||||
|
||||
case 'unbanself':
|
||||
$this->unbanSelf();
|
||||
break;
|
||||
|
||||
case 'clearlog':
|
||||
$this->clearLog();
|
||||
break;
|
||||
|
||||
case 'clearlogs':
|
||||
$this->clearLogs();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function doBan() {
|
||||
$ip = $_POST['ip'];
|
||||
$nickname = $_POST['nickname'];
|
||||
$send = array();
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
|
||||
switch(true) {
|
||||
case !loggedIn():
|
||||
$send['error'] = 'admin';
|
||||
break;
|
||||
case $ys->banned($ip):
|
||||
$send['error'] = 'already';
|
||||
break;
|
||||
default:
|
||||
$ys->ban($ip, $nickname);
|
||||
if ($ip == ip())
|
||||
$send['bannedSelf'] = true;
|
||||
$send['error'] = false;
|
||||
}
|
||||
|
||||
echo jsonEncode($send);
|
||||
}
|
||||
|
||||
function doUnban() {
|
||||
$ip = $_POST['ip'];
|
||||
$send = array();
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
|
||||
switch(true) {
|
||||
case !loggedIn():
|
||||
$send['error'] = 'admin';
|
||||
break;
|
||||
case !$ys->banned($ip):
|
||||
$send['error'] = 'already';
|
||||
break;
|
||||
default:
|
||||
$ys->unban($ip);
|
||||
$send['error'] = false;
|
||||
}
|
||||
|
||||
echo jsonEncode($send);
|
||||
}
|
||||
|
||||
function doDelete() {
|
||||
$uid = $_POST['uid'];
|
||||
$send = array();
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
|
||||
switch(true) {
|
||||
case !loggedIn():
|
||||
$send['error'] = 'admin';
|
||||
break;
|
||||
default:
|
||||
$ys->delete($uid);
|
||||
$send['error'] = false;
|
||||
}
|
||||
|
||||
echo jsonEncode($send);
|
||||
}
|
||||
|
||||
function banSelf() {
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
$nickname = $_POST['nickname'];
|
||||
$ys->ban(ip(), $nickname);
|
||||
|
||||
$send = array();
|
||||
$send['error'] = false;
|
||||
|
||||
echo jsonEncode($send);
|
||||
|
||||
}
|
||||
|
||||
function unbanSelf() {
|
||||
if (loggedIn()) {
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
$ys->unban(ip());
|
||||
|
||||
$send = array();
|
||||
$send['error'] = false;
|
||||
} else {
|
||||
$send = array();
|
||||
$send['error'] = 'admin';
|
||||
}
|
||||
|
||||
echo jsonEncode($send);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
global $prefs;
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
|
||||
$posts = $ys->latestPosts($prefs['truncate']);
|
||||
$this->setSessTimestamp($posts);
|
||||
$this->updates['posts'] = $posts;
|
||||
echo jsonEncode($this->updates);
|
||||
}
|
||||
|
||||
function initSession() {
|
||||
$_SESSION['yLatestTimestamp'] = 0;
|
||||
$_SESSION['yYPath'] = $_POST['yPath'];
|
||||
$_SESSION['yLog'] = $_POST['log'];
|
||||
$loginHash = cookieGet('yLoginHash') ;
|
||||
if (isset($loginHash) && $loginHash != '') {
|
||||
login($loginHash);
|
||||
}
|
||||
}
|
||||
|
||||
function sendBanned() {
|
||||
$this->updates = array(
|
||||
'banned' => true
|
||||
);
|
||||
|
||||
echo jsonEncode($this->updates);
|
||||
}
|
||||
|
||||
function sendUpdates() {
|
||||
global $prefs;
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
if (!$ys->hasPostsAfter($_SESSION['yLatestTimestamp'])) return;
|
||||
|
||||
$posts = $ys->postsAfter($_SESSION['yLatestTimestamp']);
|
||||
$this->setSessTimestamp($posts);
|
||||
|
||||
$this->updates['posts'] = $posts;
|
||||
|
||||
echo jsonEncode($this->updates);
|
||||
}
|
||||
|
||||
function setSessTimestamp(&$posts) {
|
||||
if (!$posts) return;
|
||||
|
||||
$latest = array_slice( $posts, -1, 1);
|
||||
$_SESSION['yLatestTimestamp'] = $latest[0]['timestamp'];
|
||||
}
|
||||
|
||||
function sendFirstUpdates() {
|
||||
global $prefs, $overrideNickname;
|
||||
|
||||
$this->updates = array();
|
||||
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
|
||||
$posts = $ys->latestPosts($prefs['truncate']);
|
||||
$this->setSessTimestamp($posts);
|
||||
|
||||
$this->updates['posts'] = $posts;
|
||||
$this->updates['prefs'] = $this->cleanPrefs($prefs);
|
||||
|
||||
if ($nickname = cookieGet('yNickname'))
|
||||
$this->updates['nickname'] = $nickname;
|
||||
|
||||
if ($overrideNickname)
|
||||
$this->updates['nickname'] = $overrideNickname;
|
||||
|
||||
if ($ys->banned(ip()))
|
||||
$this->updates['banned'] = true;
|
||||
|
||||
echo jsonEncode($this->updates);
|
||||
}
|
||||
|
||||
function cleanPrefs($prefs) {
|
||||
unset($prefs['password']);
|
||||
return $prefs;
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
$log = $_POST['log'];
|
||||
$send = array();
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
|
||||
switch(true) {
|
||||
case !loggedIn():
|
||||
$send['error'] = 'admin';
|
||||
break;
|
||||
default:
|
||||
$ys->clear();
|
||||
$send['error'] = false;
|
||||
}
|
||||
|
||||
echo jsonEncode($send);
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
global $prefs;
|
||||
|
||||
$log = $_POST['log'];
|
||||
$send = array();
|
||||
|
||||
$ys = ys($_SESSION['yLog']);
|
||||
|
||||
switch(true) {
|
||||
case !loggedIn():
|
||||
$send['error'] = 'admin';
|
||||
break;
|
||||
default:
|
||||
for ($i = 1; $i <= $prefs['logs']; $i++) {
|
||||
$ys = ys($i);
|
||||
$ys->clear();
|
||||
}
|
||||
|
||||
$send['error'] = false;
|
||||
}
|
||||
|
||||
echo jsonEncode($send);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
85
ext/chatbox/php/filestorage.class.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?
|
||||
|
||||
class FileStorage {
|
||||
|
||||
function FileStorage($path, $shoutLog = false) {
|
||||
$this->shoutLog = $shoutLog;
|
||||
$folder = 'logs';
|
||||
if (!is_dir($folder)) $folder = '../' . $folder;
|
||||
if (!is_dir($folder)) $folder = '../' . $folder;
|
||||
|
||||
$this->path = $folder . '/' . $path . '.txt';
|
||||
}
|
||||
|
||||
function open($lock = false) {
|
||||
$this->handle = fopen($this->path, 'a+');
|
||||
|
||||
if ($lock) {
|
||||
$this->lock();
|
||||
return $this->load();
|
||||
}
|
||||
}
|
||||
|
||||
function close(&$array) {
|
||||
if (isset($array))
|
||||
$this->save($array);
|
||||
|
||||
$this->unlock();
|
||||
fclose($this->handle);
|
||||
unset($this->handle);
|
||||
}
|
||||
|
||||
function load() {
|
||||
if (($contents = $this->read($this->path)) == null)
|
||||
return $this->resetArray();
|
||||
|
||||
return unserialize($contents);
|
||||
}
|
||||
|
||||
function save(&$array, $unlock = true) {
|
||||
$contents = serialize($array);
|
||||
$this->write($contents);
|
||||
if ($unlock) $this->unlock();
|
||||
}
|
||||
|
||||
function unlock() {
|
||||
if (isset($this->handle))
|
||||
flock($this->handle, LOCK_UN);
|
||||
}
|
||||
|
||||
function lock() {
|
||||
if (isset($this->handle))
|
||||
flock($this->handle, LOCK_EX);
|
||||
}
|
||||
|
||||
function read() {
|
||||
fseek($this->handle, 0);
|
||||
//return stream_get_contents($this->handle);
|
||||
return file_get_contents($this->path);
|
||||
|
||||
}
|
||||
|
||||
function write($contents) {
|
||||
ftruncate($this->handle, 0);
|
||||
fwrite($this->handle, $contents);
|
||||
}
|
||||
|
||||
function resetArray() {
|
||||
if ($this->shoutLog)
|
||||
$default = array(
|
||||
'info' => array(
|
||||
'latestTimestamp' => -1
|
||||
),
|
||||
|
||||
'posts' => array()
|
||||
);
|
||||
else
|
||||
$default = array();
|
||||
|
||||
$this->save($default, false);
|
||||
return $default;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
152
ext/chatbox/php/functions.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?
|
||||
|
||||
function cookie($name, $data) {
|
||||
return setcookie($name, $data, time() + 60 * 60 * 24 * 30, '/');
|
||||
}
|
||||
|
||||
function cookieGet($name, $default = null) {
|
||||
if (isset($_COOKIE[$name]))
|
||||
return $_COOKIE[$name];
|
||||
else
|
||||
return $default;
|
||||
}
|
||||
|
||||
function cookieClear($name) {
|
||||
setcookie ($name, false, time() - 42);
|
||||
}
|
||||
|
||||
function getVar($name) {
|
||||
if (isset($_POST[$name])) return $_POST[$name];
|
||||
if (isset($_GET[$name])) return $_GET[$name];
|
||||
return null;
|
||||
}
|
||||
|
||||
function clean($s) {
|
||||
$s = magic($s);
|
||||
$s = htmlspecialchars($s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
function magic($s) {
|
||||
if (get_magic_quotes_gpc()) $s = stripslashes($s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
function ip() {
|
||||
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
|
||||
return $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
else
|
||||
return $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
|
||||
function ipValid($ip) {
|
||||
if ($ip == long2ip(ip2long($ip)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function jsonEncode(&$array) {
|
||||
if ($array) {
|
||||
$json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
|
||||
return $json->encode($array);
|
||||
} else
|
||||
return 'ar';
|
||||
}
|
||||
|
||||
function jsonDecode($encoded) {
|
||||
$json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
|
||||
return $json->decode($encoded);
|
||||
}
|
||||
|
||||
function validIP($ip) {
|
||||
if ($ip == long2ip(ip2long($ip)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function ts() {
|
||||
// return microtime(true);
|
||||
list($usec, $sec) = explode(" ", microtime());
|
||||
return ((float)$usec + (float)$sec);
|
||||
|
||||
}
|
||||
|
||||
function len($string) {
|
||||
$i = 0; $count = 0;
|
||||
$len = strlen($string);
|
||||
|
||||
while ($i < $len) {
|
||||
$chr = ord($string[$i]);
|
||||
$count++;
|
||||
$i++;
|
||||
|
||||
if ($i >= $len) break;
|
||||
if ($chr & 0x80) {
|
||||
$chr <<= 1;
|
||||
while ($chr & 0x80) {
|
||||
$i++;
|
||||
$chr <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
function error($err) {
|
||||
echo 'Error: ' . $err;
|
||||
exit;
|
||||
}
|
||||
|
||||
function ys($log = 1) {
|
||||
global $yShout, $prefs;
|
||||
if ($yShout) return $yShout;
|
||||
|
||||
if ($log > $prefs['logs'] || $log < 0 || !is_numeric($log)) $log = 1;
|
||||
|
||||
$log = 'log.' . $log;
|
||||
return new YShout($log, loggedIn());
|
||||
}
|
||||
|
||||
function dstart() {
|
||||
global $ts;
|
||||
|
||||
$ts = ts();
|
||||
}
|
||||
|
||||
function dstop() {
|
||||
global $ts;
|
||||
echo 'Time elapsed: ' . ((ts() - $ts) * 100000);
|
||||
exit;
|
||||
}
|
||||
|
||||
function login($hash) {
|
||||
// echo 'login: ' . $hash . "\n";
|
||||
|
||||
$_SESSION['yLoginHash'] = $hash;
|
||||
cookie('yLoginHash', $hash);
|
||||
// return loggedIn();
|
||||
}
|
||||
|
||||
function logout() {
|
||||
$_SESSION['yLoginHash'] = '';
|
||||
cookie('yLoginHash', '');
|
||||
// cookieClear('yLoginHash');
|
||||
}
|
||||
|
||||
function loggedIn() {
|
||||
global $prefs;
|
||||
|
||||
$loginHash = cookieGet('yLoginHash', false);
|
||||
// echo 'loggedin: ' . $loginHash . "\n";
|
||||
// echo 'pw: ' . $prefs['password'] . "\n";
|
||||
|
||||
if (isset($loginHash)) return $loginHash == md5($prefs['password']);
|
||||
|
||||
if (isset($_SESSION['yLoginHash']))
|
||||
return $_SESSION['yLoginHash'] == md5($prefs['password']);
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
?>
|
805
ext/chatbox/php/json.class.php
Normal file
@ -0,0 +1,805 @@
|
||||
<?php
|
||||
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
||||
|
||||
/**
|
||||
* Converts to and from JSON format.
|
||||
*
|
||||
* JSON (JavaScript Object Notation) is a lightweight data-interchange
|
||||
* format. It is easy for humans to read and write. It is easy for machines
|
||||
* to parse and generate. It is based on a subset of the JavaScript
|
||||
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
|
||||
* This feature can also be found in Python. JSON is a text format that is
|
||||
* completely language independent but uses conventions that are familiar
|
||||
* to programmers of the C-family of languages, including C, C++, C#, Java,
|
||||
* JavaScript, Perl, TCL, and many others. These properties make JSON an
|
||||
* ideal data-interchange language.
|
||||
*
|
||||
* This package provides a simple encoder and decoder for JSON notation. It
|
||||
* is intended for use with client-side Javascript applications that make
|
||||
* use of HTTPRequest to perform server communication functions - data can
|
||||
* be encoded into JSON notation for use in a client-side javascript, or
|
||||
* decoded from incoming Javascript requests. JSON format is native to
|
||||
* Javascript, and can be directly eval()'ed with no further parsing
|
||||
* overhead
|
||||
*
|
||||
* All strings should be in ASCII or UTF-8 format!
|
||||
*
|
||||
* LICENSE: Redistribution and use in source and binary forms, with or
|
||||
* without modification, are permitted provided that the following
|
||||
* conditions are met: Redistributions of source code must retain the
|
||||
* above copyright notice, this list of conditions and the following
|
||||
* disclaimer. Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* @category
|
||||
* @package Services_JSON
|
||||
* @author Michal Migurski <mike-json@teczno.com>
|
||||
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
|
||||
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
|
||||
* @copyright 2005 Michal Migurski
|
||||
* @version CVS: $Id: JSON.php,v 1.30 2006/03/08 16:10:20 migurski Exp $
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php
|
||||
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
|
||||
*/
|
||||
|
||||
/**
|
||||
* Marker constant for Services_JSON::decode(), used to flag stack state
|
||||
*/
|
||||
define('SERVICES_JSON_SLICE', 1);
|
||||
|
||||
/**
|
||||
* Marker constant for Services_JSON::decode(), used to flag stack state
|
||||
*/
|
||||
define('SERVICES_JSON_IN_STR', 2);
|
||||
|
||||
/**
|
||||
* Marker constant for Services_JSON::decode(), used to flag stack state
|
||||
*/
|
||||
define('SERVICES_JSON_IN_ARR', 3);
|
||||
|
||||
/**
|
||||
* Marker constant for Services_JSON::decode(), used to flag stack state
|
||||
*/
|
||||
define('SERVICES_JSON_IN_OBJ', 4);
|
||||
|
||||
/**
|
||||
* Marker constant for Services_JSON::decode(), used to flag stack state
|
||||
*/
|
||||
define('SERVICES_JSON_IN_CMT', 5);
|
||||
|
||||
/**
|
||||
* Behavior switch for Services_JSON::decode()
|
||||
*/
|
||||
define('SERVICES_JSON_LOOSE_TYPE', 16);
|
||||
|
||||
/**
|
||||
* Behavior switch for Services_JSON::decode()
|
||||
*/
|
||||
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
|
||||
|
||||
/**
|
||||
* Converts to and from JSON format.
|
||||
*
|
||||
* Brief example of use:
|
||||
*
|
||||
* <code>
|
||||
* // create a new instance of Services_JSON
|
||||
* $json = new Services_JSON();
|
||||
*
|
||||
* // convert a complexe value to JSON notation, and send it to the browser
|
||||
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
|
||||
* $output = $json->encode($value);
|
||||
*
|
||||
* print($output);
|
||||
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
|
||||
*
|
||||
* // accept incoming POST data, assumed to be in JSON notation
|
||||
* $input = file_get_contents('php://input', 1000000);
|
||||
* $value = $json->decode($input);
|
||||
* </code>
|
||||
*/
|
||||
class Services_JSON
|
||||
{
|
||||
/**
|
||||
* constructs a new JSON instance
|
||||
*
|
||||
* @param int $use object behavior flags; combine with boolean-OR
|
||||
*
|
||||
* possible values:
|
||||
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
|
||||
* "{...}" syntax creates associative arrays
|
||||
* instead of objects in decode().
|
||||
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
|
||||
* Values which can't be encoded (e.g. resources)
|
||||
* appear as NULL instead of throwing errors.
|
||||
* By default, a deeply-nested resource will
|
||||
* bubble up with an error, so all return values
|
||||
* from encode() should be checked with isError()
|
||||
*/
|
||||
function Services_JSON($use = 0)
|
||||
{
|
||||
$this->use = $use;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a string from one UTF-16 char to one UTF-8 char
|
||||
*
|
||||
* Normally should be handled by mb_convert_encoding, but
|
||||
* provides a slower PHP-only method for installations
|
||||
* that lack the multibye string extension.
|
||||
*
|
||||
* @param string $utf16 UTF-16 character
|
||||
* @return string UTF-8 character
|
||||
* @access private
|
||||
*/
|
||||
function utf162utf8($utf16)
|
||||
{
|
||||
// oh please oh please oh please oh please oh please
|
||||
if(function_exists('mb_convert_encoding')) {
|
||||
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
|
||||
}
|
||||
|
||||
$bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
|
||||
|
||||
switch(true) {
|
||||
case ((0x7F & $bytes) == $bytes):
|
||||
// this case should never be reached, because we are in ASCII range
|
||||
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
return chr(0x7F & $bytes);
|
||||
|
||||
case (0x07FF & $bytes) == $bytes:
|
||||
// return a 2-byte UTF-8 character
|
||||
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
return chr(0xC0 | (($bytes >> 6) & 0x1F))
|
||||
. chr(0x80 | ($bytes & 0x3F));
|
||||
|
||||
case (0xFFFF & $bytes) == $bytes:
|
||||
// return a 3-byte UTF-8 character
|
||||
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
return chr(0xE0 | (($bytes >> 12) & 0x0F))
|
||||
. chr(0x80 | (($bytes >> 6) & 0x3F))
|
||||
. chr(0x80 | ($bytes & 0x3F));
|
||||
}
|
||||
|
||||
// ignoring UTF-32 for now, sorry
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a string from one UTF-8 char to one UTF-16 char
|
||||
*
|
||||
* Normally should be handled by mb_convert_encoding, but
|
||||
* provides a slower PHP-only method for installations
|
||||
* that lack the multibye string extension.
|
||||
*
|
||||
* @param string $utf8 UTF-8 character
|
||||
* @return string UTF-16 character
|
||||
* @access private
|
||||
*/
|
||||
function utf82utf16($utf8)
|
||||
{
|
||||
// oh please oh please oh please oh please oh please
|
||||
if(function_exists('mb_convert_encoding')) {
|
||||
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
|
||||
}
|
||||
|
||||
switch(strlen($utf8)) {
|
||||
case 1:
|
||||
// this case should never be reached, because we are in ASCII range
|
||||
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
return $utf8;
|
||||
|
||||
case 2:
|
||||
// return a UTF-16 character from a 2-byte UTF-8 char
|
||||
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
return chr(0x07 & (ord($utf8{0}) >> 2))
|
||||
. chr((0xC0 & (ord($utf8{0}) << 6))
|
||||
| (0x3F & ord($utf8{1})));
|
||||
|
||||
case 3:
|
||||
// return a UTF-16 character from a 3-byte UTF-8 char
|
||||
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
return chr((0xF0 & (ord($utf8{0}) << 4))
|
||||
| (0x0F & (ord($utf8{1}) >> 2)))
|
||||
. chr((0xC0 & (ord($utf8{1}) << 6))
|
||||
| (0x7F & ord($utf8{2})));
|
||||
}
|
||||
|
||||
// ignoring UTF-32 for now, sorry
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* encodes an arbitrary variable into JSON format
|
||||
*
|
||||
* @param mixed $var any number, boolean, string, array, or object to be encoded.
|
||||
* see argument 1 to Services_JSON() above for array-parsing behavior.
|
||||
* if var is a strng, note that encode() always expects it
|
||||
* to be in ASCII or UTF-8 format!
|
||||
*
|
||||
* @return mixed JSON string representation of input var or an error if a problem occurs
|
||||
* @access public
|
||||
*/
|
||||
function encode($var)
|
||||
{
|
||||
switch (gettype($var)) {
|
||||
case 'boolean':
|
||||
return $var ? 'true' : 'false';
|
||||
|
||||
case 'NULL':
|
||||
return 'null';
|
||||
|
||||
case 'integer':
|
||||
return (int) $var;
|
||||
|
||||
case 'double':
|
||||
case 'float':
|
||||
return (float) $var;
|
||||
|
||||
case 'string':
|
||||
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
|
||||
$ascii = '';
|
||||
$strlen_var = strlen($var);
|
||||
|
||||
/*
|
||||
* Iterate over every character in the string,
|
||||
* escaping with a slash or encoding to UTF-8 where necessary
|
||||
*/
|
||||
for ($c = 0; $c < $strlen_var; ++$c) {
|
||||
|
||||
$ord_var_c = ord($var{$c});
|
||||
|
||||
switch (true) {
|
||||
case $ord_var_c == 0x08:
|
||||
$ascii .= '\b';
|
||||
break;
|
||||
case $ord_var_c == 0x09:
|
||||
$ascii .= '\t';
|
||||
break;
|
||||
case $ord_var_c == 0x0A:
|
||||
$ascii .= '\n';
|
||||
break;
|
||||
case $ord_var_c == 0x0C:
|
||||
$ascii .= '\f';
|
||||
break;
|
||||
case $ord_var_c == 0x0D:
|
||||
$ascii .= '\r';
|
||||
break;
|
||||
|
||||
case $ord_var_c == 0x22:
|
||||
case $ord_var_c == 0x2F:
|
||||
case $ord_var_c == 0x5C:
|
||||
// double quote, slash, slosh
|
||||
$ascii .= '\\'.$var{$c};
|
||||
break;
|
||||
|
||||
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
|
||||
// characters U-00000000 - U-0000007F (same as ASCII)
|
||||
$ascii .= $var{$c};
|
||||
break;
|
||||
|
||||
case (($ord_var_c & 0xE0) == 0xC0):
|
||||
// characters U-00000080 - U-000007FF, mask 110XXXXX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$char = pack('C*', $ord_var_c, ord($var{$c + 1}));
|
||||
$c += 1;
|
||||
$utf16 = $this->utf82utf16($char);
|
||||
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
||||
break;
|
||||
|
||||
case (($ord_var_c & 0xF0) == 0xE0):
|
||||
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$char = pack('C*', $ord_var_c,
|
||||
ord($var{$c + 1}),
|
||||
ord($var{$c + 2}));
|
||||
$c += 2;
|
||||
$utf16 = $this->utf82utf16($char);
|
||||
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
||||
break;
|
||||
|
||||
case (($ord_var_c & 0xF8) == 0xF0):
|
||||
// characters U-00010000 - U-001FFFFF, mask 11110XXX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$char = pack('C*', $ord_var_c,
|
||||
ord($var{$c + 1}),
|
||||
ord($var{$c + 2}),
|
||||
ord($var{$c + 3}));
|
||||
$c += 3;
|
||||
$utf16 = $this->utf82utf16($char);
|
||||
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
||||
break;
|
||||
|
||||
case (($ord_var_c & 0xFC) == 0xF8):
|
||||
// characters U-00200000 - U-03FFFFFF, mask 111110XX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$char = pack('C*', $ord_var_c,
|
||||
ord($var{$c + 1}),
|
||||
ord($var{$c + 2}),
|
||||
ord($var{$c + 3}),
|
||||
ord($var{$c + 4}));
|
||||
$c += 4;
|
||||
$utf16 = $this->utf82utf16($char);
|
||||
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
||||
break;
|
||||
|
||||
case (($ord_var_c & 0xFE) == 0xFC):
|
||||
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$char = pack('C*', $ord_var_c,
|
||||
ord($var{$c + 1}),
|
||||
ord($var{$c + 2}),
|
||||
ord($var{$c + 3}),
|
||||
ord($var{$c + 4}),
|
||||
ord($var{$c + 5}));
|
||||
$c += 5;
|
||||
$utf16 = $this->utf82utf16($char);
|
||||
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return '"'.$ascii.'"';
|
||||
|
||||
case 'array':
|
||||
/*
|
||||
* As per JSON spec if any array key is not an integer
|
||||
* we must treat the the whole array as an object. We
|
||||
* also try to catch a sparsely populated associative
|
||||
* array with numeric keys here because some JS engines
|
||||
* will create an array with empty indexes up to
|
||||
* max_index which can cause memory issues and because
|
||||
* the keys, which may be relevant, will be remapped
|
||||
* otherwise.
|
||||
*
|
||||
* As per the ECMA and JSON specification an object may
|
||||
* have any string as a property. Unfortunately due to
|
||||
* a hole in the ECMA specification if the key is a
|
||||
* ECMA reserved word or starts with a digit the
|
||||
* parameter is only accessible using ECMAScript's
|
||||
* bracket notation.
|
||||
*/
|
||||
|
||||
// treat as a JSON object
|
||||
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
|
||||
$properties = array_map(array($this, 'name_value'),
|
||||
array_keys($var),
|
||||
array_values($var));
|
||||
|
||||
foreach($properties as $property) {
|
||||
if(Services_JSON::isError($property)) {
|
||||
return $property;
|
||||
}
|
||||
}
|
||||
|
||||
return '{' . join(',', $properties) . '}';
|
||||
}
|
||||
|
||||
// treat it like a regular array
|
||||
$elements = array_map(array($this, 'encode'), $var);
|
||||
|
||||
foreach($elements as $element) {
|
||||
if(Services_JSON::isError($element)) {
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
|
||||
return '[' . join(',', $elements) . ']';
|
||||
|
||||
case 'object':
|
||||
$vars = get_object_vars($var);
|
||||
|
||||
$properties = array_map(array($this, 'name_value'),
|
||||
array_keys($vars),
|
||||
array_values($vars));
|
||||
|
||||
foreach($properties as $property) {
|
||||
if(Services_JSON::isError($property)) {
|
||||
return $property;
|
||||
}
|
||||
}
|
||||
|
||||
return '{' . join(',', $properties) . '}';
|
||||
|
||||
default:
|
||||
return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
|
||||
? 'null'
|
||||
: new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* array-walking function for use in generating JSON-formatted name-value pairs
|
||||
*
|
||||
* @param string $name name of key to use
|
||||
* @param mixed $value reference to an array element to be encoded
|
||||
*
|
||||
* @return string JSON-formatted name-value pair, like '"name":value'
|
||||
* @access private
|
||||
*/
|
||||
function name_value($name, $value)
|
||||
{
|
||||
$encoded_value = $this->encode($value);
|
||||
|
||||
if(Services_JSON::isError($encoded_value)) {
|
||||
return $encoded_value;
|
||||
}
|
||||
|
||||
return $this->encode(strval($name)) . ':' . $encoded_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* reduce a string by removing leading and trailing comments and whitespace
|
||||
*
|
||||
* @param $str string string value to strip of comments and whitespace
|
||||
*
|
||||
* @return string string value stripped of comments and whitespace
|
||||
* @access private
|
||||
*/
|
||||
function reduce_string($str)
|
||||
{
|
||||
$str = preg_replace(array(
|
||||
|
||||
// eliminate single line comments in '// ...' form
|
||||
'#^\s*//(.+)$#m',
|
||||
|
||||
// eliminate multi-line comments in '/* ... */' form, at start of string
|
||||
'#^\s*/\*(.+)\*/#Us',
|
||||
|
||||
// eliminate multi-line comments in '/* ... */' form, at end of string
|
||||
'#/\*(.+)\*/\s*$#Us'
|
||||
|
||||
), '', $str);
|
||||
|
||||
// eliminate extraneous space
|
||||
return trim($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* decodes a JSON string into appropriate variable
|
||||
*
|
||||
* @param string $str JSON-formatted string
|
||||
*
|
||||
* @return mixed number, boolean, string, array, or object
|
||||
* corresponding to given JSON input string.
|
||||
* See argument 1 to Services_JSON() above for object-output behavior.
|
||||
* Note that decode() always returns strings
|
||||
* in ASCII or UTF-8 format!
|
||||
* @access public
|
||||
*/
|
||||
function decode($str)
|
||||
{
|
||||
$str = $this->reduce_string($str);
|
||||
|
||||
switch (strtolower($str)) {
|
||||
case 'true':
|
||||
return true;
|
||||
|
||||
case 'false':
|
||||
return false;
|
||||
|
||||
case 'null':
|
||||
return null;
|
||||
|
||||
default:
|
||||
$m = array();
|
||||
|
||||
if (is_numeric($str)) {
|
||||
// Lookie-loo, it's a number
|
||||
|
||||
// This would work on its own, but I'm trying to be
|
||||
// good about returning integers where appropriate:
|
||||
// return (float)$str;
|
||||
|
||||
// Return float or int, as appropriate
|
||||
return ((float)$str == (integer)$str)
|
||||
? (integer)$str
|
||||
: (float)$str;
|
||||
|
||||
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
|
||||
// STRINGS RETURNED IN UTF-8 FORMAT
|
||||
$delim = substr($str, 0, 1);
|
||||
$chrs = substr($str, 1, -1);
|
||||
$utf8 = '';
|
||||
$strlen_chrs = strlen($chrs);
|
||||
|
||||
for ($c = 0; $c < $strlen_chrs; ++$c) {
|
||||
|
||||
$substr_chrs_c_2 = substr($chrs, $c, 2);
|
||||
$ord_chrs_c = ord($chrs{$c});
|
||||
|
||||
switch (true) {
|
||||
case $substr_chrs_c_2 == '\b':
|
||||
$utf8 .= chr(0x08);
|
||||
++$c;
|
||||
break;
|
||||
case $substr_chrs_c_2 == '\t':
|
||||
$utf8 .= chr(0x09);
|
||||
++$c;
|
||||
break;
|
||||
case $substr_chrs_c_2 == '\n':
|
||||
$utf8 .= chr(0x0A);
|
||||
++$c;
|
||||
break;
|
||||
case $substr_chrs_c_2 == '\f':
|
||||
$utf8 .= chr(0x0C);
|
||||
++$c;
|
||||
break;
|
||||
case $substr_chrs_c_2 == '\r':
|
||||
$utf8 .= chr(0x0D);
|
||||
++$c;
|
||||
break;
|
||||
|
||||
case $substr_chrs_c_2 == '\\"':
|
||||
case $substr_chrs_c_2 == '\\\'':
|
||||
case $substr_chrs_c_2 == '\\\\':
|
||||
case $substr_chrs_c_2 == '\\/':
|
||||
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
|
||||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
|
||||
$utf8 .= $chrs{++$c};
|
||||
}
|
||||
break;
|
||||
|
||||
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
|
||||
// single, escaped unicode character
|
||||
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
|
||||
. chr(hexdec(substr($chrs, ($c + 4), 2)));
|
||||
$utf8 .= $this->utf162utf8($utf16);
|
||||
$c += 5;
|
||||
break;
|
||||
|
||||
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
|
||||
$utf8 .= $chrs{$c};
|
||||
break;
|
||||
|
||||
case ($ord_chrs_c & 0xE0) == 0xC0:
|
||||
// characters U-00000080 - U-000007FF, mask 110XXXXX
|
||||
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$utf8 .= substr($chrs, $c, 2);
|
||||
++$c;
|
||||
break;
|
||||
|
||||
case ($ord_chrs_c & 0xF0) == 0xE0:
|
||||
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$utf8 .= substr($chrs, $c, 3);
|
||||
$c += 2;
|
||||
break;
|
||||
|
||||
case ($ord_chrs_c & 0xF8) == 0xF0:
|
||||
// characters U-00010000 - U-001FFFFF, mask 11110XXX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$utf8 .= substr($chrs, $c, 4);
|
||||
$c += 3;
|
||||
break;
|
||||
|
||||
case ($ord_chrs_c & 0xFC) == 0xF8:
|
||||
// characters U-00200000 - U-03FFFFFF, mask 111110XX
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$utf8 .= substr($chrs, $c, 5);
|
||||
$c += 4;
|
||||
break;
|
||||
|
||||
case ($ord_chrs_c & 0xFE) == 0xFC:
|
||||
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
|
||||
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
$utf8 .= substr($chrs, $c, 6);
|
||||
$c += 5;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $utf8;
|
||||
|
||||
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
|
||||
// array, or object notation
|
||||
|
||||
if ($str{0} == '[') {
|
||||
$stk = array(SERVICES_JSON_IN_ARR);
|
||||
$arr = array();
|
||||
} else {
|
||||
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
||||
$stk = array(SERVICES_JSON_IN_OBJ);
|
||||
$obj = array();
|
||||
} else {
|
||||
$stk = array(SERVICES_JSON_IN_OBJ);
|
||||
$obj = new stdClass();
|
||||
}
|
||||
}
|
||||
|
||||
array_push($stk, array('what' => SERVICES_JSON_SLICE,
|
||||
'where' => 0,
|
||||
'delim' => false));
|
||||
|
||||
$chrs = substr($str, 1, -1);
|
||||
$chrs = $this->reduce_string($chrs);
|
||||
|
||||
if ($chrs == '') {
|
||||
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
||||
return $arr;
|
||||
|
||||
} else {
|
||||
return $obj;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//print("\nparsing {$chrs}\n");
|
||||
|
||||
$strlen_chrs = strlen($chrs);
|
||||
|
||||
for ($c = 0; $c <= $strlen_chrs; ++$c) {
|
||||
|
||||
$top = end($stk);
|
||||
$substr_chrs_c_2 = substr($chrs, $c, 2);
|
||||
|
||||
if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
|
||||
// found a comma that is not inside a string, array, etc.,
|
||||
// OR we've reached the end of the character list
|
||||
$slice = substr($chrs, $top['where'], ($c - $top['where']));
|
||||
array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
|
||||
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
||||
|
||||
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
||||
// we are in an array, so just push an element onto the stack
|
||||
array_push($arr, $this->decode($slice));
|
||||
|
||||
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
|
||||
// we are in an object, so figure
|
||||
// out the property name and set an
|
||||
// element in an associative array,
|
||||
// for now
|
||||
$parts = array();
|
||||
|
||||
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
|
||||
// "name":value pair
|
||||
$key = $this->decode($parts[1]);
|
||||
$val = $this->decode($parts[2]);
|
||||
|
||||
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
||||
$obj[$key] = $val;
|
||||
} else {
|
||||
$obj->$key = $val;
|
||||
}
|
||||
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
|
||||
// name:value pair, where name is unquoted
|
||||
$key = $parts[1];
|
||||
$val = $this->decode($parts[2]);
|
||||
|
||||
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
||||
$obj[$key] = $val;
|
||||
} else {
|
||||
$obj->$key = $val;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
|
||||
// found a quote, and we are not inside a string
|
||||
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
|
||||
//print("Found start of string at {$c}\n");
|
||||
|
||||
} elseif (($chrs{$c} == $top['delim']) &&
|
||||
($top['what'] == SERVICES_JSON_IN_STR) &&
|
||||
(($chrs{$c - 1} != '\\') ||
|
||||
($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) {
|
||||
// found a quote, we're in a string, and it's not escaped
|
||||
array_pop($stk);
|
||||
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
|
||||
|
||||
} elseif (($chrs{$c} == '[') &&
|
||||
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
||||
// found a left-bracket, and we are in an array, object, or slice
|
||||
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
|
||||
//print("Found start of array at {$c}\n");
|
||||
|
||||
} elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
|
||||
// found a right-bracket, and we're in an array
|
||||
array_pop($stk);
|
||||
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
||||
|
||||
} elseif (($chrs{$c} == '{') &&
|
||||
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
||||
// found a left-brace, and we are in an array, object, or slice
|
||||
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
|
||||
//print("Found start of object at {$c}\n");
|
||||
|
||||
} elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
|
||||
// found a right-brace, and we're in an object
|
||||
array_pop($stk);
|
||||
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
||||
|
||||
} elseif (($substr_chrs_c_2 == '/*') &&
|
||||
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
||||
// found a comment start, and we are in an array, object, or slice
|
||||
array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
|
||||
$c++;
|
||||
//print("Found start of comment at {$c}\n");
|
||||
|
||||
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
|
||||
// found a comment end, and we're in one now
|
||||
array_pop($stk);
|
||||
$c++;
|
||||
|
||||
for ($i = $top['where']; $i <= $c; ++$i)
|
||||
$chrs = substr_replace($chrs, ' ', $i, 1);
|
||||
|
||||
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
||||
return $arr;
|
||||
|
||||
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
|
||||
return $obj;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Ultimately, this should just call PEAR::isError()
|
||||
*/
|
||||
function isError($data, $code = null)
|
||||
{
|
||||
if (class_exists('pear')) {
|
||||
return PEAR::isError($data, $code);
|
||||
} elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
|
||||
is_subclass_of($data, 'services_json_error'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (class_exists('PEAR_Error')) {
|
||||
|
||||
class Services_JSON_Error extends PEAR_Error
|
||||
{
|
||||
function Services_JSON_Error($message = 'unknown error', $code = null,
|
||||
$mode = null, $options = null, $userinfo = null)
|
||||
{
|
||||
parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/**
|
||||
* @todo Ultimately, this class shall be descended from PEAR_Error
|
||||
*/
|
||||
class Services_JSON_Error
|
||||
{
|
||||
function Services_JSON_Error($message = 'unknown error', $code = null,
|
||||
$mode = null, $options = null, $userinfo = null)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
253
ext/chatbox/php/yshout.class.php
Normal file
@ -0,0 +1,253 @@
|
||||
<?
|
||||
|
||||
class YShout {
|
||||
|
||||
function YShout($path, $admin = false) {
|
||||
global $storage;
|
||||
// Redo to check for folders or just not break, because nonexistent files should be allowed.
|
||||
// if (!file_exists($path)) error('That file does not exist.');
|
||||
|
||||
$this->storage = new $storage($path, true);
|
||||
$this->admin = $admin;
|
||||
}
|
||||
|
||||
function posts() {
|
||||
global $null;
|
||||
$this->storage->open();
|
||||
$s = $this->storage->load();
|
||||
$this->storage->close($null);
|
||||
|
||||
if ($s)
|
||||
return $s['posts'];
|
||||
}
|
||||
|
||||
function info() {
|
||||
global $null;
|
||||
$s = $this->storage->open(true);
|
||||
|
||||
$this->storage->close($null);
|
||||
|
||||
if ($s)
|
||||
return $s['info'];
|
||||
}
|
||||
|
||||
function postsAfter($ts) {
|
||||
$allPosts = $this->posts();
|
||||
|
||||
$posts = array();
|
||||
|
||||
/* for ($i = sizeof($allPosts) - 1; $i > -1; $i--) {
|
||||
$post = $allPosts[$i];
|
||||
|
||||
if ($post['timestamp'] > $ts)
|
||||
$posts[] = $post;
|
||||
} */
|
||||
|
||||
foreach($allPosts as $post) {
|
||||
if ($post['timestamp'] > $ts)
|
||||
$posts[] = $post;
|
||||
}
|
||||
|
||||
$this->postProcess($posts);
|
||||
return $posts;
|
||||
}
|
||||
|
||||
function latestPosts($num) {
|
||||
$allPosts = $this->posts();
|
||||
$posts = array_slice($allPosts, -$num, $num);
|
||||
|
||||
$this->postProcess($posts);
|
||||
return array_values($posts);
|
||||
}
|
||||
|
||||
function hasPostsAfter($ts) {
|
||||
$info = $this->info();
|
||||
$timestamp = $info['latestTimestamp'];
|
||||
return $timestamp > $ts;
|
||||
}
|
||||
|
||||
function post($nickname, $message) {
|
||||
global $prefs;
|
||||
|
||||
if ($this->banned(ip()) /* && !$this->admin*/) return false;
|
||||
|
||||
if (!$this->validate($message, $prefs['messageLength'])) return false;
|
||||
if (!$this->validate($nickname, $prefs['nicknameLength'])) return false;
|
||||
|
||||
$message = trim(clean($message));
|
||||
$nickname = trim(clean($nickname));
|
||||
|
||||
if ($message == '') return false;
|
||||
if ($nickname == '') return false;
|
||||
|
||||
$timestamp = ts();
|
||||
|
||||
$message = $this->censor($message);
|
||||
$nickname = $this->censor($nickname);
|
||||
|
||||
$post = array(
|
||||
'nickname' => $nickname,
|
||||
'message' => $message,
|
||||
'timestamp' => $timestamp,
|
||||
'admin' => $this->admin,
|
||||
'uid' => md5($timestamp . ' ' . $nickname),
|
||||
'adminInfo' => array(
|
||||
'ip' => ip()
|
||||
)
|
||||
);
|
||||
|
||||
$s = $this->storage->open(true);
|
||||
|
||||
$s['posts'][] = $post;
|
||||
|
||||
if (sizeof($s['posts']) > $prefs['history'])
|
||||
$this->truncate($s['posts']);
|
||||
|
||||
$s['info']['latestTimestamp'] = $post['timestamp'];
|
||||
|
||||
$this->storage->close($s);
|
||||
$this->postProcess($post);
|
||||
return $post;
|
||||
}
|
||||
|
||||
function truncate(&$array) {
|
||||
global $prefs;
|
||||
|
||||
$array = array_slice($array, -$prefs['history']);
|
||||
$array = array_values($array);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
global $null;
|
||||
|
||||
$this->storage->open(true);
|
||||
$this->storage->resetArray();
|
||||
// ? Scared to touch it... Misspelled though. Update: Touched! Used to be $nulls...
|
||||
$this->storage->close($null);
|
||||
}
|
||||
|
||||
function bans() {
|
||||
global $storage, $null;
|
||||
|
||||
$s = new $storage('yshout.bans');
|
||||
$s->open();
|
||||
$bans = $s->load();
|
||||
$s->close($null);
|
||||
|
||||
return $bans;
|
||||
}
|
||||
|
||||
function ban($ip, $nickname = '', $info = '') {
|
||||
global $storage;
|
||||
|
||||
$s = new $storage('yshout.bans');
|
||||
$bans = $s->open(true);
|
||||
|
||||
$bans[] = array(
|
||||
'ip' => $ip,
|
||||
'nickname' => $nickname,
|
||||
'info' => $info,
|
||||
'timestamp' => ts()
|
||||
);
|
||||
|
||||
$s->close($bans);
|
||||
}
|
||||
|
||||
function banned($ip) {
|
||||
global $storage, $null;
|
||||
|
||||
$s = new $storage('yshout.bans');
|
||||
$bans = $s->open(true);
|
||||
$s->close($null);
|
||||
|
||||
foreach($bans as $ban) {
|
||||
if ($ban['ip'] == $ip)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function unban($ip) {
|
||||
global $storage;
|
||||
|
||||
$s = new $storage('yshout.bans');
|
||||
$bans = $s->open(true);
|
||||
|
||||
foreach($bans as $key=>$value)
|
||||
if ($value['ip'] == $ip) {
|
||||
unset($bans[$key]);
|
||||
}
|
||||
|
||||
$bans = array_values($bans);
|
||||
$s->close($bans);
|
||||
|
||||
}
|
||||
|
||||
function unbanAll() {
|
||||
global $storage, $null;
|
||||
|
||||
$s = new $storage('yshout.bans');
|
||||
$s->open(true);
|
||||
$s->resetArray();
|
||||
$s->close($null);
|
||||
}
|
||||
|
||||
function delete($uid) {
|
||||
global $prefs, $storage;
|
||||
|
||||
|
||||
$s = $this->storage->open(true);
|
||||
|
||||
$posts = $s['posts'];
|
||||
|
||||
foreach($posts as $key=>$value) {
|
||||
if (!isset($value['uid']))
|
||||
unset($posts['key']);
|
||||
else
|
||||
if($value['uid'] == $uid)
|
||||
unset($posts[$key]);
|
||||
}
|
||||
|
||||
$s['posts'] = array_values($posts);
|
||||
$this->storage->close($s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validate($str, $maxLen) {
|
||||
return len($str) <= $maxLen;
|
||||
}
|
||||
|
||||
function censor($str) {
|
||||
global $prefs;
|
||||
|
||||
$cWords = explode(' ', $prefs['censorWords']);
|
||||
$words = explode(' ', $str);
|
||||
$endings = '|ed|es|ing|s|er|ers';
|
||||
$arrEndings = explode('|', $endings);
|
||||
|
||||
foreach ($cWords as $cWord) foreach ($words as $i=>$word) {
|
||||
$pattern = '/^(' . $cWord . ')+(' . $endings . ')\W*$/i';
|
||||
$words[$i] = preg_replace($pattern, str_repeat('*', strlen($word)), $word);
|
||||
}
|
||||
|
||||
return implode(' ', $words);
|
||||
}
|
||||
|
||||
function postProcess(&$post) {
|
||||
if (isset($post['message'])) {
|
||||
if ($this->banned($post['adminInfo']['ip'])) $post['banned'] = true;
|
||||
if (!$this->admin) unset($post['adminInfo']);
|
||||
} else {
|
||||
foreach($post as $key=>$value) {
|
||||
if ($this->banned($value['adminInfo']['ip'])) $post[$key]['banned'] = true;
|
||||
if (!$this->admin) unset($post[$key]['adminInfo']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
?>
|
74
ext/chatbox/preferences.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?
|
||||
// If you want to change the nickname, the line below is the one to modify.
|
||||
// Simply set $overrideNickname to whatever variable you want to appear as the nickname,
|
||||
// or leave it null to use the set nicknames.
|
||||
|
||||
$overrideNickname = null;
|
||||
|
||||
$storage = 'FileStorage';
|
||||
|
||||
function loadPrefs() {
|
||||
global $prefs, $storage, $null;
|
||||
$s = new $storage('yshout.prefs');
|
||||
$s->open();
|
||||
$prefs = $s->load();
|
||||
$s->close($null);
|
||||
}
|
||||
|
||||
function savePrefs($newPrefs) {
|
||||
global $prefs, $storage;
|
||||
|
||||
$s = new $storage('yshout.prefs');
|
||||
$s->open(true);
|
||||
$s->close($newPrefs);
|
||||
$prefs = $newPrefs;
|
||||
}
|
||||
|
||||
function resetPrefs() {
|
||||
$defaultPrefs = array(
|
||||
'password' => 'fortytwo', // The password for the CP
|
||||
|
||||
'refresh' => 6000, // Refresh rate
|
||||
|
||||
'logs' => 5, // Amount of different log files to allow
|
||||
'history' => 200, // Shouts to keep in history
|
||||
|
||||
'inverse' => false, // Inverse shoutbox / form on top
|
||||
|
||||
'truncate' => 15, // Truncate messages client-side
|
||||
'doTruncate' => true, // Truncate messages?
|
||||
|
||||
'timestamp' => 12, // Timestamp format 12- or 24-hour
|
||||
|
||||
'defaultNickname' => 'Nickname',
|
||||
'defaultMessage' => 'Message Text',
|
||||
'defaultSubmit' => 'Shout!',
|
||||
'showSubmit' => true,
|
||||
|
||||
'nicknameLength' => 25,
|
||||
'messageLength' => 175,
|
||||
|
||||
'nicknameSeparator' => ':',
|
||||
|
||||
'flood' => true,
|
||||
'floodTimeout' => 5000,
|
||||
'floodMessages' => 4,
|
||||
'floodDisable' => 8000,
|
||||
'floodDelete' => false,
|
||||
|
||||
'autobanFlood' => 0, // Autoban people for flooding after X messages
|
||||
|
||||
'censorWords' => 'fuck shit bitch ass',
|
||||
|
||||
'postFormLink' => 'history',
|
||||
|
||||
'info' => 'inline'
|
||||
);
|
||||
|
||||
savePrefs($defaultPrefs);
|
||||
}
|
||||
|
||||
resetPrefs();
|
||||
//loadPrefs();
|
||||
|
||||
?>
|
BIN
ext/chatbox/smileys/biggrin.gif
Normal file
After Width: | Height: | Size: 172 B |
BIN
ext/chatbox/smileys/confused.gif
Normal file
After Width: | Height: | Size: 171 B |
BIN
ext/chatbox/smileys/cool.gif
Normal file
After Width: | Height: | Size: 172 B |
BIN
ext/chatbox/smileys/cry.gif
Normal file
After Width: | Height: | Size: 498 B |
BIN
ext/chatbox/smileys/eek.gif
Normal file
After Width: | Height: | Size: 170 B |
BIN
ext/chatbox/smileys/evil.gif
Normal file
After Width: | Height: | Size: 236 B |
BIN
ext/chatbox/smileys/lol.gif
Normal file
After Width: | Height: | Size: 336 B |
BIN
ext/chatbox/smileys/mad.gif
Normal file
After Width: | Height: | Size: 174 B |
BIN
ext/chatbox/smileys/mrgreen.gif
Normal file
After Width: | Height: | Size: 349 B |
BIN
ext/chatbox/smileys/neutral.gif
Normal file
After Width: | Height: | Size: 171 B |
BIN
ext/chatbox/smileys/razz.gif
Normal file
After Width: | Height: | Size: 176 B |
BIN
ext/chatbox/smileys/redface.gif
Normal file
After Width: | Height: | Size: 650 B |
BIN
ext/chatbox/smileys/rolleyes.gif
Normal file
After Width: | Height: | Size: 485 B |
BIN
ext/chatbox/smileys/sad.gif
Normal file
After Width: | Height: | Size: 171 B |
BIN
ext/chatbox/smileys/smile.gif
Normal file
After Width: | Height: | Size: 174 B |
BIN
ext/chatbox/smileys/surprised.gif
Normal file
After Width: | Height: | Size: 174 B |
BIN
ext/chatbox/smileys/twisted.gif
Normal file
After Width: | Height: | Size: 238 B |
BIN
ext/chatbox/smileys/wink.gif
Normal file
After Width: | Height: | Size: 170 B |
39
ext/chatbox/yshout.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?
|
||||
error_reporting(E_ALL);
|
||||
ob_start();
|
||||
set_error_handler('errorOccurred');
|
||||
include 'include.php';
|
||||
if (isset($_POST['reqFor']))
|
||||
switch($_POST['reqFor']) {
|
||||
case 'shout':
|
||||
|
||||
$ajax = new AjaxCall();
|
||||
$ajax->process();
|
||||
break;
|
||||
|
||||
case 'history':
|
||||
|
||||
// echo $_POST['log'];
|
||||
$ajax = new AjaxCall($_POST['log']);
|
||||
$ajax->process();
|
||||
break;
|
||||
|
||||
default:
|
||||
exit;
|
||||
}
|
||||
else
|
||||
include 'example.html';
|
||||
|
||||
function errorOccurred($num, $str, $file, $line) {
|
||||
$err = array (
|
||||
'yError' => "$str. \n File: $file \n Line: $line"
|
||||
);
|
||||
|
||||
if (function_exists('jsonEncode'))
|
||||
echo jsonEncode($err);
|
||||
else
|
||||
echo $err['yError'];
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
@ -42,6 +42,7 @@ class Comment {
|
||||
$this->owner_id = $row['user_id'];
|
||||
$this->owner_name = $row['user_name'];
|
||||
$this->owner_email = $row['user_email']; // deprecated
|
||||
$this->owner_class = $row['user_class'];
|
||||
$this->comment = $row['comment'];
|
||||
$this->comment_id = $row['comment_id'];
|
||||
$this->image_id = $row['image_id'];
|
||||
@ -335,7 +336,7 @@ class CommentList extends Extension {
|
||||
global $database;
|
||||
$rows = $database->get_all("
|
||||
SELECT
|
||||
users.id as user_id, users.name as user_name, users.email as user_email,
|
||||
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
|
||||
@ -356,7 +357,7 @@ class CommentList extends Extension {
|
||||
global $database;
|
||||
$rows = $database->get_all("
|
||||
SELECT
|
||||
users.id as user_id, users.name as user_name, users.email as user_email,
|
||||
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
|
||||
@ -379,7 +380,7 @@ class CommentList extends Extension {
|
||||
$i_image_id = int_escape($image_id);
|
||||
$rows = $database->get_all("
|
||||
SELECT
|
||||
users.id as user_id, users.name as user_name, users.email as user_email,
|
||||
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
|
||||
@ -546,7 +547,7 @@ class CommentList extends Extension {
|
||||
$snippet = substr($comment, 0, 100);
|
||||
$snippet = str_replace("\n", " ", $snippet);
|
||||
$snippet = str_replace("\r", " ", $snippet);
|
||||
log_info("comment", "Comment #$cid added to Image #$image_id: $snippet");
|
||||
log_info("comment", "Comment #$cid added to Image #$image_id: $snippet", false, array("image_id"=>$image_id, "comment_id"=>$cid));
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
|
@ -242,13 +242,14 @@ class CommentListTheme extends Themelet {
|
||||
$stripped_nonl = str_replace("\n", "\\n", substr($tfe->stripped, 0, 50));
|
||||
$stripped_nonl = str_replace("\r", "\\r", $stripped_nonl);
|
||||
|
||||
$hb = ($comment->owner_class == "hellbanned" ? "hb" : "");
|
||||
if($trim) {
|
||||
return '
|
||||
<div class="comment">
|
||||
'.$h_userlink.': '.$h_comment.'
|
||||
<a href="'.make_link('post/view/'.$i_image_id.'#c'.$i_comment_id).'">>>></a>
|
||||
return "
|
||||
<div class=\"comment $hb\">
|
||||
$h_userlink: $h_comment
|
||||
<a href=\"".make_link("post/view/$i_image_id#c$i_comment_id")."\">>>></a>
|
||||
</div>
|
||||
';
|
||||
";
|
||||
}
|
||||
else {
|
||||
$h_avatar = "";
|
||||
@ -262,15 +263,15 @@ class CommentListTheme extends Themelet {
|
||||
$h_del = $user->can("delete_comment") ?
|
||||
' - <a onclick="return confirm(\'Delete comment by '.$h_name.':\\n'.$stripped_nonl.'\');" '.
|
||||
'href="'.make_link('comment/delete/'.$i_comment_id.'/'.$i_image_id).'">Del</a>' : '';
|
||||
return '
|
||||
<div class="comment" id="c'.$i_comment_id.'">
|
||||
<div class="info">
|
||||
'.$h_avatar.'
|
||||
'.$h_timestamp.$h_reply.$h_ip.$h_del.'
|
||||
return "
|
||||
<div class=\"comment $hb\" id=\"c$i_comment_id\">
|
||||
<div class=\"info\">
|
||||
$h_avatar
|
||||
$h_timestamp$h_reply$h_ip$h_del
|
||||
</div>
|
||||
'.$h_userlink.': '.$h_comment.'
|
||||
$h_userlink: $h_comment
|
||||
</div>
|
||||
';
|
||||
";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
440
ext/cron_uploader/main.php
Normal file
@ -0,0 +1,440 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Cron Uploader
|
||||
* Author: YaoiFox <admin@yaoifox.com>
|
||||
* Link: http://www.yaoifox.com/
|
||||
* License: GPLv2
|
||||
* Description: Uploads images automatically using Cron Jobs
|
||||
* Documentation: Installation guide: activate this extension and navigate to www.yoursite.com/cron_upload
|
||||
*/
|
||||
class CronUploader extends Extension {
|
||||
// TODO: Checkbox option to only allow localhost + a list of additional IP adresses that can be set in /cron_upload
|
||||
// TODO: Change logging to MySQL + display log at /cron_upload
|
||||
// TODO: Move stuff to theme.php
|
||||
|
||||
/**
|
||||
* Lists all log events this session
|
||||
* @var string
|
||||
*/
|
||||
private $upload_info = "";
|
||||
|
||||
/**
|
||||
* Lists all files & info required to upload.
|
||||
* @var array
|
||||
*/
|
||||
private $image_queue = array();
|
||||
|
||||
/**
|
||||
* Cron Uploader root directory
|
||||
* @var string
|
||||
*/
|
||||
private $root_dir = "";
|
||||
|
||||
/**
|
||||
* Checks if the cron upload page has been accessed
|
||||
* and initializes the upload.
|
||||
* @param PageRequestEvent $event
|
||||
*/
|
||||
public function onPageRequest(PageRequestEvent $event) {
|
||||
global $config, $user;
|
||||
|
||||
if ($event->page_matches ( "cron_upload" )) {
|
||||
$key = $config->get_string ( "cron_uploader_key", "" );
|
||||
|
||||
// If the key is in the url, upload
|
||||
if ($key != "" && $event->get_arg ( 0 ) == $key) {
|
||||
// log in as admin
|
||||
$this->process_upload(); // Start upload
|
||||
}
|
||||
else if ($user->is_admin()) {
|
||||
$this->set_dir();
|
||||
$this->display_documentation();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private function display_documentation() {
|
||||
global $config, $page;
|
||||
$this->set_dir(); // Determines path to cron_uploader_dir
|
||||
|
||||
|
||||
$queue_dir = $this->root_dir . "/queue";
|
||||
$uploaded_dir = $this->root_dir . "/uploaded";
|
||||
$failed_dir = $this->root_dir . "/failed_to_upload";
|
||||
|
||||
$queue_dirinfo = $this->scan_dir($queue_dir);
|
||||
$uploaded_dirinfo = $this->scan_dir($uploaded_dir);
|
||||
$failed_dirinfo = $this->scan_dir($failed_dir);
|
||||
|
||||
$cron_url = make_http(make_link("/cron_upload/" . $config->get_string('cron_uploader_key', 'invalid key' )));
|
||||
$cron_cmd = "curl -f $cron_url";
|
||||
$log_path = $this->root_dir . "/uploads.log";
|
||||
|
||||
$info_html = "<b>Information</b>
|
||||
<br>
|
||||
<table style='width:470px;'>
|
||||
<tr>
|
||||
<td style='width:90px;'><b>Directory</b></td>
|
||||
<td style='width:90px;'><b>Files</b></td>
|
||||
<td style='width:90px;'><b>Size (MB)</b></td>
|
||||
<td style='width:200px;'><b>Directory Path</b></td>
|
||||
</tr><tr>
|
||||
<td>Queue</td>
|
||||
<td>{$queue_dirinfo['total_files']}</td>
|
||||
<td>{$queue_dirinfo['total_mb']}</td>
|
||||
<td><input type='text' style='width:150px;' value='$queue_dir'></td>
|
||||
</tr><tr>
|
||||
<td>Uploaded</td>
|
||||
<td>{$uploaded_dirinfo['total_files']}</td>
|
||||
<td>{$uploaded_dirinfo['total_mb']}</td>
|
||||
<td><input type='text' style='width:150px;' value='$uploaded_dir'></td>
|
||||
</tr><tr>
|
||||
<td>Failed</td>
|
||||
<td>{$failed_dirinfo['total_files']}</td>
|
||||
<td>{$failed_dirinfo['total_mb']}</td>
|
||||
<td><input type='text' style='width:150px;' value='$failed_dir'></td>
|
||||
</tr></table>
|
||||
|
||||
<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br>
|
||||
Create a cron job with the command above.<br/>
|
||||
Read the documentation if you're not sure what to do.<br>";
|
||||
|
||||
$install_html = "
|
||||
This cron uploader is fairly easy to use but has to be configured first.
|
||||
<br />1. Install & activate this plugin.
|
||||
<br />
|
||||
<br />2. Upload your images you want to be uploaded to the queue directory using your FTP client.
|
||||
<br />(<b>$queue_dir</b>)
|
||||
<br />This also supports directory names to be used as tags.
|
||||
<br />
|
||||
<br />3. Go to the Board Config to the Cron Uploader menu and copy the Cron Command.
|
||||
<br />(<b>$cron_cmd</b>)
|
||||
<br />
|
||||
<br />4. Create a cron job or something else that can open a url on specified times.
|
||||
<br />If you're not sure how to do this, you can give the command to your web host and you can ask them to create the cron job for you.
|
||||
<br />When you create the cron job, you choose when to upload new images.
|
||||
<br />
|
||||
<br />5. When the cron command is set up, your image queue will upload x file(s) at the specified times.
|
||||
<br />You can see any uploads or failed uploads in the log file. (<b>$log_path</b>)
|
||||
<br />Your uploaded images will be moved to the 'uploaded' directory, it's recommended that you remove everything out of this directory from time to time.
|
||||
<br />(<b>$uploaded_dir</b>)
|
||||
<br />
|
||||
<br />Whenever the url in that cron job command is opened, a new file will upload from the queue.
|
||||
<br />So when you want to manually upload an image, all you have to do is open the link once.
|
||||
<br />This link can be found under 'Cron Command' in the board config, just remove the 'wget ' part and only the url remains.
|
||||
<br />(<b>$cron_url</b>)";
|
||||
|
||||
|
||||
$block = new Block("Cron Uploader", $info_html, "main", 10);
|
||||
$block_install = new Block("Installation Guide", $install_html, "main", 20);
|
||||
$page->add_block($block);
|
||||
$page->add_block($block_install);
|
||||
}
|
||||
|
||||
public function onInitExt(InitExtEvent $event) {
|
||||
global $config;
|
||||
// Set default values
|
||||
$key = $this->generate_key ();
|
||||
|
||||
$config->set_default_int ( 'cron_uploader_count', 1 );
|
||||
$config->set_default_string ( 'cron_uploader_key', $key );
|
||||
$this->set_dir();
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event) {
|
||||
global $config;
|
||||
$this->set_dir();
|
||||
|
||||
$cron_url = make_http(make_link("/cron_upload/" . $config->get_string('cron_uploader_key', 'invalid key' )));
|
||||
$cron_cmd = "wget $cron_url";
|
||||
$documentation_link = make_http(make_link("cron_upload"));
|
||||
|
||||
$sb = new SetupBlock ( "Cron Uploader" );
|
||||
$sb->add_label ( "<b>Settings</b><br>" );
|
||||
$sb->add_int_option ( "cron_uploader_count", "How many to upload each time" );
|
||||
$sb->add_text_option ( "cron_uploader_dir", "<br>Set Cron Uploader root directory<br>");
|
||||
|
||||
$sb->add_label ("<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br>
|
||||
Create a cron job with the command above.<br/>
|
||||
<a href='$documentation_link'>Read the documentation</a> if you're not sure what to do.");
|
||||
|
||||
$event->panel->add_block ( $sb );
|
||||
}
|
||||
|
||||
/*
|
||||
* Generates a unique key for the website to prevent unauthorized access.
|
||||
*/
|
||||
private function generate_key() {
|
||||
$length = 20;
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$randomString = '';
|
||||
|
||||
for($i = 0; $i < $length; $i ++) {
|
||||
$randomString .= $characters [rand ( 0, strlen ( $characters ) - 1 )];
|
||||
}
|
||||
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the directory for the image queue. If no directory was given, set it to the default directory.
|
||||
*/
|
||||
private function set_dir() {
|
||||
global $config;
|
||||
// Determine directory (none = default)
|
||||
|
||||
$dir = $config->get_string("cron_uploader_dir", "");
|
||||
|
||||
// Sets new default dir if not in config yet/anymore
|
||||
if ($dir == "") {
|
||||
$dir = $_SERVER ['DOCUMENT_ROOT'] . "/data/cron_uploader";
|
||||
$config->set_string ('cron_uploader_dir', $dir);
|
||||
}
|
||||
|
||||
// Make the directory if it doesn't exist yet
|
||||
if (!is_dir($dir . "/queue/"))
|
||||
mkdir ( $dir . "/queue/", 0755, true );
|
||||
if (!is_dir($dir . "/uploaded/"))
|
||||
mkdir ( $dir . "/uploaded/", 0755, true );
|
||||
if (!is_dir($dir . "/failed_to_upload/"))
|
||||
mkdir ( $dir . "/failed_to_upload/", 0755, true );
|
||||
|
||||
$this->root_dir = $dir;
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns amount of files & total size of dir.
|
||||
* @param unknown $path
|
||||
* @return multitype:number
|
||||
*/
|
||||
function scan_dir($path){
|
||||
$ite=new RecursiveDirectoryIterator($path);
|
||||
|
||||
$bytestotal=0;
|
||||
$nbfiles=0;
|
||||
foreach (new RecursiveIteratorIterator($ite) as $filename=>$cur) {
|
||||
$filesize = $cur->getSize();
|
||||
$bytestotal += $filesize;
|
||||
$nbfiles++;
|
||||
}
|
||||
|
||||
$size_mb = $bytestotal / 1048576; // to mb
|
||||
$size_mb = number_format($size_mb, 2, '.', '');
|
||||
return array('total_files'=>$nbfiles,'total_mb'=>$size_mb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the image & handles everything
|
||||
* @param number $upload_count to upload a non-config amount of imgs
|
||||
* @return boolean returns true if the upload was successful
|
||||
*/
|
||||
public function process_upload($upload_count = 0) {
|
||||
global $config;
|
||||
set_time_limit(0);
|
||||
$this->set_dir();
|
||||
$this->generate_image_queue();
|
||||
|
||||
// Gets amount of imgs to upload
|
||||
if ($upload_count == 0) $upload_count = $config->get_int ("cron_uploader_count", 1);
|
||||
|
||||
// Throw exception if there's nothing in the queue
|
||||
if (count($this->image_queue) == 0) {
|
||||
$this->add_upload_info("Your queue is empty so nothing could be uploaded.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Randomize Images
|
||||
shuffle($this->image_queue);
|
||||
|
||||
// Upload the file(s)
|
||||
for ($i = 0; $i < $upload_count; $i++) {
|
||||
$img = $this->image_queue[$i];
|
||||
|
||||
try {
|
||||
$this->add_image($img[0], $img[1], $img[2]);
|
||||
$newPath = $this->move_uploaded($img[0], $img[1], false);
|
||||
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$newPath = $this->move_uploaded($img[0], $img[1], true);
|
||||
}
|
||||
|
||||
// Remove img from queue array
|
||||
unset($this->image_queue[$i]);
|
||||
}
|
||||
|
||||
// Display & save upload log
|
||||
$this->handle_log();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function move_uploaded($path, $filename, $corrupt = false) {
|
||||
global $config;
|
||||
|
||||
// Create
|
||||
$newDir = $this->root_dir;
|
||||
|
||||
// Determine which dir to move to
|
||||
if ($corrupt) {
|
||||
// Move to corrupt dir
|
||||
$newDir .= "/failed_to_upload/";
|
||||
$info = "ERROR: Image was not uploaded.";
|
||||
}
|
||||
else {
|
||||
$newDir .= "/uploaded/";
|
||||
$info = "Image successfully uploaded. ";
|
||||
}
|
||||
|
||||
// move file to correct dir
|
||||
$newPath = $newDir . $filename;
|
||||
rename($path, $newPath);
|
||||
|
||||
$this->add_upload_info($info . "Image \"$filename\" moved from queue to \"$newPath\".");
|
||||
}
|
||||
|
||||
/**
|
||||
* moves a directory up or gets the directory of a file
|
||||
*
|
||||
* @param string $path Path to modify
|
||||
* @param int $depth Amount of directories to go up
|
||||
* @return unknown Path to correct Directory
|
||||
*/
|
||||
private function move_directory_up($path, $depth=1)
|
||||
{
|
||||
$path = str_replace("//", "/", $path);
|
||||
$array = explode("/", $path);
|
||||
|
||||
for ($i = 0; $i < $depth; $i++) {
|
||||
$to_remove = count($array) -1; // Gets number of element to remove
|
||||
unset($array[$to_remove]);
|
||||
}
|
||||
|
||||
return implode("/", $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the necessary DataUploadEvent for a given image and tags.
|
||||
*/
|
||||
private function add_image($tmpname, $filename, $tags) {
|
||||
global $user, $image;
|
||||
assert ( file_exists ( $tmpname ) );
|
||||
|
||||
$pathinfo = pathinfo ( $filename );
|
||||
if (! array_key_exists ( 'extension', $pathinfo )) {
|
||||
throw new UploadException ( "File has no extension" );
|
||||
}
|
||||
$metadata ['filename'] = $pathinfo ['basename'];
|
||||
$metadata ['extension'] = $pathinfo ['extension'];
|
||||
$metadata ['tags'] = ""; // = $tags; doesn't work when not logged in here
|
||||
$metadata ['source'] = null;
|
||||
$event = new DataUploadEvent ( $tmpname, $metadata );
|
||||
send_event ( $event );
|
||||
|
||||
// Generate info message
|
||||
$infomsg = ""; // Will contain info message
|
||||
if ($event->image_id == -1)
|
||||
$infomsg = "File type not recognised. Filename: {$filename}";
|
||||
else $infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename} - Tags: {$tags}";
|
||||
$msgNumber = $this->add_upload_info($infomsg);
|
||||
|
||||
// Set tags
|
||||
$img = Image::by_id($event->image_id);
|
||||
$img->set_tags($tags);
|
||||
|
||||
}
|
||||
|
||||
private function generate_image_queue($base = "", $subdir = "") {
|
||||
global $config;
|
||||
|
||||
if ($base == "")
|
||||
$base = $this->root_dir . "/queue";
|
||||
|
||||
if (! is_dir ( $base )) {
|
||||
$this->add_upload_info("Image Queue Directory could not be found at \"$base\".");
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ( glob ( "$base/$subdir/*" ) as $fullpath ) {
|
||||
$fullpath = str_replace ( "//", "/", $fullpath );
|
||||
$shortpath = str_replace ( $base, "", $fullpath );
|
||||
|
||||
if (is_link ( $fullpath )) {
|
||||
// ignore
|
||||
} else if (is_dir ( $fullpath )) {
|
||||
$this->generate_image_queue ( $base, str_replace ( $base, "", $fullpath ) );
|
||||
} else {
|
||||
$pathinfo = pathinfo ( $fullpath );
|
||||
$matches = array ();
|
||||
|
||||
if (preg_match ( "/\d+ - (.*)\.([a-zA-Z]+)/", $pathinfo ["basename"], $matches )) {
|
||||
$tags = $matches [1];
|
||||
} else {
|
||||
$tags = $subdir;
|
||||
$tags = str_replace ( "/", " ", $tags );
|
||||
$tags = str_replace ( "__", " ", $tags );
|
||||
if ($tags == "") $tags = " ";
|
||||
$tags = trim ( $tags );
|
||||
}
|
||||
|
||||
$img = array (
|
||||
0 => $fullpath,
|
||||
1 => $pathinfo ["basename"],
|
||||
2 => $tags
|
||||
);
|
||||
array_push ($this->image_queue, $img );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to the info being published at the end
|
||||
* @param string $text
|
||||
* @param int $addon Enter a value to modify an existing value (enter value number)
|
||||
*/
|
||||
private function add_upload_info($text, $addon = 0) {
|
||||
$info = $this->upload_info;
|
||||
$time = "[" .date('Y-m-d H:i:s'). "]";
|
||||
|
||||
// If addon function is not used
|
||||
if ($addon == 0) {
|
||||
$this->upload_info .= "$time $text\r\n";
|
||||
|
||||
// Returns the number of the current line
|
||||
$currentLine = substr_count($this->upload_info, "\n") -1;
|
||||
return $currentLine;
|
||||
}
|
||||
|
||||
// else if addon function is used, select the line & modify it
|
||||
$lines = substr($info, "\n"); // Seperate the string to array in lines
|
||||
$lines[$addon] = "$line[$addon] $text"; // Add the content to the line
|
||||
$this->upload_info = implode("\n", $lines); // Put string back together & update
|
||||
|
||||
return $addon; // Return line number
|
||||
}
|
||||
|
||||
/**
|
||||
* This is run at the end to display & save the log.
|
||||
*/
|
||||
private function handle_log() {
|
||||
global $page, $config;
|
||||
|
||||
// Display message
|
||||
$page->set_mode("data");
|
||||
$page->set_type("text/plain");
|
||||
$page->set_data($this->upload_info);
|
||||
|
||||
// Save log
|
||||
$log_path = $this->root_dir . "/uploads.log";
|
||||
|
||||
if (file_exists($log_path))
|
||||
$prev_content = file_get_contents($log_path);
|
||||
else $prev_content = "";
|
||||
|
||||
$content = $prev_content ."\r\n".$this->upload_info;
|
||||
file_put_contents ($log_path, $content);
|
||||
}
|
||||
}
|
||||
?>
|
@ -265,7 +265,7 @@ class DanbooruApi extends Extension {
|
||||
* id: id to search for (comma delimited)
|
||||
* tags: what tags to search for
|
||||
* limit: limit
|
||||
* offset: offset
|
||||
* 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')))
|
||||
@ -288,14 +288,23 @@ class DanbooruApi extends Extension {
|
||||
} else
|
||||
{
|
||||
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
|
||||
$start = (isset($_GET['page']) ? int_escape($_GET['page'])-1 : 0) * $limit;
|
||||
|
||||
// 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>\n";
|
||||
$xml = "<posts count=\"$count\" offset=\"$start\">\n";
|
||||
foreach($results as $img)
|
||||
{
|
||||
// Sanity check to see if $img is really an image object
|
||||
@ -304,21 +313,24 @@ class DanbooruApi extends Extension {
|
||||
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(),
|
||||
"rating" => "u",
|
||||
"date" => $img->posted,
|
||||
"is_warehoused" => false,
|
||||
"tags" => $taglist,
|
||||
"source" => $img->source,
|
||||
"score" => 0,
|
||||
"author" => $owner->name
|
||||
"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>";
|
||||
|
@ -59,7 +59,7 @@ class ArchiveFileHandler extends Extension {
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
$metadata['tags'] = $tags;
|
||||
$metadata['source'] = null;
|
||||
$event = new DataUploadEvent($user, $tmpname, $metadata);
|
||||
$event = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($event);
|
||||
}
|
||||
catch(UploadException $ex) {
|
||||
@ -74,7 +74,14 @@ class ArchiveFileHandler extends Extension {
|
||||
$list = "";
|
||||
|
||||
$dir = opendir("$base/$subdir");
|
||||
while($filename = readdir($dir)) {
|
||||
|
||||
$files = array();
|
||||
while($f = readdir($dir)) {
|
||||
$files[] = $f;
|
||||
}
|
||||
sort($files);
|
||||
|
||||
foreach($files as $filename) {
|
||||
$fullpath = "$base/$subdir/$filename";
|
||||
|
||||
if(is_link($fullpath)) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
class PixelFileHandler extends DataHandlerExtension {
|
||||
protected function supported_ext($ext) {
|
||||
$exts = array("jpg", "jpeg", "gif", "png");
|
||||
$ext = (($pos = strpos($ext,'?')) !== false) ? substr($ext,0,$pos) : $ext;
|
||||
return in_array(strtolower($ext), $exts);
|
||||
}
|
||||
|
||||
@ -25,8 +26,8 @@ class PixelFileHandler extends DataHandlerExtension {
|
||||
|
||||
$image->filesize = $metadata['size'];
|
||||
$image->hash = $metadata['hash'];
|
||||
$image->filename = $metadata['filename'];
|
||||
$image->ext = $metadata['extension'];
|
||||
$image->filename = (($pos = strpos($metadata['filename'],'?')) !== false) ? substr($metadata['filename'],0,$pos) : $metadata['filename'];
|
||||
$image->ext = (($pos = strpos($metadata['extension'],'?')) !== false) ? substr($metadata['extension'],0,$pos) : $metadata['extension'];
|
||||
$image->tag_array = Tag::explode($metadata['tags']);
|
||||
$image->source = $metadata['source'];
|
||||
|
||||
@ -81,6 +82,13 @@ class PixelFileHandler extends DataHandlerExtension {
|
||||
</select>
|
||||
</form>
|
||||
", 20);
|
||||
|
||||
$u_ilink = $event->image->get_image_link();
|
||||
$event->add_part("
|
||||
<form action='{$u_ilink}'>
|
||||
<input type='submit' value='Image Only'>
|
||||
</form>
|
||||
", 21);
|
||||
}
|
||||
|
||||
// IM thumber {{{
|
||||
|
21
ext/hellban/main.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
class HellBan extends Extension {
|
||||
public function onPageRequest(PageRequestEvent $event) {
|
||||
global $page, $user;
|
||||
|
||||
if($user->can("hellbanned")) {
|
||||
$s = "";
|
||||
}
|
||||
else if($user->can("view_hellbanned")) {
|
||||
$s = "DIV.hb, TR.hb TD {border: 1px solid red !important;}";
|
||||
}
|
||||
else {
|
||||
$s = ".hb {display: none !important;}";
|
||||
}
|
||||
|
||||
if($s) {
|
||||
$page->add_html_header("<style>$s</style>");
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
@ -64,18 +64,15 @@ class ImageBan extends Extension {
|
||||
|
||||
if($hash) {
|
||||
send_event(new AddImageHashBanEvent($hash, $reason));
|
||||
|
||||
flash_message("Image ban added");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("image_hash_ban/list/1"));
|
||||
|
||||
if($image) {
|
||||
send_event(new ImageDeletionEvent($image));
|
||||
|
||||
flash_message("Image deleted");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("post/list"));
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
}
|
||||
else if($event->get_arg(0) == "remove") {
|
||||
@ -84,7 +81,7 @@ class ImageBan extends Extension {
|
||||
|
||||
flash_message("Image ban removed");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("image_hash_ban/list/1"));
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
}
|
||||
else if($event->get_arg(0) == "list") {
|
||||
|
@ -87,7 +87,7 @@ class ImageBanTheme extends Themelet {
|
||||
<input type='hidden' name='hash' value='{$image->hash}'>
|
||||
<input type='hidden' name='image_id' value='{$image->id}'>
|
||||
<input type='text' name='reason'>
|
||||
<input type='submit' value='Ban and Delete'>
|
||||
<input type='submit' value='Ban Hash and Delete Image'>
|
||||
</form>
|
||||
";
|
||||
return $html;
|
||||
|
@ -135,7 +135,7 @@ class PostListBuildingEvent extends Event {
|
||||
}
|
||||
|
||||
class Index extends Extension {
|
||||
var $val_id = 0;
|
||||
var $stpen = 0; // search term parse event number
|
||||
|
||||
public function onInitExt(InitExtEvent $event) {
|
||||
global $config;
|
||||
@ -147,7 +147,7 @@ class Index extends Extension {
|
||||
global $config, $database, $page, $user;
|
||||
if($event->page_matches("post/list")) {
|
||||
if(isset($_GET['search'])) {
|
||||
$search = url_escape(trim($_GET['search']));
|
||||
$search = url_escape(Tag::implode(Tag::resolve_aliases(Tag::explode($_GET['search'], false))));
|
||||
if(empty($search)) {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("post/list/1"));
|
||||
@ -163,6 +163,7 @@ class Index extends Extension {
|
||||
$page_number = $event->get_page_number();
|
||||
$page_size = $event->get_page_size();
|
||||
try {
|
||||
#log_debug("index", "Search for ".implode(" ", $search_terms), false, array("terms"=>$search_terms));
|
||||
$total_pages = Image::count_pages($search_terms);
|
||||
if(SPEED_HAX && count($search_terms) == 0 && ($page_number < 10)) { // extra caching for the first few post/list pages
|
||||
$images = $database->cache->get("post-list-$page_number");
|
||||
@ -241,27 +242,26 @@ class Index extends Extension {
|
||||
}
|
||||
else if(preg_match("/^ratio(<|>|<=|>=|=)(\d+):(\d+)$/", $event->term, $matches)) {
|
||||
$cmp = $matches[1];
|
||||
$args = array("width"=>int_escape($matches[2]), "height"=>int_escape($matches[3]));
|
||||
$event->add_querylet(new Querylet('width / height '.$cmp.' :width / :height', $args));
|
||||
$args = array("width{$this->stpen}"=>int_escape($matches[2]), "height{$this->stpen}"=>int_escape($matches[3]));
|
||||
$event->add_querylet(new Querylet("width / height $cmp :width{$this->stpen} / :height{$this->stpen}", $args));
|
||||
}
|
||||
else if(preg_match("/^(filesize|id)(<|>|<=|>=|=)(\d+[kmg]?b?)$/i", $event->term, $matches)) {
|
||||
$this->val_id++;
|
||||
$col = $matches[1];
|
||||
$cmp = $matches[2];
|
||||
$val = parse_shorthand_int($matches[3]);
|
||||
$event->add_querylet(new Querylet("images.$col $cmp :val{$this->val_id}", array("val{$this->val_id}"=>$val)));
|
||||
$event->add_querylet(new Querylet("images.$col $cmp :val{$this->stpen}", array("val{$this->stpen}"=>$val)));
|
||||
}
|
||||
else if(preg_match("/^(hash|md5)=([0-9a-fA-F]*)$/i", $event->term, $matches)) {
|
||||
$hash = strtolower($matches[2]);
|
||||
$event->add_querylet(new Querylet('images.hash = "'.$hash.'"'));
|
||||
$event->add_querylet(new Querylet('images.hash = :hash', array("hash" => $hash)));
|
||||
}
|
||||
else if(preg_match("/^(filetype|ext)=([a-zA-Z0-9]*)$/i", $event->term, $matches)) {
|
||||
$ext = strtolower($matches[2]);
|
||||
$event->add_querylet(new Querylet('images.ext = "'.$ext.'"'));
|
||||
$event->add_querylet(new Querylet('images.ext = :ext', array("ext" => $ext)));
|
||||
}
|
||||
else if(preg_match("/^(filename|name)=([a-zA-Z0-9]*)$/i", $event->term, $matches)) {
|
||||
$filename = strtolower($matches[2]);
|
||||
$event->add_querylet(new Querylet('images.filename LIKE :fn', array("fn"=>"%$filename%")));
|
||||
$event->add_querylet(new Querylet("images.filename LIKE :filename{$this->stpen}", array("filename{$this->stpen}"=>"%$filename%")));
|
||||
}
|
||||
else if(preg_match("/^(source)=([a-zA-Z0-9]*)$/i", $event->term, $matches)) {
|
||||
$filename = strtolower($matches[2]);
|
||||
@ -270,13 +270,15 @@ class Index extends Extension {
|
||||
else if(preg_match("/^posted(<|>|<=|>=|=)([0-9-]*)$/", $event->term, $matches)) {
|
||||
$cmp = $matches[1];
|
||||
$val = $matches[2];
|
||||
$event->add_querylet(new Querylet("images.posted $cmp :val", array("val"=>$val)));
|
||||
$event->add_querylet(new Querylet("images.posted $cmp :posted{$this->stpen}", array("posted{$this->stpen}"=>$val)));
|
||||
}
|
||||
else if(preg_match("/^size(<|>|<=|>=|=)(\d+)x(\d+)$/", $event->term, $matches)) {
|
||||
$cmp = $matches[1];
|
||||
$args = array("width"=>int_escape($matches[2]), "height"=>int_escape($matches[3]));
|
||||
$event->add_querylet(new Querylet('width '.$cmp.' :width AND height '.$cmp.' :height', $args));
|
||||
$args = array("width{$this->stpen}"=>int_escape($matches[2]), "height{$this->stpen}"=>int_escape($matches[3]));
|
||||
$event->add_querylet(new Querylet("width $cmp :width{$this->stpen} AND height $cmp :height{$this->stpen}", $args));
|
||||
}
|
||||
|
||||
$this->stpen++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -95,9 +95,10 @@ and of course start organising your images :-)
|
||||
}
|
||||
|
||||
protected function build_table($images, $query) {
|
||||
$table = "<div class='shm-image-list'>";
|
||||
$h_query = html_escape($query);
|
||||
$table = "<div class='shm-image-list' data-query='$h_query'>";
|
||||
foreach($images as $image) {
|
||||
$table .= $this->build_thumb_html($image, $query);
|
||||
$table .= $this->build_thumb_html($image);
|
||||
}
|
||||
$table .= "</div>";
|
||||
return $table;
|
||||
|
@ -24,6 +24,7 @@ class IPBanTheme extends Themelet {
|
||||
<td width='12%'>{$ban[$prefix.'ip']}</td>
|
||||
<td>{$ban[$prefix.'reason']}</td>
|
||||
<td width='10%'>{$ban['banner_name']}</td>
|
||||
<td width='10%'>".substr($ban[$prefix.'added'], 0, 10)."</td>
|
||||
<td width='15%'>{$end_human}</td>
|
||||
".make_form(make_link("ip_ban/remove"))."
|
||||
<td width='8%'>
|
||||
@ -37,13 +38,14 @@ class IPBanTheme extends Themelet {
|
||||
$html = "
|
||||
<a href='".make_link("ip_ban/list", "all=on")."'>Show All</a>
|
||||
<p><table id='bans' class='sortable zebra'>
|
||||
<thead><tr><th>IP</th><th>Reason</th><th>By</th><th>Until</th><th>Action</th></tr></thead>
|
||||
<thead><tr><th>IP</th><th>Reason</th><th>By</th><th>From</th><th>Until</th><th>Action</th></tr></thead>
|
||||
$h_bans
|
||||
<tfoot><tr id='add'>
|
||||
".make_form(make_link("ip_ban/add"))."
|
||||
<td><input type='text' name='ip' value='".html_escape(@$_GET['ip'])."'></td>
|
||||
<td><input type='text' name='reason' value='".html_escape(@$_GET['reason'])."'></td>
|
||||
<td>{$user->name}</td>
|
||||
<td></td>
|
||||
<td><input type='text' name='end' value='".html_escape(@$_GET['end'])."'></td>
|
||||
<td><input type='submit' value='Ban'></td>
|
||||
</form>
|
||||
|
@ -42,13 +42,13 @@ class MassTagger extends Extension {
|
||||
$_POST['setadd'] == 'set')
|
||||
{
|
||||
foreach($images as $image) {
|
||||
$image->set_tags($tag);
|
||||
$image->set_tags(Tag::explode($tag));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($images as $image) {
|
||||
$image->set_tags($tag . " " . $image->get_tag_list());
|
||||
$image->set_tags(Tag::explode($tag . " " . $image->get_tag_list()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,17 @@
|
||||
class NotATag extends Extension {
|
||||
public function get_priority() {return 30;} // before ImageUploadEvent and tag_history
|
||||
|
||||
public function onInitExt(InitExtEvent $event) {
|
||||
global $config, $database;
|
||||
if($config->get_int("ext_notatag_version") < 1) {
|
||||
$database->create_table("untags", "
|
||||
tag VARCHAR(128) NOT NULL PRIMARY KEY,
|
||||
redirect VARCHAR(255) NOT NULL
|
||||
");
|
||||
$config->set_int("ext_notatag_version", 1);
|
||||
}
|
||||
}
|
||||
|
||||
public function onImageAddition(ImageAdditionEvent $event) {
|
||||
$this->scan($event->image->get_tag_array());
|
||||
}
|
||||
@ -18,18 +29,13 @@ class NotATag extends Extension {
|
||||
}
|
||||
|
||||
private function scan(/*array*/ $tags_mixed) {
|
||||
global $config;
|
||||
|
||||
$text = $config->get_string("not_a_tag_untags");
|
||||
if(empty($text)) return;
|
||||
global $config, $database;
|
||||
|
||||
$tags = array();
|
||||
foreach($tags_mixed as $tag) $tags[] = strtolower($tag);
|
||||
|
||||
$pairs = explode("\n", $text);
|
||||
foreach($pairs as $pair) {
|
||||
$tag_url = explode(",", $pair);
|
||||
if(count($tag_url) != 2) continue;
|
||||
$pairs = $database->get_all("SELECT * FROM untags");
|
||||
foreach($pairs as $tag_url) {
|
||||
$tag = strtolower($tag_url[0]);
|
||||
$url = $tag_url[1];
|
||||
if(in_array($tag, $tags)) {
|
||||
@ -39,14 +45,78 @@ class NotATag extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event) {
|
||||
$sb = new SetupBlock("Un-Tags");
|
||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
|
||||
global $user;
|
||||
if($user->can("ban_image")) {
|
||||
$event->add_link("UnTags", make_link("untag/list/1"));
|
||||
}
|
||||
}
|
||||
|
||||
$sb->add_label("List tag,url pairs");
|
||||
$sb->add_longtext_option("not_a_tag_untags");
|
||||
$sb->add_label("<br>(eg. 'deleteme,/wiki/reporting-images')");
|
||||
public function onPageRequest(PageRequestEvent $event) {
|
||||
global $config, $database, $page, $user;
|
||||
|
||||
$event->panel->add_block($sb);
|
||||
if($event->page_matches("untag")) {
|
||||
if($user->can("ban_image")) {
|
||||
if($event->get_arg(0) == "add") {
|
||||
$tag = isset($_POST["tag"]) ? $_POST["tag"] : $image->tag;
|
||||
$redirect = isset($_POST['redirect']) ? $_POST['redirect'] : "DNP";
|
||||
|
||||
$database->Execute(
|
||||
"INSERT INTO untags(tag, redirect) VALUES (?, ?)",
|
||||
array($tag, $redirect));
|
||||
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
else if($event->get_arg(0) == "remove") {
|
||||
if(isset($_POST['tag'])) {
|
||||
$database->Execute("DELETE FROM untags WHERE tag = ?", array($_POST['tag']));
|
||||
|
||||
flash_message("Image ban removed");
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
}
|
||||
else if($event->get_arg(0) == "list") {
|
||||
$page_num = 0;
|
||||
if($event->count_args() == 2) {
|
||||
$page_num = int_escape($event->get_arg(1));
|
||||
}
|
||||
$page_size = 100;
|
||||
$page_count = ceil($database->get_one("SELECT COUNT(tag) FROM untags")/$page_size);
|
||||
$this->theme->display_untags($page, $page_num, $page_count, $this->get_untags($page_num, $page_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get_untags($page, $size=100) {
|
||||
global $database;
|
||||
|
||||
// FIXME: many
|
||||
$size_i = int_escape($size);
|
||||
$offset_i = int_escape($page-1)*$size_i;
|
||||
$where = array("(1=1)");
|
||||
$args = array();
|
||||
if(!empty($_GET['tag'])) {
|
||||
$where[] = 'tag SCORE_ILIKE ?';
|
||||
$args[] = "%".$_GET['tag']."%";
|
||||
}
|
||||
if(!empty($_GET['redirect'])) {
|
||||
$where[] = 'redirect SCORE_ILIKE ?';
|
||||
$args[] = "%".$_GET['redirect']."%";
|
||||
}
|
||||
$where = implode(" AND ", $where);
|
||||
$bans = $database->get_all($database->scoreql_to_sql("
|
||||
SELECT *
|
||||
FROM untags
|
||||
WHERE $where
|
||||
ORDER BY tag
|
||||
LIMIT $size_i
|
||||
OFFSET $offset_i
|
||||
"), $args);
|
||||
if($bans) {return $bans;}
|
||||
else {return array();}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
59
ext/not_a_tag/theme.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
class NotATagTheme extends Themelet {
|
||||
public function display_untags(Page $page, $page_number, $page_count, $bans) {
|
||||
$h_bans = "";
|
||||
$n = 0;
|
||||
foreach($bans as $ban) {
|
||||
$h_bans .= "
|
||||
<tr>
|
||||
".make_form(make_link("untag/remove"))."
|
||||
<td width='30%'>{$ban['tag']}</td>
|
||||
<td>{$ban['redirect']}</td>
|
||||
<td width='10%'>
|
||||
<input type='hidden' name='tag' value='{$ban['tag']}'>
|
||||
<input type='submit' value='Remove'>
|
||||
</td>
|
||||
</form>
|
||||
</tr>
|
||||
";
|
||||
}
|
||||
$html = "
|
||||
<table id='image_bans' class='zebra sortable'>
|
||||
<thead>
|
||||
<th>Tag</th><th>Redirect</th><th>Action</th>
|
||||
<tr>
|
||||
<form action='".make_link("untag/list/1")."' method='GET'>
|
||||
<td><input type='text' name='tag'></td>
|
||||
<td><input type='text' name='redirect'></td>
|
||||
<td><input type='submit' value='Search'></td>
|
||||
</form>
|
||||
</tr>
|
||||
</thead>
|
||||
$h_bans
|
||||
<tfoot><tr>
|
||||
".make_form(make_link("untag/add"))."
|
||||
<td><input type='text' name='tag'></td>
|
||||
<td><input type='text' name='redirect'></td>
|
||||
<td><input type='submit' value='Ban'></td>
|
||||
</form>
|
||||
</tr></tfoot>
|
||||
</table>
|
||||
";
|
||||
|
||||
$prev = $page_number - 1;
|
||||
$next = $page_number + 1;
|
||||
|
||||
$h_prev = ($page_number <= 1) ? "Prev" : "<a href='".make_link("untag/list/$prev")."'>Prev</a>";
|
||||
$h_index = "<a href='".make_link()."'>Index</a>";
|
||||
$h_next = ($page_number >= $page_count) ? "Next" : "<a href='".make_link("untag/list/$next")."'>Next</a>";
|
||||
|
||||
$nav = "$h_prev | $h_index | $h_next";
|
||||
|
||||
$page->set_title("UnTags");
|
||||
$page->set_heading("UnTags");
|
||||
$page->add_block(new Block("Edit UnTags", $html));
|
||||
$page->add_block(new Block("Navigation", $nav, "left", 0));
|
||||
$this->display_paginator($page, "untag/list", null, $page_number, $page_count);
|
||||
}
|
||||
}
|
||||
?>
|
@ -55,11 +55,11 @@ class NumericScore extends Extension {
|
||||
JOIN users ON numeric_score_votes.user_id=users.id
|
||||
WHERE image_id=?",
|
||||
array($image_id));
|
||||
$html = "<table>";
|
||||
$html = "<table style='width: 100%;'>";
|
||||
foreach($x as $vote) {
|
||||
$html .= "<tr><td>";
|
||||
$html .= "<a href='".make_link("user/{$vote['username']}")."'>{$vote['username']}</a>";
|
||||
$html .= "</td><td>";
|
||||
$html .= "</td><td width='10'>";
|
||||
$html .= $vote['score'];
|
||||
$html .= "</td></tr>";
|
||||
}
|
||||
@ -174,7 +174,7 @@ class NumericScore extends Extension {
|
||||
|
||||
public function onNumericScoreSet(NumericScoreSetEvent $event) {
|
||||
global $user;
|
||||
log_debug("numeric_score", "Rated Image #{$event->image_id} as {$event->score}", true);
|
||||
log_debug("numeric_score", "Rated Image #{$event->image_id} as {$event->score}", true, array("image_id"=>$event->image_id));
|
||||
$this->add_vote($event->image_id, $user->id, $event->score);
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,12 @@ class NumericScoreTheme extends Themelet {
|
||||
<input type='submit' value='Remove All Votes'>
|
||||
</form>
|
||||
|
||||
<br><a href='".make_link("numeric_score_votes/$i_image_id")."'>See All Votes</a>
|
||||
<br><div id='votes-content'>
|
||||
<a
|
||||
href='".make_link("numeric_score_votes/$i_image_id")."'
|
||||
onclick='$(\"#votes-content\").load(\"".make_link("numeric_score_votes/$i_image_id")."\"); return false;'
|
||||
>See All Votes</a>
|
||||
</div>
|
||||
";
|
||||
}
|
||||
return $html;
|
||||
|
@ -12,7 +12,8 @@ class PrivMsgTheme extends Themelet {
|
||||
foreach($pms as $pm) {
|
||||
$h_subject = html_escape($pm->subject);
|
||||
if(strlen(trim($h_subject)) == 0) $h_subject = "(No subject)";
|
||||
$from_name = User::by_id($pm->from_id)->name;
|
||||
$from = User::by_id($pm->from_id);
|
||||
$from_name = $from->name;
|
||||
$h_from = html_escape($from_name);
|
||||
$from_url = make_link("user/".url_escape($from_name));
|
||||
$pm_url = make_link("pm/read/".$pm->id);
|
||||
@ -23,7 +24,8 @@ class PrivMsgTheme extends Themelet {
|
||||
$h_subject = "<b>$h_subject</b>";
|
||||
$readYN = "N";
|
||||
}
|
||||
$html .= "<tr>
|
||||
$hb = $from->can("hellbanned") ? "hb" : "";
|
||||
$html .= "<tr class='$hb'>
|
||||
<td>$readYN</td>
|
||||
<td><a href='$pm_url'>$h_subject</a></td>
|
||||
<td><a href='$from_url'>$h_from</a></td><td>$h_date</td>
|
||||
|
@ -74,7 +74,7 @@ class ReportImage extends Extension {
|
||||
|
||||
public function onAddReportedImage(AddReportedImageEvent $event) {
|
||||
global $database;
|
||||
log_info("report_image", "Adding report of Image #{$event->image_id} with reason '{$event->reason}'");
|
||||
log_info("report_image", "Adding report of Image #{$event->image_id} with reason '{$event->reason}'", false, array("image_id" => $event->image_id));
|
||||
$database->Execute(
|
||||
"INSERT INTO image_reports(image_id, reporter_id, reason)
|
||||
VALUES (?, ?, ?)",
|
||||
|
@ -40,7 +40,7 @@ class ResizeImage extends Extension {
|
||||
global $user, $config;
|
||||
if($user->is_admin() && $config->get_bool("resize_enabled")) {
|
||||
/* Add a link to resize the image */
|
||||
$event->add_part($this->theme->get_resize_html($event->image->id));
|
||||
$event->add_part($this->theme->get_resize_html($event->image));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,19 +4,25 @@ class ResizeImageTheme extends Themelet {
|
||||
/*
|
||||
* Display a link to resize an image
|
||||
*/
|
||||
public function get_resize_html(/*int*/ $image_id) {
|
||||
public function get_resize_html(Image $image) {
|
||||
global $user, $config;
|
||||
|
||||
$i_image_id = int_escape($image_id);
|
||||
$i_image_id = int_escape($image->id);
|
||||
$default_width = $config->get_int('resize_default_width');
|
||||
$default_height = $config->get_int('resize_default_height');
|
||||
|
||||
if(!$default_width) $default_width = $image->width;
|
||||
if(!$default_height) $default_height = $image->height;
|
||||
|
||||
$html .= "
|
||||
".make_form(make_link('resize/'.$i_image_id), 'POST')."
|
||||
<input type='hidden' name='image_id' value='$i_image_id'>
|
||||
<input id='resize_width' style='width: auto;' size='5' name='resize_width' type='text' value='".$default_width."'> x
|
||||
<input id='resize_height' style='width: auto;' size='5' name='resize_height' type='text' value='".$default_height."'>
|
||||
<input id='resizebutton' type='submit' value='Resize'>
|
||||
$html = "
|
||||
".make_form(make_link("resize/{$image->id}"), 'POST')."
|
||||
<input type='hidden' name='image_id' value='{$image->id}'>
|
||||
<input id='original_width' name='original_width' type='hidden' value='{$image->width}'>
|
||||
<input id='original_height' name='original_height' type='hidden' value='{$image->height}'>
|
||||
<input id='resize_width' style='width: 70px;' name='resize_width' type='number' min='1' value='".$default_width."'> x
|
||||
<input id='resize_height' style='width: 70px;' name='resize_height' type='number' min='1' value='".$default_height."'>
|
||||
<br><label><input type='checkbox' id='resize_aspect' name='resize_aspect' style='max-width: 20px;' checked='checked'> Keep Aspect</label>
|
||||
<br><input id='resizebutton' type='submit' value='Resize'>
|
||||
</form>
|
||||
";
|
||||
|
||||
|
@ -179,7 +179,7 @@ class RotateImage extends Extension {
|
||||
$image_rotated = imagerotate($image, $deg, 0);
|
||||
|
||||
/* Temp storage while we rotate */
|
||||
$tmp_filename = tempnam("/tmp", 'shimmie_rotate');
|
||||
$tmp_filename = tempnam(ini_get('upload_tmp_dir'), 'shimmie_rotate');
|
||||
if (empty($tmp_filename)) {
|
||||
throw new ImageRotateException("Unable to save temporary image file.");
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ class RSS_Images extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function do_rss($images, $search_terms, /*int*/ $page_number) {
|
||||
global $page;
|
||||
global $config;
|
||||
@ -42,28 +41,7 @@ class RSS_Images extends Extension {
|
||||
|
||||
$data = "";
|
||||
foreach($images as $image) {
|
||||
$link = make_http(make_link("post/view/{$image->id}"));
|
||||
$tags = html_escape($image->get_tag_list());
|
||||
$owner = $image->get_owner();
|
||||
$thumb_url = $image->get_thumb_link();
|
||||
$image_url = $image->get_image_link();
|
||||
$posted = date(DATE_RSS, $image->posted_timestamp);
|
||||
$content = html_escape(
|
||||
"<p>" . $this->theme->build_thumb_html($image) . "</p>" .
|
||||
"<p>Uploaded by " . html_escape($owner->name) . "</p>"
|
||||
);
|
||||
|
||||
$data .= "
|
||||
<item>
|
||||
<title>{$image->id} - $tags</title>
|
||||
<link>$link</link>
|
||||
<guid isPermaLink=\"true\">$link</guid>
|
||||
<pubDate>$posted</pubDate>
|
||||
<description>$content</description>
|
||||
<media:thumbnail url=\"$thumb_url\"/>
|
||||
<media:content url=\"$image_url\"/>
|
||||
</item>
|
||||
";
|
||||
$data .= $this->thumb($image);
|
||||
}
|
||||
|
||||
$title = $config->get_string('title');
|
||||
@ -99,5 +77,39 @@ class RSS_Images extends Extension {
|
||||
</rss>";
|
||||
$page->set_data($xml);
|
||||
}
|
||||
|
||||
private function thumb(Image $image) {
|
||||
global $database;
|
||||
|
||||
$cached = $database->cache->get("rss-thumb:{$image->id}");
|
||||
if($cached) return $cached;
|
||||
|
||||
$link = make_http(make_link("post/view/{$image->id}"));
|
||||
$tags = html_escape($image->get_tag_list());
|
||||
$owner = $image->get_owner();
|
||||
$thumb_url = $image->get_thumb_link();
|
||||
$image_url = $image->get_image_link();
|
||||
$posted = date(DATE_RSS, $image->posted_timestamp);
|
||||
$content = html_escape(
|
||||
"<p>" . $this->theme->build_thumb_html($image) . "</p>" .
|
||||
"<p>Uploaded by " . html_escape($owner->name) . "</p>"
|
||||
);
|
||||
|
||||
$data = "
|
||||
<item>
|
||||
<title>{$image->id} - $tags</title>
|
||||
<link>$link</link>
|
||||
<guid isPermaLink=\"true\">$link</guid>
|
||||
<pubDate>$posted</pubDate>
|
||||
<description>$content</description>
|
||||
<media:thumbnail url=\"$thumb_url\"/>
|
||||
<media:content url=\"$image_url\"/>
|
||||
</item>
|
||||
";
|
||||
|
||||
$database->cache->set("rss-thumb:{$image->id}", $data, 3600);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -218,7 +218,7 @@ class Setup extends Extension {
|
||||
$host .= ":" . $_SERVER["SERVER_PORT"];
|
||||
}
|
||||
}
|
||||
$full = ($_SERVER["HTTPS"] ? "https://" : "http://") . $host . $_SERVER["PHP_SELF"];
|
||||
$full = (@$_SERVER["HTTPS"] ? "https://" : "http://") . $host . $_SERVER["PHP_SELF"];
|
||||
$test_url = str_replace("/index.php", "/nicetest", $full);
|
||||
|
||||
$nicescript = "<script language='javascript'>
|
||||
|
@ -81,7 +81,7 @@ class SetupTheme extends Themelet {
|
||||
protected function build_navigation() {
|
||||
return "
|
||||
<a href='".make_link()."'>Index</a>
|
||||
<br><a href='http://redmine.shishnet.org/wiki/shimmie2/Settings'>Help</a>
|
||||
<br><a href='https://github.com/shish/shimmie2/wiki/Settings'>Help</a>
|
||||
<br><a href='".make_link("setup/advanced")."'>Advanced</a>
|
||||
";
|
||||
}
|
||||
|
376
ext/source_history/main.php
Normal file
@ -0,0 +1,376 @@
|
||||
<?php
|
||||
/*
|
||||
* Name: Source History
|
||||
* Author: Shish, copied from Source History
|
||||
* Description: Keep a record of source changes, and allows you to revert changes.
|
||||
*/
|
||||
|
||||
class Source_History extends Extension {
|
||||
// in before source are actually set, so that "get current source" works
|
||||
public function get_priority() {return 40;}
|
||||
|
||||
public function onInitExt(InitExtEvent $event) {
|
||||
global $config;
|
||||
$config->set_default_int("history_limit", -1);
|
||||
|
||||
// shimmie is being installed so call install to create the table.
|
||||
if($config->get_int("ext_source_history_version") < 3) {
|
||||
$this->install();
|
||||
}
|
||||
}
|
||||
|
||||
public function onAdminBuilding(AdminBuildingEvent $event) {
|
||||
$this->theme->display_admin_block();
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event) {
|
||||
global $config, $page, $user;
|
||||
|
||||
if($event->page_matches("source_history/revert")) {
|
||||
// this is a request to revert to a previous version of the source
|
||||
if($user->can("edit_image_tag")) {
|
||||
if(isset($_POST['revert'])) {
|
||||
$this->process_revert_request($_POST['revert']);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if($event->page_matches("source_history/bulk_revert")) {
|
||||
if($user->can("bulk_edit_image_tag") && $user->check_auth_token()) {
|
||||
$this->process_bulk_revert_request();
|
||||
}
|
||||
}
|
||||
else if($event->page_matches("source_history/all")) {
|
||||
$page_id = int_escape($event->get_arg(0));
|
||||
$this->theme->display_global_page($page, $this->get_global_source_history($page_id), $page_id);
|
||||
}
|
||||
else if($event->page_matches("source_history") && $event->count_args() == 1) {
|
||||
// must be an attempt to view a source history
|
||||
$image_id = int_escape($event->get_arg(0));
|
||||
$this->theme->display_history_page($page, $image_id, $this->get_source_history_from_id($image_id));
|
||||
}
|
||||
}
|
||||
|
||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) {
|
||||
$event->add_part("
|
||||
<form action='".make_link("source_history/{$event->image->id}")."' method='GET'>
|
||||
<input type='submit' value='View Source History'>
|
||||
</form>
|
||||
", 20);
|
||||
}
|
||||
|
||||
/*
|
||||
// disk space is cheaper than manually rebuilding history,
|
||||
// so let's default to -1 and the user can go advanced if
|
||||
// they /really/ want to
|
||||
public function onSetupBuilding(SetupBuildingEvent $event) {
|
||||
$sb = new SetupBlock("Source History");
|
||||
$sb->add_label("Limit to ");
|
||||
$sb->add_int_option("history_limit");
|
||||
$sb->add_label(" entires per image");
|
||||
$sb->add_label("<br>(-1 for unlimited)");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
*/
|
||||
|
||||
public function onSourceSet(SourceSetEvent $event) {
|
||||
$this->add_source_history($event->image, $event->source);
|
||||
}
|
||||
|
||||
public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
|
||||
global $user;
|
||||
if($user->can("bulk_edit_image_tag")) {
|
||||
$event->add_link("Source Changes", make_link("source_history/all/1"));
|
||||
}
|
||||
}
|
||||
|
||||
protected function install() {
|
||||
global $database, $config;
|
||||
|
||||
if($config->get_int("ext_source_history_version") < 1) {
|
||||
$database->create_table("source_histories", "
|
||||
id SCORE_AIPK,
|
||||
image_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
user_ip SCORE_INET NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
date_set DATETIME NOT NULL,
|
||||
INDEX(image_id),
|
||||
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
");
|
||||
$config->set_int("ext_source_history_version", 3);
|
||||
}
|
||||
|
||||
if($config->get_int("ext_source_history_version") == 1) {
|
||||
$database->Execute("ALTER TABLE source_histories ADD COLUMN user_id INTEGER NOT NULL");
|
||||
$database->Execute("ALTER TABLE source_histories ADD COLUMN date_set DATETIME NOT NULL");
|
||||
$config->set_int("ext_source_history_version", 2);
|
||||
}
|
||||
|
||||
if($config->get_int("ext_source_history_version") == 2) {
|
||||
$database->Execute("ALTER TABLE source_histories ADD COLUMN user_ip CHAR(15) NOT NULL");
|
||||
$config->set_int("ext_source_history_version", 3);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* this function is called when a revert request is received
|
||||
*/
|
||||
private function process_revert_request($revert_id) {
|
||||
global $page;
|
||||
|
||||
$revert_id = int_escape($revert_id);
|
||||
|
||||
// check for the nothing case
|
||||
if($revert_id < 1) {
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link());
|
||||
return;
|
||||
}
|
||||
|
||||
// lets get this revert id assuming it exists
|
||||
$result = $this->get_source_history_from_revert($revert_id);
|
||||
|
||||
if(empty($result)) {
|
||||
// there is no history entry with that id so either the image was deleted
|
||||
// while the user was viewing the history, someone is playing with form
|
||||
// variables or we have messed up in code somewhere.
|
||||
/* calling die() is probably not a good idea, we should throw an Exception */
|
||||
die("Error: No source history with specified id was found.");
|
||||
}
|
||||
|
||||
// lets get the values out of the result
|
||||
$stored_result_id = $result['id'];
|
||||
$stored_image_id = $result['image_id'];
|
||||
$stored_source = $result['source'];
|
||||
|
||||
log_debug("source_history", 'Reverting source of Image #'.$stored_image_id.' to ['.$stored_source.']');
|
||||
// all should be ok so we can revert by firing the SetUserSources event.
|
||||
send_event(new SourceSetEvent(Image::by_id($stored_image_id), $stored_source));
|
||||
|
||||
// all should be done now so redirect the user back to the image
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link('post/view/'.$stored_image_id));
|
||||
}
|
||||
|
||||
protected function process_bulk_revert_request() {
|
||||
if (isset($_POST['revert_name']) && !empty($_POST['revert_name'])) {
|
||||
$revert_name = $_POST['revert_name'];
|
||||
}
|
||||
else {
|
||||
$revert_name = null;
|
||||
}
|
||||
|
||||
if (isset($_POST['revert_ip']) && !empty($_POST['revert_ip'])) {
|
||||
$revert_ip = filter_var($_POST['revert_ip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE);
|
||||
|
||||
if ($revert_ip === false) {
|
||||
// invalid ip given.
|
||||
$this->theme->display_admin_block('Invalid IP');
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$revert_ip = null;
|
||||
}
|
||||
|
||||
if (isset($_POST['revert_date']) && !empty($_POST['revert_date'])) {
|
||||
if (isValidDate($_POST['revert_date']) ){
|
||||
$revert_date = addslashes($_POST['revert_date']); // addslashes is really unnecessary since we just checked if valid, but better safe.
|
||||
}
|
||||
else {
|
||||
$this->theme->display_admin_block('Invalid Date');
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$revert_date = null;
|
||||
}
|
||||
|
||||
set_time_limit(0); // reverting changes can take a long time, disable php's timelimit if possible.
|
||||
|
||||
// Call the revert function.
|
||||
$this->process_revert_all_changes($revert_name, $revert_ip, $revert_date);
|
||||
// output results
|
||||
$this->theme->display_revert_ip_results();
|
||||
}
|
||||
|
||||
public function get_source_history_from_revert(/*int*/ $revert_id) {
|
||||
global $database;
|
||||
$row = $database->get_row("
|
||||
SELECT source_histories.*, users.name
|
||||
FROM source_histories
|
||||
JOIN users ON source_histories.user_id = users.id
|
||||
WHERE source_histories.id = ?", array($revert_id));
|
||||
return ($row ? $row : null);
|
||||
}
|
||||
|
||||
public function get_source_history_from_id(/*int*/ $image_id) {
|
||||
global $database;
|
||||
$row = $database->get_all("
|
||||
SELECT source_histories.*, users.name
|
||||
FROM source_histories
|
||||
JOIN users ON source_histories.user_id = users.id
|
||||
WHERE image_id = ?
|
||||
ORDER BY source_histories.id DESC",
|
||||
array($image_id));
|
||||
return ($row ? $row : array());
|
||||
}
|
||||
|
||||
public function get_global_source_history($page_id) {
|
||||
global $database;
|
||||
$row = $database->get_all("
|
||||
SELECT source_histories.*, users.name
|
||||
FROM source_histories
|
||||
JOIN users ON source_histories.user_id = users.id
|
||||
ORDER BY source_histories.id DESC
|
||||
LIMIT 100 OFFSET :offset
|
||||
", array("offset" => ($page_id-1)*100));
|
||||
return ($row ? $row : array());
|
||||
}
|
||||
|
||||
/*
|
||||
* This function attempts to revert all changes by a given IP within an (optional) timeframe.
|
||||
*/
|
||||
public function process_revert_all_changes($name, $ip, $date) {
|
||||
global $database;
|
||||
|
||||
$select_code = array();
|
||||
$select_args = array();
|
||||
|
||||
if(!is_null($name)) {
|
||||
$duser = User::by_name($name);
|
||||
if(is_null($duser)) {
|
||||
$this->theme->add_status($name, "user not found");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$select_code[] = 'user_id = ?';
|
||||
$select_args[] = $duser->id;
|
||||
}
|
||||
}
|
||||
|
||||
if(!is_null($date)) {
|
||||
$select_code[] = 'date_set >= ?';
|
||||
$select_args[] = $date;
|
||||
}
|
||||
|
||||
if(!is_null($ip)) {
|
||||
$select_code[] = 'user_ip = ?';
|
||||
$select_args[] = $ip;
|
||||
}
|
||||
|
||||
if(count($select_code) == 0) {
|
||||
log_error("source_history", "Tried to mass revert without any conditions");
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("source_history", 'Attempting to revert edits where '.implode(" and ", $select_code)." (".implode(" / ", $select_args).")");
|
||||
|
||||
// Get all the images that the given IP has changed source on (within the timeframe) that were last editied by the given IP
|
||||
$result = $database->get_col('
|
||||
SELECT t1.image_id
|
||||
FROM source_histories t1
|
||||
LEFT JOIN source_histories t2 ON (t1.image_id = t2.image_id AND t1.date_set < t2.date_set)
|
||||
WHERE t2.image_id IS NULL
|
||||
AND t1.image_id IN ( select image_id from source_histories where '.implode(" AND ", $select_code).')
|
||||
ORDER BY t1.image_id
|
||||
', $select_args);
|
||||
|
||||
foreach($result as $image_id) {
|
||||
// Get the first source history that was done before the given IP edit
|
||||
$row = $database->get_row('
|
||||
SELECT id, source
|
||||
FROM source_histories
|
||||
WHERE image_id='.$image_id.'
|
||||
AND NOT ('.implode(" AND ", $select_code).')
|
||||
ORDER BY date_set DESC LIMIT 1
|
||||
', $select_args);
|
||||
|
||||
if (empty($row)) {
|
||||
// we can not revert this image based on the date restriction.
|
||||
// Output a message perhaps?
|
||||
}
|
||||
else {
|
||||
$revert_id = $row['id'];
|
||||
$result = $this->get_source_history_from_revert($revert_id);
|
||||
|
||||
if(empty($result)) {
|
||||
// there is no history entry with that id so either the image was deleted
|
||||
// while the user was viewing the history, or something messed up
|
||||
/* calling die() is probably not a good idea, we should throw an Exception */
|
||||
die('Error: No source history with specified id ('.$revert_id.') was found in the database.'."\n\n".
|
||||
'Perhaps the image was deleted while processing this request.');
|
||||
}
|
||||
|
||||
// lets get the values out of the result
|
||||
$stored_result_id = $result['id'];
|
||||
$stored_image_id = $result['image_id'];
|
||||
$stored_source = $result['source'];
|
||||
|
||||
log_debug("source_history", 'Reverting source of Image #'.$stored_image_id.' to ['.$stored_source.']');
|
||||
// all should be ok so we can revert by firing the SetSources event.
|
||||
send_event(new SourceSetEvent(Image::by_id($stored_image_id), $stored_source));
|
||||
$this->theme->add_status('Reverted Change','Reverted Image #'.$image_id.' to Source History #'.$stored_result_id.' ('.$row['source'].')');
|
||||
}
|
||||
}
|
||||
|
||||
log_info("source_history", 'Reverted '.count($result).' edits.');
|
||||
}
|
||||
|
||||
/*
|
||||
* this function is called just before an images source is changed
|
||||
*/
|
||||
private function add_source_history($image, $source) {
|
||||
global $database, $config, $user;
|
||||
|
||||
$new_source = $source;
|
||||
$old_source = $image->source;
|
||||
|
||||
if($new_source == $old_source) return;
|
||||
|
||||
if(empty($old_source)) {
|
||||
/* no old source, so we are probably adding the image for the first time */
|
||||
log_debug("source_history", "adding new source history: [$new_source]");
|
||||
}
|
||||
else {
|
||||
log_debug("source_history", "adding source history: [$old_source] -> [$new_source]");
|
||||
}
|
||||
|
||||
$allowed = $config->get_int("history_limit");
|
||||
if($allowed == 0) return;
|
||||
|
||||
// if the image has no history, make one with the old source
|
||||
$entries = $database->get_one("SELECT COUNT(*) FROM source_histories WHERE image_id = ?", array($image->id));
|
||||
if($entries == 0 && !empty($old_source)) {
|
||||
$database->execute("
|
||||
INSERT INTO source_histories(image_id, source, user_id, user_ip, date_set)
|
||||
VALUES (?, ?, ?, ?, now())",
|
||||
array($image->id, $old_source, $config->get_int('anon_id'), '127.0.0.1'));
|
||||
$entries++;
|
||||
}
|
||||
|
||||
// add a history entry
|
||||
$row = $database->execute("
|
||||
INSERT INTO source_histories(image_id, source, user_id, user_ip, date_set)
|
||||
VALUES (?, ?, ?, ?, now())",
|
||||
array($image->id, $new_source, $user->id, $_SERVER['REMOTE_ADDR']));
|
||||
$entries++;
|
||||
|
||||
// if needed remove oldest one
|
||||
if($allowed == -1) return;
|
||||
if($entries > $allowed) {
|
||||
// TODO: Make these queries better
|
||||
/*
|
||||
MySQL does NOT allow you to modify the same table which you use in the SELECT part.
|
||||
Which means that these will probably have to stay as TWO separate queries...
|
||||
|
||||
http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html
|
||||
http://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause
|
||||
*/
|
||||
$min_id = $database->get_one("SELECT MIN(id) FROM source_histories WHERE image_id = ?", array($image->id));
|
||||
$database->execute("DELETE FROM source_histories WHERE id = ?", array($min_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
137
ext/source_history/theme.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
class Source_HistoryTheme extends Themelet {
|
||||
var $messages = array();
|
||||
|
||||
public function display_history_page(Page $page, /*int*/ $image_id, /*array*/ $history) {
|
||||
global $user;
|
||||
$start_string = "
|
||||
<div style='text-align: left'>
|
||||
".make_form(make_link("source_history/revert"))."
|
||||
<ul style='list-style-type:none;'>
|
||||
";
|
||||
|
||||
$history_list = "";
|
||||
$n = 0;
|
||||
foreach($history as $fields)
|
||||
{
|
||||
$n++;
|
||||
$current_id = $fields['id'];
|
||||
$current_source = html_escape($fields['source']);
|
||||
$name = $fields['name'];
|
||||
$h_ip = $user->can("view_ip") ? " ".show_ip($fields['user_ip'], "Sourcing Image #$image_id as '$current_source'") : "";
|
||||
$setter = "<a href='".make_link("user/".url_escape($name))."'>".html_escape($name)."</a>$h_ip";
|
||||
|
||||
$selected = ($n == 2) ? " checked" : "";
|
||||
|
||||
$history_list .= "
|
||||
<li>
|
||||
<input type='radio' name='revert' id='$current_id' value='$current_id'$selected>
|
||||
<label for='$current_id'>$current_source (Set by $setter)</label>
|
||||
</li>
|
||||
";
|
||||
}
|
||||
|
||||
$end_string = "
|
||||
</ul>
|
||||
<input type='submit' value='Revert To'>
|
||||
</form>
|
||||
</div>
|
||||
";
|
||||
$history_html = $start_string . $history_list . $end_string;
|
||||
|
||||
$page->set_title('Image '.$image_id.' Source History');
|
||||
$page->set_heading('Source History: '.$image_id);
|
||||
$page->add_block(new NavBlock());
|
||||
$page->add_block(new Block("Source History", $history_html, "main", 10));
|
||||
}
|
||||
|
||||
public function display_global_page(Page $page, /*array*/ $history, /*int*/ $page_number) {
|
||||
$start_string = "
|
||||
<div style='text-align: left'>
|
||||
".make_form(make_link("source_history/revert"))."
|
||||
<ul style='list-style-type:none;'>
|
||||
";
|
||||
$end_string = "
|
||||
</ul>
|
||||
<input type='submit' value='Revert To'>
|
||||
</form>
|
||||
</div>
|
||||
";
|
||||
|
||||
global $user;
|
||||
$history_list = "";
|
||||
foreach($history as $fields)
|
||||
{
|
||||
$current_id = $fields['id'];
|
||||
$image_id = $fields['image_id'];
|
||||
$current_source = html_escape($fields['source']);
|
||||
$name = $fields['name'];
|
||||
$h_ip = $user->can("view_ip") ? " ".show_ip($fields['user_ip'], "Sourcing Image #$image_id as '$current_source'") : "";
|
||||
$setter = "<a href='".make_link("user/".url_escape($name))."'>".html_escape($name)."</a>$h_ip";
|
||||
|
||||
$history_list .= '
|
||||
<li>
|
||||
<input type="radio" name="revert" value="'.$current_id.'">
|
||||
<a href="'.make_link('post/view/'.$image_id).'">'.$image_id.'</a>:
|
||||
'.$current_source.' (Set by '.$setter.')
|
||||
</li>
|
||||
';
|
||||
}
|
||||
|
||||
$history_html = $start_string . $history_list . $end_string;
|
||||
$page->set_title("Global Source History");
|
||||
$page->set_heading("Global Source History");
|
||||
$page->add_block(new Block("Source History", $history_html, "main", 10));
|
||||
|
||||
|
||||
$h_prev = ($page_number <= 1) ? "Prev" :
|
||||
'<a href="'.make_link('source_history/all/'.($page_number-1)).'">Prev</a>';
|
||||
$h_index = "<a href='".make_link()."'>Index</a>";
|
||||
$h_next = '<a href="'.make_link('source_history/all/'.($page_number+1)).'">Next</a>';
|
||||
|
||||
$nav = $h_prev.' | '.$h_index.' | '.$h_next;
|
||||
$page->add_block(new Block("Navigation", $nav, "left"));
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a section to the admin page.
|
||||
*/
|
||||
public function display_admin_block(/*string*/ $validation_msg='') {
|
||||
global $page;
|
||||
|
||||
if (!empty($validation_msg)) {
|
||||
$validation_msg = '<br><b>'. $validation_msg .'</b>';
|
||||
}
|
||||
|
||||
$html = '
|
||||
Revert source changes/edit by a specific IP address or username.
|
||||
<br>You can restrict the time frame to revert these edits as well.
|
||||
<br>(Date format: 2011-10-23)
|
||||
'.$validation_msg.'
|
||||
|
||||
<br><br>'.make_form(make_link("source_history/bulk_revert"), 'POST')."
|
||||
<table class='form'>
|
||||
<tr><th>Username</th> <td><input type='text' name='revert_name' size='15'></td></tr>
|
||||
<tr><th>IP Address</th> <td><input type='text' name='revert_ip' size='15'></td></tr>
|
||||
<tr><th>Date range</th> <td><input type='text' name='revert_date' size='15'></td></tr>
|
||||
<tr><td colspan='2'><input type='submit' value='Revert'></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
";
|
||||
$page->add_block(new Block("Mass Source Revert", $html));
|
||||
}
|
||||
|
||||
/*
|
||||
* Show a standard page for results to be put into
|
||||
*/
|
||||
public function display_revert_ip_results() {
|
||||
global $page;
|
||||
$html = implode($this->messages, "\n");
|
||||
$page->add_block(new Block("Bulk Revert Results", $html));
|
||||
}
|
||||
|
||||
public function add_status(/*string*/ $title, /*string*/ $body) {
|
||||
$this->messages[] = '<p><b>'. $title .'</b><br>'. $body .'</p>';
|
||||
}
|
||||
}
|
||||
?>
|
@ -34,13 +34,25 @@ class StatsDInterface extends Extension {
|
||||
public function onPageRequest($event) {
|
||||
$this->_stats("overall");
|
||||
|
||||
if($event->page_matches("post/list")) {
|
||||
$this->_stats("post-list");
|
||||
}
|
||||
else if($event->page_matches("post/view")) {
|
||||
if($event->page_matches("post/view")) { # 40%
|
||||
$this->_stats("post-view");
|
||||
}
|
||||
else if($event->page_matches("post/list")) { # 30%
|
||||
$this->_stats("post-list");
|
||||
}
|
||||
else if($event->page_matches("user")) {
|
||||
$this->_stats("user");
|
||||
}
|
||||
else if($event->page_matches("upload")) {
|
||||
$this->_stats("upload");
|
||||
}
|
||||
else if($event->page_matches("rss")) {
|
||||
$this->_stats("rss");
|
||||
}
|
||||
else {
|
||||
#global $_load_start;
|
||||
#$time = microtime(true) - $_load_start;
|
||||
#file_put_contents("data/other.log", "{$_SERVER['REQUEST_URI']} $time\n", FILE_APPEND);
|
||||
$this->_stats("other");
|
||||
}
|
||||
|
||||
|
125
ext/tag_categories/main.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* Name: Tag Categories
|
||||
* Author: Daniel Oaks <danneh@danneh.net>
|
||||
* Link: http://code.shishnet.org/shimmie2/
|
||||
* Description: Let tags be split into 'categories', like Danbooru's tagging
|
||||
*/
|
||||
|
||||
class TagCategories extends Extension {
|
||||
public function onInitExt(InitExtEvent $event) {
|
||||
global $config, $database;
|
||||
|
||||
// whether we split out separate categories on post view by default
|
||||
// note: only takes effect if /post/view shows the image's exact tags
|
||||
$config->set_default_bool("tag_categories_split_on_view", true);
|
||||
|
||||
if($config->get_int("ext_tag_categories_version") < 1) {
|
||||
// primary extension database, holds all our stuff!
|
||||
$database->create_table('image_tag_categories',
|
||||
'category VARCHAR(60) PRIMARY KEY,
|
||||
display_singular TEXT(60),
|
||||
display_multiple TEXT(60),
|
||||
color TEXT(7)');
|
||||
|
||||
$config->set_int("ext_tag_categories_version", 1);
|
||||
|
||||
log_info("tag_categories", "extension installed");
|
||||
}
|
||||
|
||||
// if empty, add our default values
|
||||
$number_of_db_rows = $database->execute('SELECT COUNT(*) FROM image_tag_categories;')->fetchColumn();
|
||||
|
||||
if ($number_of_db_rows == 0) {
|
||||
$database->execute('INSERT INTO image_tag_categories VALUES ("artist", "Artist", "Artists", "#BB6666");');
|
||||
$database->execute('INSERT INTO image_tag_categories VALUES ("series", "Series", "Series", "#AA00AA");');
|
||||
$database->execute('INSERT INTO image_tag_categories VALUES ("character", "Character", "Characters", "#66BB66");');
|
||||
}
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event) {
|
||||
global $page, $database, $user;
|
||||
|
||||
if($event->page_matches("tags/categories")) {
|
||||
if($user->is_admin()) {
|
||||
$this->page_update();
|
||||
$this->show_tag_categories($page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getDict() {
|
||||
global $database;
|
||||
|
||||
$tc_dict = $database->get_all('SELECT * FROM image_tag_categories;');
|
||||
|
||||
return $tc_dict;
|
||||
}
|
||||
|
||||
public function getKeyedDict($key_with = 'category') {
|
||||
$tc_dict = $this->getDict();
|
||||
$tc_keyed_dict = array();
|
||||
|
||||
foreach ($tc_dict as $row) {
|
||||
$key = $row[$key_with];
|
||||
$tc_keyed_dict[$key] = $row;
|
||||
}
|
||||
|
||||
return $tc_keyed_dict;
|
||||
}
|
||||
|
||||
public function page_update() {
|
||||
global $user, $database;
|
||||
|
||||
if(!$user->is_admin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isset($_POST['tc_status']) and
|
||||
!isset($_POST['tc_category']) and
|
||||
!isset($_POST['tc_display_singular']) and
|
||||
!isset($_POST['tc_display_multiple']) and
|
||||
!isset($_POST['tc_color'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($_POST['tc_status'] == 'edit') {
|
||||
$is_success = $database->execute('UPDATE image_tag_categories
|
||||
SET display_singular=:display_singular,
|
||||
display_multiple=:display_multiple,
|
||||
color=:color
|
||||
WHERE category=:category',
|
||||
array(
|
||||
'category' => $_POST['tc_category'],
|
||||
'display_singular' => $_POST['tc_display_singular'],
|
||||
'display_multiple' => $_POST['tc_display_multiple'],
|
||||
'color' => $_POST['tc_color'],
|
||||
));
|
||||
}
|
||||
else if($_POST['tc_status'] == 'new') {
|
||||
$is_success = $database->execute('INSERT INTO image_tag_categories
|
||||
VALUES (:category, :display_singular, :display_multiple, :color)',
|
||||
array(
|
||||
'category' => $_POST['tc_category'],
|
||||
'display_singular' => $_POST['tc_display_singular'],
|
||||
'display_multiple' => $_POST['tc_display_multiple'],
|
||||
'color' => $_POST['tc_color'],
|
||||
));
|
||||
}
|
||||
else if($_POST['tc_status'] == 'delete') {
|
||||
$is_success = $database->execute('DELETE FROM image_tag_categories
|
||||
WHERE category=:category',
|
||||
array(
|
||||
'category' => $_POST['tc_category']
|
||||
));
|
||||
}
|
||||
|
||||
return $is_success;
|
||||
}
|
||||
|
||||
public function show_tag_categories($page) {
|
||||
$this->theme->show_tag_categories($page, $this->getDict());
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
103
ext/tag_categories/theme.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
class TagCategoriesTheme extends Themelet {
|
||||
var $heading = "";
|
||||
var $list = "";
|
||||
|
||||
public function show_tag_categories($page, $tc_dict) {
|
||||
$tc_block_index = 0;
|
||||
$html = '';
|
||||
|
||||
foreach ($tc_dict as $row) {
|
||||
$tc_block_index += 1;
|
||||
$tag_category = $row['category'];
|
||||
$tag_single_name = $row['display_singular'];
|
||||
$tag_multiple_name = $row['display_multiple'];
|
||||
$tag_color = $row['color'];
|
||||
$html .= '
|
||||
<div class="tagcategoryblock">
|
||||
<form name="input" action="'.make_link("tags/categories").'" method="post">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Category</td>
|
||||
<td>
|
||||
<span>'.$tag_category.'</span>
|
||||
<!--<input type="text" name="tc_category" style="display:none" value="'.$tag_category.'">-->
|
||||
<input type="hidden" name="tc_category" value="'.$tag_category.'">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name – Single</td>
|
||||
<td>
|
||||
<span>'.$tag_single_name.'</span>
|
||||
<input type="text" name="tc_display_singular" style="display:none" value="'.$tag_single_name.'">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name – Multiple</td>
|
||||
<td>
|
||||
<span>'.$tag_multiple_name.'</span>
|
||||
<input type="text" name="tc_display_multiple" style="display:none" value="'.$tag_multiple_name.'">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Color</td>
|
||||
<td>
|
||||
<span>'.$tag_color.'</span>
|
||||
<input type="text" name="tc_color" style="display:none" value="'.$tag_color.'">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="tc_edit" type="button" onclick="$(\'.tagcategoryblock:nth-of-type('.$tc_block_index.') tr + tr td span\').hide(); $(\'.tagcategoryblock:nth-of-type('.$tc_block_index.') td input\').show(); $(\'.tagcategoryblock:nth-of-type('.$tc_block_index.') .tc_edit\').hide(); $(\'.tagcategoryblock:nth-of-type('.$tc_block_index.') .tc_submit\').show();">Edit</button>
|
||||
<button class="tc_submit" type="submit" style="display:none;" name="tc_status" value="edit">Submit</button>
|
||||
<button class="tc_submit" type="button" style="display:none.tagcategoryblock:nth-of-type('.$tc_block_index.');" onclick="$(\'.tagcategoryblock:nth-of-type('.$tc_block_index.') .tc_delete\').show(); $(this).hide();">Delete</button>
|
||||
<button class="tc_delete" type="submit" style="display:none;" name="tc_status" value="delete">Really, really delete</button>
|
||||
</form>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
// new
|
||||
$tag_category = 'example';
|
||||
$tag_single_name = 'Example';
|
||||
$tag_multiple_name = 'Examples';
|
||||
$tag_color = '#EE5542';
|
||||
$html .= '
|
||||
<div class="tagcategoryblock">
|
||||
<form name="input" action="'.make_link("tags/categories").'" method="post">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Category</td>
|
||||
<td>
|
||||
<input type="text" name="tc_category" value="'.$tag_category.'">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name – Single</td>
|
||||
<td>
|
||||
<input type="text" name="tc_display_singular" value="'.$tag_single_name.'">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name – Multiple</td>
|
||||
<td>
|
||||
<input type="text" name="tc_display_multiple" value="'.$tag_multiple_name.'">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Color</td>
|
||||
<td>
|
||||
<input type="text" name="tc_color" value="'.$tag_color.'">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="tc_submit" type="submit" name="tc_status" value="new">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
';
|
||||
|
||||
// add html to stuffs
|
||||
$page->add_block(new Block("Editing", $html, "main", 10));
|
||||
}
|
||||
}
|
||||
?>
|
@ -108,10 +108,10 @@ class TagEdit extends Extension {
|
||||
$owner = User::by_name($_POST['tag_edit__owner']);
|
||||
send_event(new OwnerSetEvent($event->image, $owner));
|
||||
}
|
||||
if($this->can_tag($event->image)) {
|
||||
if($this->can_tag($event->image) && isset($_POST['tag_edit__tags'])) {
|
||||
send_event(new TagSetEvent($event->image, $_POST['tag_edit__tags']));
|
||||
}
|
||||
if($this->can_source($event->image)) {
|
||||
if($this->can_source($event->image) && isset($_POST['tag_edit__source'])) {
|
||||
send_event(new SourceSetEvent($event->image, $_POST['tag_edit__source']));
|
||||
}
|
||||
if($user->can("edit_image_lock")) {
|
||||
|
@ -32,16 +32,26 @@ class TagEditTheme extends Themelet {
|
||||
|
||||
public function get_tag_editor_html(Image $image) {
|
||||
global $user;
|
||||
|
||||
$tag_links = array();
|
||||
foreach($image->get_tag_array() as $tag) {
|
||||
$h_tag = html_escape($tag);
|
||||
$u_tag = url_escape($tag);
|
||||
$h_link = make_link("post/list/$u_tag/1");
|
||||
$tag_links[] = "<a href='$h_link'>$h_tag</a>";
|
||||
}
|
||||
$h_tag_links = implode(" ", $tag_links);
|
||||
$h_tags = html_escape($image->get_tag_list());
|
||||
|
||||
return "
|
||||
<tr>
|
||||
<th width='50px'>Tags</th>
|
||||
<td>
|
||||
".($user->can("edit_image_tag") ? "
|
||||
<span class='view'>$h_tags</span>
|
||||
<span class='view'>$h_tag_links</span>
|
||||
<input class='edit' type='text' name='tag_edit__tags' value='$h_tags' class='autocomplete_tags' id='tag_editor'>
|
||||
" : "
|
||||
$h_tags
|
||||
$h_tag_links
|
||||
")."
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -331,10 +331,10 @@ class Tag_History extends Extension {
|
||||
|
||||
if(empty($old_tags)) {
|
||||
/* no old tags, so we are probably adding the image for the first time */
|
||||
log_debug("tag_history", "adding new tag history: [$new_tags]");
|
||||
log_debug("tag_history", "adding new tag history: [$new_tags]", false, array("image_id" => $image->id));
|
||||
}
|
||||
else {
|
||||
log_debug("tag_history", "adding tag history: [$old_tags] -> [$new_tags]");
|
||||
log_debug("tag_history", "adding tag history: [$old_tags] -> [$new_tags]", false, array("image_id" => $image->id));
|
||||
}
|
||||
|
||||
$allowed = $config->get_int("history_limit");
|
||||
|
@ -40,7 +40,7 @@ class TagList extends Extension {
|
||||
break;
|
||||
case 'categories':
|
||||
$this->theme->set_heading("Popular Categories");
|
||||
$this->theme->set_tag_list($this->build_tag_categories());
|
||||
$this->theme->set_tag_list($this->build_tag_list());
|
||||
break;
|
||||
}
|
||||
$this->theme->display_page($page);
|
||||
@ -81,7 +81,12 @@ class TagList extends Extension {
|
||||
$this->add_related_block($page, $event->image);
|
||||
}
|
||||
else {
|
||||
$this->add_tags_block($page, $event->image);
|
||||
if(class_exists("TagCategories") and $config->get_bool('tag_categories_split_on_view')) {
|
||||
$this->add_split_tags_block($page, $event->image);
|
||||
}
|
||||
else {
|
||||
$this->add_tags_block($page, $event->image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,7 +295,7 @@ class TagList extends Extension {
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function build_tag_categories() {
|
||||
private function build_tag_list() {
|
||||
global $database;
|
||||
|
||||
$tags_min = $this->get_tags_min();
|
||||
@ -348,6 +353,25 @@ class TagList extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
private function add_split_tags_block(Page $page, Image $image) {
|
||||
global $database;
|
||||
global $config;
|
||||
|
||||
$query = "
|
||||
SELECT tags.tag, tags.count as calc_count
|
||||
FROM tags, image_tags
|
||||
WHERE tags.id = image_tags.tag_id
|
||||
AND image_tags.image_id = :image_id
|
||||
ORDER BY calc_count DESC
|
||||
";
|
||||
$args = array("image_id"=>$image->id);
|
||||
|
||||
$tags = $database->get_all($query, $args);
|
||||
if(count($tags) > 0) {
|
||||
$this->theme->display_split_related_block($page, $tags);
|
||||
}
|
||||
}
|
||||
|
||||
private function add_tags_block(Page $page, Image $image) {
|
||||
global $database;
|
||||
global $config;
|
||||
@ -374,7 +398,7 @@ class TagList extends Extension {
|
||||
$tags = $database->cache->get("popular_tags");
|
||||
if(empty($tags)) {
|
||||
$query = "
|
||||
SELECT tag, count
|
||||
SELECT tag, count as calc_count
|
||||
FROM tags
|
||||
WHERE count > 0
|
||||
ORDER BY count DESC
|
||||
|
@ -25,6 +25,64 @@ class TagListTheme extends Themelet {
|
||||
|
||||
// =======================================================================
|
||||
|
||||
/*
|
||||
* $tag_infos = array(
|
||||
* array('tag' => $tag, 'count' => $number_of_uses),
|
||||
* ...
|
||||
* )
|
||||
*/
|
||||
public function display_split_related_block(Page $page, $tag_infos) {
|
||||
global $config;
|
||||
|
||||
if($config->get_string('tag_list_related_sort') == 'alphabetical') asort($tag_infos);
|
||||
|
||||
if(class_exists('TagCategories')) {
|
||||
$this->tagcategories = new TagCategories;
|
||||
$tag_category_dict = $this->tagcategories->getKeyedDict();
|
||||
}
|
||||
else {
|
||||
$tag_category_dict = array();
|
||||
}
|
||||
$tag_categories_html = array();
|
||||
$tag_categories_count = array();
|
||||
|
||||
foreach($tag_infos as $row) {
|
||||
$split = self::return_tag($row, $tag_category_dict);
|
||||
$category = $split[0];
|
||||
$tag_html = $split[1];
|
||||
if(!isset($tag_categories_html[$category])) {
|
||||
$tag_categories_html[$category] = '';
|
||||
}
|
||||
$tag_categories_html[$category] .= $tag_html . '<br />';
|
||||
|
||||
if(!isset($tag_categories_count[$category])) {
|
||||
$tag_categories_count[$category] = 0;
|
||||
}
|
||||
$tag_categories_count[$category] += 1;
|
||||
}
|
||||
|
||||
asort($tag_categories_html);
|
||||
$main_html = $tag_categories_html[' '];
|
||||
unset($tag_categories_html[' ']);
|
||||
|
||||
foreach(array_keys($tag_categories_html) as $category) {
|
||||
if($tag_categories_count[$category] < 2) {
|
||||
$category_display_name = html_escape($tag_category_dict[$category]['display_singular']);
|
||||
}
|
||||
else{
|
||||
$category_display_name = html_escape($tag_category_dict[$category]['display_multiple']);
|
||||
}
|
||||
$page->add_block(new Block($category_display_name, $tag_categories_html[$category], "left", 9));
|
||||
}
|
||||
|
||||
if($config->get_string('tag_list_image_type')=="tags") {
|
||||
$page->add_block(new Block("Tags", $main_html, "left", 10));
|
||||
}
|
||||
else {
|
||||
$page->add_block(new Block("Related Tags", $main_html, "left", 10));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* $tag_infos = array(
|
||||
* array('tag' => $tag, 'count' => $number_of_uses),
|
||||
@ -34,32 +92,29 @@ class TagListTheme extends Themelet {
|
||||
public function display_related_block(Page $page, $tag_infos) {
|
||||
global $config;
|
||||
|
||||
$html = "";
|
||||
$n = 0;
|
||||
if($config->get_string('tag_list_related_sort') == 'alphabetical') asort($tag_infos);
|
||||
|
||||
foreach($tag_infos as $row) {
|
||||
$tag = $row['tag'];
|
||||
$h_tag = html_escape($tag);
|
||||
$h_tag_no_underscores = str_replace("_", " ", $h_tag);
|
||||
$count = $row['calc_count'];
|
||||
if($n++) $html .= "\n<br/>";
|
||||
if(!is_null($config->get_string('info_link'))) {
|
||||
$link = str_replace('$tag', $tag, $config->get_string('info_link'));
|
||||
$html .= " <a class='tag_info_link' href='$link'>?</a>";
|
||||
}
|
||||
$link = $this->tag_link($row['tag']);
|
||||
$html .= " <a class='tag_name' href='$link'>$h_tag_no_underscores</a>";
|
||||
if($config->get_bool("tag_list_numbers")) {
|
||||
$html .= " <span class='tag_count'>$count</span>";
|
||||
}
|
||||
}
|
||||
|
||||
if($config->get_string('tag_list_image_type')=="tags") {
|
||||
$page->add_block(new Block("Tags", $html, "left", 10));
|
||||
if(class_exists('TagCategories')) {
|
||||
$this->tagcategories = new TagCategories;
|
||||
$tag_category_dict = $this->tagcategories->getKeyedDict();
|
||||
}
|
||||
else {
|
||||
$page->add_block(new Block("Related Tags", $html, "left", 10));
|
||||
$tag_category_dict = array();
|
||||
}
|
||||
$main_html = '';
|
||||
|
||||
foreach($tag_infos as $row) {
|
||||
$split = $this->return_tag($row, $tag_category_dict);
|
||||
$category = $split[0];
|
||||
$tag_html = $split[1];
|
||||
$main_html .= $tag_html . '<br />';
|
||||
}
|
||||
|
||||
if($config->get_string('tag_list_image_type')=="tags") {
|
||||
$page->add_block(new Block("Tags", $main_html, "left", 10));
|
||||
}
|
||||
else {
|
||||
$page->add_block(new Block("Related Tags", $main_html, "left", 10));
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,34 +127,27 @@ class TagListTheme extends Themelet {
|
||||
*/
|
||||
public function display_popular_block(Page $page, $tag_infos) {
|
||||
global $config;
|
||||
|
||||
// store local copies for speed.
|
||||
$info_link = $config->get_string('info_link');
|
||||
$tag_list_num = $config->get_bool("tag_list_numbers");
|
||||
|
||||
$html = "";
|
||||
$n = 0;
|
||||
if($config->get_string('tag_list_popular_sort') == 'alphabetical') asort($tag_infos);
|
||||
|
||||
|
||||
if(class_exists('TagCategories')) {
|
||||
$this->tagcategories = new TagCategories;
|
||||
$tag_category_dict = $this->tagcategories->getKeyedDict();
|
||||
}
|
||||
else {
|
||||
$tag_category_dict = array();
|
||||
}
|
||||
$main_html = '';
|
||||
|
||||
foreach($tag_infos as $row) {
|
||||
$tag = $row['tag'];
|
||||
$h_tag = html_escape($tag);
|
||||
$h_tag_no_underscores = str_replace("_", " ", $h_tag);
|
||||
$count = $row['count'];
|
||||
if($n++) $html .= "\n<br/>";
|
||||
if(!is_null($info_link)) {
|
||||
$link = str_replace('$tag', $tag, $info_link);
|
||||
$html .= ' <a class="tag_info_link" href="'.$link.'">?</a>';
|
||||
}
|
||||
$link = $this->tag_link($row['tag']);
|
||||
$html .= ' <a class="tag_name" href="'.$link.'">'.$h_tag_no_underscores.'</a>';
|
||||
if($tag_list_num) {
|
||||
$html .= ' <span class="tag_count">'.$count.'</span>';
|
||||
}
|
||||
$split = self::return_tag($row, $tag_category_dict);
|
||||
$category = $split[0];
|
||||
$tag_html = $split[1];
|
||||
$main_html .= $tag_html . '<br />';
|
||||
}
|
||||
|
||||
$html .= "<br> <br><a class='more' href='".make_link("tags")."'>Full List</a>\n";
|
||||
$page->add_block(new Block("Popular Tags", $html, "left", 60));
|
||||
$main_html .= " <br><a class='more' href='".make_link("tags")."'>Full List</a>\n";
|
||||
$page->add_block(new Block("Popular Tags", $main_html, "left", 60));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -112,27 +160,63 @@ class TagListTheme extends Themelet {
|
||||
public function display_refine_block(Page $page, $tag_infos, $search) {
|
||||
global $config;
|
||||
|
||||
// store local copy for speed.
|
||||
$info_link = $config->get_string('info_link');
|
||||
|
||||
$html = "";
|
||||
$n = 0;
|
||||
|
||||
if($config->get_string('tag_list_popular_sort') == 'alphabetical') asort($tag_infos);
|
||||
|
||||
if(class_exists('TagCategories')) {
|
||||
$this->tagcategories = new TagCategories;
|
||||
$tag_category_dict = $this->tagcategories->getKeyedDict();
|
||||
}
|
||||
else {
|
||||
$tag_category_dict = array();
|
||||
}
|
||||
$main_html = '';
|
||||
|
||||
foreach($tag_infos as $row) {
|
||||
$tag = $row['tag'];
|
||||
$h_tag = html_escape($tag);
|
||||
$h_tag_no_underscores = str_replace("_", " ", $h_tag);
|
||||
if($n++) $html .= "\n<br/>";
|
||||
if(!is_null($info_link)) {
|
||||
$link = str_replace('$tag', $tag, $info_link);
|
||||
$html .= ' <a class="tag_info_link" href="'.$link.'">?</a>';
|
||||
}
|
||||
$link = $this->tag_link($row['tag']);
|
||||
$html .= ' <a class="tag_name" href="'.$link.'">'.$h_tag_no_underscores.'</a>';
|
||||
$html .= $this->ars($tag, $search);
|
||||
$split = self::return_tag($row, $tag_category_dict);
|
||||
$category = $split[0];
|
||||
$tag_html = $split[1];
|
||||
$main_html .= $tag_html . '<br />';
|
||||
}
|
||||
|
||||
$page->add_block(new Block("Refine Search", $html, "left", 60));
|
||||
$main_html .= " <br><a class='more' href='".make_link("tags")."'>Full List</a>\n";
|
||||
$page->add_block(new Block("refine Search", $main_html, "left", 60));
|
||||
}
|
||||
|
||||
public function return_tag($row, $tag_category_dict) {
|
||||
global $config;
|
||||
|
||||
$display_html = '';
|
||||
$tag = $row['tag'];
|
||||
$h_tag = html_escape($tag);
|
||||
|
||||
$tag_category_css = '';
|
||||
$tag_category_style = '';
|
||||
$h_tag_split = explode(':', html_escape($tag), 2);
|
||||
$category = ' ';
|
||||
|
||||
// we found a tag, see if it's valid!
|
||||
if((count($h_tag_split) > 1) and array_key_exists($h_tag_split[0], $tag_category_dict)) {
|
||||
$category = $h_tag_split[0];
|
||||
$h_tag = $h_tag_split[1];
|
||||
$tag_category_css .= ' tag_category_'.$category;
|
||||
$tag_category_style .= 'style="color:'.html_escape($tag_category_dict[$category]['color']).';" ';
|
||||
}
|
||||
|
||||
$h_tag_no_underscores = str_replace("_", " ", $h_tag);
|
||||
$count = $row['calc_count'];
|
||||
// if($n++) $display_html .= "\n<br/>";
|
||||
if(!is_null($config->get_string('info_link'))) {
|
||||
$link = str_replace('$tag', $tag, $config->get_string('info_link'));
|
||||
$display_html .= ' <a class="tag_info_link'.$tag_category_css.'" '.$tag_category_style.'href="'.$link.'">?</a>';
|
||||
}
|
||||
$link = $this->tag_link($row['tag']);
|
||||
$display_html .= ' <a class="tag_name'.$tag_category_css.'" '.$tag_category_style.'href="'.$link.'">'.$h_tag_no_underscores.'</a>';
|
||||
|
||||
if($config->get_bool("tag_list_numbers")) {
|
||||
$display_html .= " <span class='tag_count'>$count</span>";
|
||||
}
|
||||
|
||||
return array($category, $display_html);
|
||||
}
|
||||
|
||||
protected function ars(/*string*/ $tag, /*array(string)*/ $tags) {
|
||||
|
@ -56,9 +56,7 @@ class Upload extends Extension {
|
||||
$this->is_full = false;
|
||||
}
|
||||
else {
|
||||
// TODO: This size limit should be configureable by the admin...
|
||||
// currently set to 100 MB
|
||||
$this->is_full = $free_num < 100*1024*1024;
|
||||
$this->is_full = $free_num < MIN_FREE_SPACE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -193,7 +191,7 @@ class Upload extends Extension {
|
||||
}
|
||||
else if(!empty($_GET['url'])) {
|
||||
$url = $_GET['url'];
|
||||
$source = isset($_POST['source']) ? $_POST['source'] : $url;
|
||||
$source = isset($_GET['source']) ? $_GET['source'] : $url;
|
||||
$tags = array('tagme');
|
||||
if(!empty($_GET['tags']) && $_GET['tags'] != "null") {
|
||||
$tags = Tag::explode($_GET['tags']);
|
||||
@ -330,9 +328,7 @@ class Upload extends Extension {
|
||||
$rating = "";
|
||||
}
|
||||
|
||||
// PHP falls back to system default if /tmp fails, can't we just
|
||||
// use the system default to start with? :-/
|
||||
$tmp_filename = tempnam("/tmp", "shimmie_transload");
|
||||
$tmp_filename = tempnam(ini_get('upload_tmp_dir'), "shimmie_transload");
|
||||
$filename = basename($url);
|
||||
|
||||
if(!transload($url, $tmp_filename)) {
|
||||
|
@ -212,8 +212,15 @@ class UserPage extends Extension {
|
||||
global $page, $user, $config;
|
||||
|
||||
$h_join_date = autodate($event->display_user->join_date);
|
||||
if($event->display_user->can("hellbanned")) {
|
||||
$h_class = $event->display_user->class->parent->name;
|
||||
}
|
||||
else {
|
||||
$h_class = $event->display_user->class->name;
|
||||
}
|
||||
|
||||
$event->add_stats("Joined: $h_join_date", 10);
|
||||
$event->add_stats("Class: {$event->display_user->class->name}", 90);
|
||||
$event->add_stats("Class: $h_class", 90);
|
||||
|
||||
$av = $event->display_user->get_avatar_html();
|
||||
if($av) {
|
||||
@ -224,8 +231,8 @@ class UserPage extends Extension {
|
||||
($user->id == $event->display_user->id)
|
||||
) {
|
||||
$event->add_stats(
|
||||
"No avatar? This gallery uses <a href='http://gravatar.com'>Gravatar</a> for avatar "+
|
||||
"hosting, use the same email address here and there to have your avatar synced",
|
||||
"No avatar? This gallery uses <a href='http://gravatar.com'>Gravatar</a> for avatar hosting, use the".
|
||||
"<br>same email address here and there to have your avatar synced<br>",
|
||||
0
|
||||
);
|
||||
}
|
||||
@ -457,7 +464,7 @@ class UserPage extends Extension {
|
||||
// FIXME: send_event()
|
||||
$duser->set_password($pass1);
|
||||
|
||||
if($id == $user->id) {
|
||||
if($duser->id == $user->id) {
|
||||
$this->set_login_cookie($duser->name, $pass1);
|
||||
}
|
||||
|
||||
|
1
lib/jquery.timeago-0.10.0.min.js
vendored
@ -1 +0,0 @@
|
||||
(function(d){d.timeago=function(g){if(g instanceof Date){return a(g)}else{if(typeof g==="string"){return a(d.timeago.parse(g))}else{return a(d.timeago.datetime(g))}}};var f=d.timeago;d.extend(d.timeago,{settings:{refreshMillis:60000,allowFuture:false,strings:{prefixAgo:null,prefixFromNow:null,suffixAgo:"ago",suffixFromNow:"from now",seconds:"less than a minute",minute:"a minute",minutes:"%d minutes",hour:"an hour",hours:"%d hours",day:"a day",days:"%d days",month:"a month",months:"%d months",year:"a year",years:"%d years",numbers:[]}},inWords:function(l){var m=this.settings.strings;var i=m.prefixAgo;var q=m.suffixAgo;if(this.settings.allowFuture){if(l<0){i=m.prefixFromNow;q=m.suffixFromNow}}var o=Math.abs(l)/1000;var g=o/60;var n=g/60;var p=n/24;var j=p/365;function h(r,t){var s=d.isFunction(r)?r(t,l):r;var u=(m.numbers&&m.numbers[t])||t;return s.replace(/%d/i,u)}var k=o<45&&h(m.seconds,Math.round(o))||o<90&&h(m.minute,1)||g<45&&h(m.minutes,Math.round(g))||g<90&&h(m.hour,1)||n<24&&h(m.hours,Math.round(n))||n<48&&h(m.day,1)||p<30&&h(m.days,Math.floor(p))||p<60&&h(m.month,1)||p<365&&h(m.months,Math.floor(p/30))||j<2&&h(m.year,1)||h(m.years,Math.floor(j));return d.trim([i,k,q].join(" "))},parse:function(h){var g=d.trim(h);g=g.replace(/\.\d\d\d+/,"");g=g.replace(/-/,"/").replace(/-/,"/");g=g.replace(/T/," ").replace(/Z/," UTC");g=g.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2");return new Date(g)},datetime:function(h){var i=d(h).get(0).tagName.toLowerCase()==="time";var g=i?d(h).attr("datetime"):d(h).attr("title");return f.parse(g)}});d.fn.timeago=function(){var h=this;h.each(c);var g=f.settings;if(g.refreshMillis>0){setInterval(function(){h.each(c)},g.refreshMillis)}return h};function c(){var g=b(this);if(!isNaN(g.datetime)){d(this).text(a(g.datetime))}return this}function b(g){g=d(g);if(!g.data("timeago")){g.data("timeago",{datetime:f.datetime(g)});var h=d.trim(g.text());if(h.length>0){g.attr("title",h)}}return g.data("timeago")}function a(g){return f.inWords(e(g))}function e(g){return(new Date().getTime()-g.getTime())}document.createElement("abbr");document.createElement("time")}(jQuery));
|
189
lib/jquery.timeago-1.1.1.js
Normal file
@ -0,0 +1,189 @@
|
||||
/**
|
||||
* Timeago is a jQuery plugin that makes it easy to support automatically
|
||||
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
|
||||
*
|
||||
* @name timeago
|
||||
* @version 1.1.0
|
||||
* @requires jQuery v1.2.3+
|
||||
* @author Ryan McGeary
|
||||
* @license MIT License - http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* For usage and examples, visit:
|
||||
* http://timeago.yarp.com/
|
||||
*
|
||||
* Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
|
||||
*/
|
||||
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['jquery'], factory);
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
$.timeago = function(timestamp) {
|
||||
if (timestamp instanceof Date) {
|
||||
return inWords(timestamp);
|
||||
} else if (typeof timestamp === "string") {
|
||||
return inWords($.timeago.parse(timestamp));
|
||||
} else if (typeof timestamp === "number") {
|
||||
return inWords(new Date(timestamp));
|
||||
} else {
|
||||
return inWords($.timeago.datetime(timestamp));
|
||||
}
|
||||
};
|
||||
var $t = $.timeago;
|
||||
|
||||
$.extend($.timeago, {
|
||||
settings: {
|
||||
refreshMillis: 60000,
|
||||
allowFuture: false,
|
||||
localeTitle: false,
|
||||
cutoff: 0,
|
||||
strings: {
|
||||
prefixAgo: null,
|
||||
prefixFromNow: null,
|
||||
suffixAgo: "ago",
|
||||
suffixFromNow: "from now",
|
||||
seconds: "less than a minute",
|
||||
minute: "about a minute",
|
||||
minutes: "%d minutes",
|
||||
hour: "about an hour",
|
||||
hours: "about %d hours",
|
||||
day: "a day",
|
||||
days: "%d days",
|
||||
month: "about a month",
|
||||
months: "%d months",
|
||||
year: "about a year",
|
||||
years: "%d years",
|
||||
wordSeparator: " ",
|
||||
numbers: []
|
||||
}
|
||||
},
|
||||
inWords: function(distanceMillis) {
|
||||
var $l = this.settings.strings;
|
||||
var prefix = $l.prefixAgo;
|
||||
var suffix = $l.suffixAgo;
|
||||
if (this.settings.allowFuture) {
|
||||
if (distanceMillis < 0) {
|
||||
prefix = $l.prefixFromNow;
|
||||
suffix = $l.suffixFromNow;
|
||||
}
|
||||
}
|
||||
|
||||
var seconds = Math.abs(distanceMillis) / 1000;
|
||||
var minutes = seconds / 60;
|
||||
var hours = minutes / 60;
|
||||
var days = hours / 24;
|
||||
var years = days / 365;
|
||||
|
||||
function substitute(stringOrFunction, number) {
|
||||
var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
|
||||
var value = ($l.numbers && $l.numbers[number]) || number;
|
||||
return string.replace(/%d/i, value);
|
||||
}
|
||||
|
||||
var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
|
||||
seconds < 90 && substitute($l.minute, 1) ||
|
||||
minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
|
||||
minutes < 90 && substitute($l.hour, 1) ||
|
||||
hours < 24 && substitute($l.hours, Math.round(hours)) ||
|
||||
hours < 42 && substitute($l.day, 1) ||
|
||||
days < 30 && substitute($l.days, Math.round(days)) ||
|
||||
days < 45 && substitute($l.month, 1) ||
|
||||
days < 365 && substitute($l.months, Math.round(days / 30)) ||
|
||||
years < 1.5 && substitute($l.year, 1) ||
|
||||
substitute($l.years, Math.round(years));
|
||||
|
||||
var separator = $l.wordSeparator || "";
|
||||
if ($l.wordSeparator === undefined) { separator = " "; }
|
||||
return $.trim([prefix, words, suffix].join(separator));
|
||||
},
|
||||
parse: function(iso8601) {
|
||||
var s = $.trim(iso8601);
|
||||
s = s.replace(/\.\d+/,""); // remove milliseconds
|
||||
s = s.replace(/-/,"/").replace(/-/,"/");
|
||||
s = s.replace(/T/," ").replace(/Z/," UTC");
|
||||
s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
|
||||
return new Date(s);
|
||||
},
|
||||
datetime: function(elem) {
|
||||
var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
|
||||
return $t.parse(iso8601);
|
||||
},
|
||||
isTime: function(elem) {
|
||||
// jQuery's `is()` doesn't play well with HTML5 in IE
|
||||
return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
|
||||
}
|
||||
});
|
||||
|
||||
// functions that can be called via $(el).timeago('action')
|
||||
// init is default when no action is given
|
||||
// functions are called with context of a single element
|
||||
var functions = {
|
||||
init: function(){
|
||||
var refresh_el = $.proxy(refresh, this);
|
||||
refresh_el();
|
||||
var $s = $t.settings;
|
||||
if ($s.refreshMillis > 0) {
|
||||
setInterval(refresh_el, $s.refreshMillis);
|
||||
}
|
||||
},
|
||||
update: function(time){
|
||||
$(this).data('timeago', { datetime: $t.parse(time) });
|
||||
refresh.apply(this);
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.timeago = function(action, options) {
|
||||
var fn = action ? functions[action] : functions.init;
|
||||
if(!fn){
|
||||
throw new Error("Unknown function name '"+ action +"' for timeago");
|
||||
}
|
||||
// each over objects here and call the requested function
|
||||
this.each(function(){
|
||||
fn.call(this, options);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
function refresh() {
|
||||
var data = prepareData(this);
|
||||
var $s = $t.settings;
|
||||
|
||||
if (!isNaN(data.datetime)) {
|
||||
if ( $s.cutoff == 0 || distance(data.datetime) < $s.cutoff) {
|
||||
$(this).text(inWords(data.datetime));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
function prepareData(element) {
|
||||
element = $(element);
|
||||
if (!element.data("timeago")) {
|
||||
element.data("timeago", { datetime: $t.datetime(element) });
|
||||
var text = $.trim(element.text());
|
||||
if ($t.settings.localeTitle) {
|
||||
element.attr("title", element.data('timeago').datetime.toLocaleString());
|
||||
} else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
|
||||
element.attr("title", text);
|
||||
}
|
||||
}
|
||||
return element.data("timeago");
|
||||
}
|
||||
|
||||
function inWords(date) {
|
||||
return $t.inWords(distance(date));
|
||||
}
|
||||
|
||||
function distance(date) {
|
||||
return (new Date().getTime() - date.getTime());
|
||||
}
|
||||
|
||||
// fix for IE6 suckage
|
||||
document.createElement("abbr");
|
||||
document.createElement("time");
|
||||
}));
|
6
lib/jquery.ui-1.10.3.custom.min.js
vendored
Normal file
@ -1,6 +1,8 @@
|
||||
|
||||
// Adding jQuery ui stuff
|
||||
$(document).ready(function() {
|
||||
var dayMS = 1000 * 60 * 60 * 24;
|
||||
jQuery.timeago.settings.cutoff = 365 * dayMS;
|
||||
$("time").timeago();
|
||||
|
||||
$('.autocomplete_tags').autocomplete(base_href + '/api/internal/tag_list/complete', {
|
||||
@ -29,6 +31,7 @@ $(document).ready(function() {
|
||||
$(elm).attr("href", target_id);
|
||||
// highlight it when clicked
|
||||
$(elm).click(function(e) {
|
||||
// This needs jQuery UI
|
||||
$(target_id).effect('highlight', {}, 5000);
|
||||
});
|
||||
// vanilla target name should already be in the URL tag, but this
|
||||
@ -83,6 +86,21 @@ $(document).ready(function() {
|
||||
a = document.getElementById("nextlink");
|
||||
a.href = a.href + '?' + query;
|
||||
}
|
||||
|
||||
/*
|
||||
* If an image list has a data-query attribute, append
|
||||
* that query string to all thumb links inside the list.
|
||||
* This allows us to cache the same thumb for all query
|
||||
* strings, adding the query in the browser.
|
||||
*/
|
||||
$(".shm-image-list").each(function(idx, elm) {
|
||||
var query = $(this).data("query");
|
||||
if(query) {
|
||||
$(this).find(".shm-thumb-link").each(function(idx2, elm2) {
|
||||
$(this).attr("href", $(this).attr("href") + query);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
@ -49,9 +49,10 @@ class CustomIndexTheme extends IndexTheme {
|
||||
}
|
||||
|
||||
protected function build_table($images, $query) {
|
||||
$table = "<div class='shm-image-list'>";
|
||||
$h_query = html_escape($query);
|
||||
$table = "<div class='shm-image-list' data-query='$h_query'>";
|
||||
foreach($images as $image) {
|
||||
$table .= "\t<span class=\"thumb\">" . $this->build_thumb_html($image, $query) . "</span>\n";
|
||||
$table .= "\t<span class=\"thumb\">" . $this->build_thumb_html($image) . "</span>\n";
|
||||
}
|
||||
$table .= "</div>";
|
||||
return $table;
|
||||
|