Browse Source

Merge branch 'master' of https://github.com/SimpleMachines/SMF2.1

emanuele 12 years ago
parent
commit
70c0e1170d

+ 2 - 12
Sources/MessageIndex.php

@@ -406,7 +406,7 @@ function MessageIndex()
 				$row['first_body'] = strip_tags(strtr(parse_bbc($row['first_body'], $row['first_smileys'], $row['id_first_msg']), array('<br />' => '&#10;')));
 				if ($smcFunc['strlen']($row['first_body']) > $modSettings['preview_characters'])
 					$row['first_body'] = $smcFunc['substr']($row['first_body'], 0, $modSettings['preview_characters']) . '...';
-				
+
 				$row['last_body'] = strip_tags(strtr(parse_bbc($row['last_body'], $row['last_smileys'], $row['id_last_msg']), array('<br />' => '&#10;')));
 				if ($smcFunc['strlen']($row['last_body']) > $modSettings['preview_characters'])
 					$row['last_body'] = $smcFunc['substr']($row['last_body'], 0, $modSettings['preview_characters']) . '...';
@@ -684,17 +684,7 @@ function QuickModeration()
 		 * @todo Ugly. There's no getting around this, is there?
 		 * @todo Maybe just do this on the actions people want to use?
 		 */
-		$boards_can = array(
-			'make_sticky' => boardsAllowedTo('make_sticky'),
-			'move_any' => boardsAllowedTo('move_any'),
-			'move_own' => boardsAllowedTo('move_own'),
-			'remove_any' => boardsAllowedTo('remove_any'),
-			'remove_own' => boardsAllowedTo('remove_own'),
-			'lock_any' => boardsAllowedTo('lock_any'),
-			'lock_own' => boardsAllowedTo('lock_own'),
-			'merge_any' => boardsAllowedTo('merge_any'),
-			'approve_posts' => boardsAllowedTo('approve_posts'),
-		);
+		$boards_can = boardsAllowedTo(array('make_sticky', 'move_any', 'move_own', 'remove_any', 'remove_own', 'lock_any', 'lock_own', 'merge_any', 'approve_posts'));
 
 		$redirect_url = isset($_POST['redirect_url']) ? $_POST['redirect_url'] : (isset($_SESSION['old_url']) ? $_SESSION['old_url'] : '');
 	}

+ 3 - 0
Sources/Reminder.php

@@ -70,6 +70,9 @@ function RemindPick()
 	// You must enter a username/email address.
 	if (empty($where))
 		fatal_lang_error('username_no_exist', false);
+		
+	// Make sure we are not being slammed
+	spamProtection('remind');
 
 	// Find the user!
 	$request = $smcFunc['db_query']('', '

+ 9 - 20
Sources/Search.php

@@ -248,7 +248,7 @@ function PlushSearch2()
 	global $scripturl, $modSettings, $sourcedir, $txt, $db_connection;
 	global $user_info, $context, $options, $messages_request, $boards_can;
 	global $excludedWords, $participants, $smcFunc;
-	
+
 	// if comming from the quick search box, and we want to search on members, well we need to do that ;)
 	if (isset($_REQUEST['search_selection']) && $_REQUEST['search_selection'] === 'members')
 		redirectexit($scripturl . '?action=mlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']));
@@ -296,7 +296,7 @@ function PlushSearch2()
 
 	// Number of pages hard maximum - normally not set at all.
 	$modSettings['search_max_results'] = empty($modSettings['search_max_results']) ? 200 * $modSettings['search_results_per_page'] : (int) $modSettings['search_max_results'];
-	
+
 	// Maximum length of the string.
 	$context['search_string_limit'] = 100;
 
@@ -327,7 +327,7 @@ function PlushSearch2()
 	{
 		// Due to IE's 2083 character limit, we have to compress long search strings
 		$temp_params = base64_decode(str_replace(array('-', '_', '.'), array('+', '/', '='), $_REQUEST['params']));
-		
+
 		// Test for gzuncompress failing
 		$temp_params2 = @gzuncompress($temp_params);
 		$temp_params = explode('|"|', (!empty($temp_params2) ? $temp_params2 : $temp_params));
@@ -337,7 +337,7 @@ function PlushSearch2()
 			@list($k, $v) = explode('|\'|', $data);
 			$search_params[$k] = $v;
 		}
-		
+
 		if (isset($search_params['brd']))
 			$search_params['brd'] = empty($search_params['brd']) ? array() : explode(',', $search_params['brd']);
 	}
@@ -360,7 +360,7 @@ function PlushSearch2()
 
 	// Searching a specific topic?
 	if (!empty($_REQUEST['topic']) || (!empty($_REQUEST['search_selection']) && $_REQUEST['search_selection'] == 'topic'))
