From 9e67e531aa7f1b62662999c44eb9f8b7fd6959ef Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" Date: Sat, 4 Feb 2012 23:16:50 -0500 Subject: [PATCH 01/34] How is this typo still in here? --- core/extension.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/extension.class.php b/core/extension.class.php index e5ce13d2..b54956de 100644 --- a/core/extension.class.php +++ b/core/extension.class.php @@ -191,7 +191,7 @@ abstract class DataHandlerExtension extends SimpleExtension { } } - public function onThumnbnailGeneration(ThumbnailGenerationEvent $event) { + public function onThumbnailGeneration(ThumbnailGenerationEvent $event) { if($this->supported_ext($event->type)) { if (method_exists($this, 'create_thumb_force') && $event->force == true) { $this->create_thumb_force($event->hash); From 5843aae4f2e8a7d1a194859bd4bb626e3de5da16 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 5 Feb 2012 04:20:42 +0000 Subject: [PATCH 02/34] random unused global o_O --- ext/comment/main.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/comment/main.php b/ext/comment/main.php index 9300b50d..d2d7f32b 100644 --- a/ext/comment/main.php +++ b/ext/comment/main.php @@ -173,7 +173,6 @@ class CommentList extends SimpleExtension { $h_comment_rate = sprintf("%.1f", ($i_comment_count / $i_days_old)); $event->add_stats("Comments made: $i_comment_count, $h_comment_rate per day"); - global $user; $recent = $this->get_user_recent_comments($event->display_user->id, 10); $this->theme->display_user_comments($recent); } From a55eca44622b7f492d4d091e663fbe2ab37ba2e4 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 5 Feb 2012 04:21:03 +0000 Subject: [PATCH 03/34] so many bots crawling the infinite search space... --- core/imageboard.pack.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index bac574b7..97ff62a8 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -109,13 +109,19 @@ class Image { assert(is_numeric($start)); assert(is_numeric($limit)); assert(is_array($tags)); - global $database; + global $database, $user; $images = array(); if($start < 0) $start = 0; if($limit < 1) $limit = 1; + if(SPEED_HAX) { + if($user->is_anonymous() and count($tags) > 3) { + die("Anonymous users may only search for up to 3 tags at a time"); // FIXME: throw an exception? + } + } + $querylet = Image::build_search_querylet($tags); $querylet->append(new Querylet("ORDER BY images.id DESC LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start))); #var_dump($querylet->sql); var_dump($querylet->variables); From 0b13db8ed71fa873d715741a3947614db72f7c28 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 5 Feb 2012 04:25:50 +0000 Subject: [PATCH 04/34] hide anonymous's user page - having made a million comments, it was taking upwards of 60 seconds to process that one page, triggering the DOS defences... --- ext/comment/theme.php | 15 ++++++++++----- ext/user/main.php | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ext/comment/theme.php b/ext/comment/theme.php index 56afa513..c054a80b 100644 --- a/ext/comment/theme.php +++ b/ext/comment/theme.php @@ -144,12 +144,17 @@ class CommentListTheme extends Themelet { $i_comment_id = int_escape($comment->comment_id); $i_image_id = int_escape($comment->image_id); - $anoncode = ""; - if($h_name == "Anonymous" && $this->anon_id >= 0) { - $anoncode = ''.$this->anon_id.''; - $this->anon_id++; + if($h_name == "Anonymous") { + $anoncode = ""; + if($this->anon_id >= 0) { + $anoncode = ''.$this->anon_id.''; + $this->anon_id++; + } + $h_userlink = $h_name . $anoncode; + } + else { + $h_userlink = ''.$h_name.''; } - $h_userlink = ''.$h_name.''.$anoncode; $stripped_nonl = str_replace("\n", "\\n", substr($tfe->stripped, 0, 50)); $stripped_nonl = str_replace("\r", "\\r", $stripped_nonl); $h_dellink = $user->is_admin() ? diff --git a/ext/user/main.php b/ext/user/main.php index 33a1d4a5..7348b490 100644 --- a/ext/user/main.php +++ b/ext/user/main.php @@ -158,7 +158,7 @@ class UserPage extends SimpleExtension { $this->theme->display_error($page, "Not Logged In", "You aren't logged in. First do that, then you can see your stats."); } - else if(!is_null($display_user)) { + else if(!is_null($display_user) && ($display_user->id != $config->get_int("anon_id"))) { send_event(new UserPageBuildingEvent($display_user)); } else { From 2d334e08ea1ae1c3d815f6cff6b723ade45654b5 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 5 Feb 2012 05:06:55 +0000 Subject: [PATCH 05/34] How did this typo get back in here? :/ --- core/extension.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/extension.class.php b/core/extension.class.php index e5ce13d2..b54956de 100644 --- a/core/extension.class.php +++ b/core/extension.class.php @@ -191,7 +191,7 @@ abstract class DataHandlerExtension extends SimpleExtension { } } - public function onThumnbnailGeneration(ThumbnailGenerationEvent $event) { + public function onThumbnailGeneration(ThumbnailGenerationEvent $event) { if($this->supported_ext($event->type)) { if (method_exists($this, 'create_thumb_force') && $event->force == true) { $this->create_thumb_force($event->hash); From 9db912d996ff38f0eba2d432ff884eafc5931d54 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 5 Feb 2012 06:59:55 +0000 Subject: [PATCH 06/34] now that the cache key has changed, the invalidation needs updating... --- contrib/ipban/main.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/ipban/main.php b/contrib/ipban/main.php index 06e86b1d..bd9a11d0 100644 --- a/contrib/ipban/main.php +++ b/contrib/ipban/main.php @@ -92,7 +92,7 @@ class IPBan extends SimpleExtension { public function onRemoveIPBan($event) { global $database; $database->Execute("DELETE FROM bans WHERE id = :id", array("id"=>$event->id)); - $database->cache->delete("ip_bans"); + $database->cache->delete("ip_bans_sorted"); } // installer {{{ @@ -261,7 +261,7 @@ class IPBan extends SimpleExtension { global $database; $sql = "INSERT INTO bans (ip, reason, end_timestamp, banner_id) VALUES (:ip, :reason, :end, :admin_id)"; $database->Execute($sql, array("ip"=>$ip, "reason"=>$reason, "end"=>strtotime($end), "admin_id"=>$user->id)); - $database->cache->delete("ip_bans"); + $database->cache->delete("ip_bans_sorted"); log_info("ipban", "'$user->name' has banned '$ip' because '$reason' until '$end'"); } // }}} From 7a25fad4b03b7fdf1f28858408a9726b81393142 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 5 Feb 2012 07:37:20 +0000 Subject: [PATCH 07/34] link to specific comments --- ext/bbcode/main.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/bbcode/main.php b/ext/bbcode/main.php index 239f06c2..420b5033 100644 --- a/ext/bbcode/main.php +++ b/ext/bbcode/main.php @@ -38,7 +38,8 @@ class BBCode extends FormatterExtension { $text = preg_replace("/\[i\](.*?)\[\/i\]/s", "\\1", $text); $text = preg_replace("/\[u\](.*?)\[\/u\]/s", "\\1", $text); $text = preg_replace("/\[s\](.*?)\[\/s\]/s", "\\1", $text); - $text = preg_replace("/>>(\d+)/s", ">>\\1", $text); + $text = preg_replace("/>>(\d+)(#\d+)?/s", ">>\\1\\2", $text); + $text = preg_replace("/(^|\s)#(\d+)/s", "\\1#\\2", $text); $text = preg_replace("/>>([^\d].+)/", "
\\1
", $text); $text = preg_replace("/\[url=((?:https?|ftp|irc|mailto):\/\/.*?)\](.*?)\[\/url\]/s", "\\2", $text); $text = preg_replace("/\[url\]((?:https?|ftp|irc|mailto):\/\/.*?)\[\/url\]/s", "\\1", $text); From 8db3546c8d87fff7ab396a32fe2c910c98bc89e2 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" Date: Sun, 5 Feb 2012 12:11:56 -0500 Subject: [PATCH 08/34] Changes to the install script for Issue 101. --- install.php | 78 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/install.php b/install.php index 09da4d33..61a1bf69 100755 --- a/install.php +++ b/install.php @@ -1,7 +1,8 @@ - + + Shimmie Installation - @@ -31,9 +33,10 @@ TD INPUT {width: 350px;}

Install Error

Shimmie needs to be run via a web server with PHP support -- you appear to be either opening the file from your hard disk, or your - web server is mis-configured. + web server is mis-configured.

If you've installed a web server on your desktop PC, you probably - want to visit the local web server. + want to visit the local web server.

+

@@ -52,6 +55,7 @@ if(is_readable("config.php")) { <h1>Shimmie Repair Console</h1> <?php include "config.php"; + if($_SESSION['dsn'] == DATABASE_DSN || $_POST['dsn'] == DATABASE_DSN) { if($_POST['dsn']) {$_SESSION['dsn'] = $_POST['dsn'];} @@ -76,10 +80,12 @@ if(is_readable("config.php")) { </form> "; */ - echo "<h3>Database quick fix for User deletion</h3>"; - echo "just a database fix for those who instaled shimmie before 2012 january the 22rd.<br>"; - echo "Note: some things needs to be done manually, to work properly.<br>"; - echo "WARNING: ONLY PROCEEDS IF YOU KNOW WHAT YOU ARE DOING!"; + echo "<h3>Database Fix for User deletion</h3>"; + echo "This is a database fix for those who instaled shimmie before 2012 January 22rd.<br/>"; + echo "<b>This is only for users with <u>MySQL</u> databases!</b><br/>"; + echo "Note: Some things needs to be done manually, to work properly.<br/>"; + echo "Please BACKUP YOUR DATABASE before performing this fix!<br>"; + echo "WARNING: ONLY PROCEEDS IF YOU KNOW WHAT YOU ARE DOING!<br>"; echo " <form action='install.php?action=Database_user_deletion_fix' method='POST'> <input type='submit' value='go!'> @@ -102,8 +108,7 @@ if(is_readable("config.php")) { } else { echo " <h3>Login</h3> - Enter the database DSN exactly as in config.php (ie, as originally - installed) to access advanced recovery tools: + <p>Enter the database DSN exactly as in config.php (ie, as originally installed) to access advanced recovery tools:</p> <form action='install.php' method='POST'> <center> @@ -249,7 +254,7 @@ function begin() { // {{{ <h3>Help</h3> <p>Please make sure the database you have chosen exists and is empty.<br> - The username provided must have access to create tables within the database. + The username provided must have access to create tables within the database.</p> </div> EOD; @@ -354,11 +359,12 @@ function build_dirs() { // {{{ !file_exists("images") || !file_exists("thumbs") || !file_exists("data") || !is_writable("images") || !is_writable("thumbs") || !is_writable("data") ) { - print "Shimmie needs three folders in it's directory, 'images', 'thumbs', and 'data', - and they need to be writable by the PHP user (if you see this error, - if probably means the folders are owned by you, and they need to be - writable by the web server). - <p>Once you have created these folders, hit 'refresh' to continue."; + print "<p>Shimmie needs three folders in it's directory, 'images', 'thumbs', and 'data', + and they need to be writable by the PHP user.</p> + <p>If you see this error, if probably means the folders are owned by you, and they need to be + writable by the web server.</p> + <p>PHP reports that it is currently running as user: ".$_ENV["USER"]." (". $_SERVER["USER"] .")</p> + <p>Once you have created these folders and/or changed the ownership of the shimmie folder, hit 'refresh' to continue.</p>"; exit; } } // }}} @@ -393,10 +399,18 @@ function Database_user_deletion_fix() { require_once "core/database.class.php"; $db = new Database(); - echo "Fixing user_favorites table...."; + if ($database->db->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') { + echo "<br><br>Database is not MySQL - Aborting changes.<br><br>"; + echo '<a href="install.php">Go Back</a>'; + return; + } else { + echo "<h3>Performing Database Fix Operations</h3><br>"; + } + + echo "Fixing user_favorites table....<br><br>"; ($db->Execute("ALTER TABLE user_favorites ENGINE=InnoDB;")) ? print_r("ok<br>") : print_r("failed<br>"); - echo "adding Foreign key to user ids..."; + echo "adding Foreign key to user ids...<br><br>"; ($db->Execute("ALTER TABLE user_favorites ADD CONSTRAINT foreign_user_favorites_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;"))? print_r("ok<br>"):print_r("failed<br>"); echo "cleaning, the table from deleted image favorites...<br>"; @@ -406,17 +420,17 @@ function Database_user_deletion_fix() { foreach( $rows as $key => $value) $db->Execute("DELETE FROM user_favorites WHERE image_id = :image_id;", array("image_id" => $value["image_id"])); - echo "adding forign key to image ids..."; + echo "adding forign key to image ids...<br><br>"; ($db->Execute("ALTER TABLE user_favorites ADD CONSTRAINT user_favorites_image_id FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;"))? print_r("ok<br>"):print_r("failed<br>"); - echo "adding foreign keys to private messages..."; + echo "adding foreign keys to private messages...<br><br>"; ($db->Execute("ALTER TABLE private_message ADD CONSTRAINT foreign_private_message_from_id FOREIGN KEY (from_id) REFERENCES users(id) ON DELETE CASCADE, ADD CONSTRAINT foreign_private_message_to_id FOREIGN KEY (to_id) REFERENCES users(id) ON DELETE CASCADE;")) ? print_r("ok<br>"):print_r("failed<br>"); - echo "Just one more step...which you need to do manually:<br>"; + echo "<br><br>Just one more step...which you need to do manually:<br>"; echo "You need to go to your database and Delete the foreign key on the owner_id in the images table.<br><br>"; echo "<a href='http://www.justin-cook.com/wp/2006/05/09/how-to-remove-foreign-keys-in-mysql/'>How to remove foreign keys</a><br><br>"; echo "and finally execute this querry:<br><br>"; From a78ca78c9b0210f1eac1ebe798d6b3a55c9ac191 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" <jgen.tech@gmail.com> Date: Sun, 5 Feb 2012 16:35:34 -0500 Subject: [PATCH 09/34] More changes to the install script. More error checking, better formating, etc.. --- core/database.class.php | 2 +- install.php | 65 +++++++++++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/core/database.class.php b/core/database.class.php index 27805521..53abd16e 100644 --- a/core/database.class.php +++ b/core/database.class.php @@ -310,7 +310,7 @@ class Database { } $matches = array(); - if(CACHE_DSN && preg_match("#(memcache|apc)://(.*)#", CACHE_DSN, $matches)) { + if( defined("CACHE_DSN") && CACHE_DSN && preg_match("#(memcache|apc)://(.*)#", CACHE_DSN, $matches)) { if($matches[1] == "memcache") { $this->cache = new MemcacheCache($matches[2]); } diff --git a/install.php b/install.php index 61a1bf69..a14c4c7a 100755 --- a/install.php +++ b/install.php @@ -54,10 +54,32 @@ if(is_readable("config.php")) { <div id="iblock"> <h1>Shimmie Repair Console</h1> <?php - include "config.php"; + + /* + * Compute the path to the folder containing "install.php" and + * store it as the 'Shimmie Root' folder for later on. + * + * Example: + * __SHIMMIE_ROOT__ = '/var/www/shimmie2/' + * + */ + define('__SHIMMIE_ROOT__', trim( remove_trailing_slash( dirname(__FILE__) ) ) . '/' ); + + // Pull in necessary files + require_once __SHIMMIE_ROOT__."config.php"; // Load user/site specifics First + require_once __SHIMMIE_ROOT__."core/default_config.inc.php"; // Defaults for the rest. + require_once __SHIMMIE_ROOT__."core/util.inc.php"; + require_once __SHIMMIE_ROOT__."core/database.class.php"; - if($_SESSION['dsn'] == DATABASE_DSN || $_POST['dsn'] == DATABASE_DSN) { - if($_POST['dsn']) {$_SESSION['dsn'] = $_POST['dsn'];} + if ( + ( array_key_exists('dsn', $_SESSION) && $_SESSION['dsn'] === DATABASE_DSN ) || + ( array_key_exists('dsn', $_POST) && $_POST['dsn'] === DATABASE_DSN ) + ) + { + if ( array_key_exists('dsn', $_POST) && !empty($_POST['dsn']) ) + { + $_SESSION['dsn'] = $_POST['dsn']; + } if(empty($_GET["action"])) { echo "<h3>Basic Checks</h3>"; @@ -81,14 +103,14 @@ if(is_readable("config.php")) { "; */ echo "<h3>Database Fix for User deletion</h3>"; - echo "This is a database fix for those who instaled shimmie before 2012 January 22rd.<br/>"; - echo "<b>This is only for users with <u>MySQL</u> databases!</b><br/>"; - echo "Note: Some things needs to be done manually, to work properly.<br/>"; + echo "<p>This is a database fix for those who instaled shimmie before 2012 January 22rd.</p>"; + echo "<p><b>This is only for users with <u>MySQL</u> databases!</b></p>"; + echo "<p>Note: Some things needs to be done manually, to work properly.<br/>"; echo "Please BACKUP YOUR DATABASE before performing this fix!<br>"; - echo "WARNING: ONLY PROCEEDS IF YOU KNOW WHAT YOU ARE DOING!<br>"; + echo "WARNING: ONLY PROCEED IF YOU KNOW WHAT YOU ARE DOING!<br></p>"; echo " <form action='install.php?action=Database_user_deletion_fix' method='POST'> - <input type='submit' value='go!'> + <input type='submit' value='Go'> </form> "; @@ -101,6 +123,7 @@ if(is_readable("config.php")) { } else if($_GET["action"] == "logout") { session_destroy(); + echo "<h3>Logged Out</h3><p>You have been logged out.</p><a href='index.php'>Main Shimmie Page</a>"; } else if($_GET["action"] == "Database_user_deletion_fix") { Database_user_deletion_fix(); @@ -123,13 +146,24 @@ if(is_readable("config.php")) { echo "\t\t</div>"; exit; } -require_once "core/compat.inc.php"; -require_once "core/util.inc.php"; -require_once "core/database.class.php"; do_install(); // utilities {{{ + +/** + * Strips off any kind of slash at the end so as to normalise the path. + * @param string $path Path to normalise. + * @return string Path without trailing slash. + */ +function remove_trailing_slash($path) { + if ((substr($path, -1) === '/') || (substr($path, -1) === '\\')) { + return substr($path, 0, -1); + } else { + return $path; + } +} + function check_gd_version() { $gdversion = 0; @@ -394,15 +428,14 @@ EOD; } } // }}} -function Database_user_deletion_fix() { +function Database_user_deletion_fix() { // {{{ try { - require_once "core/database.class.php"; $db = new Database(); - if ($database->db->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') { + if ($db->db->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') { echo "<br><br>Database is not MySQL - Aborting changes.<br><br>"; echo '<a href="install.php">Go Back</a>'; - return; + throw new PDOException("Database is not MySQL."); } else { echo "<h3>Performing Database Fix Operations</h3><br>"; } @@ -443,7 +476,7 @@ function Database_user_deletion_fix() { // FIXME: Make the error message user friendly exit($e->getMessage()); } -} +} // }}} ?> </body> </html> From 4bf42acdd13d931ff0d8397f83dcc1fc8843fb57 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Mon, 6 Feb 2012 05:22:51 +0000 Subject: [PATCH 10/34] database_dsn (lowercase) is no more --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index 8bd09936..f392be7c 100644 --- a/index.php +++ b/index.php @@ -50,7 +50,7 @@ * Each of these can be imported at the start of a function with eg "global $page, $user;" */ -if(empty($database_dsn) && !file_exists("config.php")) { +if(!file_exists("config.php")) { header("Location: install.php"); exit; } From 9fd10fb6d20106ef9b7cebf747ae439cd9a7611e Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Mon, 6 Feb 2012 05:34:06 +0000 Subject: [PATCH 11/34] more explicit credit for the shimmie team :3 --- themes/danbooru/layout.class.php | 4 +++- themes/default/layout.class.php | 4 +++- themes/flat/layout.class.php | 4 +++- themes/futaba/layout.class.php | 4 +++- themes/lite/layout.class.php | 4 +++- themes/old_default/layout.class.php | 4 +++- themes/warm/layout.class.php | 4 +++- 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/themes/danbooru/layout.class.php b/themes/danbooru/layout.class.php index b1b06999..534ea285 100644 --- a/themes/danbooru/layout.class.php +++ b/themes/danbooru/layout.class.php @@ -223,7 +223,9 @@ $header_html <em> Images &copy; their respective owners, <a href="http://code.shishnet.org/shimmie2/">Shimmie</a> &copy; - <a href="http://www.shishnet.org/">Shish</a> &amp; Co 2007-2012, + <a href="http://www.shishnet.org/">Shish</a> &amp; + <a href="https://github.com/shish/shimmie2/contributors">The Team</a> + 2007-2012, based on the Danbooru concept. $debug $contact diff --git a/themes/default/layout.class.php b/themes/default/layout.class.php index 3846c72c..08584b62 100644 --- a/themes/default/layout.class.php +++ b/themes/default/layout.class.php @@ -71,7 +71,9 @@ $header_html <div id="footer"> Images &copy; their respective owners, <a href="http://code.shishnet.org/shimmie2/">Shimmie</a> &copy; - <a href="http://www.shishnet.org/">Shish</a> &amp; Co 2007-2012, + <a href="http://www.shishnet.org/">Shish</a> &amp; + <a href="https://github.com/shish/shimmie2/contributors">The Team</a> + 2007-2012, based on the Danbooru concept. $debug $contact diff --git a/themes/flat/layout.class.php b/themes/flat/layout.class.php index 91b24ebf..c7b2ac64 100644 --- a/themes/flat/layout.class.php +++ b/themes/flat/layout.class.php @@ -71,7 +71,9 @@ $header_html <div id="footer"> Images &copy; their respective owners, <a href="http://code.shishnet.org/shimmie2/">Shimmie</a> &copy; - <a href="http://www.shishnet.org/">Shish</a> &amp; Co 2007-2012, + <a href="http://www.shishnet.org/">Shish</a> &amp; + <a href="https://github.com/shish/shimmie2/contributors">The Team</a> + 2007-2012, based on the Danbooru concept. $debug $contact diff --git a/themes/futaba/layout.class.php b/themes/futaba/layout.class.php index 3ca6727e..d434de74 100644 --- a/themes/futaba/layout.class.php +++ b/themes/futaba/layout.class.php @@ -79,7 +79,9 @@ $header_html <hr> Images &copy; their respective owners, <a href="http://code.shishnet.org/shimmie2/">Shimmie</a> &copy; - <a href="http://www.shishnet.org/">Shish</a> &amp; Co 2007-2012, + <a href="http://www.shishnet.org/">Shish</a> &amp; + <a href="https://github.com/shish/shimmie2/contributors">The Team</a> + 2007-2012, based on the Danbooru concept. <br>Futaba theme based on 4chan's layout and CSS :3 $debug diff --git a/themes/lite/layout.class.php b/themes/lite/layout.class.php index 16fa89ef..227a3cad 100644 --- a/themes/lite/layout.class.php +++ b/themes/lite/layout.class.php @@ -184,7 +184,9 @@ class Layout { <div id="footer"> Images &copy; their respective owners, <a href="http://code.shishnet.org/shimmie2/">Shimmie</a> &copy; - <a href="http://www.shishnet.org/">Shish</a> &amp; Co 2007-2012, + <a href="http://www.shishnet.org/">Shish</a> &amp; + <a href="https://github.com/shish/shimmie2/contributors">The Team</a> + 2007-2012, based on the Danbooru concept.<br /> Lite Theme by <a href="http://seemslegit.com">Zach</a> $debug diff --git a/themes/old_default/layout.class.php b/themes/old_default/layout.class.php index 1e5325cb..2660709d 100644 --- a/themes/old_default/layout.class.php +++ b/themes/old_default/layout.class.php @@ -69,7 +69,9 @@ $header_html <hr> Images &copy; their respective owners, <a href="http://code.shishnet.org/shimmie2/">Shimmie</a> &copy; - <a href="http://www.shishnet.org/">Shish</a> &amp; Co 2007-2012, + <a href="http://www.shishnet.org/">Shish</a> &amp; + <a href="https://github.com/shish/shimmie2/contributors">The Team</a> + 2007-2012, based on the Danbooru concept. $debug $contact diff --git a/themes/warm/layout.class.php b/themes/warm/layout.class.php index e60a7e4a..264d5984 100644 --- a/themes/warm/layout.class.php +++ b/themes/warm/layout.class.php @@ -85,7 +85,9 @@ $header_html <div id="footer"> Images &copy; their respective owners, <a href="http://code.shishnet.org/shimmie2/">Shimmie</a> &copy; - <a href="http://www.shishnet.org/">Shish</a> &amp; Co 2007-2012, + <a href="http://www.shishnet.org/">Shish</a> &amp; + <a href="https://github.com/shish/shimmie2/contributors">The Team</a> + 2007-2012, based on the Danbooru concept. $debug $contact From d2d03396af1ab7fd6efd198cbfe9687455afbe07 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Mon, 6 Feb 2012 12:23:56 +0000 Subject: [PATCH 12/34] info box --- ext/comment/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/comment/theme.php b/ext/comment/theme.php index c054a80b..9f47f139 100644 --- a/ext/comment/theme.php +++ b/ext/comment/theme.php @@ -179,7 +179,7 @@ class CommentListTheme extends Themelet { return ' <a name="'.$i_comment_id.'"></a> <div class="'.$oe.' comment"> - <!--<span class="timeago" style="float: right;">'.$h_timestamp.'</span>--> + <!--<div class="info" style="float: right;">'.$h_timestamp.'</div>--> '.$h_userlink.': '.$h_comment.' '.$h_dellink.' </div> From e38f9e720f06ba076ab82c36162f3e3fc96d69b9 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Mon, 6 Feb 2012 12:24:13 +0000 Subject: [PATCH 13/34] CSS has odd/even built-in now --- ext/comment/theme.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/comment/theme.php b/ext/comment/theme.php index 9f47f139..995744ee 100644 --- a/ext/comment/theme.php +++ b/ext/comment/theme.php @@ -175,10 +175,9 @@ class CommentListTheme extends Themelet { // $hash = md5(strtolower($comment->owner->email)); // $avatar = "<img src=\"http://www.gravatar.com/avatar/$hash.jpg\"><br>"; //} - $oe = ($this->comments_shown++ % 2 == 0) ? "even" : "odd"; return ' <a name="'.$i_comment_id.'"></a> - <div class="'.$oe.' comment"> + <div class="comment"> <!--<div class="info" style="float: right;">'.$h_timestamp.'</div>--> '.$h_userlink.': '.$h_comment.' '.$h_dellink.' From b20fd1b3e3b9116be189f35933e1ba0432f2300b Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Mon, 6 Feb 2012 14:41:36 +0000 Subject: [PATCH 14/34] js function for inserting comment links --- lib/shimmie.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/shimmie.js b/lib/shimmie.js index 258d592b..ae4bc7aa 100644 --- a/lib/shimmie.js +++ b/lib/shimmie.js @@ -164,3 +164,10 @@ function deleteCookie( name, path, domain ) { ";expires=Thu, 01-Jan-1970 00:00:01 GMT"; } +function replyTo(imageId, commentId) { + var box = $("#comment_on_"+imageId); + var text = ">>"+imageId+"#"+commentId+": "; + + box.focus(); + box.val(box.val() + text); +} From aa2696ca077a88bf973cb1fe33dee5fb59b637f0 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Mon, 6 Feb 2012 14:42:07 +0000 Subject: [PATCH 15/34] only add ellipsis if the comment actually is too long --- ext/comment/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/comment/theme.php b/ext/comment/theme.php index 995744ee..08976d9d 100644 --- a/ext/comment/theme.php +++ b/ext/comment/theme.php @@ -140,7 +140,7 @@ class CommentListTheme extends Themelet { $h_name = html_escape($comment->owner_name); $h_poster_ip = html_escape($comment->poster_ip); $h_timestamp = autodate($comment->posted); - $h_comment = ($trim ? substr($tfe->stripped, 0, 50)."..." : $tfe->formatted); + $h_comment = ($trim ? substr($tfe->stripped, 0, 50) . (strlen($tfe->stripped) > 50 ? "..." : "") : $tfe->formatted); $i_comment_id = int_escape($comment->comment_id); $i_image_id = int_escape($comment->image_id); From a9ec9b6b7065457457ce9dad7b0869cd18fbbcaf Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Mon, 6 Feb 2012 14:42:38 +0000 Subject: [PATCH 16/34] comment info box --- ext/comment/theme.php | 32 ++++++++++++++++++-------------- themes/default/style.css | 5 ++--- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/ext/comment/theme.php b/ext/comment/theme.php index 08976d9d..c0f8ca0b 100644 --- a/ext/comment/theme.php +++ b/ext/comment/theme.php @@ -157,33 +157,37 @@ class CommentListTheme extends Themelet { } $stripped_nonl = str_replace("\n", "\\n", substr($tfe->stripped, 0, 50)); $stripped_nonl = str_replace("\r", "\\r", $stripped_nonl); - $h_dellink = $user->is_admin() ? - '<br>('.$h_poster_ip.', '.$h_timestamp.', <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>)' : ''; if($trim) { return ' '.$h_userlink.': '.$h_comment.' <a href="'.make_link('post/view/'.$i_image_id).'">&gt;&gt;&gt;</a> - '.$h_dellink.' '; } else { - //$avatar = ""; - //if(!empty($comment->owner->email)) { - // $hash = md5(strtolower($comment->owner->email)); - // $avatar = "<img src=\"http://www.gravatar.com/avatar/$hash.jpg\"><br>"; - //} + $avatar = ""; + if(!empty($comment->owner_email)) { + $hash = md5(strtolower($comment->owner_email)); + $avatar = "<img src=\"http://www.gravatar.com/avatar/$hash.jpg\"><br>"; + } + $a = $user->is_admin(); + $h_reply = " - <a href='javascript: replyTo($i_image_id, $i_comment_id)'>Reply</a>"; + $h_ip = $a ? "<br>$h_poster_ip" : ""; + $h_del = $a ? + ' - <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 ' <a name="'.$i_comment_id.'"></a> <div class="comment"> - <!--<div class="info" style="float: right;">'.$h_timestamp.'</div>--> - '.$h_userlink.': '.$h_comment.' - '.$h_dellink.' + <div class="info"> + '.$avatar.' + '.$h_timestamp.$h_reply.$h_ip.$h_del.' + </div> + '.$h_userlink.': '.$h_comment.' </div> '; } + return ""; } protected function build_postbox($image_id) { @@ -197,7 +201,7 @@ class CommentListTheme extends Themelet { '.make_form(make_link("comment/add")).' <input type="hidden" name="image_id" value="'.$i_image_id.'" /> <input type="hidden" name="hash" value="'.$hash.'" /> - <textarea name="comment" rows="5" cols="50"></textarea> + <textarea id="comment_on_'.$i_image_id.'" name="comment" rows="5" cols="50"></textarea> '.$captcha.' <br><input type="submit" value="Post Comment" /> </form> diff --git a/themes/default/style.css b/themes/default/style.css index db384169..1b2c7355 100644 --- a/themes/default/style.css +++ b/themes/default/style.css @@ -128,9 +128,8 @@ UL { .comment { text-align: left; } -.comment .timeago { - float: right; - font-size: 75%; +.comment .info { + display: none; } .more:after { From 7211b0a30b0285cdf43ddbc11825e35365449991 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 10:17:37 +0000 Subject: [PATCH 17/34] vary cookie only with CACHE_HTTP --- core/page.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/page.class.php b/core/page.class.php index 6570632b..946db6c1 100644 --- a/core/page.class.php +++ b/core/page.class.php @@ -196,8 +196,8 @@ class Page { switch($this->mode) { case "page": - header("Vary: Cookie, Accept-Encoding"); if(CACHE_HTTP) { + header("Vary: Cookie, Accept-Encoding"); if($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") { header("Cache-control: public, max-age=600"); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT'); From 3b205d98ebf518a543fa70d34d0182621f31c931 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 10:56:46 +0000 Subject: [PATCH 18/34] charset --- core/page.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/page.class.php b/core/page.class.php index 946db6c1..a3b77cab 100644 --- a/core/page.class.php +++ b/core/page.class.php @@ -39,7 +39,7 @@ class Page { /** @private */ var $mode = "page"; /** @private */ - var $type = "text/html"; + var $type = "text/html; charset=utf-8"; /** * Set what this page should do; "page", "data", or "redirect". From 2162dbebdd743da1eda0329543984c350cbc5cb3 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 11:33:27 +0000 Subject: [PATCH 19/34] base_href variable for javascript, to avoid generating dynamic JS with PHP --- core/page.class.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/page.class.php b/core/page.class.php index a3b77cab..4060d4f7 100644 --- a/core/page.class.php +++ b/core/page.class.php @@ -236,6 +236,8 @@ class Page { protected function add_auto_html_headers() { $data_href = get_base_href(); + + $this->add_html_header("<script>base_href = '$data_href';</script>"); /* Attempt to cache the CSS & JavaScript files */ if ($this->add_cached_auto_html_headers() === FALSE) { From ab7c62515efbe5f7dc1e19c52cc6196f4a80dcda Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 11:33:42 +0000 Subject: [PATCH 20/34] move JS bits into static files --- ext/index/theme.php | 17 +---------------- lib/shimmie.js | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/ext/index/theme.php b/ext/index/theme.php index c7d2cb95..0a4b1fa8 100644 --- a/ext/index/theme.php +++ b/ext/index/theme.php @@ -79,23 +79,8 @@ and of course start organising your images :-) $h_search_string = html_escape(implode(" ", $search_terms)); $h_search_link = make_link(); $h_search = " - <script type='text/javascript'><!-- - $(document).ready(function() { - $('#search_input').DefaultValue('Search'); - $('#search_input').autocomplete('".make_link("api/internal/tag_list/complete")."', { - width: 320, - max: 15, - highlight: false, - multiple: true, - multipleSeparator: ' ', - scroll: true, - scrollHeight: 300, - selectFirst: false - }); - }); - //--></script> <p><form action='$h_search_link' method='GET'> - <input id='search_input' name='search' type='text' + <input class='search_input' id='search_input' name='search' type='text' value='$h_search_string' autocomplete='off' /> <input type='hidden' name='q' value='/post/list'> <input type='submit' value='Find' style='display: none;' /> diff --git a/lib/shimmie.js b/lib/shimmie.js index ae4bc7aa..6ed41062 100644 --- a/lib/shimmie.js +++ b/lib/shimmie.js @@ -27,11 +27,19 @@ $(document).ready(function() { }); $("time").timeago(); -}); -var defaultTexts = new Array(); + $('.search_input').DefaultValue('Search'); + $('#search_input').autocomplete(base_href + '/api/internal/tag_list/complete', { + width: 320, + max: 15, + highlight: false, + multiple: true, + multipleSeparator: ' ', + scroll: true, + scrollHeight: 300, + selectFirst: false + }); -window.onload = function(e) { var sections=get_sections(); for(var i=0;i<sections.length;i++) toggle(sections[i]); @@ -44,7 +52,7 @@ window.onload = function(e) { if(pass_confirm) { pass_confirm.style.display = "none"; } -} +}); function initGray(boxname, text) { var box = byId(boxname); From bb49c09279a9d87eed890f7107526b7b1bb2f3fb Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 11:36:28 +0000 Subject: [PATCH 21/34] jquery does lots now --- lib/shimmie.js | 40 ++-------------------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/lib/shimmie.js b/lib/shimmie.js index 6ed41062..31e7e7e3 100644 --- a/lib/shimmie.js +++ b/lib/shimmie.js @@ -43,9 +43,8 @@ $(document).ready(function() { var sections=get_sections(); for(var i=0;i<sections.length;i++) toggle(sections[i]); - initGray("search_input", "Search"); - initGray("commentBox", "Comment"); - initGray("tagBox", "tagme"); + $("#commentBox").DefaultValue("Comment"); + $("#tagBox").DefaultValue("tagme"); // if we're going to show with JS, hide with JS first pass_confirm = byId("pass_confirm"); @@ -54,41 +53,6 @@ $(document).ready(function() { } }); -function initGray(boxname, text) { - var box = byId(boxname); - if(!box) return; - - var clr = function () {cleargray(box, text);}; - var set = function () {setgray(box, text);}; - - addEvent(box, "focus", clr, false); - addEvent(box, "blur", set, false); - - if(box.value == text) { - box.style.color = "#999"; - box.style.textAlign = "center"; - } - else { - box.style.color = "#000"; - box.style.textAlign = "left"; - } -} - -function cleargray(box, text) { - if(box.value == text) { - box.value = ""; - box.style.color = "#000"; - box.style.textAlign = "left"; - } -} -function setgray(box, text) { - if(box.value == "") { - box.style.textAlign = "center"; - box.style.color = "gray"; - box.value = text; - } -} - function showUp(elem) { e = document.getElementById(elem) if(!e) return; From bff5a8453f52e3ebfc7c7fc923608d4b84aa8f73 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 13:44:54 +0000 Subject: [PATCH 22/34] start of fine-grained permissions --- core/user.class.php | 14 ++++++++++++++ core/util.inc.php | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/user.class.php b/core/user.class.php index 85d90df5..653a6929 100644 --- a/core/user.class.php +++ b/core/user.class.php @@ -4,6 +4,11 @@ function _new_user($row) { return new User($row); } +$_perm_map = array( + "override_config" => "admin", +); + + /** * An object representing a row in the "users" table. * @@ -90,6 +95,15 @@ class User { /* * useful user object functions start here */ + public function can($ability) { + global $_perm_map; + $needed = $_perm_map[$ability]; + if($needed == "admin" && $this->is_admin()) return true; + if($needed == "user" && $this->is_logged_in()) return true; + if($needed == "anon") return true; + return false; + } + /** * Test if this user is anonymous (not logged in) diff --git a/core/util.inc.php b/core/util.inc.php index 882c8560..3529fb87 100644 --- a/core/util.inc.php +++ b/core/util.inc.php @@ -879,7 +879,7 @@ function get_debug_info() { // print_obj ($object, $title, $return) function print_obj($object,$title="Object Information", $return=false) { global $user; - if(DEBUG && isset($_GET['debug']) && $user->is_admin()) { + if(DEBUG && isset($_GET['DEBUG']) && $user->can("override_config")) { $pr = print_r($object,true); $count = substr_count($pr,"\n")<=25?substr_count($pr,"\n"):25; $pr = "<textarea rows='".$count."' cols='80'>$pr</textarea>"; From 0589f9d72e94af74557cbb92a283c0aafd113121 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 15:15:18 +0000 Subject: [PATCH 23/34] fine grained permission bits --- core/imageboard.pack.php | 2 +- core/user.class.php | 78 +++++++++++++++++++++++++++++++++----- ext/alias_editor/main.php | 10 ++--- ext/alias_editor/theme.php | 13 ++++--- ext/comment/main.php | 2 +- ext/comment/theme.php | 5 +-- ext/ext_manager/main.php | 4 +- ext/image/main.php | 8 ++-- ext/setup/main.php | 4 +- ext/tag_edit/main.php | 16 ++++---- ext/upload/main.php | 4 +- ext/user/main.php | 14 +++---- ext/user/theme.php | 4 +- ext/view/theme.php | 2 +- lib/shimmie.js | 27 +------------ 15 files changed, 114 insertions(+), 79 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index 73eb31f3..ca198452 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -119,7 +119,7 @@ class Image { if($limit < 1) $limit = 1; if(SPEED_HAX) { - if($user->is_anonymous() and count($tags) > 3) { + if(!$user->can("big_search") and count($tags) > 3) { die("Anonymous users may only search for up to 3 tags at a time"); // FIXME: throw an exception? } } diff --git a/core/user.class.php b/core/user.class.php index 653a6929..39204ff6 100644 --- a/core/user.class.php +++ b/core/user.class.php @@ -4,10 +4,6 @@ function _new_user($row) { return new User($row); } -$_perm_map = array( - "override_config" => "admin", -); - /** * An object representing a row in the "users" table. @@ -96,12 +92,74 @@ class User { * useful user object functions start here */ public function can($ability) { - global $_perm_map; - $needed = $_perm_map[$ability]; - if($needed == "admin" && $this->is_admin()) return true; - if($needed == "user" && $this->is_logged_in()) return true; - if($needed == "anon") return true; - return false; + global $config; + + // TODO: make this into an editable database table + $user_classes = array( + "anonymous" => array( + "change_setting" => False, # web-level settings, eg the config table + "override_config" => False, # sys-level config, eg config.php + "big_search" => False, # more than 3 tags (speed mode only) + "lock_image" => False, + "view_ip" => False, # view IP addresses associated with things + "change_password" => False, + "change_user_info" => False, + "delete_user" => False, + "delete_image" => False, + "delete_comment" => False, + "replace_image" => False, + "manage_extension_list" => False, + "manage_alias_list" => False, + "edit_tag" => $config->get_bool("tag_edit_anon"), + "edit_source" => $config->get_bool("source_edit_anon"), + "mass_tag_edit" => False, + ), + "user" => array( + "change_setting" => False, + "override_config" => False, + "big_search" => True, + "lock_image" => False, + "view_ip" => False, + "change_password" => False, + "change_user_info" => False, + "delete_user" => False, + "delete_image" => False, + "delete_comment" => False, + "replace_image" => False, + "manage_extension_list" => False, + "manage_alias_list" => False, + "edit_tag" => True, + "edit_source" => True, + "mass_tag_edit" => False, + ), + "admin" => array( + "change_setting" => True, + "override_config" => True, + "big_search" => True, + "lock_image" => True, + "view_ip" => True, + "change_password" => True, + "change_user_info" => True, + "delete_user" => True, + "delete_image" => True, + "delete_comment" => True, + "replace_image" => True, + "manage_extension_list" => True, + "manage_alias_list" => True, + "edit_tag" => True, + "edit_source" => True, + "mass_tag_edit" => True, + ), + ); + + return $user_classes[$this->get_class()][$action]; + } + + // FIXME: this should be a column in the users table + public function get_class() { + if($this->is_admin()) return "admin"; + else if($this->is_logged_in()) return "user"; + else return"anonymous"; } diff --git a/ext/alias_editor/main.php b/ext/alias_editor/main.php index 2942809d..7a246144 100755 --- a/ext/alias_editor/main.php +++ b/ext/alias_editor/main.php @@ -28,7 +28,7 @@ class AliasEditor extends SimpleExtension { if($event->page_matches("alias")) { if($event->get_arg(0) == "add") { - if($user->is_admin()) { + if($user->can("manage_alias_list")) { if(isset($_POST['oldtag']) && isset($_POST['newtag'])) { try { $aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']); @@ -43,7 +43,7 @@ class AliasEditor extends SimpleExtension { } } else if($event->get_arg(0) == "remove") { - if($user->is_admin()) { + if($user->can("manage_alias_list")) { if(isset($_POST['oldtag'])) { $database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", array("oldtag" => $_POST['oldtag'])); log_info("alias_editor", "Deleted alias for ".$_POST['oldtag']); @@ -74,7 +74,7 @@ class AliasEditor extends SimpleExtension { $total_pages = ceil($database->get_one("SELECT COUNT(*) FROM aliases") / $alias_per_page); - $this->theme->display_aliases($page, $alias, $user->is_admin(), $page_number + 1, $total_pages); + $this->theme->display_aliases($alias, $page_number + 1, $total_pages); } else if($event->get_arg(0) == "export") { $page->set_mode("data"); @@ -82,7 +82,7 @@ class AliasEditor extends SimpleExtension { $page->set_data($this->get_alias_csv($database)); } else if($event->get_arg(0) == "import") { - if($user->is_admin()) { + if($user->can("manage_alias_list")) { if(count($_FILES) > 0) { $tmp = $_FILES['alias_file']['tmp_name']; $contents = file_get_contents($tmp); @@ -115,7 +115,7 @@ class AliasEditor extends SimpleExtension { public function onUserBlockBuilding(UserBlockBuildingEvent $event) { global $user; - if($user->is_admin()) { + if($user->can("manage_alias_list")) { $event->add_link("Alias Editor", make_link("alias/list")); } } diff --git a/ext/alias_editor/theme.php b/ext/alias_editor/theme.php index cec35af0..7b6bfb49 100644 --- a/ext/alias_editor/theme.php +++ b/ext/alias_editor/theme.php @@ -5,10 +5,13 @@ class AliasEditorTheme extends Themelet { * Show a page of aliases: * * $aliases = an array of ($old_tag => $new_tag) - * $is_admin = whether things like "add new alias" should be shown + * $can_manage = whether things like "add new alias" should be shown */ - public function display_aliases(Page $page, $aliases, $is_admin, $pageNumber, $totalPages) { - if($is_admin) { + public function display_aliases($aliases, $pageNumber, $totalPages) { + global $page, $user; + + $can_manage = $user->can("manage_alias_list"); + if($can_manage) { $action = "<th width='10%'>Action</th>"; $add = " <tr> @@ -33,7 +36,7 @@ class AliasEditorTheme extends Themelet { $oe = ($n++ % 2 == 0) ? "even" : "odd"; $h_aliases .= "<tr class='$oe'><td>$h_old</td><td>$h_new</td>"; - if($is_admin) { + if($can_manage) { $h_aliases .= " <td> ".make_form(make_link("alias/remove"))." @@ -70,7 +73,7 @@ class AliasEditorTheme extends Themelet { $page->set_heading("Alias List"); $page->add_block(new NavBlock()); $page->add_block(new Block("Aliases", $html)); - if($is_admin) { + if($can_manage) { $page->add_block(new Block("Bulk Upload", $bulk_html, "main", 51)); } diff --git a/ext/comment/main.php b/ext/comment/main.php index 0a1aaebf..b64a8984 100644 --- a/ext/comment/main.php +++ b/ext/comment/main.php @@ -128,7 +128,7 @@ class CommentList extends SimpleExtension { } } else if($event->get_arg(0) === "delete") { - if($user->is_admin()) { + if($user->can("delete_comment")) { // FIXME: post, not args if($event->count_args() === 3) { send_event(new CommentDeletionEvent($event->get_arg(1))); diff --git a/ext/comment/theme.php b/ext/comment/theme.php index c0f8ca0b..d594ebe8 100644 --- a/ext/comment/theme.php +++ b/ext/comment/theme.php @@ -170,10 +170,9 @@ class CommentListTheme extends Themelet { $hash = md5(strtolower($comment->owner_email)); $avatar = "<img src=\"http://www.gravatar.com/avatar/$hash.jpg\"><br>"; } - $a = $user->is_admin(); $h_reply = " - <a href='javascript: replyTo($i_image_id, $i_comment_id)'>Reply</a>"; - $h_ip = $a ? "<br>$h_poster_ip" : ""; - $h_del = $a ? + $h_ip = $user->can("view_ip") ? "<br>$h_poster_ip" : ""; + $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 ' diff --git a/ext/ext_manager/main.php b/ext/ext_manager/main.php index 1b90ef0a..3500401c 100644 --- a/ext/ext_manager/main.php +++ b/ext/ext_manager/main.php @@ -91,7 +91,7 @@ class ExtManager extends SimpleExtension { public function onPageRequest(PageRequestEvent $event) { global $page, $user; if($event->page_matches("ext_manager")) { - if($user->is_admin()) { + if($user->can("manage_extension_list")) { if($event->get_arg(0) == "set" && $user->check_auth_token()) { if(is_writable("ext")) { $this->set_things($_POST); @@ -130,7 +130,7 @@ class ExtManager extends SimpleExtension { public function onUserBlockBuilding(UserBlockBuildingEvent $event) { global $user; - if($user->is_admin()) { + if($user->can("manage_extension_list")) { $event->add_link("Extension Manager", make_link("ext_manager")); } else { diff --git a/ext/image/main.php b/ext/image/main.php index 0fde1e24..d0288131 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -162,7 +162,7 @@ class ImageIO extends SimpleExtension { } if($event->page_matches("image_admin/delete")) { global $page, $user; - if($user->is_admin() && isset($_POST['image_id']) && $user->check_auth_token()) { + if($user->can("delete_image") && isset($_POST['image_id']) && $user->check_auth_token()) { $image = Image::by_id($_POST['image_id']); if($image) { send_event(new ImageDeletionEvent($image)); @@ -173,7 +173,7 @@ class ImageIO extends SimpleExtension { } if($event->page_matches("image_admin/replace")) { global $page, $user; - if($user->is_admin() && isset($_POST['image_id']) && $user->check_auth_token()) { + if($user->can("replace_image") && isset($_POST['image_id']) && $user->check_auth_token()) { $image = Image::by_id($_POST['image_id']); if($image) { $page->set_mode("redirect"); @@ -190,11 +190,11 @@ class ImageIO extends SimpleExtension { global $user; global $config; - if($user->is_admin()) { + if($user->can("delete_image")) { $event->add_part($this->theme->get_deleter_html($event->image->id)); } /* In the future, could perhaps allow users to replace images that they own as well... */ - if ($user->is_admin() && $config->get_bool("upload_replace")) { + if ($user->can("replace_image") && $config->get_bool("upload_replace")) { $event->add_part($this->theme->get_replace_html($event->image->id)); } } diff --git a/ext/setup/main.php b/ext/setup/main.php index f93ac7fa..4e5b950a 100644 --- a/ext/setup/main.php +++ b/ext/setup/main.php @@ -187,7 +187,7 @@ class Setup extends SimpleExtension { } if($event->page_matches("setup")) { - if(!$user->is_admin()) { + if(!$user->can("change_setting")) { $this->theme->display_permission_denied($page); } else { @@ -329,7 +329,7 @@ class Setup extends SimpleExtension { public function onUserBlockBuilding(UserBlockBuildingEvent $event) { global $user; - if($user->is_admin()) { + if($user->can("change_setting")) { $event->add_link("Board Config", make_link("setup")); } } diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php index a06425c1..184a5dba 100644 --- a/ext/tag_edit/main.php +++ b/ext/tag_edit/main.php @@ -60,7 +60,7 @@ class TagEdit extends SimpleExtension { global $user, $page; if($event->page_matches("tag_edit")) { if($event->get_arg(0) == "replace") { - if($user->is_admin() && isset($_POST['search']) && isset($_POST['replace'])) { + if($user->can("mass_tag_edit") && isset($_POST['search']) && isset($_POST['replace'])) { $search = $_POST['search']; $replace = $_POST['replace']; $this->mass_tag_edit($search, $replace); @@ -82,7 +82,7 @@ class TagEdit extends SimpleExtension { else { $this->theme->display_error($page, "Error", "Anonymous tag editing is disabled"); } - if($user->is_admin()) { + if($user->can("lock_image")) { $locked = isset($_POST['tag_edit__locked']) && $_POST['tag_edit__locked']=="on"; send_event(new LockSetEvent($event->image, $locked)); } @@ -90,21 +90,21 @@ class TagEdit extends SimpleExtension { public function onTagSet(TagSetEvent $event) { global $user; - if($user->is_admin() || !$event->image->is_locked()) { + if($user->can("edit_tag") || !$event->image->is_locked()) { $event->image->set_tags($event->tags); } } public function onSourceSet(SourceSetEvent $event) { global $user; - if($user->is_admin() || !$event->image->is_locked()) { + if($user->can("edit_tag") || !$event->image->is_locked()) { $event->image->set_source($event->source); } } public function onLockSet(LockSetEvent $event) { global $user; - if($user->is_admin()) { + if($user->can("lock_image")) { $event->image->set_locked($event->locked); } } @@ -130,7 +130,7 @@ class TagEdit extends SimpleExtension { if($this->can_source($event->image)) { $event->add_part($this->theme->get_source_editor_html($event->image), 41); } - if($user->is_admin()) { + if($user->can("lock_image")) { $event->add_part($this->theme->get_lock_editor_html($event->image), 42); } } @@ -147,7 +147,7 @@ class TagEdit extends SimpleExtension { global $config, $user; return ( ($config->get_bool("tag_edit_anon") || !$user->is_anonymous()) && - ($user->is_admin() || !$image->is_locked()) + ($user->can("edit_tag") || !$image->is_locked()) ); } @@ -155,7 +155,7 @@ class TagEdit extends SimpleExtension { global $config, $user; return ( ($config->get_bool("source_edit_anon") || !$user->is_anonymous()) && - ($user->is_admin() || !$image->is_locked()) + ($user->can("edit_source") || !$image->is_locked()) ); } diff --git a/ext/upload/main.php b/ext/upload/main.php index 979ab413..938a27fa 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -120,7 +120,7 @@ class Upload extends SimpleExtension { } // check if the user is an administrator and can upload files. - if(!$user->is_admin()) { + if(!$user->can("replace_image")) { $this->theme->display_permission_denied($page); } else { @@ -308,7 +308,7 @@ class Upload extends SimpleExtension { } // Checks if user is admin > check if you want locked. - if($user->is_admin() && !empty($_GET['locked'])){ + if($user->can("lock_image") && !empty($_GET['locked'])){ $locked = bool_escape($_GET['locked']); } diff --git a/ext/user/main.php b/ext/user/main.php index b5c9dc64..678b5b9c 100644 --- a/ext/user/main.php +++ b/ext/user/main.php @@ -187,7 +187,7 @@ class UserPage extends SimpleExtension { $this->theme->display_user_links($page, $user, $ubbe->parts); } if( - ($user->is_admin() || ($user->is_logged_in() && $user->id == $event->display_user->id)) && # admin or self-user + ($user->can("view_ip") || ($user->is_logged_in() && $user->id == $event->display_user->id)) && # admin or self-user ($event->display_user->id != $config->get_int('anon_id')) # don't show anon's IP list, it is le huge ) { $this->theme->display_ip_list( @@ -256,7 +256,7 @@ class UserPage extends SimpleExtension { $user_id = int_escape($matches[2]); $event->add_querylet(new Querylet("images.owner_id = $user_id")); } - else if($user->is_admin() && preg_match("/^(poster|user)_ip=([0-9\.]+)$/i", $event->term, $matches)) { + else if($user->can("view_ip") && preg_match("/^(poster|user)_ip=([0-9\.]+)$/i", $event->term, $matches)) { $user_ip = $matches[2]; // FIXME: ip_escape? $event->add_querylet(new Querylet("images.owner_ip = '$user_ip'")); } @@ -354,7 +354,7 @@ class UserPage extends SimpleExtension { $duser = User::by_id($id); - if((!$user->is_admin()) && ($duser->name != $user->name)) { + if((!$user->can("change_user_info")) && ($duser->name != $user->name)) { $this->theme->display_error($page, "Error", "You need to be an admin to change other people's passwords"); } @@ -392,7 +392,7 @@ class UserPage extends SimpleExtension { $duser = User::by_id($id); - if((!$user->is_admin()) && ($duser->name != $user->name)) { + if((!$user->can("change_user_info")) && ($duser->name != $user->name)) { $this->theme->display_error($page, "Error", "You need to be an admin to change other people's addressess"); } @@ -419,7 +419,7 @@ class UserPage extends SimpleExtension { $page->set_title("Error"); $page->set_heading("Error"); $page->add_block(new NavBlock()); - if(!$user->is_admin()) { + if(!$user->can("change_user_info")) { $page->add_block(new Block("Not Admin", "Only admins can edit accounts")); } else if(!isset($_POST['id']) || !is_numeric($_POST['id'])) { @@ -479,7 +479,7 @@ class UserPage extends SimpleExtension { $page->set_heading("Error"); $page->add_block(new NavBlock()); - if (!$user->is_admin()) { + if (!$user->can("delete_user")) { $page->add_block(new Block("Not Admin", "Only admins can delete accounts")); } else if(!isset($_POST['id']) || !is_numeric($_POST['id'])) { @@ -510,7 +510,7 @@ class UserPage extends SimpleExtension { $page->set_heading("Error"); $page->add_block(new NavBlock()); - if (!$user->is_admin()) { + if (!$user->can("delete_user") || !$user->can("delete_image")) { $page->add_block(new Block("Not Admin", "Only admins can delete accounts")); } else if(!isset($_POST['id']) || !is_numeric($_POST['id'])) { diff --git a/ext/user/theme.php b/ext/user/theme.php index df6b7553..69b852fc 100644 --- a/ext/user/theme.php +++ b/ext/user/theme.php @@ -141,7 +141,7 @@ class UserPageTheme extends Themelet { $page->add_block(new Block("Stats", join("<br>", $stats), "main", 0)); if(!$user->is_anonymous()) { - if($user->id == $duser->id || $user->is_admin()) { + if($user->id == $duser->id || $user->can("change_user_info")) { $page->add_block(new Block("Options", $this->build_options($duser), "main", 20)); } } @@ -173,7 +173,7 @@ class UserPageTheme extends Themelet { </form> "; - if($user->is_admin()) { + if($user->can("change_user_info")) { $i_user_id = int_escape($duser->id); $h_is_admin = $duser->is_admin() ? " checked" : ""; $html .= " diff --git a/ext/view/theme.php b/ext/view/theme.php index 59f6b730..666ac8bf 100644 --- a/ext/view/theme.php +++ b/ext/view/theme.php @@ -90,7 +90,7 @@ class ViewImageTheme extends Themelet { $html = ""; $html .= "<p>Uploaded by <a href='".make_link("user/$h_owner")."'>$h_owner</a> $h_date"; - if($user->is_admin()) { + if($user->can("view_ip")) { $html .= " ($h_ip)"; } if(!is_null($image->source)) { diff --git a/lib/shimmie.js b/lib/shimmie.js index 31e7e7e3..611d49ee 100644 --- a/lib/shimmie.js +++ b/lib/shimmie.js @@ -45,25 +45,8 @@ $(document).ready(function() { $("#commentBox").DefaultValue("Comment"); $("#tagBox").DefaultValue("tagme"); - - // if we're going to show with JS, hide with JS first - pass_confirm = byId("pass_confirm"); - if(pass_confirm) { - pass_confirm.style.display = "none"; - } }); -function showUp(elem) { - e = document.getElementById(elem) - if(!e) return; - e.style.display = ""; -// alert(e.type+": "+e.value); - if(e.value.match(/^http|^ftp/)) { - e.type = "text"; - alert("Box is web upload"); - } -} - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * LibShish-JS * @@ -83,6 +66,7 @@ function byId(id) { } +// used once in ext/setup/main function getHTTPObject() { if (window.XMLHttpRequest){ return new XMLHttpRequest(); @@ -92,15 +76,6 @@ function getHTTPObject() { } } -function ajaxRequest(url, callback) { - var http = getHTTPObject(); - http.open("GET", url, true); - http.onreadystatechange = function() { - if(http.readyState == 4) callback(http.responseText); - } - http.send(null); -} - /* get, set, and delete cookies */ function getCookie( name ) { From d715b3d8a6d1b0b58bc9aa43e4d45e51238b8d92 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 18:27:53 +0000 Subject: [PATCH 24/34] brain-o --- core/user.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/user.class.php b/core/user.class.php index 39204ff6..0c98597b 100644 --- a/core/user.class.php +++ b/core/user.class.php @@ -152,7 +152,7 @@ class User { ), ); - return $user_classes[$this->get_class()][$action]; + return $user_classes[$this->get_class()][$ability]; } // FIXME: this should be a column in the users table From 1455956f187bfa6c451b77f8a9cef3a2a6f1a3c5 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 19:18:58 +0000 Subject: [PATCH 25/34] putting foreign key additions into auto-upgrade ext --- ext/upgrade/main.php | 58 +++++++++++++++++++++++++++++---------- install.php | 64 -------------------------------------------- 2 files changed, 44 insertions(+), 78 deletions(-) diff --git a/ext/upgrade/main.php b/ext/upgrade/main.php index 4d8f32b9..81b79f70 100644 --- a/ext/upgrade/main.php +++ b/ext/upgrade/main.php @@ -10,6 +10,8 @@ class Upgrade extends SimpleExtension { public function onInitExt(InitExtEvent $event) { global $config, $database; + if($config->get_bool("in_upgrade")) return; + if(!is_numeric($config->get_string("db_version"))) { $config->set_int("db_version", 2); } @@ -18,28 +20,56 @@ class Upgrade extends SimpleExtension { // cry :S } - if($config->get_int("db_version") < 7) { - /* - // mysql-adodb specific - if($database->engine->name == "mysql") { - $tables = $database->db->MetaTables(); - foreach($tables as $table) { - log_info("upgrade", "converting $table to innodb"); - $database->execute("ALTER TABLE $table TYPE=INNODB"); - } - } - */ - $config->set_int("db_version", 7); - log_info("upgrade", "Database at version 7"); - } + // v7 is convert to innodb with adodb + // now done again as v9 with PDO if($config->get_int("db_version") < 8) { // if this fails, don't try again + $config->set_bool("in_upgrade", true); $config->set_int("db_version", 8); $database->execute($database->engine->scoreql_to_sql( "ALTER TABLE images ADD COLUMN locked SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N" )); log_info("upgrade", "Database at version 8"); + $config->set_bool("in_upgrade", false); + } + + if($config->get_int("db_version") < 9) { + $config->set_bool("in_upgrade", true); + if($database->db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') { + $tables = $database->get_col("SHOW TABLES"); + foreach($tables as $table) { + log_info("upgrade", "converting $table to innodb"); + $database->execute("ALTER TABLE $table TYPE=INNODB"); + } + } + $config->set_int("db_version", 9); + log_info("upgrade", "Database at version 9"); + $config->set_bool("in_upgrade", false); + } + + if($config->get_int("db_version") < 10) { + $config->set_bool("in_upgrade", true); + + log_info("upgrade", "Cleaning user favourites"); + $database->Execute("DELETE FROM user_favorites WHERE user_id NOT IN (SELECT id FROM users)"); + $database->Execute("DELETE FROM user_favorites WHERE image_id NOT IN (SELECT id FROM images)"); + + log_info("upgrade", "Adding foreign keys to user favourites"); + $database->Execute("ALTER TABLE user_favorites ADD CONSTRAINT foreign_user_favorites_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;"); + $database->Execute("ALTER TABLE user_favorites ADD CONSTRAINT user_favorites_image_id FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;"); + + log_info("upgrade", "Adding foreign keys to private messages"); + $database->Execute("ALTER TABLE private_message + ADD CONSTRAINT foreign_private_message_from_id FOREIGN KEY (from_id) REFERENCES users(id) ON DELETE CASCADE, + ADD CONSTRAINT foreign_private_message_to_id FOREIGN KEY (to_id) REFERENCES users(id) ON DELETE CASCADE;"); + + log_info("upgrade", "Adding foreign keys to images"); + $database->Execute("ALTER TABLE images ADD CONSTRAINT foreign_images_owner_id FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT"); + + $config->set_int("db_version", 10); + log_info("upgrade", "Database at version 10"); + $config->set_bool("in_upgrade", false); } } diff --git a/install.php b/install.php index a14c4c7a..89d6e50c 100755 --- a/install.php +++ b/install.php @@ -102,17 +102,6 @@ if(is_readable("config.php")) { </form> "; */ - echo "<h3>Database Fix for User deletion</h3>"; - echo "<p>This is a database fix for those who instaled shimmie before 2012 January 22rd.</p>"; - echo "<p><b>This is only for users with <u>MySQL</u> databases!</b></p>"; - echo "<p>Note: Some things needs to be done manually, to work properly.<br/>"; - echo "Please BACKUP YOUR DATABASE before performing this fix!<br>"; - echo "WARNING: ONLY PROCEED IF YOU KNOW WHAT YOU ARE DOING!<br></p>"; - echo " - <form action='install.php?action=Database_user_deletion_fix' method='POST'> - <input type='submit' value='Go'> - </form> - "; echo "<h3>Log Out</h3>"; echo " @@ -125,9 +114,6 @@ if(is_readable("config.php")) { session_destroy(); echo "<h3>Logged Out</h3><p>You have been logged out.</p><a href='index.php'>Main Shimmie Page</a>"; } - else if($_GET["action"] == "Database_user_deletion_fix") { - Database_user_deletion_fix(); - } } else { echo " <h3>Login</h3> @@ -427,56 +413,6 @@ EOD; exit; } } // }}} - -function Database_user_deletion_fix() { // {{{ - try { - $db = new Database(); - - if ($db->db->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') { - echo "<br><br>Database is not MySQL - Aborting changes.<br><br>"; - echo '<a href="install.php">Go Back</a>'; - throw new PDOException("Database is not MySQL."); - } else { - echo "<h3>Performing Database Fix Operations</h3><br>"; - } - - echo "Fixing user_favorites table....<br><br>"; - - ($db->Execute("ALTER TABLE user_favorites ENGINE=InnoDB;")) ? print_r("ok<br>") : print_r("failed<br>"); - echo "adding Foreign key to user ids...<br><br>"; - - ($db->Execute("ALTER TABLE user_favorites ADD CONSTRAINT foreign_user_favorites_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;"))? print_r("ok<br>"):print_r("failed<br>"); - echo "cleaning, the table from deleted image favorites...<br>"; - - $rows = $db->get_all("SELECT * FROM user_favorites WHERE image_id NOT IN ( SELECT id FROM images );"); - - foreach( $rows as $key => $value) - $db->Execute("DELETE FROM user_favorites WHERE image_id = :image_id;", array("image_id" => $value["image_id"])); - - echo "adding forign key to image ids...<br><br>"; - - ($db->Execute("ALTER TABLE user_favorites ADD CONSTRAINT user_favorites_image_id FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;"))? print_r("ok<br>"):print_r("failed<br>"); - - echo "adding foreign keys to private messages...<br><br>"; - - ($db->Execute("ALTER TABLE private_message - ADD CONSTRAINT foreign_private_message_from_id FOREIGN KEY (from_id) REFERENCES users(id) ON DELETE CASCADE, - ADD CONSTRAINT foreign_private_message_to_id FOREIGN KEY (to_id) REFERENCES users(id) ON DELETE CASCADE;")) ? print_r("ok<br>"):print_r("failed<br>"); - - echo "<br><br>Just one more step...which you need to do manually:<br>"; - echo "You need to go to your database and Delete the foreign key on the owner_id in the images table.<br><br>"; - echo "<a href='http://www.justin-cook.com/wp/2006/05/09/how-to-remove-foreign-keys-in-mysql/'>How to remove foreign keys</a><br><br>"; - echo "and finally execute this querry:<br><br>"; - echo "ALTER TABLE images ADD CONSTRAINT foreign_images_owner_id FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT;<br><br>"; - echo "if this is all sucesfull you are done!"; - - } - catch (PDOException $e) - { - // FIXME: Make the error message user friendly - exit($e->getMessage()); - } -} // }}} ?> </body> </html> From 25b8193191c7c5da5d354dce002645da4a26fc02 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 19:20:58 +0000 Subject: [PATCH 26/34] db_version starts at 10 --- install.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.php b/install.php index 89d6e50c..139bc4c5 100755 --- a/install.php +++ b/install.php @@ -340,7 +340,7 @@ function create_tables() { // {{{ CONSTRAINT foreign_image_tags_image_id FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE, CONSTRAINT foreign_image_tags_tag_id FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE "); - $db->execute("INSERT INTO config(name, value) VALUES('db_version', 8)"); + $db->execute("INSERT INTO config(name, value) VALUES('db_version', 10)"); } catch (PDOException $e) { From 37bffe06890febfea82a28cc7a4cd4d754e87b0a Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 19:26:40 +0000 Subject: [PATCH 27/34] actually, ext-specific DB changes should live in that ext... --- contrib/favorites/main.php | 11 +++++++++++ contrib/pm/main.php | 13 ++++++++++++- ext/upgrade/main.php | 13 ------------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/contrib/favorites/main.php b/contrib/favorites/main.php index ad37edd0..15f266c2 100644 --- a/contrib/favorites/main.php +++ b/contrib/favorites/main.php @@ -152,6 +152,17 @@ class Favorites extends SimpleExtension { "); $config->set_int("ext_favorites_version", 1); } + + if($config->get_int("ext_favorites_version") < 2) { + log_info("favorites", "Cleaning user favourites"); + $database->Execute("DELETE FROM user_favorites WHERE user_id NOT IN (SELECT id FROM users)"); + $database->Execute("DELETE FROM user_favorites WHERE image_id NOT IN (SELECT id FROM images)"); + + log_info("favorites", "Adding foreign keys to user favourites"); + $database->Execute("ALTER TABLE user_favorites ADD CONSTRAINT foreign_user_favorites_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;"); + $database->Execute("ALTER TABLE user_favorites ADD CONSTRAINT user_favorites_image_id FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;"); + $config->set_int("ext_favorites_version", 2); + } } private function add_vote($image_id, $user_id, $do_set) { diff --git a/contrib/pm/main.php b/contrib/pm/main.php index e0190f1a..ae75d228 100755 --- a/contrib/pm/main.php +++ b/contrib/pm/main.php @@ -57,11 +57,22 @@ class PrivMsg extends SimpleExtension { subject VARCHAR(64) NOT NULL, message TEXT NOT NULL, is_read SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N, - INDEX (to_id) + INDEX (to_id), + FOREIGN KEY (from_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (to_id) REFERENCES users(id) ON DELETE CASCADE "); $config->set_int("pm_version", 1); log_info("pm", "extension installed"); } + + if($config->get_int("pm_version") < 2) { + log_info("pm", "Adding foreign keys to private messages"); + $database->Execute("ALTER TABLE private_message + ADD CONSTRAINT foreign_private_message_from_id FOREIGN KEY (from_id) REFERENCES users(id) ON DELETE CASCADE, + ADD CONSTRAINT foreign_private_message_to_id FOREIGN KEY (to_id) REFERENCES users(id) ON DELETE CASCADE;"); + $config->set_int("pm_version", 2); + log_info("pm", "extension installed"); + } } /* diff --git a/ext/upgrade/main.php b/ext/upgrade/main.php index 81b79f70..35854e1d 100644 --- a/ext/upgrade/main.php +++ b/ext/upgrade/main.php @@ -51,19 +51,6 @@ class Upgrade extends SimpleExtension { if($config->get_int("db_version") < 10) { $config->set_bool("in_upgrade", true); - log_info("upgrade", "Cleaning user favourites"); - $database->Execute("DELETE FROM user_favorites WHERE user_id NOT IN (SELECT id FROM users)"); - $database->Execute("DELETE FROM user_favorites WHERE image_id NOT IN (SELECT id FROM images)"); - - log_info("upgrade", "Adding foreign keys to user favourites"); - $database->Execute("ALTER TABLE user_favorites ADD CONSTRAINT foreign_user_favorites_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;"); - $database->Execute("ALTER TABLE user_favorites ADD CONSTRAINT user_favorites_image_id FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE;"); - - log_info("upgrade", "Adding foreign keys to private messages"); - $database->Execute("ALTER TABLE private_message - ADD CONSTRAINT foreign_private_message_from_id FOREIGN KEY (from_id) REFERENCES users(id) ON DELETE CASCADE, - ADD CONSTRAINT foreign_private_message_to_id FOREIGN KEY (to_id) REFERENCES users(id) ON DELETE CASCADE;"); - log_info("upgrade", "Adding foreign keys to images"); $database->Execute("ALTER TABLE images ADD CONSTRAINT foreign_images_owner_id FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT"); From 763c00ccdf84f5a13fea2b961a57b29f045759c3 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 19:30:51 +0000 Subject: [PATCH 28/34] clean PMs first --- contrib/pm/main.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/pm/main.php b/contrib/pm/main.php index ae75d228..0e050f55 100755 --- a/contrib/pm/main.php +++ b/contrib/pm/main.php @@ -67,6 +67,8 @@ class PrivMsg extends SimpleExtension { if($config->get_int("pm_version") < 2) { log_info("pm", "Adding foreign keys to private messages"); + $database->Execute("delete from private_message where to_id not in (select id from users);"); + $database->Execute("delete from private_message where from_id not in (select id from users);"); $database->Execute("ALTER TABLE private_message ADD CONSTRAINT foreign_private_message_from_id FOREIGN KEY (from_id) REFERENCES users(id) ON DELETE CASCADE, ADD CONSTRAINT foreign_private_message_to_id FOREIGN KEY (to_id) REFERENCES users(id) ON DELETE CASCADE;"); From 5c1f028249c106e0dc051f65fb39e4a13e3fca82 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Tue, 7 Feb 2012 19:55:31 +0000 Subject: [PATCH 29/34] more type hints --- core/config.class.php | 60 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/core/config.class.php b/core/config.class.php index 2f918173..6d85b971 100644 --- a/core/config.class.php +++ b/core/config.class.php @@ -8,17 +8,17 @@ interface Config { * so that the next time a page is loaded it will use the new * configuration */ - public function save($name=null); + public function save(/*string*/ $name=null); /** @name set_* * Set a configuration option to a new value, regardless * of what the value is at the moment */ //@{ - public function set_int($name, $value); - public function set_string($name, $value); - public function set_bool($name, $value); - public function set_array($name, $value); + public function set_int(/*string*/ $name, $value); + public function set_string(/*string*/ $name, $value); + public function set_bool(/*string*/ $name, $value); + public function set_array(/*string*/ $name, $value); //@} /** @name set_default_* @@ -30,10 +30,10 @@ interface Config { * "default" paramater won't show up. */ //@{ - public function set_default_int($name, $value); - public function set_default_string($name, $value); - public function set_default_bool($name, $value); - public function set_default_array($name, $value); + public function set_default_int(/*string*/ $name, $value); + public function set_default_string(/*string*/ $name, $value); + public function set_default_bool(/*string*/ $name, $value); + public function set_default_array(/*string*/ $name, $value); //@} /** @name get_* @@ -41,10 +41,10 @@ interface Config { * appropritate data type */ //@{ - public function get_int($name, $default=null); - public function get_string($name, $default=null); - public function get_bool($name, $default=null); - public function get_array($name, $default=array()); + public function get_int(/*string*/ $name, $default=null); + public function get_string(/*string*/ $name, $default=null); + public function get_bool(/*string*/ $name, $default=null); + public function get_array(/*string*/ $name, $default=array()); //@} } @@ -56,60 +56,60 @@ interface Config { abstract class BaseConfig implements Config { var $values = array(); - public function set_int($name, $value) { + public function set_int(/*string*/ $name, $value) { $this->values[$name] = parse_shorthand_int($value); $this->save($name); } - public function set_string($name, $value) { + public function set_string(/*string*/ $name, $value) { $this->values[$name] = $value; $this->save($name); } - public function set_bool($name, $value) { + public function set_bool(/*string*/ $name, $value) { $this->values[$name] = (($value == 'on' || $value === true) ? 'Y' : 'N'); $this->save($name); } - public function set_array($name, $value) { + public function set_array(/*string*/ $name, $value) { assert(is_array($value)); $this->values[$name] = implode(",", $value); $this->save($name); } - public function set_default_int($name, $value) { + public function set_default_int(/*string*/ $name, $value) { if(is_null($this->get($name))) { $this->values[$name] = parse_shorthand_int($value); } } - public function set_default_string($name, $value) { + public function set_default_string(/*string*/ $name, $value) { if(is_null($this->get($name))) { $this->values[$name] = $value; } } - public function set_default_bool($name, $value) { + public function set_default_bool(/*string*/ $name, $value) { if(is_null($this->get($name))) { $this->values[$name] = (($value == 'on' || $value === true) ? 'Y' : 'N'); } } - public function set_default_array($name, $value) { + public function set_default_array(/*string*/ $name, $value) { assert(is_array($value)); if(is_null($this->get($name))) { $this->values[$name] = implode(",", $value); } } - public function get_int($name, $default=null) { + public function get_int(/*string*/ $name, $default=null) { return (int)($this->get($name, $default)); } - public function get_string($name, $default=null) { + public function get_string(/*string*/ $name, $default=null) { return $this->get($name, $default); } - public function get_bool($name, $default=null) { + public function get_bool(/*string*/ $name, $default=null) { return undb_bool($this->get($name, $default)); } - public function get_array($name, $default=array()) { + public function get_array(/*string*/ $name, $default=array()) { return explode(",", $this->get($name, "")); } - private function get($name, $default=null) { + private function get(/*string*/ $name, $default=null) { if(isset($this->values[$name])) { return $this->values[$name]; } @@ -144,7 +144,7 @@ class StaticConfig extends BaseConfig { } } - public function save($name=null) { + public function save(/*string*/ $name=null) { // static config is static } } @@ -167,7 +167,7 @@ class DatabaseConfig extends BaseConfig { /* * Load the config table from a database */ - public function DatabaseConfig($database) { + public function DatabaseConfig(Database $database) { $this->database = $database; $cached = $this->database->cache->get("config"); @@ -186,10 +186,10 @@ class DatabaseConfig extends BaseConfig { /* * Save the current values as the new config table */ - public function save($name=null) { + public function save(/*string*/ $name=null) { if(is_null($name)) { foreach($this->values as $name => $value) { - $this->save($name); + $this->save(/*string*/ $name); } } else { From 9707c1f7ce7a994d0266621ab2a279b125f1bcf3 Mon Sep 17 00:00:00 2001 From: Shish <shish@shishnet.org> Date: Wed, 8 Feb 2012 00:29:01 +0000 Subject: [PATCH 30/34] we already have a global jquery install... --- contrib/notes/jquery.js | 3408 --------------------------------------- 1 file changed, 3408 deletions(-) delete mode 100644 contrib/notes/jquery.js diff --git a/contrib/notes/jquery.js b/contrib/notes/jquery.js deleted file mode 100644 index 2e43a823..00000000 --- a/contrib/notes/jquery.js +++ /dev/null @@ -1,3408 +0,0 @@ -(function(){ -/* - * jQuery 1.2.3 - New Wave Javascript - * - * Copyright (c) 2008 John Resig (jquery.com) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * $Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008) $ - * $Rev: 4663 $ - */ - -// Map over jQuery in case of overwrite -if ( window.jQuery ) - var _jQuery = window.jQuery; - -var jQuery = window.jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.prototype.init( selector, context ); -}; - -// Map over the $ in case of overwrite -if ( window.$ ) - var _$ = window.$; - -// Map the jQuery namespace to the '$' one -window.$ = jQuery; - -// A simple way to check for HTML strings or ID strings -// (both of which we optimize for) -var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; - -// Is it a simple selector -var isSimple = /^.[^:#\[\.]*$/; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - // Make sure that a selection was provided - selector = selector || document; - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this[0] = selector; - this.length = 1; - return this; - - // Handle HTML strings - } else if ( typeof selector == "string" ) { - // Are we dealing with HTML string or an ID? - var match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) - selector = jQuery.clean( [ match[1] ], context ); - - // HANDLE: $("#id") - else { - var elem = document.getElementById( match[3] ); - - // Make sure an element was located - if ( elem ) - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id != match[3] ) - return jQuery().find( selector ); - - // Otherwise, we inject the element directly into the jQuery object - else { - this[0] = elem; - this.length = 1; - return this; - } - - else - selector = []; - } - - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return new jQuery( context ).find( selector ); - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); - - return this.setArray( - // HANDLE: $(array) - selector.constructor == Array && selector || - - // HANDLE: $(arraylike) - // Watch for when an array-like object, contains DOM nodes, is passed in as the selector - (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || - - // HANDLE: $(*) - [ selector ] ); - }, - - // The current version of jQuery being used - jquery: "1.2.3", - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - // The number of elements contained in the matched element set - length: 0, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == undefined ? - - // Return a 'clean' array - jQuery.makeArray( this ) : - - // Return just the object - this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - // Build a new jQuery matched element set - var ret = jQuery( elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - var ret = -1; - - // Locate the position of the desired element - this.each(function(i){ - if ( this == elem ) - ret = i; - }); - - return ret; - }, - - attr: function( name, value, type ) { - var options = name; - - // Look for the case where we're accessing a style value - if ( name.constructor == String ) - if ( value == undefined ) - return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined; - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - if ( typeof text != "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - if ( this[0] ) - // The elements to wrap the target around - jQuery( html, this[0].ownerDocument ) - .clone() - .insertBefore( this[0] ) - .map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; - - return elem; - }) - .append(this); - - return this; - }, - - wrapInner: function( html ) { - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); - }, - - append: function() { - return this.domManip(arguments, true, false, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); - }, - - prepend: function() { - return this.domManip(arguments, true, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); - }, - - before: function() { - return this.domManip(arguments, false, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); - }, - - after: function() { - return this.domManip(arguments, false, true, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - }, - - end: function() { - return this.prevObject || jQuery( [] ); - }, - - find: function( selector ) { - var elems = jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - }); - - return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? - jQuery.unique( elems ) : - elems ); - }, - - clone: function( events ) { - // Do the clone - var ret = this.map(function(){ - if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var clone = this.cloneNode(true), - container = document.createElement("div"); - container.appendChild(clone); - return jQuery.clean([container.innerHTML])[0]; - } else - return this.cloneNode(true); - }); - - // Need to set the expando to null on the cloned set if it exists - // removeData doesn't work here, IE removes it from the original as well - // this is primarily for IE but the data expando shouldn't be copied over in any browser - var clone = ret.find("*").andSelf().each(function(){ - if ( this[ expando ] != undefined ) - this[ expando ] = null; - }); - - // Copy the events from the original to the clone - if ( events === true ) - this.find("*").andSelf().each(function(i){ - if (this.nodeType == 3) - return; - var events = jQuery.data( this, "events" ); - - for ( var type in events ) - for ( var handler in events[ type ] ) - jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); - }); - - // Return the cloned set - return ret; - }, - - filter: function( selector ) { - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || - - jQuery.multiFilter( selector, this ) ); - }, - - not: function( selector ) { - if ( selector.constructor == String ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ) ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, - - add: function( selector ) { - return !selector ? this : this.pushStack( jQuery.merge( - this.get(), - selector.constructor == String ? - jQuery( selector ).get() : - selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ? - selector : [selector] ) ); - }, - - is: function( selector ) { - return selector ? - jQuery.multiFilter( selector, this ).length > 0 : - false; - }, - - hasClass: function( selector ) { - return this.is( "." + selector ); - }, - - val: function( value ) { - if ( value == undefined ) { - - if ( this.length ) { - var elem = this[0]; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; - - // We don't need an array for one selects - if ( one ) - return value; - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - - // Everything else, we just grab the value - } else - return (this[0].value || "").replace(/\r/g, ""); - - } - - return undefined; - } - - return this.each(function(){ - if ( this.nodeType != 1 ) - return; - - if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); - - else if ( jQuery.nodeName( this, "select" ) ) { - var values = value.constructor == Array ? - value : - [ value ]; - - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); - - if ( !values.length ) - this.selectedIndex = -1; - - } else - this.value = value; - }); - }, - - html: function( value ) { - return value == undefined ? - (this.length ? - this[0].innerHTML : - null) : - this.empty().append( value ); - }, - - replaceWith: function( value ) { - return this.after( value ).remove(); - }, - - eq: function( i ) { - return this.slice( i, i + 1 ); - }, - - slice: function() { - return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); - }, - - andSelf: function() { - return this.add( this.prevObject ); - }, - - data: function( key, value ){ - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value == null ) { - var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - if ( data == undefined && this.length ) - data = jQuery.data( this[0], key ); - - return data == null && parts[1] ? - this.data( parts[0] ) : - data; - } else - return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ - jQuery.data( this, key, value ); - }); - }, - - removeData: function( key ){ - return this.each(function(){ - jQuery.removeData( this, key ); - }); - }, - - domManip: function( args, table, reverse, callback ) { - var clone = this.length > 1, elems; - - return this.each(function(){ - if ( !elems ) { - elems = jQuery.clean( args, this.ownerDocument ); - - if ( reverse ) - elems.reverse(); - } - - var obj = this; - - if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) - obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); - - var scripts = jQuery( [] ); - - jQuery.each(elems, function(){ - var elem = clone ? - jQuery( this ).clone( true )[0] : - this; - - // execute all scripts after the elements have been injected - if ( jQuery.nodeName( elem, "script" ) ) { - scripts = scripts.add( elem ); - } else { - // Remove any inner scripts for later evaluation - if ( elem.nodeType == 1 ) - scripts = scripts.add( jQuery( "script", elem ).remove() ); - - // Inject the elements into the document - callback.call( obj, elem ); - } - }); - - scripts.each( evalScript ); - }); - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.prototype.init.prototype = jQuery.prototype; - -function evalScript( i, elem ) { - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // Handle a deep copy situation - if ( target.constructor == Boolean ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target != "object" && typeof target != "function" ) - target = {}; - - // extend jQuery itself if only one argument is passed - if ( length == 1 ) { - target = this; - i = 0; - } - - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - // Prevent never-ending loop - if ( target === options[ name ] ) - continue; - - // Recurse if we're merging object values - if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType ) - target[ name ] = jQuery.extend( target[ name ], options[ name ] ); - - // Don't bring in undefined values - else if ( options[ name ] != undefined ) - target[ name ] = options[ name ]; - - } - - // Return the modified object - return target; -}; - -var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {}; - -// exclude the following css properties to add px -var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) - window.jQuery = _jQuery; - - return jQuery; - }, - - // See test/unit/core.js for details concerning this function. - isFunction: function( fn ) { - return !!fn && typeof fn != "string" && !fn.nodeName && - fn.constructor != Array && /function/i.test( fn + "" ); - }, - - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - return elem.documentElement && !elem.body || - elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; - }, - - // Evalulates a script in a global context - globalEval: function( data ) { - data = jQuery.trim( data ); - - if ( data ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - if ( jQuery.browser.msie ) - script.text = data; - else - script.appendChild( document.createTextNode( data ) ); - - head.appendChild( script ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); - }, - - cache: {}, - - data: function( elem, name, data ) { - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ]; - - // Compute a unique ID for the element - if ( !id ) - id = elem[ expando ] = ++uuid; - - // Only generate the data cache if we're - // trying to access or manipulate it - if ( name && !jQuery.cache[ id ] ) - jQuery.cache[ id ] = {}; - - // Prevent overriding the named cache with undefined values - if ( data != undefined ) - jQuery.cache[ id ][ name ] = data; - - // Return the named cache data, or the ID for the element - return name ? - jQuery.cache[ id ][ name ] : - id; - }, - - removeData: function( elem, name ) { - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ]; - - // If we want to remove a specific section of the element's data - if ( name ) { - if ( jQuery.cache[ id ] ) { - // Remove the section of cache data - delete jQuery.cache[ id ][ name ]; - - // If we've removed all the data, remove the element's cache - name = ""; - - for ( name in jQuery.cache[ id ] ) - break; - - if ( !name ) - jQuery.removeData( elem ); - } - - // Otherwise, we want to remove all of the element's data - } else { - // Clean up the element expando - try { - delete elem[ expando ]; - } catch(e){ - // IE has trouble directly removing the expando - // but it's ok with using removeAttribute - if ( elem.removeAttribute ) - elem.removeAttribute( expando ); - } - - // Completely remove the data cache - delete jQuery.cache[ id ]; - } - }, - - // args is for internal usage only - each: function( object, callback, args ) { - if ( args ) { - if ( object.length == undefined ) { - for ( var name in object ) - if ( callback.apply( object[ name ], args ) === false ) - break; - } else - for ( var i = 0, length = object.length; i < length; i++ ) - if ( callback.apply( object[ i ], args ) === false ) - break; - - // A special, fast, case for the most common use of each - } else { - if ( object.length == undefined ) { - for ( var name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) - break; - } else - for ( var i = 0, length = object.length, value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} - } - - return object; - }, - - prop: function( elem, value, type, i, name ) { - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); - }, - - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - if (elem.nodeType == 1) - elem.className = classNames != undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, - - // internal only, use is(".class") - has: function( elem, className ) { - return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; - }, - - css: function( elem, name, force ) { - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - var padding = 0, border = 0; - jQuery.each( which, function() { - padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); - val -= Math.round(padding + border); - } - - if ( jQuery(elem).is(":visible") ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, val); - } - - return jQuery.curCSS( elem, name, force ); - }, - - curCSS: function( elem, name, force ) { - var ret; - - // A helper method for determining if an element's values are broken - function color( elem ) { - if ( !jQuery.browser.safari ) - return false; - - var ret = document.defaultView.getComputedStyle( elem, null ); - return !ret || ret.getPropertyValue("color") == ""; - } - - // We need to handle opacity special in IE - if ( name == "opacity" && jQuery.browser.msie ) { - ret = jQuery.attr( elem.style, "opacity" ); - - return ret == "" ? - "1" : - ret; - } - // Opera sometimes will give the wrong display answer, this fixes it, see #2037 - if ( jQuery.browser.opera && name == "display" ) { - var save = elem.style.outline; - elem.style.outline = "0 solid black"; - elem.style.outline = save; - } - - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; - - if ( !force && elem.style && elem.style[ name ] ) - ret = elem.style[ name ]; - - else if ( document.defaultView && document.defaultView.getComputedStyle ) { - - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; - - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); - - var getComputedStyle = document.defaultView.getComputedStyle( elem, null ); - - if ( getComputedStyle && !color( elem ) ) - ret = getComputedStyle.getPropertyValue( name ); - - // If the element isn't reporting its values properly in Safari - // then some display: none elements are involved - else { - var swap = [], stack = []; - - // Locate all of the parent display: none elements - for ( var a = elem; a && color(a); a = a.parentNode ) - stack.unshift(a); - - // Go through and make them visible, but in reverse - // (It would be better if we knew the exact display type that they had) - for ( var i = 0; i < stack.length; i++ ) - if ( color( stack[ i ] ) ) { - swap[ i ] = stack[ i ].style.display; - stack[ i ].style.display = "block"; - } - - // Since we flip the display style, we have to handle that - // one special, otherwise get the value - ret = name == "display" && swap[ stack.length - 1 ] != null ? - "none" : - ( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || ""; - - // Finally, revert the display styles back - for ( var i = 0; i < swap.length; i++ ) - if ( swap[ i ] != null ) - stack[ i ].style.display = swap[ i ]; - } - - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; - - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - elem.style.left = ret || 0; - ret = elem.style.pixelLeft + "px"; - - // Revert the changed values - elem.style.left = style; - elem.runtimeStyle.left = runtimeStyle; - } - } - - return ret; - }, - - clean: function( elems, context ) { - var ret = []; - context = context || document; - // !context.createElement fails in IE with an error but returns typeof 'object' - if (typeof context.createElement == 'undefined') - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - - jQuery.each(elems, function(i, elem){ - if ( !elem ) - return; - - if ( elem.constructor == Number ) - elem = elem.toString(); - - // Convert html string into DOM nodes - if ( typeof elem == "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + "></" + tag + ">"; - }); - - // Trim whitespace, otherwise indexOf won't work as expected - var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); - - var wrap = - // option or optgroup - !tags.indexOf("<opt") && - [ 1, "<select multiple='multiple'>", "</select>" ] || - - !tags.indexOf("<leg") && - [ 1, "<fieldset>", "</fieldset>" ] || - - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "<table>", "</table>" ] || - - !tags.indexOf("<tr") && - [ 2, "<table><tbody>", "</tbody></table>" ] || - - // <thead> matched above - (!tags.indexOf("<td") || !tags.indexOf("<th")) && - [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] || - - !tags.indexOf("<col") && - [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] || - - // IE can't serialize <link> and <script> tags normally - jQuery.browser.msie && - [ 1, "div<div>", "</div>" ] || - - [ 0, "", "" ]; - - // Go to html and back, then peel off extra wrappers - div.innerHTML = wrap[1] + elem + wrap[2]; - - // Move to the right depth - while ( wrap[0]-- ) - div = div.lastChild; - - // Remove IE's autoinserted <tbody> from table fragments - if ( jQuery.browser.msie ) { - - // String was a <table>, *may* have spurious <tbody> - var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ? - div.firstChild && div.firstChild.childNodes : - - // String was a bare <thead> or <tfoot> - wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ? - div.childNodes : - []; - - for ( var j = tbody.length - 1; j >= 0 ; --j ) - if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) - tbody[ j ].parentNode.removeChild( tbody[ j ] ); - - // IE completely kills leading whitespace when innerHTML is used - if ( /^\s/.test( elem ) ) - div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild ); - - } - - elem = jQuery.makeArray( div.childNodes ); - } - - if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) ) - return; - - if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options ) - ret.push( elem ); - - else - ret = jQuery.merge( ret, elem ); - - }); - - return ret; - }, - - attr: function( elem, name, value ) { - // don't set attributes on text and comment nodes - if (!elem || elem.nodeType == 3 || elem.nodeType == 8) - return undefined; - - var fix = jQuery.isXMLDoc( elem ) ? - {} : - jQuery.props; - - // Safari mis-reports the default selected property of a hidden option - // Accessing the parent's selectedIndex property fixes it - if ( name == "selected" && jQuery.browser.safari ) - elem.parentNode.selectedIndex; - - // Certain attributes only work when accessed via the old DOM 0 way - if ( fix[ name ] ) { - if ( value != undefined ) - elem[ fix[ name ] ] = value; - - return elem[ fix[ name ] ]; - - } else if ( jQuery.browser.msie && name == "style" ) - return jQuery.attr( elem.style, "cssText", value ); - - else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName( elem, "form" ) && (name == "action" || name == "method") ) - return elem.getAttributeNode( name ).nodeValue; - - // IE elem.getAttribute passes even for style - else if ( elem.tagName ) { - - if ( value != undefined ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode ) - throw "type property can't be changed"; - - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); - } - - if ( jQuery.browser.msie && /href|src/.test( name ) && !jQuery.isXMLDoc( elem ) ) - return elem.getAttribute( name, 2 ); - - return elem.getAttribute( name ); - - // elem is actually elem.style ... set the style - } else { - // IE actually uses filters for opacity - if ( name == "opacity" && jQuery.browser.msie ) { - if ( value != undefined ) { - // IE has trouble with opacity if it does not have layout - // Force it by setting the zoom level - elem.zoom = 1; - - // Set the alpha filter to set the opacity - elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) + - (parseFloat( value ).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")"); - } - - return elem.filter && elem.filter.indexOf("opacity=") >= 0 ? - (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() : - ""; - } - - name = name.replace(/-([a-z])/ig, function(all, letter){ - return letter.toUpperCase(); - }); - - if ( value != undefined ) - elem[ name ] = value; - - return elem[ name ]; - } - }, - - trim: function( text ) { - return (text || "").replace( /^\s+|\s+$/g, "" ); - }, - - makeArray: function( array ) { - var ret = []; - - // Need to use typeof to fight Safari childNodes crashes - if ( typeof array != "array" ) - for ( var i = 0, length = array.length; i < length; i++ ) - ret.push( array[ i ] ); - else - ret = array.slice( 0 ); - - return ret; - }, - - inArray: function( elem, array ) { - for ( var i = 0, length = array.length; i < length; i++ ) - if ( array[ i ] == elem ) - return i; - - return -1; - }, - - merge: function( first, second ) { - // We have to loop this way because IE & Opera overwrite the length - // expando of getElementsByTagName - - // Also, we need to make sure that the correct elements are being returned - // (IE returns comment nodes in a '*' query) - if ( jQuery.browser.msie ) { - for ( var i = 0; second[ i ]; i++ ) - if ( second[ i ].nodeType != 8 ) - first.push( second[ i ] ); - - } else - for ( var i = 0; second[ i ]; i++ ) - first.push( second[ i ] ); - - return first; - }, - - unique: function( array ) { - var ret = [], done = {}; - - try { - - for ( var i = 0, length = array.length; i < length; i++ ) { - var id = jQuery.data( array[ i ] ); - - if ( !done[ id ] ) { - done[ id ] = true; - ret.push( array[ i ] ); - } - } - - } catch( e ) { - ret = array; - } - - return ret; - }, - - grep: function( elems, callback, inv ) { - var ret = []; - - // Go through the array, only saving the items - // that pass the validator function - for ( var i = 0, length = elems.length; i < length; i++ ) - if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) ) - ret.push( elems[ i ] ); - - return ret; - }, - - map: function( elems, callback ) { - var ret = []; - - // Go through the array, translating each of the items to their - // new value (or values). - for ( var i = 0, length = elems.length; i < length; i++ ) { - var value = callback( elems[ i ], i ); - - if ( value !== null && value != undefined ) { - if ( value.constructor != Array ) - value = [ value ]; - - ret = ret.concat( value ); - } - } - - return ret; - } -}); - -var userAgent = navigator.userAgent.toLowerCase(); - -// Figure out what browser is being used -jQuery.browser = { - version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1], - safari: /webkit/.test( userAgent ), - opera: /opera/.test( userAgent ), - msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ), - mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent ) -}; - -var styleFloat = jQuery.browser.msie ? - "styleFloat" : - "cssFloat"; - -jQuery.extend({ - // Check to see if the W3C box model is being used - boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat", - - props: { - "for": "htmlFor", - "class": "className", - "float": styleFloat, - cssFloat: styleFloat, - styleFloat: styleFloat, - innerHTML: "innerHTML", - className: "className", - value: "value", - disabled: "disabled", - checked: "checked", - readonly: "readOnly", - selected: "selected", - maxlength: "maxLength", - selectedIndex: "selectedIndex", - defaultValue: "defaultValue", - tagName: "tagName", - nodeName: "nodeName" - } -}); - -jQuery.each({ - parent: function(elem){return elem.parentNode;}, - parents: function(elem){return jQuery.dir(elem,"parentNode");}, - next: function(elem){return jQuery.nth(elem,2,"nextSibling");}, - prev: function(elem){return jQuery.nth(elem,2,"previousSibling");}, - nextAll: function(elem){return jQuery.dir(elem,"nextSibling");}, - prevAll: function(elem){return jQuery.dir(elem,"previousSibling");}, - siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);}, - children: function(elem){return jQuery.sibling(elem.firstChild);}, - contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);} -}, function(name, fn){ - jQuery.fn[ name ] = function( selector ) { - var ret = jQuery.map( this, fn ); - - if ( selector && typeof selector == "string" ) - ret = jQuery.multiFilter( selector, ret ); - - return this.pushStack( jQuery.unique( ret ) ); - }; -}); - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function(name, original){ - jQuery.fn[ name ] = function() { - var args = arguments; - - return this.each(function(){ - for ( var i = 0, length = args.length; i < length; i++ ) - jQuery( args[ i ] )[ original ]( this ); - }); - }; -}); - -jQuery.each({ - removeAttr: function( name ) { - jQuery.attr( this, name, "" ); - if (this.nodeType == 1) - this.removeAttribute( name ); - }, - - addClass: function( classNames ) { - jQuery.className.add( this, classNames ); - }, - - removeClass: function( classNames ) { - jQuery.className.remove( this, classNames ); - }, - - toggleClass: function( classNames ) { - jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames ); - }, - - remove: function( selector ) { - if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) { - // Prevent memory leaks - jQuery( "*", this ).add(this).each(function(){ - jQuery.event.remove(this); - jQuery.removeData(this); - }); - if (this.parentNode) - this.parentNode.removeChild( this ); - } - }, - - empty: function() { - // Remove element nodes and prevent memory leaks - jQuery( ">*", this ).remove(); - - // Remove any remaining nodes - while ( this.firstChild ) - this.removeChild( this.firstChild ); - } -}, function(name, fn){ - jQuery.fn[ name ] = function(){ - return this.each( fn, arguments ); - }; -}); - -jQuery.each([ "Height", "Width" ], function(i, name){ - var type = name.toLowerCase(); - - jQuery.fn[ type ] = function( size ) { - // Get window width or height - return this[0] == window ? - // Opera reports document.body.client[Width/Height] properly in both quirks and standards - jQuery.browser.opera && document.body[ "client" + name ] || - - // Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths) - jQuery.browser.safari && window[ "inner" + name ] || - - // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode - document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] : - - // Get document width or height - this[0] == document ? - // Either scroll[Width/Height] or offset[Width/Height], whichever is greater - Math.max( - Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]), - Math.max(document.body["offset" + name], document.documentElement["offset" + name]) - ) : - - // Get or set width or height on the element - size == undefined ? - // Get width or height on the element - (this.length ? jQuery.css( this[0], type ) : null) : - - // Set the width or height on the element (default to pixels if value is unitless) - this.css( type, size.constructor == String ? size : size + "px" ); - }; -}); - -var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ? - "(?:[\\w*_-]|\\\\.)" : - "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)", - quickChild = new RegExp("^>\\s*(" + chars + "+)"), - quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"), - quickClass = new RegExp("^([#.]?)(" + chars + "*)"); - -jQuery.extend({ - expr: { - "": function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);}, - "#": function(a,i,m){return a.getAttribute("id")==m[2];}, - ":": { - // Position Checks - lt: function(a,i,m){return i<m[3]-0;}, - gt: function(a,i,m){return i>m[3]-0;}, - nth: function(a,i,m){return m[3]-0==i;}, - eq: function(a,i,m){return m[3]-0==i;}, - first: function(a,i){return i==0;}, - last: function(a,i,m,r){return i==r.length-1;}, - even: function(a,i){return i%2==0;}, - odd: function(a,i){return i%2;}, - - // Child Checks - "first-child": function(a){return a.parentNode.getElementsByTagName("*")[0]==a;}, - "last-child": function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;}, - "only-child": function(a){return !jQuery.nth(a.parentNode.lastChild,2,"previousSibling");}, - - // Parent Checks - parent: function(a){return a.firstChild;}, - empty: function(a){return !a.firstChild;}, - - // Text Check - contains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;}, - - // Visibility - visible: function(a){return "hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";}, - hidden: function(a){return "hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";}, - - // Form attributes - enabled: function(a){return !a.disabled;}, - disabled: function(a){return a.disabled;}, - checked: function(a){return a.checked;}, - selected: function(a){return a.selected||jQuery.attr(a,"selected");}, - - // Form elements - text: function(a){return "text"==a.type;}, - radio: function(a){return "radio"==a.type;}, - checkbox: function(a){return "checkbox"==a.type;}, - file: function(a){return "file"==a.type;}, - password: function(a){return "password"==a.type;}, - submit: function(a){return "submit"==a.type;}, - image: function(a){return "image"==a.type;}, - reset: function(a){return "reset"==a.type;}, - button: function(a){return "button"==a.type||jQuery.nodeName(a,"button");}, - input: function(a){return /input|select|textarea|button/i.test(a.nodeName);}, - - // :has() - has: function(a,i,m){return jQuery.find(m[3],a).length;}, - - // :header - header: function(a){return /h\d/i.test(a.nodeName);}, - - // :animated - animated: function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;} - } - }, - - // The regular expressions that power the parsing engine - parse: [ - // Match: [@value='test'], [@foo] - /^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/, - - // Match: :contains('foo') - /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/, - - // Match: :even, :last-chlid, #id, .class - new RegExp("^([:.#]*)(" + chars + "+)") - ], - - multiFilter: function( expr, elems, not ) { - var old, cur = []; - - while ( expr && expr != old ) { - old = expr; - var f = jQuery.filter( expr, elems, not ); - expr = f.t.replace(/^\s*,\s*/, "" ); - cur = not ? elems = f.r : jQuery.merge( cur, f.r ); - } - - return cur; - }, - - find: function( t, context ) { - // Quickly handle non-string expressions - if ( typeof t != "string" ) - return [ t ]; - - // check to make sure context is a DOM element or a document - if ( context && context.nodeType != 1 && context.nodeType != 9) - return [ ]; - - // Set the correct context (if none is provided) - context = context || document; - - // Initialize the search - var ret = [context], done = [], last, nodeName; - - // Continue while a selector expression exists, and while - // we're no longer looping upon ourselves - while ( t && last != t ) { - var r = []; - last = t; - - t = jQuery.trim(t); - - var foundToken = false; - - // An attempt at speeding up child selectors that - // point to a specific element tag - var re = quickChild; - var m = re.exec(t); - - if ( m ) { - nodeName = m[1].toUpperCase(); - - // Perform our own iteration and filter - for ( var i = 0; ret[i]; i++ ) - for ( var c = ret[i].firstChild; c; c = c.nextSibling ) - if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) ) - r.push( c ); - - ret = r; - t = t.replace( re, "" ); - if ( t.indexOf(" ") == 0 ) continue; - foundToken = true; - } else { - re = /^([>+~])\s*(\w*)/i; - - if ( (m = re.exec(t)) != null ) { - r = []; - - var merge = {}; - nodeName = m[2].toUpperCase(); - m = m[1]; - - for ( var j = 0, rl = ret.length; j < rl; j++ ) { - var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild; - for ( ; n; n = n.nextSibling ) - if ( n.nodeType == 1 ) { - var id = jQuery.data(n); - - if ( m == "~" && merge[id] ) break; - - if (!nodeName || n.nodeName.toUpperCase() == nodeName ) { - if ( m == "~" ) merge[id] = true; - r.push( n ); - } - - if ( m == "+" ) break; - } - } - - ret = r; - - // And remove the token - t = jQuery.trim( t.replace( re, "" ) ); - foundToken = true; - } - } - - // See if there's still an expression, and that we haven't already - // matched a token - if ( t && !foundToken ) { - // Handle multiple expressions - if ( !t.indexOf(",") ) { - // Clean the result set - if ( context == ret[0] ) ret.shift(); - - // Merge the result sets - done = jQuery.merge( done, ret ); - - // Reset the context - r = ret = [context]; - - // Touch up the selector string - t = " " + t.substr(1,t.length); - - } else { - // Optimize for the case nodeName#idName - var re2 = quickID; - var m = re2.exec(t); - - // Re-organize the results, so that they're consistent - if ( m ) { - m = [ 0, m[2], m[3], m[1] ]; - - } else { - // Otherwise, do a traditional filter check for - // ID, class, and element selectors - re2 = quickClass; - m = re2.exec(t); - } - - m[2] = m[2].replace(/\\/g, ""); - - var elem = ret[ret.length-1]; - - // Try to do a global search by ID, where we can - if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) { - // Optimization for HTML document case - var oid = elem.getElementById(m[2]); - - // Do a quick check for the existence of the actual ID attribute - // to avoid selecting by the name attribute in IE - // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form - if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] ) - oid = jQuery('[@id="'+m[2]+'"]', elem)[0]; - - // Do a quick check for node name (where applicable) so - // that div#foo searches will be really fast - ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : []; - } else { - // We need to find all descendant elements - for ( var i = 0; ret[i]; i++ ) { - // Grab the tag name being searched for - var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2]; - - // Handle IE7 being really dumb about <object>s - if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" ) - tag = "param"; - - r = jQuery.merge( r, ret[i].getElementsByTagName( tag )); - } - - // It's faster to filter by class and be done with it - if ( m[1] == "." ) - r = jQuery.classFilter( r, m[2] ); - - // Same with ID filtering - if ( m[1] == "#" ) { - var tmp = []; - - // Try to find the element with the ID - for ( var i = 0; r[i]; i++ ) - if ( r[i].getAttribute("id") == m[2] ) { - tmp = [ r[i] ]; - break; - } - - r = tmp; - } - - ret = r; - } - - t = t.replace( re2, "" ); - } - - } - - // If a selector string still exists - if ( t ) { - // Attempt to filter it - var val = jQuery.filter(t,r); - ret = r = val.r; - t = jQuery.trim(val.t); - } - } - - // An error occurred with the selector; - // just return an empty set instead - if ( t ) - ret = []; - - // Remove the root context - if ( ret && context == ret[0] ) - ret.shift(); - - // And combine the results - done = jQuery.merge( done, ret ); - - return done; - }, - - classFilter: function(r,m,not){ - m = " " + m + " "; - var tmp = []; - for ( var i = 0; r[i]; i++ ) { - var pass = (" " + r[i].className + " ").indexOf( m ) >= 0; - if ( !not && pass || not && !pass ) - tmp.push( r[i] ); - } - return tmp; - }, - - filter: function(t,r,not) { - var last; - - // Look for common filter expressions - while ( t && t != last ) { - last = t; - - var p = jQuery.parse, m; - - for ( var i = 0; p[i]; i++ ) { - m = p[i].exec( t ); - - if ( m ) { - // Remove what we just matched - t = t.substring( m[0].length ); - - m[2] = m[2].replace(/\\/g, ""); - break; - } - } - - if ( !m ) - break; - - // :not() is a special case that can be optimized by - // keeping it out of the expression list - if ( m[1] == ":" && m[2] == "not" ) - // optimize if only one selector found (most common case) - r = isSimple.test( m[3] ) ? - jQuery.filter(m[3], r, true).r : - jQuery( r ).not( m[3] ); - - // We can get a big speed boost by filtering by class here - else if ( m[1] == "." ) - r = jQuery.classFilter(r, m[2], not); - - else if ( m[1] == "[" ) { - var tmp = [], type = m[3]; - - for ( var i = 0, rl = r.length; i < rl; i++ ) { - var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ]; - - if ( z == null || /href|src|selected/.test(m[2]) ) - z = jQuery.attr(a,m[2]) || ''; - - if ( (type == "" && !!z || - type == "=" && z == m[5] || - type == "!=" && z != m[5] || - type == "^=" && z && !z.indexOf(m[5]) || - type == "$=" && z.substr(z.length - m[5].length) == m[5] || - (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not ) - tmp.push( a ); - } - - r = tmp; - - // We can get a speed boost by handling nth-child here - } else if ( m[1] == ":" && m[2] == "nth-child" ) { - var merge = {}, tmp = [], - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" || - !/\D/.test(m[3]) && "0n+" + m[3] || m[3]), - // calculate the numbers (first)n+(last) including if they are negative - first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0; - - // loop through all the elements left in the jQuery object - for ( var i = 0, rl = r.length; i < rl; i++ ) { - var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode); - - if ( !merge[id] ) { - var c = 1; - - for ( var n = parentNode.firstChild; n; n = n.nextSibling ) - if ( n.nodeType == 1 ) - n.nodeIndex = c++; - - merge[id] = true; - } - - var add = false; - - if ( first == 0 ) { - if ( node.nodeIndex == last ) - add = true; - } else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 ) - add = true; - - if ( add ^ not ) - tmp.push( node ); - } - - r = tmp; - - // Otherwise, find the expression to execute - } else { - var fn = jQuery.expr[ m[1] ]; - if ( typeof fn == "object" ) - fn = fn[ m[2] ]; - - if ( typeof fn == "string" ) - fn = eval("false||function(a,i){return " + fn + ";}"); - - // Execute it against the current filter - r = jQuery.grep( r, function(elem, i){ - return fn(elem, i, m, r); - }, not ); - } - } - - // Return an array of filtered elements (r) - // and the modified expression string (t) - return { r: r, t: t }; - }, - - dir: function( elem, dir ){ - var matched = []; - var cur = elem[dir]; - while ( cur && cur != document ) { - if ( cur.nodeType == 1 ) - matched.push( cur ); - cur = cur[dir]; - } - return matched; - }, - - nth: function(cur,result,dir,elem){ - result = result || 1; - var num = 0; - - for ( ; cur; cur = cur[dir] ) - if ( cur.nodeType == 1 && ++num == result ) - break; - - return cur; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType == 1 && (!elem || n != elem) ) - r.push( n ); - } - - return r; - } -}); - -/* - * A number of helper functions used for managing events. - * Many of the ideas behind this code orignated from - * Dean Edwards' addEvent library. - */ -jQuery.event = { - - // Bind an event to an element - // Original by Dean Edwards - add: function(elem, types, handler, data) { - if ( elem.nodeType == 3 || elem.nodeType == 8 ) - return; - - // For whatever reason, IE has trouble passing the window object - // around, causing it to be cloned in the process - if ( jQuery.browser.msie && elem.setInterval != undefined ) - elem = window; - - // Make sure that the function being executed has a unique ID - if ( !handler.guid ) - handler.guid = this.guid++; - - // if data is passed, bind to handler - if( data != undefined ) { - // Create temporary function pointer to original handler - var fn = handler; - - // Create unique handler function, wrapped around original handler - handler = function() { - // Pass arguments and context to original handler - return fn.apply(this, arguments); - }; - - // Store data in unique handler - handler.data = data; - - // Set the guid of unique handler to the same of original handler, so it can be removed - handler.guid = fn.guid; - } - - // Init the element's event structure - var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}), - handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){ - // returned undefined or false - var val; - - // Handle the second event of a trigger and when - // an event is called after a page has unloaded - if ( typeof jQuery == "undefined" || jQuery.event.triggered ) - return val; - - val = jQuery.event.handle.apply(arguments.callee.elem, arguments); - - return val; - }); - // Add elem as a property of the handle function - // This is to prevent a memory leak with non-native - // event in IE. - handle.elem = elem; - - // Handle multiple events seperated by a space - // jQuery(...).bind("mouseover mouseout", fn); - jQuery.each(types.split(/\s+/), function(index, type) { - // Namespaced event handlers - var parts = type.split("."); - type = parts[0]; - handler.type = parts[1]; - - // Get the current list of functions bound to this event - var handlers = events[type]; - - // Init the event handler queue - if (!handlers) { - handlers = events[type] = {}; - - // Check for a special event handler - // Only use addEventListener/attachEvent if the special - // events handler returns false - if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) { - // Bind the global event handler to the element - if (elem.addEventListener) - elem.addEventListener(type, handle, false); - else if (elem.attachEvent) - elem.attachEvent("on" + type, handle); - } - } - - // Add the function to the element's handler list - handlers[handler.guid] = handler; - - // Keep track of which events have been used, for global triggering - jQuery.event.global[type] = true; - }); - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - guid: 1, - global: {}, - - // Detach an event or set of events from an element - remove: function(elem, types, handler) { - // don't do events on text and comment nodes - if ( elem.nodeType == 3 || elem.nodeType == 8 ) - return; - - var events = jQuery.data(elem, "events"), ret, index; - - if ( events ) { - // Unbind all events for the element - if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") ) - for ( var type in events ) - this.remove( elem, type + (types || "") ); - else { - // types is actually an event object here - if ( types.type ) { - handler = types.handler; - types = types.type; - } - - // Handle multiple events seperated by a space - // jQuery(...).unbind("mouseover mouseout", fn); - jQuery.each(types.split(/\s+/), function(index, type){ - // Namespaced event handlers - var parts = type.split("."); - type = parts[0]; - - if ( events[type] ) { - // remove the given handler for the given type - if ( handler ) - delete events[type][handler.guid]; - - // remove all handlers for the given type - else - for ( handler in events[type] ) - // Handle the removal of namespaced events - if ( !parts[1] || events[type][handler].type == parts[1] ) - delete events[type][handler]; - - // remove generic event handler if no more handlers exist - for ( ret in events[type] ) break; - if ( !ret ) { - if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) { - if (elem.removeEventListener) - elem.removeEventListener(type, jQuery.data(elem, "handle"), false); - else if (elem.detachEvent) - elem.detachEvent("on" + type, jQuery.data(elem, "handle")); - } - ret = null; - delete events[type]; - } - } - }); - } - - // Remove the expando if it's no longer used - for ( ret in events ) break; - if ( !ret ) { - var handle = jQuery.data( elem, "handle" ); - if ( handle ) handle.elem = null; - jQuery.removeData( elem, "events" ); - jQuery.removeData( elem, "handle" ); - } - } - }, - - trigger: function(type, data, elem, donative, extra) { - // Clone the incoming data, if any - data = jQuery.makeArray(data || []); - - if ( type.indexOf("!") >= 0 ) { - type = type.slice(0, -1); - var exclusive = true; - } - - // Handle a global trigger - if ( !elem ) { - // Only trigger if we've ever bound an event for it - if ( this.global[type] ) - jQuery("*").add([window, document]).trigger(type, data); - - // Handle triggering a single element - } else { - // don't do events on text and comment nodes - if ( elem.nodeType == 3 || elem.nodeType == 8 ) - return undefined; - - var val, ret, fn = jQuery.isFunction( elem[ type ] || null ), - // Check to see if we need to provide a fake event, or not - event = !data[0] || !data[0].preventDefault; - - // Pass along a fake event - if ( event ) - data.unshift( this.fix({ type: type, target: elem }) ); - - // Enforce the right trigger type - data[0].type = type; - if ( exclusive ) - data[0].exclusive = true; - - // Trigger the event - if ( jQuery.isFunction( jQuery.data(elem, "handle") ) ) - val = jQuery.data(elem, "handle").apply( elem, data ); - - // Handle triggering native .onfoo handlers - if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) - val = false; - - // Extra functions don't get the custom event object - if ( event ) - data.shift(); - - // Handle triggering of extra function - if ( extra && jQuery.isFunction( extra ) ) { - // call the extra function and tack the current return value on the end for possible inspection - ret = extra.apply( elem, val == null ? data : data.concat( val ) ); - // if anything is returned, give it precedence and have it overwrite the previous value - if (ret !== undefined) - val = ret; - } - - // Trigger the native events (except for clicks on links) - if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) { - this.triggered = true; - try { - elem[ type ](); - // prevent IE from throwing an error for some hidden elements - } catch (e) {} - } - - this.triggered = false; - } - - return val; - }, - - handle: function(event) { - // returned undefined or false - var val; - - // Empty object is for triggered events with no data - event = jQuery.event.fix( event || window.event || {} ); - - // Namespaced event handlers - var parts = event.type.split("."); - event.type = parts[0]; - - var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 ); - args.unshift( event ); - - for ( var j in handlers ) { - var handler = handlers[j]; - // Pass in a reference to the handler function itself - // So that we can later remove it - args[0].handler = handler; - args[0].data = handler.data; - - // Filter the functions by class - if ( !parts[1] && !event.exclusive || handler.type == parts[1] ) { - var ret = handler.apply( this, args ); - - if ( val !== false ) - val = ret; - - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - - // Clean up added properties in IE to prevent memory leak - if (jQuery.browser.msie) - event.target = event.preventDefault = event.stopPropagation = - event.handler = event.data = null; - - return val; - }, - - fix: function(event) { - // store a copy of the original event object - // and clone to set read-only properties - var originalEvent = event; - event = jQuery.extend({}, originalEvent); - - // add preventDefault and stopPropagation since - // they will not work on the clone - event.preventDefault = function() { - // if preventDefault exists run it on the original event - if (originalEvent.preventDefault) - originalEvent.preventDefault(); - // otherwise set the returnValue property of the original event to false (IE) - originalEvent.returnValue = false; - }; - event.stopPropagation = function() { - // if stopPropagation exists run it on the original event - if (originalEvent.stopPropagation) - originalEvent.stopPropagation(); - // otherwise set the cancelBubble property of the original event to true (IE) - originalEvent.cancelBubble = true; - }; - - // Fix target property, if necessary - if ( !event.target ) - event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either - - // check if target is a textnode (safari) - if ( event.target.nodeType == 3 ) - event.target = originalEvent.target.parentNode; - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) - event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, body = document.body; - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0); - } - - // Add which for key events - if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) - event.which = event.charCode || event.keyCode; - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) - event.metaKey = event.ctrlKey; - - // Add which for click: 1 == left; 2 == middle; 3 == right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button ) - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); - - return event; - }, - - special: { - ready: { - setup: function() { - // Make sure the ready event is setup - bindReady(); - return; - }, - - teardown: function() { return; } - }, - - mouseenter: { - setup: function() { - if ( jQuery.browser.msie ) return false; - jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler); - return true; - }, - - teardown: function() { - if ( jQuery.browser.msie ) return false; - jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler); - return true; - }, - - handler: function(event) { - // If we actually just moused on to a sub-element, ignore it - if ( withinElement(event, this) ) return true; - // Execute the right handlers by setting the event type to mouseenter - arguments[0].type = "mouseenter"; - return jQuery.event.handle.apply(this, arguments); - } - }, - - mouseleave: { - setup: function() { - if ( jQuery.browser.msie ) return false; - jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler); - return true; - }, - - teardown: function() { - if ( jQuery.browser.msie ) return false; - jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler); - return true; - }, - - handler: function(event) { - // If we actually just moused on to a sub-element, ignore it - if ( withinElement(event, this) ) return true; - // Execute the right handlers by setting the event type to mouseleave - arguments[0].type = "mouseleave"; - return jQuery.event.handle.apply(this, arguments); - } - } - } -}; - -jQuery.fn.extend({ - bind: function( type, data, fn ) { - return type == "unload" ? this.one(type, data, fn) : this.each(function(){ - jQuery.event.add( this, type, fn || data, fn && data ); - }); - }, - - one: function( type, data, fn ) { - return this.each(function(){ - jQuery.event.add( this, type, function(event) { - jQuery(this).unbind(event); - return (fn || data).apply( this, arguments); - }, fn && data); - }); - }, - - unbind: function( type, fn ) { - return this.each(function(){ - jQuery.event.remove( this, type, fn ); - }); - }, - - trigger: function( type, data, fn ) { - return this.each(function(){ - jQuery.event.trigger( type, data, this, true, fn ); - }); - }, - - triggerHandler: function( type, data, fn ) { - if ( this[0] ) - return jQuery.event.trigger( type, data, this[0], false, fn ); - return undefined; - }, - - toggle: function() { - // Save reference to arguments for access in closure - var args = arguments; - - return this.click(function(event) { - // Figure out which function to execute - this.lastToggle = 0 == this.lastToggle ? 1 : 0; - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[this.lastToggle].apply( this, arguments ) || false; - }); - }, - - hover: function(fnOver, fnOut) { - return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut); - }, - - ready: function(fn) { - // Attach the listeners - bindReady(); - - // If the DOM is already ready - if ( jQuery.isReady ) - // Execute the function immediately - fn.call( document, jQuery ); - - // Otherwise, remember the function for later - else - // Add the function to the wait list - jQuery.readyList.push( function() { return fn.call(this, jQuery); } ); - - return this; - } -}); - -jQuery.extend({ - isReady: false, - readyList: [], - // Handle when the DOM is ready - ready: function() { - // Make sure that the DOM is not already loaded - if ( !jQuery.isReady ) { - // Remember that the DOM is ready - jQuery.isReady = true; - - // If there are functions bound, to execute - if ( jQuery.readyList ) { - // Execute all of them - jQuery.each( jQuery.readyList, function(){ - this.apply( document ); - }); - - // Reset the list of functions - jQuery.readyList = null; - } - - // Trigger any bound ready events - jQuery(document).triggerHandler("ready"); - } - } -}); - -var readyBound = false; - -function bindReady(){ - if ( readyBound ) return; - readyBound = true; - - // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event - if ( document.addEventListener && !jQuery.browser.opera) - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", jQuery.ready, false ); - - // If IE is used and is not in a frame - // Continually check to see if the document is ready - if ( jQuery.browser.msie && window == top ) (function(){ - if (jQuery.isReady) return; - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch( error ) { - setTimeout( arguments.callee, 0 ); - return; - } - // and execute any waiting functions - jQuery.ready(); - })(); - - if ( jQuery.browser.opera ) - document.addEventListener( "DOMContentLoaded", function () { - if (jQuery.isReady) return; - for (var i = 0; i < document.styleSheets.length; i++) - if (document.styleSheets[i].disabled) { - setTimeout( arguments.callee, 0 ); - return; - } - // and execute any waiting functions - jQuery.ready(); - }, false); - - if ( jQuery.browser.safari ) { - var numStyles; - (function(){ - if (jQuery.isReady) return; - if ( document.readyState != "loaded" && document.readyState != "complete" ) { - setTimeout( arguments.callee, 0 ); - return; - } - if ( numStyles === undefined ) - numStyles = jQuery("style, link[rel=stylesheet]").length; - if ( document.styleSheets.length != numStyles ) { - setTimeout( arguments.callee, 0 ); - return; - } - // and execute any waiting functions - jQuery.ready(); - })(); - } - - // A fallback to window.onload, that will always work - jQuery.event.add( window, "load", jQuery.ready ); -} - -jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + - "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + - "submit,keydown,keypress,keyup,error").split(","), function(i, name){ - - // Handle event binding - jQuery.fn[name] = function(fn){ - return fn ? this.bind(name, fn) : this.trigger(name); - }; -}); - -// Checks if an event happened on an element within another element -// Used in jQuery.event.special.mouseenter and mouseleave handlers -var withinElement = function(event, elem) { - // Check if mouse(over|out) are still within the same parent element - var parent = event.relatedTarget; - // Traverse up the tree - while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; } - // Return true if we actually just moused on to a sub-element - return parent == elem; -}; - -// Prevent memory leaks in IE -// And prevent errors on refresh with events like mouseover in other browsers -// Window isn't included so as not to unbind existing unload events -jQuery(window).bind("unload", function() { - jQuery("*").add(document).unbind(); -}); -jQuery.fn.extend({ - load: function( url, params, callback ) { - if ( jQuery.isFunction( url ) ) - return this.bind("load", url); - - var off = url.indexOf(" "); - if ( off >= 0 ) { - var selector = url.slice(off, url.length); - url = url.slice(0, off); - } - - callback = callback || function(){}; - - // Default to a GET request - var type = "GET"; - - // If the second parameter was provided - if ( params ) - // If it's a function - if ( jQuery.isFunction( params ) ) { - // We assume that it's the callback - callback = params; - params = null; - - // Otherwise, build a param string - } else { - params = jQuery.param( params ); - type = "POST"; - } - - var self = this; - - // Request the remote document - jQuery.ajax({ - url: url, - type: type, - dataType: "html", - data: params, - complete: function(res, status){ - // If successful, inject the HTML into all the matched elements - if ( status == "success" || status == "notmodified" ) - // See if a selector was specified - self.html( selector ? - // Create a dummy div to hold the results - jQuery("<div/>") - // inject the contents of the document in, removing the scripts - // to avoid any 'Permission Denied' errors in IE - .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, "")) - - // Locate the specified elements - .find(selector) : - - // If not, just inject the full result - res.responseText ); - - self.each( callback, [res.responseText, status, res] ); - } - }); - return this; - }, - - serialize: function() { - return jQuery.param(this.serializeArray()); - }, - serializeArray: function() { - return this.map(function(){ - return jQuery.nodeName(this, "form") ? - jQuery.makeArray(this.elements) : this; - }) - .filter(function(){ - return this.name && !this.disabled && - (this.checked || /select|textarea/i.test(this.nodeName) || - /text|hidden|password/i.test(this.type)); - }) - .map(function(i, elem){ - var val = jQuery(this).val(); - return val == null ? null : - val.constructor == Array ? - jQuery.map( val, function(val, i){ - return {name: elem.name, value: val}; - }) : - {name: elem.name, value: val}; - }).get(); - } -}); - -// Attach a bunch of functions for handling common AJAX events -jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){ - jQuery.fn[o] = function(f){ - return this.bind(o, f); - }; -}); - -var jsc = (new Date).getTime(); - -jQuery.extend({ - get: function( url, data, callback, type ) { - // shift arguments if data argument was ommited - if ( jQuery.isFunction( data ) ) { - callback = data; - data = null; - } - - return jQuery.ajax({ - type: "GET", - url: url, - data: data, - success: callback, - dataType: type - }); - }, - - getScript: function( url, callback ) { - return jQuery.get(url, null, callback, "script"); - }, - - getJSON: function( url, data, callback ) { - return jQuery.get(url, data, callback, "json"); - }, - - post: function( url, data, callback, type ) { - if ( jQuery.isFunction( data ) ) { - callback = data; - data = {}; - } - - return jQuery.ajax({ - type: "POST", - url: url, - data: data, - success: callback, - dataType: type - }); - }, - - ajaxSetup: function( settings ) { - jQuery.extend( jQuery.ajaxSettings, settings ); - }, - - ajaxSettings: { - global: true, - type: "GET", - timeout: 0, - contentType: "application/x-www-form-urlencoded", - processData: true, - async: true, - data: null, - username: null, - password: null, - accepts: { - xml: "application/xml, text/xml", - html: "text/html", - script: "text/javascript, application/javascript", - json: "application/json, text/javascript", - text: "text/plain", - _default: "*/*" - } - }, - - // Last-Modified header cache for next request - lastModified: {}, - - ajax: function( s ) { - var jsonp, jsre = /=\?(&|$)/g, status, data; - - // Extend the settings, but re-extend 's' so that it can be - // checked again later (in the test suite, specifically) - s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); - - // convert data if not already a string - if ( s.data && s.processData && typeof s.data != "string" ) - s.data = jQuery.param(s.data); - - // Handle JSONP Parameter Callbacks - if ( s.dataType == "jsonp" ) { - if ( s.type.toLowerCase() == "get" ) { - if ( !s.url.match(jsre) ) - s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?"; - } else if ( !s.data || !s.data.match(jsre) ) - s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; - s.dataType = "json"; - } - - // Build temporary JSONP function - if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) { - jsonp = "jsonp" + jsc++; - - // Replace the =? sequence both in the query string and the data - if ( s.data ) - s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); - s.url = s.url.replace(jsre, "=" + jsonp + "$1"); - - // We need to make sure - // that a JSONP style response is executed properly - s.dataType = "script"; - - // Handle JSONP-style loading - window[ jsonp ] = function(tmp){ - data = tmp; - success(); - complete(); - // Garbage collect - window[ jsonp ] = undefined; - try{ delete window[ jsonp ]; } catch(e){} - if ( head ) - head.removeChild( script ); - }; - } - - if ( s.dataType == "script" && s.cache == null ) - s.cache = false; - - if ( s.cache === false && s.type.toLowerCase() == "get" ) { - var ts = (new Date()).getTime(); - // try replacing _= if it is there - var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2"); - // if nothing was replaced, add timestamp to the end - s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : ""); - } - - // If data is available, append data to url for get requests - if ( s.data && s.type.toLowerCase() == "get" ) { - s.url += (s.url.match(/\?/) ? "&" : "?") + s.data; - - // IE likes to send both get and post data, prevent this - s.data = null; - } - - // Watch for a new set of requests - if ( s.global && ! jQuery.active++ ) - jQuery.event.trigger( "ajaxStart" ); - - // If we're requesting a remote document - // and trying to load JSON or Script with a GET - if ( (!s.url.indexOf("http") || !s.url.indexOf("//")) && s.dataType == "script" && s.type.toLowerCase() == "get" ) { - var head = document.getElementsByTagName("head")[0]; - var script = document.createElement("script"); - script.src = s.url; - if (s.scriptCharset) - script.charset = s.scriptCharset; - - // Handle Script loading - if ( !jsonp ) { - var done = false; - - // Attach handlers for all browsers - script.onload = script.onreadystatechange = function(){ - if ( !done && (!this.readyState || - this.readyState == "loaded" || this.readyState == "complete") ) { - done = true; - success(); - complete(); - head.removeChild( script ); - } - }; - } - - head.appendChild(script); - - // We handle everything using the script element injection - return undefined; - } - - var requestDone = false; - - // Create the request object; Microsoft failed to properly - // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available - var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest(); - - // Open the socket - xml.open(s.type, s.url, s.async, s.username, s.password); - - // Need an extra try/catch for cross domain requests in Firefox 3 - try { - // Set the correct header, if data is being sent - if ( s.data ) - xml.setRequestHeader("Content-Type", s.contentType); - - // Set the If-Modified-Since header, if ifModified mode. - if ( s.ifModified ) - xml.setRequestHeader("If-Modified-Since", - jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" ); - - // Set header so the called script knows that it's an XMLHttpRequest - xml.setRequestHeader("X-Requested-With", "XMLHttpRequest"); - - // Set the Accepts header for the server, depending on the dataType - xml.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? - s.accepts[ s.dataType ] + ", */*" : - s.accepts._default ); - } catch(e){} - - // Allow custom headers/mimetypes - if ( s.beforeSend ) - s.beforeSend(xml); - - if ( s.global ) - jQuery.event.trigger("ajaxSend", [xml, s]); - - // Wait for a response to come back - var onreadystatechange = function(isTimeout){ - // The transfer is complete and the data is available, or the request timed out - if ( !requestDone && xml && (xml.readyState == 4 || isTimeout == "timeout") ) { - requestDone = true; - - // clear poll interval - if (ival) { - clearInterval(ival); - ival = null; - } - - status = isTimeout == "timeout" && "timeout" || - !jQuery.httpSuccess( xml ) && "error" || - s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" || - "success"; - - if ( status == "success" ) { - // Watch for, and catch, XML document parse errors - try { - // process the data (runs the xml through httpData regardless of callback) - data = jQuery.httpData( xml, s.dataType ); - } catch(e) { - status = "parsererror"; - } - } - - // Make sure that the request was successful or notmodified - if ( status == "success" ) { - // Cache Last-Modified header, if ifModified mode. - var modRes; - try { - modRes = xml.getResponseHeader("Last-Modified"); - } catch(e) {} // swallow exception thrown by FF if header is not available - - if ( s.ifModified && modRes ) - jQuery.lastModified[s.url] = modRes; - - // JSONP handles its own success callback - if ( !jsonp ) - success(); - } else - jQuery.handleError(s, xml, status); - - // Fire the complete handlers - complete(); - - // Stop memory leaks - if ( s.async ) - xml = null; - } - }; - - if ( s.async ) { - // don't attach the handler to the request, just poll it instead - var ival = setInterval(onreadystatechange, 13); - - // Timeout checker - if ( s.timeout > 0 ) - setTimeout(function(){ - // Check to see if the request is still happening - if ( xml ) { - // Cancel the request - xml.abort(); - - if( !requestDone ) - onreadystatechange( "timeout" ); - } - }, s.timeout); - } - - // Send the data - try { - xml.send(s.data); - } catch(e) { - jQuery.handleError(s, xml, null, e); - } - - // firefox 1.5 doesn't fire statechange for sync requests - if ( !s.async ) - onreadystatechange(); - - function success(){ - // If a local callback was specified, fire it and pass it the data - if ( s.success ) - s.success( data, status ); - - // Fire the global callback - if ( s.global ) - jQuery.event.trigger( "ajaxSuccess", [xml, s] ); - } - - function complete(){ - // Process result - if ( s.complete ) - s.complete(xml, status); - - // The request was completed - if ( s.global ) - jQuery.event.trigger( "ajaxComplete", [xml, s] ); - - // Handle the global AJAX counter - if ( s.global && ! --jQuery.active ) - jQuery.event.trigger( "ajaxStop" ); - } - - // return XMLHttpRequest to allow aborting the request etc. - return xml; - }, - - handleError: function( s, xml, status, e ) { - // If a local callback was specified, fire it - if ( s.error ) s.error( xml, status, e ); - - // Fire the global callback - if ( s.global ) - jQuery.event.trigger( "ajaxError", [xml, s, e] ); - }, - - // Counter for holding the number of active queries - active: 0, - - // Determines if an XMLHttpRequest was successful or not - httpSuccess: function( r ) { - try { - // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 - return !r.status && location.protocol == "file:" || - ( r.status >= 200 && r.status < 300 ) || r.status == 304 || r.status == 1223 || - jQuery.browser.safari && r.status == undefined; - } catch(e){} - return false; - }, - - // Determines if an XMLHttpRequest returns NotModified - httpNotModified: function( xml, url ) { - try { - var xmlRes = xml.getResponseHeader("Last-Modified"); - - // Firefox always returns 200. check Last-Modified date - return xml.status == 304 || xmlRes == jQuery.lastModified[url] || - jQuery.browser.safari && xml.status == undefined; - } catch(e){} - return false; - }, - - httpData: function( r, type ) { - var ct = r.getResponseHeader("content-type"); - var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0; - var data = xml ? r.responseXML : r.responseText; - - if ( xml && data.documentElement.tagName == "parsererror" ) - throw "parsererror"; - - // If the type is "script", eval it in global context - if ( type == "script" ) - jQuery.globalEval( data ); - - // Get the JavaScript object, if JSON is used. - if ( type == "json" ) - data = eval("(" + data + ")"); - - return data; - }, - - // Serialize an array of form elements or a set of - // key/values into a query string - param: function( a ) { - var s = []; - - // If an array was passed in, assume that it is an array - // of form elements - if ( a.constructor == Array || a.jquery ) - // Serialize the form elements - jQuery.each( a, function(){ - s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) ); - }); - - // Otherwise, assume that it's an object of key/value pairs - else - // Serialize the key/values - for ( var j in a ) - // If the value is an array then the key names need to be repeated - if ( a[j] && a[j].constructor == Array ) - jQuery.each( a[j], function(){ - s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) ); - }); - else - s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) ); - - // Return the resulting serialization - return s.join("&").replace(/%20/g, "+"); - } - -}); -jQuery.fn.extend({ - show: function(speed,callback){ - return speed ? - this.animate({ - height: "show", width: "show", opacity: "show" - }, speed, callback) : - - this.filter(":hidden").each(function(){ - this.style.display = this.oldblock || ""; - if ( jQuery.css(this,"display") == "none" ) { - var elem = jQuery("<" + this.tagName + " />").appendTo("body"); - this.style.display = elem.css("display"); - // handle an edge condition where css is - div { display:none; } or similar - if (this.style.display == "none") - this.style.display = "block"; - elem.remove(); - } - }).end(); - }, - - hide: function(speed,callback){ - return speed ? - this.animate({ - height: "hide", width: "hide", opacity: "hide" - }, speed, callback) : - - this.filter(":visible").each(function(){ - this.oldblock = this.oldblock || jQuery.css(this,"display"); - this.style.display = "none"; - }).end(); - }, - - // Save the old toggle function - _toggle: jQuery.fn.toggle, - - toggle: function( fn, fn2 ){ - return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ? - this._toggle( fn, fn2 ) : - fn ? - this.animate({ - height: "toggle", width: "toggle", opacity: "toggle" - }, fn, fn2) : - this.each(function(){ - jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ](); - }); - }, - - slideDown: function(speed,callback){ - return this.animate({height: "show"}, speed, callback); - }, - - slideUp: function(speed,callback){ - return this.animate({height: "hide"}, speed, callback); - }, - - slideToggle: function(speed, callback){ - return this.animate({height: "toggle"}, speed, callback); - }, - - fadeIn: function(speed, callback){ - return this.animate({opacity: "show"}, speed, callback); - }, - - fadeOut: function(speed, callback){ - return this.animate({opacity: "hide"}, speed, callback); - }, - - fadeTo: function(speed,to,callback){ - return this.animate({opacity: to}, speed, callback); - }, - - animate: function( prop, speed, easing, callback ) { - var optall = jQuery.speed(speed, easing, callback); - - return this[ optall.queue === false ? "each" : "queue" ](function(){ - if ( this.nodeType != 1) - return false; - - var opt = jQuery.extend({}, optall); - var hidden = jQuery(this).is(":hidden"), self = this; - - for ( var p in prop ) { - if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden ) - return jQuery.isFunction(opt.complete) && opt.complete.apply(this); - - if ( p == "height" || p == "width" ) { - // Store display property - opt.display = jQuery.css(this, "display"); - - // Make sure that nothing sneaks out - opt.overflow = this.style.overflow; - } - } - - if ( opt.overflow != null ) - this.style.overflow = "hidden"; - - opt.curAnim = jQuery.extend({}, prop); - - jQuery.each( prop, function(name, val){ - var e = new jQuery.fx( self, opt, name ); - - if ( /toggle|show|hide/.test(val) ) - e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop ); - else { - var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/), - start = e.cur(true) || 0; - - if ( parts ) { - var end = parseFloat(parts[2]), - unit = parts[3] || "px"; - - // We need to compute starting value - if ( unit != "px" ) { - self.style[ name ] = (end || 1) + unit; - start = ((end || 1) / e.cur(true)) * start; - self.style[ name ] = start + unit; - } - - // If a +=/-= token was provided, we're doing a relative animation - if ( parts[1] ) - end = ((parts[1] == "-=" ? -1 : 1) * end) + start; - - e.custom( start, end, unit ); - } else - e.custom( start, val, "" ); - } - }); - - // For JS strict compliance - return true; - }); - }, - - queue: function(type, fn){ - if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) { - fn = type; - type = "fx"; - } - - if ( !type || (typeof type == "string" && !fn) ) - return queue( this[0], type ); - - return this.each(function(){ - if ( fn.constructor == Array ) - queue(this, type, fn); - else { - queue(this, type).push( fn ); - - if ( queue(this, type).length == 1 ) - fn.apply(this); - } - }); - }, - - stop: function(clearQueue, gotoEnd){ - var timers = jQuery.timers; - - if (clearQueue) - this.queue([]); - - this.each(function(){ - // go in reverse order so anything added to the queue during the loop is ignored - for ( var i = timers.length - 1; i >= 0; i-- ) - if ( timers[i].elem == this ) { - if (gotoEnd) - // force the next step to be the last - timers[i](true); - timers.splice(i, 1); - } - }); - - // start the next in the queue if the last step wasn't forced - if (!gotoEnd) - this.dequeue(); - - return this; - } - -}); - -var queue = function( elem, type, array ) { - if ( !elem ) - return undefined; - - type = type || "fx"; - - var q = jQuery.data( elem, type + "queue" ); - - if ( !q || array ) - q = jQuery.data( elem, type + "queue", - array ? jQuery.makeArray(array) : [] ); - - return q; -}; - -jQuery.fn.dequeue = function(type){ - type = type || "fx"; - - return this.each(function(){ - var q = queue(this, type); - - q.shift(); - - if ( q.length ) - q[0].apply( this ); - }); -}; - -jQuery.extend({ - - speed: function(speed, easing, fn) { - var opt = speed && speed.constructor == Object ? speed : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && easing.constructor != Function && easing - }; - - opt.duration = (opt.duration && opt.duration.constructor == Number ? - opt.duration : - { slow: 600, fast: 200 }[opt.duration]) || 400; - - // Queueing - opt.old = opt.complete; - opt.complete = function(){ - if ( opt.queue !== false ) - jQuery(this).dequeue(); - if ( jQuery.isFunction( opt.old ) ) - opt.old.apply( this ); - }; - - return opt; - }, - - easing: { - linear: function( p, n, firstNum, diff ) { - return firstNum + diff * p; - }, - swing: function( p, n, firstNum, diff ) { - return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; - } - }, - - timers: [], - timerId: null, - - fx: function( elem, options, prop ){ - this.options = options; - this.elem = elem; - this.prop = prop; - - if ( !options.orig ) - options.orig = {}; - } - -}); - -jQuery.fx.prototype = { - - // Simple function for setting a style value - update: function(){ - if ( this.options.step ) - this.options.step.apply( this.elem, [ this.now, this ] ); - - (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); - - // Set display property to block for height/width animations - if ( this.prop == "height" || this.prop == "width" ) - this.elem.style.display = "block"; - }, - - // Get the current size - cur: function(force){ - if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null ) - return this.elem[ this.prop ]; - - var r = parseFloat(jQuery.css(this.elem, this.prop, force)); - return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0; - }, - - // Start an animation from one number to another - custom: function(from, to, unit){ - this.startTime = (new Date()).getTime(); - this.start = from; - this.end = to; - this.unit = unit || this.unit || "px"; - this.now = this.start; - this.pos = this.state = 0; - this.update(); - - var self = this; - function t(gotoEnd){ - return self.step(gotoEnd); - } - - t.elem = this.elem; - - jQuery.timers.push(t); - - if ( jQuery.timerId == null ) { - jQuery.timerId = setInterval(function(){ - var timers = jQuery.timers; - - for ( var i = 0; i < timers.length; i++ ) - if ( !timers[i]() ) - timers.splice(i--, 1); - - if ( !timers.length ) { - clearInterval( jQuery.timerId ); - jQuery.timerId = null; - } - }, 13); - } - }, - - // Simple 'show' function - show: function(){ - // Remember where we started, so that we can go back to it later - this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop ); - this.options.show = true; - - // Begin the animation - this.custom(0, this.cur()); - - // Make sure that we start at a small width/height to avoid any - // flash of content - if ( this.prop == "width" || this.prop == "height" ) - this.elem.style[this.prop] = "1px"; - - // Start by showing the element - jQuery(this.elem).show(); - }, - - // Simple 'hide' function - hide: function(){ - // Remember where we started, so that we can go back to it later - this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop ); - this.options.hide = true; - - // Begin the animation - this.custom(this.cur(), 0); - }, - - // Each step of an animation - step: function(gotoEnd){ - var t = (new Date()).getTime(); - - if ( gotoEnd || t > this.options.duration + this.startTime ) { - this.now = this.end; - this.pos = this.state = 1; - this.update(); - - this.options.curAnim[ this.prop ] = true; - - var done = true; - for ( var i in this.options.curAnim ) - if ( this.options.curAnim[i] !== true ) - done = false; - - if ( done ) { - if ( this.options.display != null ) { - // Reset the overflow - this.elem.style.overflow = this.options.overflow; - - // Reset the display - this.elem.style.display = this.options.display; - if ( jQuery.css(this.elem, "display") == "none" ) - this.elem.style.display = "block"; - } - - // Hide the element if the "hide" operation was done - if ( this.options.hide ) - this.elem.style.display = "none"; - - // Reset the properties, if the item has been hidden or shown - if ( this.options.hide || this.options.show ) - for ( var p in this.options.curAnim ) - jQuery.attr(this.elem.style, p, this.options.orig[p]); - } - - // If a callback was provided, execute it - if ( done && jQuery.isFunction( this.options.complete ) ) - // Execute the complete function - this.options.complete.apply( this.elem ); - - return false; - } else { - var n = t - this.startTime; - this.state = n / this.options.duration; - - // Perform the easing function, defaults to swing - this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration); - this.now = this.start + ((this.end - this.start) * this.pos); - - // Perform the next step of the animation - this.update(); - } - - return true; - } - -}; - -jQuery.fx.step = { - scrollLeft: function(fx){ - fx.elem.scrollLeft = fx.now; - }, - - scrollTop: function(fx){ - fx.elem.scrollTop = fx.now; - }, - - opacity: function(fx){ - jQuery.attr(fx.elem.style, "opacity", fx.now); - }, - - _default: function(fx){ - fx.elem.style[ fx.prop ] = fx.now + fx.unit; - } -}; -// The Offset Method -// Originally By Brandon Aaron, part of the Dimension Plugin -// http://jquery.com/plugins/project/dimensions -jQuery.fn.offset = function() { - var left = 0, top = 0, elem = this[0], results; - - if ( elem ) with ( jQuery.browser ) { - var parent = elem.parentNode, - offsetChild = elem, - offsetParent = elem.offsetParent, - doc = elem.ownerDocument, - safari2 = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent), - fixed = jQuery.css(elem, "position") == "fixed"; - - // Use getBoundingClientRect if available - if ( elem.getBoundingClientRect ) { - var box = elem.getBoundingClientRect(); - - // Add the document scroll offsets - add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft), - box.top + Math.max(doc.documentElement.scrollTop, doc.body.scrollTop)); - - // IE adds the HTML element's border, by default it is medium which is 2px - // IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; } - // IE 7 standards mode, the border is always 2px - // This border/offset is typically represented by the clientLeft and clientTop properties - // However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS - // Therefore this method will be off by 2px in IE while in quirksmode - add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop ); - - // Otherwise loop through the offsetParents and parentNodes - } else { - - // Initial element offsets - add( elem.offsetLeft, elem.offsetTop ); - - // Get parent offsets - while ( offsetParent ) { - // Add offsetParent offsets - add( offsetParent.offsetLeft, offsetParent.offsetTop ); - - // Mozilla and Safari > 2 does not include the border on offset parents - // However Mozilla adds the border for table or table cells - if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 ) - border( offsetParent ); - - // Add the document scroll offsets if position is fixed on any offsetParent - if ( !fixed && jQuery.css(offsetParent, "position") == "fixed" ) - fixed = true; - - // Set offsetChild to previous offsetParent unless it is the body element - offsetChild = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent; - // Get next offsetParent - offsetParent = offsetParent.offsetParent; - } - - // Get parent scroll offsets - while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) { - // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug - if ( !/^inline|table.*$/i.test(jQuery.css(parent, "display")) ) - // Subtract parent scroll offsets - add( -parent.scrollLeft, -parent.scrollTop ); - - // Mozilla does not add the border for a parent that has overflow != visible - if ( mozilla && jQuery.css(parent, "overflow") != "visible" ) - border( parent ); - - // Get next parent - parent = parent.parentNode; - } - - // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild - // Mozilla doubles body offsets with a non-absolutely positioned offsetChild - if ( (safari2 && (fixed || jQuery.css(offsetChild, "position") == "absolute")) || - (mozilla && jQuery.css(offsetChild, "position") != "absolute") ) - add( -doc.body.offsetLeft, -doc.body.offsetTop ); - - // Add the document scroll offsets if position is fixed - if ( fixed ) - add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft), - Math.max(doc.documentElement.scrollTop, doc.body.scrollTop)); - } - - // Return an object with top and left properties - results = { top: top, left: left }; - } - - function border(elem) { - add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) ); - } - - function add(l, t) { - left += parseInt(l) || 0; - top += parseInt(t) || 0; - } - - return results; -}; -})(); From 5af54bb9e091d2a6166f30044e6e370a6c792920 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" <jgen.tech@gmail.com> Date: Tue, 7 Feb 2012 20:05:38 -0500 Subject: [PATCH 31/34] Added some more comments and type hints. --- core/imageboard.pack.php | 11 ++++++++--- core/user.class.php | 3 +++ core/util.inc.php | 29 +++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index ca198452..084cd712 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -106,8 +106,10 @@ class Image { /** * Search for an array of images + * + * @retval Array */ - public static function find_images($start, $limit, $tags=array()) { + public static function find_images(/*int*/ $start, /*int*/ $limit, $tags=array()) { assert(is_numeric($start)); assert(is_numeric($limit)); assert(is_array($tags)); @@ -383,7 +385,7 @@ class Image { /** * Set the image's source URL */ - public function set_source($source) { + public function set_source(/*string*/ $source) { global $database; if(empty($source)) $source = null; if($source != $this->source) { @@ -392,7 +394,10 @@ class Image { } } - + /** + * Check if the image is locked. + * @retval bool + */ public function is_locked() { return ($this->locked === true || $this->locked == "Y" || $this->locked == "t"); } diff --git a/core/user.class.php b/core/user.class.php index 0c98597b..843d6c9d 100644 --- a/core/user.class.php +++ b/core/user.class.php @@ -216,6 +216,7 @@ class User { /** * Get a snippet of HTML which will render the user's avatar, be that * a local file, a remote file, a gravatar, a something else, etc + * @retval String of HTML */ public function get_avatar_html() { // FIXME: configurable @@ -242,6 +243,8 @@ class User { * authtok = md5(sesskey, salt), presented to the user in web forms, to make sure that * the form was generated within the session. Salted and re-hashed so that * reading a web page from the user's cache doesn't give access to the session key + * + * @retval String containing auth token (MD5sum) */ public function get_auth_token() { global $config; diff --git a/core/util.inc.php b/core/util.inc.php index 3529fb87..8ddb2fb3 100644 --- a/core/util.inc.php +++ b/core/util.inc.php @@ -190,12 +190,26 @@ function undb_bool($val) { if($val === false || $val == 'N' || $val == 'n' || $val == 'F' || $val == 'f' || $val === 0) return false; } -function startsWith($haystack, $needle) { +/** + * Checks if a given string contains another at the beginning. + * + * @param $haystack String to examine. + * @param $needle String to look for. + * @retval bool + */ +function startsWith(/*string*/ $haystack, /*string*/ $needle) { $length = strlen($needle); return (substr($haystack, 0, $length) === $needle); } -function endsWith($haystack, $needle) { +/** + * Checks if a given string contains another at the end. + * + * @param $haystack String to examine. + * @param $needle String to look for. + * @retval bool + */ +function endsWith(/*string*/ $haystack, /*string*/ $needle) { $length = strlen($needle); $start = $length * -1; //negative return (substr($haystack, $start) === $needle); @@ -621,6 +635,7 @@ function log_msg($section, $priority, $message) { send_event(new LogEvent($section, $priority, $message)); } +// More shorthand ways of logging function log_debug($section, $message) {log_msg($section, SCORE_LOG_DEBUG, $message);} function log_info($section, $message) {log_msg($section, SCORE_LOG_INFO, $message);} function log_warning($section, $message) {log_msg($section, SCORE_LOG_WARNING, $message);} @@ -847,6 +862,13 @@ function send_event(Event $event) { // string representation of a number, it's two numbers separated by a space. // What the fuck were the PHP developers smoking. $_load_start = microtime(true); + +/** + * Collects some debug information (execution time, memory usage, queries, etc) + * and formats it to stick in the footer of the page. + * + * @retval String of debug info to add to the page. + */ function get_debug_info() { global $config, $_event_count, $database, $_execs, $_load_start; @@ -1051,6 +1073,9 @@ function _load_extensions() { ctx_log_endok(); } +/** + * Used to display fatal errors to the web user. + */ function _fatal_error(Exception $e) { $version = VERSION; $message = $e->getMessage(); From ac1b3d00e296da55b944f94c6a9dc305a1b34ba0 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" <jgen.tech@gmail.com> Date: Tue, 7 Feb 2012 20:13:58 -0500 Subject: [PATCH 32/34] Some more comments. --- core/event.class.php | 11 +++++++++++ core/page.class.php | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/core/event.class.php b/core/event.class.php index cbff2fcf..e6511cff 100644 --- a/core/event.class.php +++ b/core/event.class.php @@ -39,6 +39,8 @@ class PageRequestEvent extends Event { * Test if the requested path matches a given pattern. * * If it matches, store the remaining path elements in $args + * + * @retval bool */ public function page_matches(/*string*/ $name) { $parts = explode("/", $name); @@ -57,6 +59,11 @@ class PageRequestEvent extends Event { return true; } + /** + * Get the n th argument of the page request (if it exists.) + * @param $n integer + * @retval The argmuent (string) or NULL + */ public function get_arg(/*int*/ $n) { $offset = $this->part_count + $n; if($offset >= 0 && $offset < $this->arg_count) { @@ -67,6 +74,10 @@ class PageRequestEvent extends Event { } } + /** + * Returns the number of arguments the page request has. + * @retval int + */ public function count_args() { return (int)($this->arg_count - $this->part_count); } diff --git a/core/page.class.php b/core/page.class.php index 4060d4f7..e3102ccc 100644 --- a/core/page.class.php +++ b/core/page.class.php @@ -237,7 +237,7 @@ class Page { protected function add_auto_html_headers() { $data_href = get_base_href(); - $this->add_html_header("<script>base_href = '$data_href';</script>"); + $this->add_html_header("<script type='text/javascript'>base_href = '$data_href';</script>"); /* Attempt to cache the CSS & JavaScript files */ if ($this->add_cached_auto_html_headers() === FALSE) { From 0cdc3033979021f03c9dd41b62138b1305ef46c8 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" <jgen.tech@gmail.com> Date: Tue, 7 Feb 2012 20:25:05 -0500 Subject: [PATCH 33/34] Just more comments. --- ext/upload/main.php | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/ext/upload/main.php b/ext/upload/main.php index 938a27fa..571a56af 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -1,5 +1,5 @@ <?php -/* +/** * Name: Uploader * Author: Shish * Description: Allows people to upload files to the website @@ -218,14 +218,28 @@ class Upload extends SimpleExtension { } // }}} // do things {{{ + + /** + * Check if a given user can upload. + * @param $user The user to check. + * @retval bool + */ private function can_upload(User $user) { global $config; return ($config->get_bool("upload_anon") || !$user->is_anonymous()); } - // Helper function based on the one from the online PHP Documentation - // which is licensed under Creative Commons Attribution 3.0 License - // TODO: Make these messages user/admin editable + /** + * Returns a descriptive error message for the specified PHP error code. + * + * This is a helper function based on the one from the online PHP Documentation + * which is licensed under Creative Commons Attribution 3.0 License + * + * TODO: Make these messages user/admin editable + * + * @param $error_code PHP error code (int) + * @retval String + */ private function upload_error_message($error_code) { switch ($error_code) { case UPLOAD_ERR_INI_SIZE: @@ -247,6 +261,10 @@ class Upload extends SimpleExtension { } } + /** + * Handle an upload. + * @retval bool TRUE on upload successful. + */ private function try_upload($file, $tags, $source, $replace='') { global $page; global $config; @@ -293,6 +311,10 @@ class Upload extends SimpleExtension { return $ok; } + /** + * Handle an transload. + * @retval bool TRUE on transload successful. + */ private function try_transload($url, $tags, $source, $replace='') { global $page; global $config; From 2d443f0be9c3fef126886a9f3df8081226cfc8b1 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" <jgen.tech@gmail.com> Date: Tue, 7 Feb 2012 21:52:11 -0500 Subject: [PATCH 34/34] Mostly just adding the Link to comments. --- contrib/ban_words/main.php | 1 + contrib/bulk_add/main.php | 5 ++++- contrib/et/main.php | 3 +++ contrib/featured/main.php | 1 + contrib/handle_flash/main.php | 3 ++- contrib/handle_svg/main.php | 3 ++- contrib/ipban/main.php | 1 + contrib/log_db/main.php | 5 +++-- contrib/news/main.php | 1 + contrib/numeric_score/main.php | 1 + contrib/random_image/main.php | 1 + contrib/rating/main.php | 1 + contrib/regen_thumb/main.php | 1 + contrib/site_description/main.php | 1 + ext/tag_list/main.php | 18 +++++++++++++++--- ext/upgrade/main.php | 3 ++- 16 files changed, 40 insertions(+), 9 deletions(-) diff --git a/contrib/ban_words/main.php b/contrib/ban_words/main.php index aee98417..f64b1f69 100644 --- a/contrib/ban_words/main.php +++ b/contrib/ban_words/main.php @@ -2,6 +2,7 @@ /* * Name: Comment Word Ban * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: For stopping spam and other comment abuse * Documentation: diff --git a/contrib/bulk_add/main.php b/contrib/bulk_add/main.php index 0fa89029..c31d43fb 100644 --- a/contrib/bulk_add/main.php +++ b/contrib/bulk_add/main.php @@ -2,6 +2,7 @@ /* * Name: Bulk Add * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: Bulk add server-side images * Documentation: @@ -30,7 +31,9 @@ class BulkAdd extends SimpleExtension { $this->theme->display_admin_block(); } - + /** + * Generate the necessary DataUploadEvent for a given image and tags. + */ private function add_image($tmpname, $filename, $tags) { assert(file_exists($tmpname)); diff --git a/contrib/et/main.php b/contrib/et/main.php index dd6babe2..088d21f6 100644 --- a/contrib/et/main.php +++ b/contrib/et/main.php @@ -29,6 +29,9 @@ class ET extends SimpleExtension { } } + /** + * Collect the information and return it in a keyed array. + */ private function get_info() { global $config, $database; global $_event_listeners; // yay for using secret globals \o/ diff --git a/contrib/featured/main.php b/contrib/featured/main.php index 12f06b08..83ace62e 100644 --- a/contrib/featured/main.php +++ b/contrib/featured/main.php @@ -2,6 +2,7 @@ /* * Name: Featured Image * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: Bring a specific image to the users' attentions * Documentation: diff --git a/contrib/handle_flash/main.php b/contrib/handle_flash/main.php index 013528ac..21b19b30 100644 --- a/contrib/handle_flash/main.php +++ b/contrib/handle_flash/main.php @@ -2,7 +2,8 @@ /* * Name: Handle Flash * Author: Shish <webmaster@shishnet.org> - * Description: Handle Flash files + * Link: http://code.shishnet.org/shimmie2/ + * Description: Handle Flash files. (No thumbnail is generated for flash files) */ class FlashFileHandler extends DataHandlerExtension { diff --git a/contrib/handle_svg/main.php b/contrib/handle_svg/main.php index e2d02a3f..bab0cf14 100644 --- a/contrib/handle_svg/main.php +++ b/contrib/handle_svg/main.php @@ -2,7 +2,8 @@ /* * Name: Handle SVG * Author: Shish <webmaster@shishnet.org> - * Description: Handle SVG files + * Link: http://code.shishnet.org/shimmie2/ + * Description: Handle SVG files. (No thumbnail is generated for SVG files) */ class SVGFileHandler implements Extension { diff --git a/contrib/ipban/main.php b/contrib/ipban/main.php index bd9a11d0..3af43ead 100644 --- a/contrib/ipban/main.php +++ b/contrib/ipban/main.php @@ -2,6 +2,7 @@ /* * Name: IP Ban * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: Ban IP addresses * Documentation: diff --git a/contrib/log_db/main.php b/contrib/log_db/main.php index 2e51ce24..da5a590c 100644 --- a/contrib/log_db/main.php +++ b/contrib/log_db/main.php @@ -1,8 +1,9 @@ <?php /* * Name: Logging (Database) - * Author: Shish - * Description: Keep a record of SCore events + * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ + * Description: Keep a record of SCore events (in the database). * Visibility: admin */ diff --git a/contrib/news/main.php b/contrib/news/main.php index 69c87255..bd3f8388 100644 --- a/contrib/news/main.php +++ b/contrib/news/main.php @@ -2,6 +2,7 @@ /* * Name: News * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: Show a short amount of text in a block on the post list * Documentation: diff --git a/contrib/numeric_score/main.php b/contrib/numeric_score/main.php index 0e7d5181..79a0ca35 100755 --- a/contrib/numeric_score/main.php +++ b/contrib/numeric_score/main.php @@ -2,6 +2,7 @@ /* * Name: Image Scores (Numeric) * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: Allow users to score images * Documentation: diff --git a/contrib/random_image/main.php b/contrib/random_image/main.php index bad07792..75fcccf0 100644 --- a/contrib/random_image/main.php +++ b/contrib/random_image/main.php @@ -2,6 +2,7 @@ /* * Name: Random Image * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: Do things with a random image * Documentation: diff --git a/contrib/rating/main.php b/contrib/rating/main.php index b246b14e..e672c311 100644 --- a/contrib/rating/main.php +++ b/contrib/rating/main.php @@ -2,6 +2,7 @@ /* * Name: Image Ratings * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: Allow users to rate images "safe", "questionable" or "explicit" */ diff --git a/contrib/regen_thumb/main.php b/contrib/regen_thumb/main.php index cf3e2eb6..22950666 100644 --- a/contrib/regen_thumb/main.php +++ b/contrib/regen_thumb/main.php @@ -2,6 +2,7 @@ /* * Name: Regen Thumb * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Description: Regenerate a thumbnail image * Documentation: diff --git a/contrib/site_description/main.php b/contrib/site_description/main.php index 43ed38b1..5f294e3b 100644 --- a/contrib/site_description/main.php +++ b/contrib/site_description/main.php @@ -2,6 +2,7 @@ /* * Name: Site Description * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * License: GPLv2 * Visibility: admin * Description: A description for search engines diff --git a/ext/tag_list/main.php b/ext/tag_list/main.php index c30aacb6..c7d57716 100644 --- a/ext/tag_list/main.php +++ b/ext/tag_list/main.php @@ -1,7 +1,8 @@ <?php -/* +/** * Name: Tag List - * Author: Shish + * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * Description: Show the tags in various ways */ @@ -107,13 +108,18 @@ class TagList extends SimpleExtension { return make_link("post/list/$u_tag/1"); } + /** + * Get the minimum number of times a tag needs to be used + * in order to be considered in the tag list. + * @retval int + */ private function get_tags_min() { if(isset($_GET['mincount'])) { return int_escape($_GET['mincount']); } else { global $config; - return $config->get_int('tags_min'); + return $config->get_int('tags_min'); // get the default. } } @@ -170,6 +176,8 @@ class TagList extends SimpleExtension { $tags_min = $this->get_tags_min(); $starts_with = $this->get_starts_with(); + + // check if we have a cached version $cache_key = "data/tag_cloud-" . md5("tc" . $tags_min . $starts_with) . ".html"; if(file_exists($cache_key)) {return file_get_contents($cache_key);} @@ -205,6 +213,8 @@ class TagList extends SimpleExtension { $tags_min = $this->get_tags_min(); $starts_with = $this->get_starts_with(); + + // check if we have a cached version $cache_key = "data/tag_alpha-" . md5("ta" . $tags_min . $starts_with) . ".html"; if(file_exists($cache_key)) {return file_get_contents($cache_key);} @@ -239,6 +249,8 @@ class TagList extends SimpleExtension { global $database; $tags_min = $this->get_tags_min(); + + // check if we have a cached version $cache_key = "data/tag_popul-" . md5("tp" . $tags_min) . ".html"; if(file_exists($cache_key)) {return file_get_contents($cache_key);} diff --git a/ext/upgrade/main.php b/ext/upgrade/main.php index 35854e1d..7445d2d4 100644 --- a/ext/upgrade/main.php +++ b/ext/upgrade/main.php @@ -1,7 +1,8 @@ <?php /* * Name: Database Upgrader - * Author: Shish + * Author: Shish <webmaster@shishnet.org> + * Link: http://code.shishnet.org/shimmie2/ * Description: Keeps things happy behind the scenes * Visibility: admin */