-	{	
+	{
 		$search_params['topic'] = empty($_REQUEST['search_selection']) ? (int) $_REQUEST['topic'] : (isset($_REQUEST['sd_topic']) ? (int) $_REQUEST['sd_topic'] : '');
 		$search_params['show_complete'] = true;
 	}
@@ -1252,7 +1252,7 @@ function PlushSearch2()
 							if (in_array($subjectWord, $excludedSubjectWords))
 							{
 								if (($subject_query['from'] != '{db_prefix}messages AS m') && !$excluded)
-								{ 
+								{
 									$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
 									$excluded = true;
 								}
@@ -1319,7 +1319,7 @@ function PlushSearch2()
 						// Nothing to search for?
 						if (empty($subject_query['where']))
 							continue;
-						
+
 						$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_topics', ($smcFunc['db_support_ignore'] ? ( '
 							INSERT IGNORE INTO {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics
 								(' . ($createTemporary ? '' : 'id_search, ') . 'id_topic)') : '') . '
@@ -1701,23 +1701,12 @@ function PlushSearch2()
 	if (!empty($context['topics']))
 	{
 		// Create an array for the permissions.
-		$boards_can = array(
-			'post_reply_own' => boardsAllowedTo('post_reply_own'),
-			'post_reply_any' => boardsAllowedTo('post_reply_any'),
-			'mark_any_notify' => boardsAllowedTo('mark_any_notify')
-		);
+		$boards_can = boardsAllowedTo(array('post_reply_own', 'post_reply_any', 'mark_any_notify', true, false));
 
 		// How's about some quick moderation?
 		if (!empty($options['display_quick_mod']))
 		{
-			$boards_can['lock_any'] = boardsAllowedTo('lock_any');
-			$boards_can['lock_own'] = boardsAllowedTo('lock_own');
-			$boards_can['make_sticky'] = boardsAllowedTo('make_sticky');
-			$boards_can['move_any'] = boardsAllowedTo('move_any');
-			$boards_can['move_own'] = boardsAllowedTo('move_own');
-			$boards_can['remove_any'] = boardsAllowedTo('remove_any');
-			$boards_can['remove_own'] = boardsAllowedTo('remove_own');
-			$boards_can['merge_any'] = boardsAllowedTo('merge_any');
+			$boards_can = boardsAllowedTo(array('lock_any', 'lock_own', 'make_sticky', 'move_any', 'move_own', 'remove_any', 'remove_own', 'merge_any', true, false));
 
 			$context['can_lock'] = in_array(0, $boards_can['lock_any']);
 			$context['can_sticky'] = in_array(0, $boards_can['make_sticky']) && !empty($modSettings['enableStickyTopics']);

+ 47 - 15
Sources/Security.php

@@ -42,7 +42,7 @@ function validateSession($type = 'admin')
 	// Is the security option off?
 	if (!empty($modSettings['securityDisable' . ($type != 'admin' ? '_' . $type : '')]))
 		return;
-		
+
 	// Or are they already logged in?, Moderator or admin sesssion is need for this area
 	if ((!empty($_SESSION[$type . '_time']) && $_SESSION[$type . '_time'] + $refreshTime >= time()) || (!empty($_SESSION['admin_time']) && $_SESSION['admin_time'] + $refreshTime >= time()))
 		return;
@@ -1000,18 +1000,36 @@ function isAllowedTo($permission, $boards = null)
  * If check_access is true will also make sure the group has proper access to that board.
  * @param array $permissions
  * @param bool $check_access = true
+ * @param bool $simple = true
  */
-function boardsAllowedTo($permissions, $check_access = true)
+function boardsAllowedTo($permissions, $check_access = true, $simple = true)
 {
 	global $user_info, $modSettings, $smcFunc;
 
+	// Arrays are nice, most of the time.
+	$permissions = (array) $permissions;
+
+	/*
+	 * Set $simple to true to use this function as it were in SMF 2.0.x.
+	 * Otherwise, the resultant array becomes split into the multiple
+	 * permissions that were passed. Other than that, it's just the normal
+	 * state of play that you're used to.
+	 */
+
 	// Administrators are all powerful, sorry.
 	if ($user_info['is_admin'])
-		return array(0);
+	{
+		if ($simple)
+			return array(0);
+		else
+		{
+			$result = array();
+			foreach ($permissions as $permission)
+				$result[$permission] = array(0);
 
-	// Arrays are nice, most of the time.
-	if (!is_array($permissions))
-		$permissions = array($permissions);
+			return $result;
+		}
+	}
 
 	// All groups the user is in except 'moderator'.
 	$groups = array_diff($user_info['groups'], array(3));
@@ -1032,20 +1050,33 @@ function boardsAllowedTo($permissions, $check_access = true)
 			'permissions' => $permissions,
 		)
 	);
-	$boards = array();
-	$deny_boards = array();
+	$boards = $deny_boards = $result = array();
 	while ($row = $smcFunc['db_fetch_assoc']($request))
 	{
-		if (empty($row['add_deny']))
-			$deny_boards[] = $row['id_board'];
+		if ($simple)
+		{
+			if (empty($row['add_deny']))
+				$deny_boards[$row['permission']][] = $row['id_board'];
+			else
+				$boards[$row['permission']][] = $row['id_board'];
+		}
 		else
-			$boards[] = $row['id_board'];
+		{
+			if (empty($row['add_deny']))
+				$deny_boards[$row['permission']][] = $row['id_board'];
+			else
+				$boards[$row['permission']][] = $row['id_board'];
+		}
 	}
 	$smcFunc['db_free_result']($request);
 
-	$boards = array_unique(array_values(array_diff($boards, $deny_boards)));
+	if ($simple)
+		$result = array_unique(array_values(array_diff($boards, $deny_boards)));
+	else
+		foreach ($permissions as $permission)
+			$result[$permission] = array_unique(array_values(array_diff($boards[$permission], $deny_boards[$permission])));
 
-	return $boards;
+	return $result;
 }
 
 /**
@@ -1101,7 +1132,8 @@ function spamProtection($error_type)
 	$timeOverrides = array(
 		'login' => 2,
 		'register' => 2,
-		'sendtopc' => $modSettings['spamWaitTime'] * 4,
+		'remind' => 30,
+		'sendtopic' => $modSettings['spamWaitTime'] * 4,
 		'sendmail' => $modSettings['spamWaitTime'] * 5,
 		'reporttm' => $modSettings['spamWaitTime'] * 4,
 		'search' => !empty($modSettings['search_floodcontrol_time']) ? $modSettings['search_floodcontrol_time'] : 1,
@@ -1216,7 +1248,7 @@ else
 }
 
 /**
- * Another helper function that put together the 
+ * Another helper function that put together the
  * @param string $fullip An IP address either IPv6 or not
  * @return string A SQL condition
  */

+ 1 - 1
Sources/SendTopic.php

@@ -91,7 +91,7 @@ function SendTopic()
 
 	// Actually send the message...
 	checkSession();
-	spamProtection('sendtopc');
+	spamProtection('sendtopic');
 
 	// This is needed for sendmail().
 	require_once($sourcedir . '/Subs-Post.php');

+ 2 - 1
Themes/default/Errors.template.php

@@ -33,7 +33,8 @@ function template_fatal_error()
 			<div ', $context['error_code'], 'class="padding">', $context['error_message'], '</div>
 			<span class="botslice"><span></span></span>
 		</div>
-	</div>';
+	</div>
+	<br class="clear" />';
 
 	// Show a back button (using javascript.)
 	echo '

+ 7 - 6
Themes/default/Login.template.php

@@ -30,9 +30,8 @@ function template_login()
 
 	// Did they make a mistake last time?
 	if (!empty($context['login_errors']))
-		foreach ($context['login_errors'] as $error)
-			echo '
-				<p class="error">', $error, '</p>';
+		echo '
+			<p class="errorbox">', implode('<br />', $context['login_errors']), '</p><br />';
 
 	// Or perhaps there's some special description for this time?
 	if (isset($context['description']))
@@ -52,8 +51,9 @@ function template_login()
 		echo '<p><strong>&mdash;', $txt['or'], '&mdash;</strong></p>
 				<dl>
 					<dt>', $txt['openid'], ':</dt>
-					<dd><input type="text" name="openid_identifier" class="input_text openid_login" size="17" />&nbsp;<em><a href="', $scripturl, '?action=helpadmin;help=register_openid" onclick="return reqWin(this.href);" class="help">(?)</a></em></dd>
-				</dl><hr />';
+					<dd><input type="text" name="openid_identifier" class="input_text openid_login" size="17" />&nbsp;<a href="', $scripturl, '?action=helpadmin;help=register_openid" onclick="return reqWin(this.href);" class="help"><img src="', $settings['images_url'], '/helptopics.png" alt="', $txt['help'], '" class="centericon" /></a></dd>
+				</dl>
+				<hr />';
 
 	echo '
 				<dl>
@@ -75,7 +75,8 @@ function template_login()
 				<input type="hidden" name="', $context['login_token_var'], '" value="', $context['login_token'], '" />
 			</div>
 			<span class="lowerframe"><span></span></span>
-		</div></form>';
+		</div>
+		</form>';
 
 	// Focus on the correct input - username or password.
 	echo '

+ 6 - 2
Themes/default/Reminder.template.php

@@ -28,7 +28,8 @@ function template_main()
 					<dt>', $txt['user_email'], ':</dt>
 					<dd><input type="text" name="user" size="30" class="input_text" /></dd>
 				</dl>
-				<p class="centertext"><input type="submit" value="', $txt['reminder_continue'], '" class="button_submit" /></p>
+				<input type="submit" value="', $txt['reminder_continue'], '" class="button_submit" />
+				<br class="clear" />
 			</div>
 			<span class="lowerframe"><span></span></span>
 		</div>
@@ -59,10 +60,12 @@ function template_reminder_pick()
 					<input type="radio" name="reminder_type" id="reminder_type_secret" value="secret" class="input_radio" />
 					<label for="reminder_type_secret">', $txt['authentication_' . $context['account_type'] . '_secret'], '</label>
 				</p>
-				<p class="centertext"><input type="submit" value="', $txt['reminder_continue'], '" class="button_submit" /></p>
+				<input type="submit" value="', $txt['reminder_continue'], '" class="button_submit" />
+				<br class="clear" />
 			</div>
 			<span class="lowerframe"><span></span></span>
 		</div>
+		
 		<input type="hidden" name="uid" value="', $context['current_member']['id'], '" />
 		<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
 		<input type="hidden" name="', $context['remind_token_var'], '" value="', $context['remind_token'], '" />
@@ -178,6 +181,7 @@ function template_ask()
 			</div>
 			<span class="lowerframe"><span></span></span>
 		</div>
+		<br class="clear" />
 		<input type="hidden" name="uid" value="', $context['remind_user'], '" />
 		<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
 		<input type="hidden" name="', $context['remind-sai_token_var'], '" value="', $context['remind-sai_token'], '" />

+ 2 - 2
Themes/default/css/admin.css

@@ -71,10 +71,10 @@ ol.search_results li {
 }
 #version_details {
 	overflow: auto;
-	height: 9.5em;
+	height: 12em;
 }
 #smfAnnouncements {
-	height: 13.5em;
+	height: 16.5em;
 	padding: 0 0.5em;
 	overflow: auto;
 }

+ 9 - 9
Themes/default/images/english-utf8/index.php

@@ -1,9 +1,9 @@
-<?php
-
-// Try to handle it with the upper level index.php. (it should know what to do.)
-if (file_exists(dirname(dirname(__FILE__)) . '/index.php'))
-	include (dirname(dirname(__FILE__)) . '/index.php');
-else
-	exit;
-
-?>
+<?php
+
+// Try to handle it with the upper level index.php. (it should know what to do.)
+if (file_exists(dirname(dirname(__FILE__)) . '/index.php'))
+	include (dirname(dirname(__FILE__)) . '/index.php');
+else
+	exit;
+
+?>

+ 2 - 1
Themes/default/languages/Errors.english.php

@@ -206,9 +206,10 @@ $txt['registerWaitTime_broken'] = 'You already registered just %1$d seconds ago!
 $txt['loginWaitTime_broken'] = 'You will have to wait about %1$d seconds to login again, sorry.';
 $txt['pmWaitTime_broken'] = 'The last personal message from your IP was less than %1$d seconds ago. Please try again later.';
 $txt['reporttmWaitTime_broken'] = 'The last topic report from your IP was less than %1$d seconds ago. Please try again later.';
-$txt['sendtopcWaitTime_broken'] = 'The last topic sent from your IP was less than %1$d seconds ago. Please try again later.';
+$txt['sendtopicWaitTime_broken'] = 'The last topic sent from your IP was less than %1$d seconds ago. Please try again later.';
 $txt['sendmailWaitTime_broken'] = 'The last email sent from your IP was less than %1$d seconds ago. Please try again later.';
 $txt['searchWaitTime_broken'] = 'Your last search was less than %1$d seconds ago. Please try again later.';
+$txt['remindWaitTime_broken'] = 'Your last reminder was less than %1$d seconds ago. Please try again later.';
 
 $txt['email_missing_data'] = 'You must enter something in both the subject and message boxes.';
 

+ 9 - 9
Themes/penguin/images/english-utf8/index.php

@@ -1,9 +1,9 @@
-<?php
-
-// Try to handle it with the upper level index.php. (it should know what to do.)
-if (file_exists(dirname(dirname(__FILE__)) . '/index.php'))
-	include (dirname(dirname(__FILE__)) . '/index.php');
-else
-	exit;
-
-?>
+<?php
+
+// Try to handle it with the upper level index.php. (it should know what to do.)
+if (file_exists(dirname(dirname(__FILE__)) . '/index.php'))
+	include (dirname(dirname(__FILE__)) . '/index.php');
+else
+	exit;
+
+?>

+ 497 - 497
Themes/penguin/scripts/jquery.bt.js

@@ -1,497 +1,497 @@
-/* @name BeautyTips
- * @desc a tooltips/baloon-help plugin for jQuery
- * @author Jeff Robbins - Lullabot - http://www.lullabot.com
- * @version 0.9.5 release candidate 1  (5/20/2009) */
- 
-jQuery.bt = {version: '0.9.5-rc1'};
- 
-/* @type jQuery
- * @cat Plugins/bt
- * @requires jQuery v1.2+
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- * Encourage development. If you use BeautyTips for anything cool 
- * or on a site that people have heard of, please drop me a note.
- * - jeff ^at lullabot > com
- * No guarantees, warranties, or promises of any kind */
-
-;(function($) { 
-  /* @credit Inspired by Karl Swedberg's ClueTip
-   * (http://plugins.learningjquery.com/cluetip/), which in turn was inspired
-   * by Cody Lindley's jTip (http://www.codylindley.com)
-   * Usage:
-   * The function can be called in a number of ways.
-   * $(selector).bt();
-   * $(selector).bt('Content text');
-   * $(selector).bt('Content text', {option1: value, option2: value});
-   * $(selector).bt({option1: value, option2: value});
-   * For more/better documentation and lots of examples, visit the demo page included with the distribution */ 
-
-  jQuery.fn.bt = function(content, options) {
-    if (typeof content != 'string') {
-      var contentSelect = true;
-      options = content;
-      content = false;
-    }
-    else {
-      var contentSelect = false;
-    }
-    if (jQuery.fn.hoverIntent && jQuery.bt.defaults.trigger == 'hover') {
-      jQuery.bt.defaults.trigger = 'hoverIntent';
-    }
-    return this.each(function(index) {
-      var opts = jQuery.extend(false, jQuery.bt.defaults, jQuery.bt.options, options);
-      opts.overlap = -10;
-      var ajaxTimeout = false;
-      if (opts.killTitle) {
-        $(this).find('[title]').andSelf().each(function() {
-          if (!$(this).attr('bt-xTitle')) {
-            $(this).attr('bt-xTitle', $(this).attr('title')).attr('title', '');
-          }
-        });
-      }
-      if (typeof opts.trigger == 'string') {
-        opts.trigger = [opts.trigger];
-      }
-      if (opts.trigger[0] == 'hoverIntent') {
-        var hoverOpts = jQuery.extend(opts.hoverIntentOpts, {
-          over: function() {
-            this.btOn();
-          },
-          out: function() {
-            this.btOff();
-          }});
-        $(this).hoverIntent(hoverOpts);
-      }
-      else if (opts.trigger[0] == 'hover') {
-        $(this).hover(
-          function() {
-            this.btOn();
-          },
-          function() {
-            this.btOff();
-          }
-        );
-      }
-      else if (opts.trigger[0] == 'now') {
-        if ($(this).hasClass('bt-active')) {
-          this.btOff();
-        }
-        else {
-          this.btOn();
-        }
-      }
-      else if (opts.trigger[0] == 'none') {
-      }
-      else if (opts.trigger.length > 1 && opts.trigger[0] != opts.trigger[1]) {
-        $(this)
-          .bind(opts.trigger[0], function() {
-            this.btOn();
-          })
-          .bind(opts.trigger[1], function() {
-            this.btOff();
-          });
-      }
-      else {
-        $(this).bind(opts.trigger[0], function() {
-          if ($(this).hasClass('bt-active')) {
-            this.btOff();
-          }
-          else {
-            this.btOn();
-          }
-        });
-      }
-      this.btOn = function () {
-        if (typeof $(this).data('bt-box') == 'object') {
-          this.btOff();
-        }
-        opts.preBuild.apply(this);
-        $(jQuery.bt.vars.closeWhenOpenStack).btOff();
-        $(this).addClass('bt-active ' + opts.activeClass);
-        if (contentSelect && opts.ajaxPath == null) {
-          if (opts.killTitle) {
-            $(this).attr('title', $(this).attr('bt-xTitle'));
-          }
-          content = $.isFunction(opts.contentSelector) ? opts.contentSelector.apply(this) : eval(opts.contentSelector);
-          if (opts.killTitle) {
-            $(this).attr('title', '');
-          }
-        }
-        if (opts.ajaxPath != null && content == false) {
-          if (typeof opts.ajaxPath == 'object') {
-            var url = eval(opts.ajaxPath[0]);
-            url += opts.ajaxPath[1] ? ' ' + opts.ajaxPath[1] : '';
-          }
-          else {
-            var url = opts.ajaxPath;
-          }
-          var off = url.indexOf(" ");
-          if ( off >= 0 ) {
-            var selector = url.slice(off, url.length);
-            url = url.slice(0, off);
-          }
-          var cacheData = opts.ajaxCache ? $(document.body).data('btCache-' + url.replace(/\./g, '')) : null;
-          if (typeof cacheData == 'string') {
-            content = selector ? $("<div/>").append(cacheData.replace(/<script(.|\s)*?\/script>/g, "")).find(selector) : cacheData;
-          }
-          else {
-            var target = this;
-            var ajaxOpts = jQuery.extend(false,
-            {
-              type: opts.ajaxType,
-              data: opts.ajaxData,
-              cache: opts.ajaxCache,
-              url: url,
-              complete: function(XMLHttpRequest, textStatus) {
-                if (textStatus == 'success' || textStatus == 'notmodified') {
-                  if (opts.ajaxCache) {
-                    $(document.body).data('btCache-' + url.replace(/\./g, ''), XMLHttpRequest.responseText);
-                  }
-                  ajaxTimeout = false;
-                  content = selector ?
-                    $("<div/>")
-                      .append(XMLHttpRequest.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
-                      .find(selector) :
-                    XMLHttpRequest.responseText;
-                }
-                else {
-                  if (textStatus == 'timeout') {
-                    ajaxTimeout = true;
-                  }
-                  content = opts.ajaxError.replace(/%error/g, XMLHttpRequest.statusText);
-                }
-                if ($(target).hasClass('bt-active')) {
-                  target.btOn();
-                }
-              }
-            }, opts.ajaxOpts);
-            jQuery.ajax(ajaxOpts);
-            content = opts.ajaxLoading;
-          }
-        }
-        if (opts.offsetParent){
-          var offsetParent = $(opts.offsetParent);
-          var offsetParentPos = offsetParent.offset();
-          var pos = $(this).offset();
-          var top = numb(pos.top) - numb(offsetParentPos.top) + numb($(this).css('margin-top'));
-          var left = numb(pos.left) - numb(offsetParentPos.left) + numb($(this).css('margin-left'));
-        }
-        else {
-          var offsetParent = ($(this).css('position') == 'absolute') ? $(this).parents().eq(0).offsetParent() : $(this).offsetParent();
-          var pos = $(this).btPosition();
-          var top = numb(pos.top) + numb($(this).css('margin-top'));
-          var left = numb(pos.left) + numb($(this).css('margin-left'));
-        }
-        var width = $(this).btOuterWidth();
-        var height = $(this).outerHeight();
-        if (typeof content == 'object') {
-          var original = content;
-          var clone = $(original).clone(true).show();
-          var origClones = $(original).data('bt-clones') || [];
-          origClones.push(clone);
-          $(original).data('bt-clones', origClones);
-          $(clone).data('bt-orig', original);
-          $(this).data('bt-content-orig', {original: original, clone: clone});
-          content = clone;
-        }
-        if (typeof content == 'null' || content == '') {
-          return;
-        }
-        var $text = $('<div class="bt-content"></div>').append(content).css({position: 'absolute', zIndex: opts.textzIndex, left: 0, top: 0});
-        var $box = $('<div class="bt-wrapper"></div>').append($text).css({position: 'absolute', zIndex: opts.wrapperzIndex, visibility:'hidden'}).appendTo(offsetParent);
-        $(this).data('bt-box', $box);
-        var scrollTop = numb($(document).scrollTop());
-        var scrollLeft = numb($(document).scrollLeft());
-        var docWidth = numb($(window).width());
-        var docHeight = numb($(window).height());
-        var winRight = scrollLeft + docWidth;
-        var winBottom = scrollTop + docHeight;
-        var space = new Object();
-        var thisOffset = $(this).offset();
-        space.top = thisOffset.top - scrollTop;
-        space.bottom = docHeight - ((thisOffset + height) - scrollTop);
-        space.left = thisOffset.left - scrollLeft;
-        space.right = docWidth - ((thisOffset.left + width) - scrollLeft);
-        var textOutHeight = numb($text.outerHeight());
-        var textOutWidth = numb($text.btOuterWidth());
-        if (opts.positions.constructor == String) {
-          opts.positions = opts.positions.replace(/ /, '').split(',');
-        }
-        if (opts.positions[0] == 'most') {
-          var position = 'top';
-          for (var pig in space) {  //      <-------  pigs in space!
-            position = space[pig] > space[position] ? pig : position;
-          }
-        }
-        else {
-          for (var x in opts.positions) {
-            var position = opts.positions[x];
-            if ((position == 'left' || position == 'right') && space[position] > textOutWidth) {
-              break;
-            }
-            else if ((position == 'top' || position == 'bottom') && space[position] > textOutHeight) {
-              break;
-            }
-          }
-        }
-		// Keep the next two lines intact as backups.
-		//var horiz = left + ((width - textOutWidth) * .5);
-        var horiz = left + (width * .5);
-        var vert = top + ((height - textOutHeight) * .5);
-        var points = new Array();
-        var textTop, textLeft, textRight, textBottom, textTopSpace, textBottomSpace, textLeftSpace, textRightSpace, textCenter;
-        switch(position) {
-
-         case 'top':
-            $text.css('margin-bottom', 0);
-            $box.css({top: (top - $text.outerHeight(true)), left: horiz});
-            textRightSpace = (winRight - opts.windowMargin) - ($text.offset().left + $text.btOuterWidth(true));
-            var xShift = 0;
-            if (textRightSpace < 0) {
-              // shift it left
-              $box.css('left', (numb($box.css('left')) + textRightSpace) + 'px');
-              xShift -= textRightSpace;
-            }
-            // we test left space second to ensure that left of box is visible
-            textLeftSpace = ($text.offset().left + numb($text.css('margin-left'))) - (scrollLeft + opts.windowMargin);
-            if (textLeftSpace < 0) {
-              // shift it right
-              $box.css('left', (numb($box.css('left')) - textLeftSpace) + 'px');
-              xShift += textLeftSpace;
-            }
-            textTop = $text.btPosition().top + numb($text.css('margin-top'));
-            textLeft = $text.btPosition().left + numb($text.css('margin-left'));
-            textRight = textLeft + $text.btOuterWidth();
-            textBottom = textTop + $text.outerHeight();
-            textCenter = {x: textLeft + $text.btOuterWidth(), y: textTop + $text.outerHeight()};
-            break;
-
-          case 'bottom':
-            // spike on top
-            $text.css('margin-top', 0);
-            $box.css({top: (top + height), left: horiz});
-            // move text up/down if extends out of window
-            textRightSpace = (winRight - opts.windowMargin) - ($text.offset().left + $text.btOuterWidth(true));
-            var xShift = 0;
-            if (textRightSpace < 0) {
-              // shift it left
-              $box.css('left', (numb($box.css('left')) + textRightSpace) + 'px');
-              xShift -= textRightSpace;
-            }
-            // we ensure left space second to ensure that left of box is visible
-            textLeftSpace = ($text.offset().left + numb($text.css('margin-left')))  - (scrollLeft + opts.windowMargin);
-            if (textLeftSpace < 0) {
-              // shift it right
-              $box.css('left', (numb($box.css('left')) - textLeftSpace) + 'px');
-              xShift += textLeftSpace;
-            }
-            textTop = $text.btPosition().top + numb($text.css('margin-top'));
-            textLeft = $text.btPosition().left + numb($text.css('margin-left'));
-            textRight = textLeft + $text.btOuterWidth();
-            textBottom = textTop + $text.outerHeight();
-            textCenter = {x: textLeft + $text.btOuterWidth(), y: textTop + $text.outerHeight()};
-            break;
-
-          case 'left':
-            $text.css('margin-right', 0);
-            $box.css({top: vert + 'px', left: (left - $text.btOuterWidth(true)) + 'px'});
-            textBottomSpace = (winBottom - opts.windowMargin) - ($text.offset().top + $text.outerHeight(true));
-            var yShift = 0;
-            if (textBottomSpace < 0) {
-              $box.css('top', (numb($box.css('top')) + textBottomSpace) + 'px');
-              yShift -= textBottomSpace;
-            }
-            textTopSpace = ($text.offset().top + numb($text.css('margin-top'))) - (scrollTop + opts.windowMargin);
-            if (textTopSpace < 0) {
-              $box.css('top', (numb($box.css('top')) - textTopSpace) + 'px');
-              yShift += textTopSpace;
-            }
-            textTop = $text.btPosition().top + numb($text.css('margin-top'));
-            textLeft = $text.btPosition().left + numb($text.css('margin-left'));
-            textRight = textLeft + $text.btOuterWidth();
-            textBottom = textTop + $text.outerHeight();
-            textCenter = {x: textLeft + $text.btOuterWidth(), y: textTop + $text.outerHeight()};
-            break;
-          case 'right':
-            $text.css('margin-left', 0);
-            $box.css({top: vert + 'px', left: ((left + width) - opts.overlap) + 'px'});
-            textBottomSpace = (winBottom - opts.windowMargin) - ($text.offset().top + $text.outerHeight(true));
-            var yShift = 0;
-            if (textBottomSpace < 0) {
-              $box.css('top', (numb($box.css('top')) + textBottomSpace) + 'px');
-              yShift -= textBottomSpace;
-            }
-            textTopSpace = ($text.offset().top + numb($text.css('margin-top'))) - (scrollTop + opts.windowMargin);
-            if (textTopSpace < 0) {
-              $box.css('top', (numb($box.css('top')) - textTopSpace) + 'px');
-              yShift += textTopSpace;
-            }
-            textTop = $text.btPosition().top + numb($text.css('margin-top'));
-            textLeft = $text.btPosition().left + numb($text.css('margin-left'));
-            textRight = textLeft + $text.btOuterWidth();
-            textBottom = textTop + $text.outerHeight();
-            textCenter = {x: textLeft + $text.btOuterWidth(), y: textTop + $text.outerHeight()};
-            break;
-        }
-        opts.preShow.apply(this, [$box[0]]);
-        $box.css({display:'none', visibility: 'visible'});
-        opts.showTip.apply(this, [$box[0]]);
-        if ((opts.ajaxPath != null && opts.ajaxCache == false) || ajaxTimeout) {
-          content = false;
-        }
-        if (opts.clickAnywhereToClose) {
-          jQuery.bt.vars.clickAnywhereStack.push(this);
-          $(document).click(jQuery.bt.docClick);
-        }
-        if (opts.closeWhenOthersOpen) {
-          jQuery.bt.vars.closeWhenOpenStack.push(this);
-        }
-        opts.postShow.apply(this, [$box[0]]);
-      };
-      this.btOff = function() {
-        var box = $(this).data('bt-box');
-        opts.preHide.apply(this, [box]);
-        var i = this;
-        i.btCleanup = function(){
-          var box = $(i).data('bt-box');
-          var contentOrig = $(i).data('bt-content-orig');
-          var overlay = $(i).data('bt-overlay');
-          if (typeof box == 'object') {
-            $(box).remove();
-            $(i).removeData('bt-box');
-          }
-          if (typeof contentOrig == 'object') {
-            var clones = $(contentOrig.original).data('bt-clones');
-            $(contentOrig).data('bt-clones', arrayRemove(clones, contentOrig.clone));        
-          }
-          if (typeof overlay == 'object') {
-            $(overlay).remove();
-            $(i).removeData('bt-overlay');
-          }
-          jQuery.bt.vars.clickAnywhereStack = arrayRemove(jQuery.bt.vars.clickAnywhereStack, i);
-          jQuery.bt.vars.closeWhenOpenStack = arrayRemove(jQuery.bt.vars.closeWhenOpenStack, i);
-          $(i).removeClass('bt-active ' + opts.activeClass);
-          opts.postHide.apply(i);
-        }
-        opts.hideTip.apply(this, [box, i.btCleanup]);
-      };
-      var refresh = this.btRefresh = function() {
-        this.btOff();
-        this.btOn();
-      };
-    });
-    function numb(num) {
-      return parseInt(num) || 0;
-    }; 
-    function arrayRemove(arr, elem) {
-      var x, newArr = new Array();
-      for (x in arr) {
-        if (arr[x] != elem) {
-          newArr.push(arr[x]);
-        }
-      }
-      return newArr;
-    };
-  };
-  jQuery.fn.btPosition = function() {
-    function num(elem, prop) {
-      return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
-    };
-    var left = 0, top = 0, results;
-    if ( this[0] ) {
-      var offsetParent = this.offsetParent(),
-      offset       = this.offset(),
-      parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
-      offset.top  -= num( this, 'marginTop' );
-      offset.left -= num( this, 'marginLeft' );
-      parentOffset.top  += num( offsetParent, 'borderTopWidth' );
-      parentOffset.left += num( offsetParent, 'borderLeftWidth' );
-      results = {
-        top:  offset.top  - parentOffset.top,
-        left: offset.left - parentOffset.left
-      };
-    }
-    return results;
-  };
-  jQuery.fn.btOuterWidth = function(margin) {
-      function num(elem, prop) {
-          return elem[0] && parseInt(jQuery.curCSS(elem[0], prop, true), 10) || 0;
-      };
-      return this["innerWidth"]()
-      + num(this, "borderLeftWidth")
-      + num(this, "borderRightWidth")
-      + (margin ? num(this, "marginLeft")
-      + num(this, "marginRight") : 0);
-  };
-  jQuery.fn.btOn = function() {
-    return this.each(function(index){
-      if (jQuery.isFunction(this.btOn)) {
-        this.btOn();
-      }
-    });
-  };
-  jQuery.fn.btOff = function() {
-    return this.each(function(index){
-      if (jQuery.isFunction(this.btOff)) {
-        this.btOff();
-      }
-    });
-  };
-  jQuery.bt.vars = {clickAnywhereStack: [], closeWhenOpenStack: []};
-  jQuery.bt.docClick = function(e) {
-    if (!e) {
-      var e = window.event;
-    };
-    if (!$(e.target).parents().andSelf().filter('.bt-wrapper, .bt-active').length && jQuery.bt.vars.clickAnywhereStack.length) {
-      $(jQuery.bt.vars.clickAnywhereStack).btOff();
-      $(document).unbind('click', jQuery.bt.docClick);
-    }
-  };
-  /* Defaults can be written for an entire page by redefining attributes:
-   * jQuery.bt.options.width = 400;
-   * Be sure to use *jQuery.bt.options* and not jQuery.bt.defaults when overriding
-   * Each of these options may also be overridden globally or at time of call.*/
-  jQuery.bt.defaults = {
-    trigger:             'hover',            // trigger to show/hide tip - hoverIntent becomes default if available
-    clickAnywhereToClose:true,               // clicking outside of the tip will close it 
-    closeWhenOthersOpen: true,               // tip will be closed before another opens
-    killTitle:           true,               // kill title tags to avoid double tooltips
-    textzIndex:          9999,               // z-index for the text
-    boxzIndex:           9998,               // z-index for the "talk" box (should always be less than textzIndex)
-    wrapperzIndex:       9997,
-    offsetParent:        null,               // DOM node to append the tooltip into. Must be positioned relative or absolute.
-    positions:           ['top', 'bottom'],  // preference of positions for tip (will use first with available space) 'top', 'bottom', 'left', 'right', 'most'
-    windowMargin:     10,                    // space (px) to leave between text box and browser edge
-    activeClass:      'bt-active',           // class added to TARGET element when its BeautyTip is active
-    contentSelector:  "$(this).attr('title')", // if there is no content argument, use this selector to retrieve the title
-                                             // a function which returns the content may also be passed here
-    ajaxPath:         null,                  // if using ajax request for content, this contains url and (opt) selector                                             
-    ajaxError:        '<strong>ERROR:</strong> <em>%error</em>',
-                                             // error text, use "%error" to insert error from server
-    ajaxLoading:     '<blink>Loading...</blink>',  // yes folks, it's the blink tag!
-    ajaxData:         {},                    // key/value pairs
-    ajaxType:         'GET',                 // 'GET' or 'POST'
-    ajaxCache:        true,                  // cache ajax results and do not send request to same url multiple times
-    ajaxOpts:         {},                    // any other ajax options - timeout, passwords, processing functions, etc...
-    preBuild:         function(){},          // function to run before popup is built
-    preShow:          function(box){},       // function to run before popup is displayed
-    showTip:          function(box){
-                        $(box).show();
-                      },
-    postShow:         function(box){},       // function to run after popup is built and displayed
-    
-    preHide:          function(box){},       // function to run before popup is removed
-    hideTip:          function(box, callback) {
-                        $(box).hide();
-                        callback();          // you MUST call "callback" at the end of your animations
-                      },
-    postHide:         function(){},          // function to run after popup is removed
-    hoverIntentOpts:  {                      // options for hoverIntent (if installed)
-                        interval: 100,       // http://cherne.net/brian/resources/jquery.hoverIntent.html
-                        timeout: 500
-                      }
-  };
-  jQuery.bt.options = {};
-})(jQuery);
+/* @name BeautyTips
+ * @desc a tooltips/baloon-help plugin for jQuery
+ * @author Jeff Robbins - Lullabot - http://www.lullabot.com
+ * @version 0.9.5 release candidate 1  (5/20/2009) */
+ 
+jQuery.bt = {version: '0.9.5-rc1'};
+ 
+/* @type jQuery
+ * @cat Plugins/bt
+ * @requires jQuery v1.2+
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ * Encourage development. If you use BeautyTips for anything cool 
+ * or on a site that people have heard of, please drop me a note.
+ * - jeff ^at lullabot > com
+ * No guarantees, warranties, or promises of any kind */
+
+;(function($) { 
+  /* @credit Inspired by Karl Swedberg's ClueTip
+   * (http://plugins.learningjquery.com/cluetip/), which in turn was inspired
+   * by Cody Lindley's jTip (http://www.codylindley.com)
+   * Usage:
+   * The function can be called in a number of ways.
+   * $(selector).bt();
+   * $(selector).bt('Content text');
+   * $(selector).bt('Content text', {option1: value, option2: value});
+   * $(selector).bt({option1: value, option2: value});
+   * For more/better documentation and lots of examples, visit the demo page included with the distribution */ 
+
+  jQuery.fn.bt = function(content, options) {
+    if (typeof content != 'string') {
+      var contentSelect = true;
+      options = content;
+      content = false;
+    }
+    else {
+      var contentSelect = false;
+    }
+    if (jQuery.fn.hoverIntent && jQuery.bt.defaults.trigger == 'hover') {
+      jQuery.bt.defaults.trigger = 'hoverIntent';
+    }
+    return this.each(function(index) {
+      var opts = jQuery.extend(false, jQuery.bt.defaults, jQuery.bt.options, options);
+      opts.overlap = -10;
+      var ajaxTimeout = false;
+      if (opts.killTitle) {
+        $(this).find('[title]').andSelf().each(function() {
+          if (!$(this).attr('bt-xTitle')) {
+            $(this).attr('bt-xTitle', $(this).attr('title')).attr('title', '');
+          }
+        });
+      }
+      if (typeof opts.trigger == 'string') {
+        opts.trigger = [opts.trigger];
+      }
+      if (opts.trigger[0] == 'hoverIntent') {
+        var hoverOpts = jQuery.extend(opts.hoverIntentOpts, {
+          over: function() {
+            this.btOn();
+          },
+          out: function() {
+            this.btOff();
+          }});
+        $(this).hoverIntent(hoverOpts);
+      }
+      else if (opts.trigger[0] == 'hover') {
+        $(this).hover(
+          function() {
+            this.btOn();
+          },
+          function() {
+            this.btOff();
+          }
+        );
+      }
+      else if (opts.trigger[0] == 'now') {
+        if ($(this).hasClass('bt-active')) {
+          this.btOff();
+        }
+        else {
+          this.btOn();
+        }
+      }
+      else if (opts.trigger[0] == 'none') {
+      }
+      else if (opts.trigger.length > 1 && opts.trigger[0] != opts.trigger[1]) {
+        $(this)
+          .bind(opts.trigger[0], function() {
+            this.btOn();
+          })
+          .bind(opts.trigger[1], function() {
+            this.btOff();
+          });
+      }
+      else {
+        $(this).bind(opts.trigger[0], function() {
+          if ($(this).hasClass('bt-active')) {
+            this.btOff();
+          }
+          else {
+            this.btOn();
+          }
+        });
+      }
+      this.btOn = function () {
+        if (typeof $(this).data('bt-box') == 'object') {
+          this.btOff();
+        }
+        opts.preBuild.apply(this);
+        $(jQuery.bt.vars.closeWhenOpenStack).btOff();
+        $(this).addClass('bt-active ' + opts.activeClass);
+        if (contentSelect && opts.ajaxPath == null) {
+          if (opts.killTitle) {
+            $(this).attr('title', $(this).attr('bt-xTitle'));
+          }
+          content = $.isFunction(opts.contentSelector) ? opts.contentSelector.apply(this) : eval(opts.contentSelector);
+          if (opts.killTitle) {
+            $(this).attr('title', '');
+          }
+        }
+        if (opts.ajaxPath != null && content == false) {
+          if (typeof opts.ajaxPath == 'object') {
+            var url = eval(opts.ajaxPath[0]);
+            url += opts.ajaxPath[1] ? ' ' + opts.ajaxPath[1] : '';
+          }
+          else {
+            var url = opts.ajaxPath;
+          }
+          var off = url.indexOf(" ");
+          if ( off >= 0 ) {
+            var selector = url.slice(off, url.length);
+            url = url.slice(0, off);
+          }
+          var cacheData = opts.ajaxCache ? $(document.body).data('btCache-' + url.replace(/\./g, '')) : null;
+          if (typeof cacheData == 'string') {
+            content = selector ? $("<div/>").append(cacheData.replace(/<script(.|\s)*?\/script>/g, "")).find(selector) : cacheData;
+          }
+          else {
+            var target = this;
+            var ajaxOpts = jQuery.extend(false,
+            {
+              type: opts.ajaxType,
+              data: opts.ajaxData,
+              cache: opts.ajaxCache,
+              url: url,
+              complete: function(XMLHttpRequest, textStatus) {
+                if (textStatus == 'success' || textStatus == 'notmodified') {
+                  if (opts.ajaxCache) {
+                    $(document.body).data('btCache-' + url.replace(/\./g, ''), XMLHttpRequest.responseText);
+                  }
+                  ajaxTimeout = false;
+                  content = selector ?
+                    $("<div/>")
+                      .append(XMLHttpRequest.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+                      .find(selector) :
+                    XMLHttpRequest.responseText;
+                }
+                else {
+                  if (textStatus == 'timeout') {
+                    ajaxTimeout = true;
+                  }
+                  content = opts.ajaxError.replace(/%error/g, XMLHttpRequest.statusText);
+                }
+                if ($(target).hasClass('bt-active')) {
+                  target.btOn();
+                }
+              }
+            }, opts.ajaxOpts);
+            jQuery.ajax(ajaxOpts);
+            content = opts.ajaxLoading;
+          }
+        }
+        if (opts.offsetParent){
+          var offsetParent = $(opts.offsetParent);
+          var offsetParentPos = offsetParent.offset();
+          var pos = $(this).offset();
+          var top = numb(pos.top) - numb(offsetParentPos.top) + numb($(this).css('margin-top'));
+          var left = numb(pos.left) - numb(offsetParentPos.left) + numb($(this).css('margin-left'));
+        }
+        else {
+          var offsetParent = ($(this).css('position') == 'absolute') ? $(this).parents().eq(0).offsetParent() : $(this).offsetParent();
+          var pos = $(this).btPosition();
+          var top = numb(pos.top) + numb($(this).css('margin-top'));
+          var left = numb(pos.left) + numb($(this).css('margin-left'));
+        }
+        var width = $(this).btOuterWidth();
+        var height = $(this).outerHeight();
+        if (typeof content == 'object') {
+          var original = content;
+          var clone = $(original).clone(true).show();
+          var origClones = $(original).data('bt-clones') || [];
+          origClones.push(clone);
+          $(original).data('bt-clones', origClones);
+          $(clone).data('bt-orig', original);
+          $(this).data('bt-content-orig', {original: original, clone: clone});
+          content = clone;
+        }
+        if (typeof content == 'null' || content == '') {
+          return;
+        }
+        var $text = $('<div class="bt-content"></div>').append(content).css({position: 'absolute', zIndex: opts.textzIndex, left: 0, top: 0});
+        var $box = $('<div class="bt-wrapper"></div>').append($text).css({position: 'absolute', zIndex: opts.wrapperzIndex, visibility:'hidden'}).appendTo(offsetParent);
+        $(this).data('bt-box', $box);
+        var scrollTop = numb($(document).scrollTop());
+        var scrollLeft = numb($(document).scrollLeft());
+        var docWidth = numb($(window).width());
+        var docHeight = numb($(window).height());
+        var winRight = scrollLeft + docWidth;
+        var winBottom = scrollTop + docHeight;
+        var space = new Object();
+        var thisOffset = $(this).offset();
+        space.top = thisOffset.top - scrollTop;
+        space.bottom = docHeight - ((thisOffset + height) - scrollTop);
+        space.left = thisOffset.left - scrollLeft;
+        space.right = docWidth - ((thisOffset.left + width) - scrollLeft);
+        var textOutHeight = numb($text.outerHeight());
+        var textOutWidth = numb($text.btOuterWidth());
+        if (opts.positions.constructor == String) {
+          opts.positions = opts.positions.replace(/ /, '').split(',');
+        }
+        if (opts.positions[0] == 'most') {
+          var position = 'top';
+          for (var pig in space) {  //      <-------  pigs in space!
+            position = space[pig] > space[position] ? pig : position;
+          }
+        }
+        else {
+          for (var x in opts.positions) {
+            var position = opts.positions[x];
+            if ((position == 'left' || position == 'right') && space[position] > textOutWidth) {
+              break;
+            }
+            else if ((position == 'top' || position == 'bottom') && space[position] > textOutHeight) {
+              break;
+            }
+          }
+        }
+		// Keep the next two lines intact as backups.
+		//var horiz = left + ((width - textOutWidth) * .5);
+        var horiz = left + (width * .5);
+        var vert = top + ((height - textOutHeight) * .5);
+        var points = new Array();
+        var textTop, textLeft, textRight, textBottom, textTopSpace, textBottomSpace, textLeftSpace, textRightSpace, textCenter;
+        switch(position) {
+
+         case 'top':
+            $text.css('margin-bottom', 0);
+            $box.css({top: (top - $text.outerHeight(true)), left: horiz});
+            textRightSpace = (winRight - opts.windowMargin) - ($text.offset().left + $text.btOuterWidth(true));
+            var xShift = 0;
+            if (textRightSpace < 0) {
+              // shift it left
+              $box.css('left', (numb($box.css('left')) + textRightSpace) + 'px');
+              xShift -= textRightSpace;
+            }
+            // we test left space second to ensure that left of box is visible
+            textLeftSpace = ($text.offset().left + numb($text.css('margin-left'))) - (scrollLeft + opts.windowMargin);
+            if (textLeftSpace < 0) {
+              // shift it right
+              $box.css('left', (numb($box.css('left')) - textLeftSpace) + 'px');
+              xShift += textLeftSpace;
+            }
+            textTop = $text.btPosition().top + numb($text.css('margin-top'));
+            textLeft = $text.btPosition().left + numb($text.css('margin-left'));
+            textRight = textLeft + $text.btOuterWidth();
+            textBottom = textTop + $text.outerHeight();
+            textCenter = {x: textLeft + $text.btOuterWidth(), y: textTop + $text.outerHeight()};
+            break;
+
+          case 'bottom':
+            // spike on top
+            $text.css('margin-top', 0);
+            $box.css({top: (top + height), left: horiz});
+            // move text up/down if extends out of window
+            textRightSpace = (winRight - opts.windowMargin) - ($text.offset().left + $text.btOuterWidth(true));
+            var xShift = 0;
+            if (textRightSpace < 0) {
+              // shift it left
+              $box.css('left', (numb($box.css('left')) + textRightSpace) + 'px');
+              xShift -= textRightSpace;
+            }
+            // we ensure left space second to ensure that left of box is visible
+            textLeftSpace = ($text.offset().left + numb($text.css('margin-left')))  - (scrollLeft + opts.windowMargin);
+            if (textLeftSpace < 0) {
+              // shift it right
+              $box.css('left', (numb($box.css('left')) - textLeftSpace) + 'px');
+              xShift += textLeftSpace;
+            }
+            textTop = $text.btPosition().top + numb($text.css('margin-top'));
+            textLeft = $text.btPosition().left + numb($text.css('margin-left'));
+            textRight = textLeft + $text.btOuterWidth();
+            textBottom = textTop + $text.outerHeight();
+            textCenter = {x: textLeft + $text.btOuterWidth(), y: textTop + $text.outerHeight()};
+            break;
+
+          case 'left':
+            $text.css('margin-right', 0);
+            $box.css({top: vert + 'px', left: (left - $text.btOuterWidth(true)) + 'px'});
+            textBottomSpace = (winBottom - opts.windowMargin) - ($text.offset().top + $text.outerHeight(true));
+            var yShift = 0;
+            if (textBottomSpace < 0) {
+              $box.css('top', (numb($box.css('top')) + textBottomSpace) + 'px');
+              yShift -= textBottomSpace;
+            }
+            textTopSpace = ($text.offset().top + numb($text.css('margin-top'))) - (scrollTop + opts.windowMargin);
+            if (textTopSpace < 0) {
+              $box.css('top', (numb($box.css('top')) - textTopSpace) + 'px');
+              yShift += textTopSpace;
+            }
+            textTop = $text.btPosition().top + numb($text.css('margin-top'));
+            textLeft = $text.btPosition().left + numb($text.css('margin-left'));
+            textRight = textLeft + $text.btOuterWidth();
+            textBottom = textTop + $text.outerHeight();
+            textCenter = {x: textLeft + $text.btOuterWidth(), y: textTop + $text.outerHeight()};
+            break;
+          case 'right':
+            $text.css('margin-left', 0);
+            $box.css({top: vert + 'px', left: ((left + width) - opts.overlap) + 'px'});
+            textBottomSpace = (winBottom - opts.windowMargin) - ($text.offset().top + $text.outerHeight(true));
+            var yShift = 0;
+            if (textBottomSpace < 0) {
+              $box.css('top', (numb($box.css('top')) + textBottomSpace) + 'px');
+              yShift -= textBottomSpace;
+            }
+            textTopSpace = ($text.offset().top + numb($text.css('margin-top'))) - (scrollTop + opts.windowMargin);
+            if (textTopSpace < 0) {
+              $box.css('top', (numb($box.css('top')) - textTopSpace) + 'px');
+              yShift += textTopSpace;
+            }
+            textTop = $text.btPosition().top + numb($text.css('margin-top'));
+            textLeft = $text.btPosition().left + numb($text.css('margin-left'));
+            textRight = textLeft + $text.btOuterWidth();
+            textBottom = textTop + $text.outerHeight();
+            textCenter = {x: textLeft + $text.btOuterWidth(), y: textTop + $text.outerHeight()};
+            break;
+        }
+        opts.preShow.apply(this, [$box[0]]);
+        $box.css({display:'none', visibility: 'visible'});
+        opts.showTip.apply(this, [$box[0]]);
+        if ((opts.ajaxPath != null && opts.ajaxCache == false) || ajaxTimeout) {
+          content = false;
+        }
+        if (opts.clickAnywhereToClose) {
+          jQuery.bt.vars.clickAnywhereStack.push(this);
+          $(document).click(jQuery.bt.docClick);
+        }
+        if (opts.closeWhenOthersOpen) {
+          jQuery.bt.vars.closeWhenOpenStack.push(this);
+        }
+        opts.postShow.apply(this, [$box[0]]);
+      };
+      this.btOff = function() {
+        var box = $(this).data('bt-box');
+        opts.preHide.apply(this, [box]);
+        var i = this;
+        i.btCleanup = function(){
+          var box = $(i).data('bt-box');
+          var contentOrig = $(i).data('bt-content-orig');
+          var overlay = $(i).data('bt-overlay');
+          if (typeof box == 'object') {
+            $(box).remove();
+            $(i).removeData('bt-box');
+          }
+          if (typeof contentOrig == 'object') {
+            var clones = $(contentOrig.original).data('bt-clones');
+            $(contentOrig).data('bt-clones', arrayRemove(clones, contentOrig.clone));        
+          }
+          if (typeof overlay == 'object') {
+            $(overlay).remove();
+            $(i).removeData('bt-overlay');
+          }
+          jQuery.bt.vars.clickAnywhereStack = arrayRemove(jQuery.bt.vars.clickAnywhereStack, i);
+          jQuery.bt.vars.closeWhenOpenStack = arrayRemove(jQuery.bt.vars.closeWhenOpenStack, i);
+          $(i).removeClass('bt-active ' + opts.activeClass);
+          opts.postHide.apply(i);
+        }
+        opts.hideTip.apply(this, [box, i.btCleanup]);
+      };
+      var refresh = this.btRefresh = function() {
+        this.btOff();
+        this.btOn();
+      };
+    });
+    function numb(num) {
+      return parseInt(num) || 0;
+    }; 
+    function arrayRemove(arr, elem) {
+      var x, newArr = new Array();
+      for (x in arr) {
+        if (arr[x] != elem) {
+          newArr.push(arr[x]);
+        }
+      }
+      return newArr;
+    };
+  };
+  jQuery.fn.btPosition = function() {
+    function num(elem, prop) {
+      return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
+    };
+    var left = 0, top = 0, results;
+    if ( this[0] ) {
+      var offsetParent = this.offsetParent(),
+      offset       = this.offset(),
+      parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
+      offset.top  -= num( this, 'marginTop' );
+      offset.left -= num( this, 'marginLeft' );
+      parentOffset.top  += num( offsetParent, 'borderTopWidth' );
+      parentOffset.left += num( offsetParent, 'borderLeftWidth' );
+      results = {
+        top:  offset.top  - parentOffset.top,
+        left: offset.left - parentOffset.left
+      };
+    }
+    return results;
+  };
+  jQuery.fn.btOuterWidth = function(margin) {
+      function num(elem, prop) {
+          return elem[0] && parseInt(jQuery.curCSS(elem[0], prop, true), 10) || 0;
+      };
+      return this["innerWidth"]()
+      + num(this, "borderLeftWidth")
+      + num(this, "borderRightWidth")
+      + (margin ? num(this, "marginLeft")
+      + num(this, "marginRight") : 0);
+  };
+  jQuery.fn.btOn = function() {
+    return this.each(function(index){
+      if (jQuery.isFunction(this.btOn)) {
+        this.btOn();
+      }
+    });
+  };
+  jQuery.fn.btOff = function() {
+    return this.each(function(index){
+      if (jQuery.isFunction(this.btOff)) {
+        this.btOff();
+      }
+    });
+  };
+  jQuery.bt.vars = {clickAnywhereStack: [], closeWhenOpenStack: []};
+  jQuery.bt.docClick = function(e) {
+    if (!e) {
+      var e = window.event;
+    };
+    if (!$(e.target).parents().andSelf().filter('.bt-wrapper, .bt-active').length && jQuery.bt.vars.clickAnywhereStack.length) {
+      $(jQuery.bt.vars.clickAnywhereStack).btOff();
+      $(document).unbind('click', jQuery.bt.docClick);
+    }
+  };
+  /* Defaults can be written for an entire page by redefining attributes:
+   * jQuery.bt.options.width = 400;
+   * Be sure to use *jQuery.bt.options* and not jQuery.bt.defaults when overriding
+   * Each of these options may also be overridden globally or at time of call.*/
+  jQuery.bt.defaults = {
+    trigger:             'hover',            // trigger to show/hide tip - hoverIntent becomes default if available
+    clickAnywhereToClose:true,               // clicking outside of the tip will close it 
+    closeWhenOthersOpen: true,               // tip will be closed before another opens
+    killTitle:           true,               // kill title tags to avoid double tooltips
+    textzIndex:          9999,               // z-index for the text
+    boxzIndex:           9998,               // z-index for the "talk" box (should always be less than textzIndex)
+    wrapperzIndex:       9997,
+    offsetParent:        null,               // DOM node to append the tooltip into. Must be positioned relative or absolute.
+    positions:           ['top', 'bottom'],  // preference of positions for tip (will use first with available space) 'top', 'bottom', 'left', 'right', 'most'
+    windowMargin:     10,                    // space (px) to leave between text box and browser edge
+    activeClass:      'bt-active',           // class added to TARGET element when its BeautyTip is active
+    contentSelector:  "$(this).attr('title')", // if there is no content argument, use this selector to retrieve the title
+                                             // a function which returns the content may also be passed here
+    ajaxPath:         null,                  // if using ajax request for content, this contains url and (opt) selector                                             
+    ajaxError:        '<strong>ERROR:</strong> <em>%error</em>',
+                                             // error text, use "%error" to insert error from server
+    ajaxLoading:     '<blink>Loading...</blink>',  // yes folks, it's the blink tag!
+    ajaxData:         {},                    // key/value pairs
+    ajaxType:         'GET',                 // 'GET' or 'POST'
+    ajaxCache:        true,                  // cache ajax results and do not send request to same url multiple times
+    ajaxOpts:         {},                    // any other ajax options - timeout, passwords, processing functions, etc...
+    preBuild:         function(){},          // function to run before popup is built
+    preShow:          function(box){},       // function to run before popup is displayed
+    showTip:          function(box){
+                        $(box).show();
+                      },
+    postShow:         function(box){},       // function to run after popup is built and displayed
+    
+    preHide:          function(box){},       // function to run before popup is removed
+    hideTip:          function(box, callback) {
+                        $(box).hide();
+                        callback();          // you MUST call "callback" at the end of your animations
+                      },
+    postHide:         function(){},          // function to run after popup is removed
+    hoverIntentOpts:  {                      // options for hoverIntent (if installed)
+                        interval: 100,       // http://cherne.net/brian/resources/jquery.hoverIntent.html
+                        timeout: 500
+                      }
+  };
+  jQuery.bt.options = {};
+})(jQuery);

+ 6 - 3
index.php

@@ -185,7 +185,7 @@ function smf_main()
 	// Load the current theme.  (note that ?theme=1 will also work, may be used for guest theming.)
 	else
 		loadTheme();
-	
+
 	// Check if the user should be disallowed access.
 	is_not_banned();
 
@@ -234,8 +234,11 @@ function smf_main()
 		// Action and board are both empty... BoardIndex!
 		if (empty($board) && empty($topic))
 		{
-			require_once($sourcedir . '/BoardIndex.php');
-			return 'BoardIndex';
+			$call = 'BoardIndex';
+			call_integration_hook('integrate_default_action', $call);
+			require_once($sourcedir . '/' . $call . '.php');
+
+			return $call;
 		}
 		// Topic is empty, and action is empty.... MessageIndex!
 		elseif (empty($topic))