author | dan@fuhry |
Wed, 13 Jun 2007 22:33:54 -0400 | |
changeset 0 | 0417a5a0c7be |
child 1 | 6f8b7c6fac02 |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/bbcode.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,98 @@ +<?php +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * bbcode.php - BBcode-to-HTML renderer + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +function str_replace_once($needle1, $needle2, $haystack) +{ + $len_h = strlen($haystack); + $len_1 = strlen($needle1); + $len_2 = strlen($needle2); + if ( $len_h < $len_1 ) + return $haystack; + if ( $needle1 == $haystack ) + return $needle1; + for ( $i = 0; $i < $len_h; $i++ ) + { + if ( substr($haystack, $i, $len_1) == $needle1 ) + { + $haystack = substr($haystack, 0, $i) . + $needle2 . + substr($haystack, $i + $len_1); + return $haystack; + } + } +} + +function render_bbcode($text, $bbcode_uid) +{ + // First things first, strip out all [code] sections + $text = decir_bbcode_strip_code($text, $bbcode_uid, $_code); + + // Bold text + $text = preg_replace("/\[b:$bbcode_uid\](.*?)\[\/b:$bbcode_uid\]/is", '<b>\\1</b>', $text); + + // Italicized text + $text = preg_replace("/\[i:$bbcode_uid\](.*?)\[\/i:$bbcode_uid\]/is", '<i>\\1</i>', $text); + + // Uunderlined text + $text = preg_replace("/\[u:$bbcode_uid\](.*?)\[\/u:$bbcode_uid\]/is", '<u>\\1</u>', $text); + + // Colored text + $text = preg_replace("/\[color=\#([A-F0-9]*){3,6}:$bbcode_uid\](.*?)\[\/color:$bbcode_uid\]/is", '<span style="color: #\\1">\\2</span>', $text); + + // Quotes + $text = preg_replace("/\[quote:$bbcode_uid\](.*?)\[\/quote:$bbcode_uid\]/is", '<blockquote>\\1</blockquote>', $text); + + // Newlines + $text = str_replace("\n", "<br />\n", $text); + + // Restore [code] blocks + $text = decir_bbcode_restore_code($text, $bbcode_uid, $_code); + + // Code + $text = preg_replace("/\[code:$bbcode_uid\](.*?)\[\/code:$bbcode_uid\]/is", '<pre>\\1</pre>', $text); + + return $text; +} + +function decir_bbcode_strip_code($text, $uid, &$code_secs) +{ + preg_match_all("/\[code:$uid\](.*?)\[\/code:$uid\]/is", $text, $matches); + foreach ( $matches[1] as $i => $m ) + { + $text = str_replace_once($m, "{CODE_SECTION|$i:$uid}", $text); + $code_secs[$i] = $m; + } + return $text; +} + +function decir_bbcode_restore_code($text, $uid, $code_secs) +{ + foreach ( $code_secs as $i => $code ) + { + $text = str_replace("{CODE_SECTION|$i:$uid}", $code, $text); + } + return $text; +} + +function bbcode_strip_uid($bbcode, $uid) +{ + // BBcode tags with attributes + $bbcode = preg_replace("/\[([a-z]+?):{$uid}=([^\]]+?)\](.*?)\[\/\\1:{$uid}\]/is", '[\\1=\\2]\\3[/\\1]', $bbcode); + + // BBcode tags without attributes + $bbcode = preg_replace("/\[([a-z]+?):{$uid}\](.*?)\[\/\\1:{$uid}\]/is", '[\\1]\\2[/\\1]', $bbcode); + + return $bbcode; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/common.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,45 @@ +<?php +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * common.php - Loader and common basic functions + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +if(!defined('DECIR_ROOT')) +{ + $_GET['title'] = 'null'; + header('HTTP/1.1 403 Forbidden'); + require('../includes/common.php'); + die_friendly('Access denied', '<p>This script cannot be run outside of Enano.</p>'); +} + +require('constants.php'); + +$html = ' <!-- Decir\'s updated namespace extractor function --> + <script type="text/javascript"> + function strToPageID(string) + { + var ret; + for(var i in namespace_list) + if(namespace_list[i] != \'\') + if(namespace_list[i] == string.substr(0, namespace_list[i].length)) + ret = [string.substr(namespace_list[i].length), i]; + + if ( ret ) + return ret; + + return [string, \'Article\']; + } + </script> + '; + +$template->add_header($html); + +?>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/constants.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,25 @@ +<?php +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * constants.php - important values + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +if(!defined('ENANO_DECIR_VERSION')) + die('Hacking attempt'); + +define('FORUM_FORUM', 1); +define('FORUM_CATEGORY', 2); + +define('TOPIC_NORMAL', 1); +define('TOPIC_STICKY', 4); +define('TOPIC_ANNOUNCE', 5); + +?>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/forum_index.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,87 @@ +<?php +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * install.php - Database installation wizard + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +require('common.php'); + +$template->header(); + +// Not much left now but to just do it... +$q = $db->sql_query('SELECT f.forum_id,f.forum_type,f.forum_name,f.forum_desc,f.num_topics,f.num_posts, + p.post_id,t.topic_id,t.topic_title,u.username,u.user_level,p.timestamp FROM '.table_prefix.'decir_forums AS f + LEFT JOIN '.table_prefix.'decir_topics AS t + ON (t.forum_id=f.forum_id) + LEFT JOIN '.table_prefix.'decir_posts AS p + ON (p.topic_id=t.topic_id) + LEFT JOIN '.table_prefix.'users AS u + ON (u.user_id=f.last_post_user OR f.last_post_user IS NULL) + WHERE ( t.topic_id=f.last_post_topic AND p.post_id=f.last_post_id ) OR ( f.last_post_topic IS NULL AND f.last_post_id IS NULL ) + GROUP BY f.parent,f.forum_id + ORDER BY f.forum_order;'); + +if (!$q) + $db->_die(); + +echo '<div class="tblholder"> + <table border="0" cellspacing="1" cellpadding="4"> + <tr> + <th colspan="2">Forum</th> + <th style="max-width: 50px;">Topics</th> + <th style="max-width: 50px;">Posts</th> + <th>Last post</th> + </tr>'; +$cat_open = false; +if ( $row = $db->fetchrow($q) ) +{ + do { + switch ( $row['forum_type'] ) + { + case FORUM_FORUM: + $color = ( $row['user_level'] >= USER_LEVEL_ADMIN ) ? 'AA0000' : ( ( $row['user_level'] >= USER_LEVEL_MOD ) ? '00AA00' : '0000AA' ); + // Forum + echo '<tr><td class="row3" style="text-align: center;"><icon></td><td class="row2"><b><a href="' . makeUrlNS('DecirForum', $row['forum_id']) . '">' + . $row['forum_name'] . '</a></b><br />' . $row['forum_desc'].'</td> + <td class="row3" style="text-align: center;">' . $row['num_topics'] . '</td> + <td class="row3" style="text-align: center;">' . $row['num_posts'] . '</td> + <td class="row1" style="text-align: center;"> + <small> + <a href="' . makeUrlNS('DecirTopic', $row['topic_id']) . '#post' . $row['post_id'] . '">' . $row['topic_title'] . '</a><br /> + ' . date('d M Y h:i a', $row['timestamp']) . '<br /> + by <b><a style="color: #' . $color . '" href="' . makeUrlNS('User', $row['username']) . '">' . $row['username'] . '</a></b> + </small> + </td> + </tr>'; + break; + case FORUM_CATEGORY: + // Category + if ( $cat_open ) + echo '</tbody>'; + echo '<tr><td class="row1" colspan="2"><h3 style="margin: 0; padding: 0;">' . $row['forum_name'] . '</h3></td><td class="row2" colspan="3"></td></tr> + <tbody id="forum_cat_' . $row['forum_id'] . '">'; + $cat_open = true; + break; + } + } while ( $row = $db->fetchrow($q) ); +} +else +{ + echo '<td class="row1" colspan="4">This board has no forums.</td>'; +} +if ( $cat_open ) + echo '</tbody>'; +echo '</table> + </div>'; + +$template->footer(); + +?>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/index.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,13 @@ +<?php + +$_GET['title'] = 'Enano:Access_denied'; +require('../includes/common.php'); +header('HTTP/1.1 403 Forbidden'); +$session->perms['edit_page'] = AUTH_DENY; +$session->perms['view_source'] = AUTH_DENY; +$template->tpl_strings['PAGE_NAME'] = 'Access denied'; + +$template->header(); +echo '<p>The administrator has flagged the page "' . $_SERVER['REQUEST_URI'] . '" so that it cannot be accessed from the web. Perhaps this is because this is a cache or includes directory and only needs to be accessed by scripts.</p><p>HTTP error: 403 Forbidden</p>'; +$template->footer(); +$db->close();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/install.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,61 @@ +<?php +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * install.php - Database installation wizard + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +require('common.php'); + +if ( $session->auth_level < USER_LEVEL_ADMIN ) +{ + header('Location: ' . makeUrlComplete('Special', 'Login/' . $paths->page, 'level=9', true)); + exit; +} + +if ( $v = getConfig('decir_version') ) +{ + $mode = 'upgrade'; + $upg_ver = $v; +} +else +{ + $mode = 'install'; +} + +$page = ( isset($_POST['step']) && in_array($_POST['step'], array('welcome', 'install', 'finish')) ) ? $_POST['step'] : 'welcome'; + +$template->header(); + +switch($page) +{ + case 'welcome': + ?> + <h3>Welcome to Decir, the Enano bulletin board suite.</h3> + <p>Before you can use your forum, we'll need to run a few database queries to get the forum set up.</p> + <form action="<?php echo makeUrl($paths->page); ?>" method="post"> + <input type="hidden" name="step" value="install" /> + <input type="submit" value="Continue" style="display: block; margin: 0 auto;" /> + </form> + <?php + break; + case 'install': + setConfig('decir_version', ENANO_DECIR_VERSION); + ?> + <form action="<?php echo makeUrl($paths->page); ?>" method="post"> + <input type="hidden" name="step" value="finish" /> + <input type="submit" name="do_install_finish" value="Next >" style="display: block; margin: 0 auto;" /> + </form> + <?php + break; +} + +$template->footer(); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/install.sql Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,51 @@ +CREATE TABLE decir_forums( + forum_id int(12) unsigned NOT NULL auto_increment, + forum_type tinyint(2) unsigned NOT NULL DEFAULT 1, + forum_name varchar(255) NOT NULL, + forum_desc text NOT NULL, + parent int(12) unsigned NOT NULL DEFAULT 0, + forum_order int(12) unsigned NOT NULL DEFAULT 1, + last_post_id int(18) unsigned, + last_post_topic int(12) unsigned, + last_post_user int(12) unsigned, + num_topics int(15) unsigned, + num_posts int(18) unsigned, + forum_extra text, + PRIMARY KEY ( forum_id ) +); +CREATE TABLE decir_topics( + topic_id int(15) unsigned NOT NULL auto_increment, + forum_id int(12) unsigned NOT NULL, + topic_title varchar(255) NOT NULL, + topic_icon tinyint(3) unsigned NOT NULL, + topic_starter int(12) unsigned NOT NULL, + topic_type tinyint(2) unsigned NOT NULL DEFAULT 1, + topic_locked tinyint(1) unsigned NOT NULL DEFAULT 0, + topic_moved tinyint(1) unsigned NOT NULL DEFAULT 0, + timestamp int(11) unsigned NOT NULL, + PRIMARY KEY ( topic_id ) +); +CREATE TABLE decir_posts( + post_id bigint(18) unsigned NOT NULL auto_increment, + topic_id bigint(15) unsigned NOT NULL, + poster_id int(12) unsigned NOT NULL, + poster_name varchar(255) NOT NULL, + timestamp int(11) unsigned NOT NULL, + last_edited_by int(12) unsigned DEFAULT NULL, + edit_count int(5) unsigned, + edit_reason varchar(255), + PRIMARY KEY ( post_id ) +); +CREATE TABLE decir_posts_text( + post_id bigint(18) unsigned NOT NULL, + post_text longtext NOT NULL, + bbcode_uid varchar(10) NOT NULL, + PRIMARY KEY ( post_id ) +); +CREATE TABLE decir_hits( + hit_id bigint(21) unsigned NOT NULL auto_increment, + user_id int(12) unsigned NOT NULL DEFAULT 1, + topic_id bigint(15) unsigned NOT NULL, + timestamp int(11) unsigned NOT NULL, + PRIMARY KEY ( hit_id ) +);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/js/bbcedit.css Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,54 @@ +input.bbcbutton { + font-family: arial, sans-serif; + font-size: 8pt; + border: 1px solid #000000; + padding: 2px; + margin-right: 2px; + margin-bottom: 2px; +} + +fieldset { + border-color: #000000; + border-style: solid; + border-width: 1px; +} + +fieldset legend { + font-size: 8pt; + font-family: arial, sans-serif; +} + +input.clicksmiley { + padding-right: 1px; + padding-bottom: 1px; + border-width: 0; + padding: 0; + background-color: transparent; +} + +input.clicksmiley:active { + padding-right: 0px; + padding-bottom: 0px; + padding-left: 1px; + padding-top: 1px; +} + +.sizepick_td { + font-family: arial, sans-serif; + border: 1px solid #CCCCCC; + padding: 3px; + max-width: 75px; + clip: rect(0px,75px,auto,0px); + overflow: hidden; + white-space: nowrap; + cursor: pointer; +} +pre.code { + border: 1px dotted #cccccc; + background-color: #F0F0F0; + padding: 10px; + white-space: nowrap; + color: #00AA00; + font-family: deja vu sans mono, courier new, monospace; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/js/bbcedit.js Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,632 @@ +// Client detection from MediaWiki +var clientPC = navigator.userAgent.toLowerCase(); // Get client info +var is_gecko = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1) + && (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1)); +var is_safari = ((clientPC.indexOf('applewebkit')!=-1) && (clientPC.indexOf('spoofer')==-1)); +var is_khtml = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled )); +if (clientPC.indexOf('opera') != -1) { + var is_opera = true; + var is_opera_preseven = (window.opera && !document.childNodes); + var is_opera_seven = (window.opera && document.childNodes); +} + +var $_GET=new Object(); +var aParams=document.location.search.substr(1).split('&'); +for ( i = 0; i < aParams.length; i++ ) { + var aParam=aParams[i].split('='); + var sParamName=aParam[0]; + var sParamValue=aParam[1]; + $_GET[sParamName]=sParamValue; +} + +// List of BBcode buttons + +var buttons = [ + { + 'start' : '[b]', + 'end' : '[/b]', + 'desc' : 'Bold', + 'style' : { 'fontWeight' : 'bold' } + }, + { + 'start' : '[i]', + 'end' : '[/i]', + 'desc' : 'Italics', + 'style' : { 'fontStyle' : 'italic' } + }, + { + 'start' : '[u]', + 'end' : '[/u]', + 'desc' : 'Underline', + 'style' : { 'textDecoration' : 'underline' } + }, + { + 'start' : '[color=black]', + 'end' : '[/color]', + 'custom': true, + 'func' : function() { openColorPicker(this); }, + 'desc' : 'Color', + 'style' : { 'color' : 'red' } + }, + { + 'start' : '[size=1]', + 'end' : '[/size]', + 'custom': true, + 'func' : function() { openSizePicker(this); }, + 'desc' : 'Size' + }, + { + 'start' : '[code]', + 'end' : '[/code]', + 'desc' : 'Code', + 'style' : { 'fontFamily' : 'courier new, monospace' } + }, + { + 'start' : '[quote]', + 'end' : '[/quote]', + 'desc' : 'Quote' + } + ]; + +// List of valid smilies +var smilies = { + 'O:-)' : 'face-angel.png', + 'O:)' : 'face-angel.png', + 'O=)' : 'face-angel.png', + ':-)' : 'face-smile.png', + ':)' : 'face-smile.png', + '=)' : 'face-smile-big.png', + ':-(' : 'face-sad.png', + ':(' : 'face-sad.png', + ';(' : 'face-sad.png', + ':-O' : 'face-surprise.png', + ';-)' : 'face-wink.png', + ';)' : 'face-wink.png', + '8-)' : 'face-glasses.png', + '8)' : 'face-glasses.png', + ':-D' : 'face-grin.png', + ':D' : 'face-grin.png', + '=D' : 'face-grin.png', + ':-*' : 'face-kiss.png', + ':*' : 'face-kiss.png', + '=*' : 'face-kiss.png', + ':\'(' : 'face-crying.png', + ':-|' : 'face-plain.png', + ':-\\' : 'face-plain.png', + ':-/' : 'face-plain.png', + ':joke:' : 'face-plain.png', + ']:->' : 'face-devil-grin.png', + ':kiss:' : 'face-kiss.png', + ':-P' : 'face-tongue-out.png', + ':P' : 'face-tongue-out.png', + ':-p' : 'face-tongue-out.png', + ':p' : 'face-tongue-out.png', + ':-X' : 'face-sick.png', + ':X' : 'face-sick.png', + ':sick:' : 'face-sick.png', + ':-]' : 'face-oops.png', + ':]' : 'face-oops.png', + ':oops:' : 'face-oops.png', + ':-[' : 'face-embarassed.png', + ':[' : 'face-embarassed.png' +}; + +function initBBCodeControls() +{ + txtars = getElementsByClassName(document, 'textarea', 'bbcode'); + for ( i = 0; i < txtars.length; i++ ) + { + convertTextAreaToBBCode(txtars[i]); + } +} + +var smileycache = { 'td' : [], 'img' : [] }; + +function convertTextAreaToBBCode(txtarea) +{ + var pn = txtarea.parentNode; + + var loadingDiv = document.createElement('div'); + loadingDiv.appendChild(document.createTextNode('Initializing editor...')); + pn.appendChild(loadingDiv); + + if(!IE) + { + + var smileybox = document.createElement('div'); + smileybox.style.cssFloat = 'left'; // Mozilla + smileybox.style.styleFloat = 'left'; // IE + smileybox.style.marginRight = '10px'; + smileybox.style.maxWidth = '220px'; + smileybox.style.maxHeight = '300px'; + smileybox.style.clip = 'rect(0px,auto,auto,0px)'; + smileybox.style.overflow = 'auto'; + + var fl = document.createElement('fieldset'); + var lb = document.createElement('legend'); + lb.appendChild(document.createTextNode('Smilies')); + fl.appendChild(lb); + var used = []; + + var scriptPath = ''; // REMOVE FOR ENANO IMPLEMENTATION! + + var frm = document.createElement('form'); + frm.action='javascript:void(0)'; + frm.onsubmit = function(){return false;}; + + var tbl = document.createElement('table'); + tbl.border = '0'; + tbl.cellspacing = '0'; + tbl.cellpadding = '0'; + tbl.width = '100%'; + + var tr = document.createElement('tr'); + var tick = -1; + var apd = false; + + for ( var i in smilies ) + { + apd = false; + if ( in_array(smilies[i], used) ) + continue; + used.push(smilies[i]); + + tick++; + if ( tick == 3 ) + { + tick = 0; + tbl.appendChild(tr); + tr = document.createElement('tr'); + apd = true; + } + + var smile = i.replace(/\\/g, '\\\\'); + + var td = document.createElement('td'); + td.style.textAlign = 'center'; + td.style.padding = '0'; + + var img = ( IE ) ? new Image() : document.createElement('input'); + img.type = 'image'; + img.className = 'clicksmiley'; + img.src = scriptPath + '/images/smilies/' + smilies[i]; + img.style.cursor = 'pointer'; + img.style.margin = '2px'; + img.onclick = insertSmiley; + img.title = i; + img.alt = i; + if (IE) + { + // This IE bug (yet another) is stupid BEYOND reason. + setTimeout('smileycache.td['+smileycache.td.length+'].appendChild(smileycache.img['+smileycache.img.length+']);', 20); + smileycache.img[smileycache.img.length] = img; + smileycache.td[smileycache.td.length] = td; + } + else + { + td.appendChild(img); + } + tr.appendChild(td); + } + + if (!apd) + tbl.appendChild(tr); + + frm.appendChild(tbl); + fl.appendChild(frm); + + smileybox.appendChild(fl); + pn.insertBefore(smileybox, txtarea); + + } + else + { + var div = document.createElement('div'); + var html = '<fieldset style="padding: 10px; display: inline;"><legend>Available smilies:</legend>'; + var c = 0; + for ( var i in smilies ) + { + c++; + html += i + ' '; + if ( c == 10 ) + { + html += '<br />'; + c = 0; + } + } + html += '</fieldset>'; + div.innerHTML = html; + pn.appendChild(div, txtarea); + } + + var toolbar = document.createElement('div'); + for ( j = 0; j < buttons.length; j++ ) + { + var btn = document.createElement('input'); + btn.type='button'; + btn.className = 'bbcbutton'; + btn.value = buttons[j].desc; + if ( buttons[j].custom ) + btn.onclick = buttons[j].func; + else + btn.onclick = BBCodeClickHandler; + if ( buttons[j].style ) + { + for ( var k in buttons[j].style ) + { + btn.style[k] = buttons[j].style[k]; + } + } + toolbar.appendChild(btn); + } + + pn.insertBefore(toolbar, txtarea); + pn.removeChild(loadingDiv); +} + +function insertSmiley() +{ + var imgid = this.src; + imgid = imgid.split('/'); + imgid = imgid[imgid.length-1]; + emot = array_search(imgid, smilies) + ' '; + var o = this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.nextSibling.firstChild; + formatBBCode(o, emot, "", ""); + return false; +} + +function BBCodeClickHandler() +{ + var obj = false; + for ( i = 0; i < buttons.length; i++ ) + { + if ( buttons[i]['desc'] == this.value ) + { + obj = buttons[i]; + break; + } + } + if(!obj) + return false; + formatBBCode(this, obj['start'], obj['end'], obj['desc']); + return true; +} + +// +// COLOR PICKER +// + +function openColorPicker(parent) +{ + var off = fetch_offset(parent); + var dim = fetch_dimensions(parent); + var top = off['top'] + dim['h'] - 1; + var left = off['left']; + + var div = document.createElement('div'); + div.style.border = '1px solid #000000'; + div.style.padding = '10px'; + div.style.position = 'absolute'; + div.style.top = top + 'px'; + div.style.left = left + 'px'; + div.style.backgroundColor = '#ffffff'; + + var cwheel = document.createElement('div'); + cwheel.id = 'color_wheel'; + + var cinput = document.createElement('input'); + cinput.size = '7'; + cinput.id = 'color_val'; + cinput.value = '#ff0000'; + + var btni = document.createElement('input'); + btni.type = 'button'; + btni.value = 'Insert'; + btni.onclick = finishColorPicker; + + var btnc = document.createElement('input'); + btnc.type = 'button'; + btnc.value = 'Cancel'; + btnc.onclick = closeColorPicker; + + div.appendChild(cwheel); + div.appendChild(cinput); + div.appendChild(btni); + div.appendChild(btnc); + + parent.parentNode.appendChild(div); + + $jq('#color_wheel').farbtastic('#color_val'); +} + +function finishColorPicker() +{ + parent = this.parentNode; + input = parent.getElementsByTagName('input')[0]; + color = input.value; + formatBBCode(parent, '[color=' + color + ']', '[/color]', 'Colored text'); + parent.parentNode.removeChild(parent); +} + +function closeColorPicker() +{ + parent = this.parentNode; + parent.parentNode.removeChild(parent); +} + +// +// SIZE PICKER +// + +function openSizePicker(parent) +{ + + var off = fetch_offset(parent); + var dim = fetch_dimensions(parent); + var top = off['top'] + dim['h'] - 1; + var left = off['left']; + + var div = document.createElement('div'); + div.style.border = '1px solid #000000'; + div.style.padding = '3px'; + div.style.position = 'absolute'; + div.style.top = top + 'px'; + div.style.left = left + 'px'; + div.style.backgroundColor = '#ffffff'; + div.style.width = '130px'; + //div.style.maxHeight = '400px'; + div.style.clip = 'rect(0px,auto,auto,0px)'; + div.style.overflow = 'hidden'; + + var tbl = document.createElement('table'); + tbl.border = '0'; + tbl.cellspacing = '0'; + tbl.cellpadding = '0'; + tbl.style.maxWidth = '75px'; + tbl.style.clip = 'rect(0px,75px,auto,0px)'; + tbl.style.overflow = 'hidden'; + + var i = 0; + + for ( i = 0.5; i <= 4; i=i+0.5 ) + { + var tr = document.createElement('tr'); + var td = document.createElement('td'); + td.innerHTML = i; + tr.appendChild(td); + var td = document.createElement('td'); + td.className = 'sizepick_td'; + td.style.fontSize = i + 'em'; + td.innerHTML = 'The quick brown fox jumps over the lazy dog.'; + td.onclick = function() { sizePickClickHandler(this); } + tr.appendChild(td); + tbl.appendChild(tr); + } + + var a = document.createElement('a'); + a.href='#'; + a.onclick = function() { this.parentNode.parentNode.removeChild(this.parentNode); return false; }; + a.appendChild(document.createTextNode('Close size picker')); + + div.appendChild(tbl); + div.appendChild(a); + parent.parentNode.appendChild(div); + +} + +function sizePickClickHandler(parent) +{ + size = parent.style.fontSize.substr(0, parent.style.fontSize.length - 2); + formatBBCode(parent.parentNode.parentNode.parentNode, '[size=' + size + ']', '[/size]', 'Large/small text'); + parent.parentNode.parentNode.parentNode.parentNode.removeChild(parent.parentNode.parentNode.parentNode); +} + +// +// HTML RENDERER +// + +function htmlspecialchars(text) +{ + text = text.replace(/</g, '<'); + text = text.replace(/>/g, '>'); + return text; +} + +function render_bbcode(text) +{ + // Smilies + for(var i in smilies) + { + if ( text.indexOf(i) > -1 ) + { + while ( text.indexOf(i) > -1 ) + { + text = text.replace(i, '<img alt="' + rawhtmlcode(i) + '" src="/images/smilies/' + smilies[i] + '" />'); + } + } + } + + // Destroy (X|HT)ML tags + text = htmlspecialchars(text); + text = text.replace(/ /g, ' '); + + // Bold text + text = text.replace(/\[b\]([\w\W]+?)\[\/b\]/g, '<span style="font-weight: bold;">$1</span>'); + + // Italicized text + text = text.replace(/\[i\]([\w\W]+?)\[\/i\]/g, '<span style="font-style: italic;">$1</span>'); + + // Underlined text + text = text.replace(/\[u\]([\w\W]+?)\[\/u\]/g, '<span style="text-decoration: underline;">$1</span>'); + + // Quotes + text = text.replace(/\[quote\]([\w\W]+?)\[\/quote\]/g, '<blockquote>$1</blockquote>'); + + // Colored text + text = text.replace(/\[color=#([0-9A-Fa-f]+?)\]([\w\W]*?)\[\/color\]/g, '<span style="color: #$1">$2</span>'); + + // Sized text + text = text.replace(/\[size=([0-9\.]+?)\]([\w\W]*?)\[\/size\]/g, '<span style="font-size: $1em">$2</span>'); + + // Newlines + var nlre = new RegExp(unescape('%0A'), 'g'); + text = text.replace(nlre, '<br />' + unescape('%0A')); + + // Preformatted text + text = text.replace(/\[code\]([\w\W]+?)\[\/code\]/gi, '<pre class="code">$1</pre>'); + text = text.replace(/<pre class=\"code\">([\s]+)/gi, '<pre class="code">'); + text = text.replace(/([\s]+)<\/pre>/gi, '</pre>'); + + return text; +} + +function rawhtmlcode(text) +{ + var ret = ''; + for ( var i = 0; i < text.length; i++ ) + { + chr = text.charCodeAt(i); + chr = '&#' + chr + ';'; + ret += chr; + } + return ret; +} + +// Preview function +function makePreview(obj) +{ + obj = document.getElementById(obj); + var bbcode = obj.value; + var body = document.getElementsByTagName('body')[0]; + var div = document.createElement('div'); + div.style.border = '1px solid #000'; + div.style.padding = '10px'; + div.innerHTML = render_bbcode(bbcode); + //body.insertBefore(div, body.firstChild); + body.appendChild(div); +} + +function fetch_offset(obj) { + var left_offset = obj.offsetLeft; + var top_offset = obj.offsetTop; + while ((obj = obj.offsetParent) != null) { + left_offset += obj.offsetLeft; + top_offset += obj.offsetTop; + } + return { 'left' : left_offset, 'top' : top_offset }; +} + +function fetch_dimensions(o) { + var w = o.offsetWidth; + var h = o.offsetHeight; + return { 'w' : w, 'h' : h }; +} + +function getElementsByClassName(parent, type, cls) { + if(!type) + type = '*'; + if(!parent) + parent = document; + ret = new Array(); + el = parent.getElementsByTagName(type); + for ( var i in el ) + { + if(el[i].className) + { + if(el[i].className.indexOf(' ') > 0) + { + classes = el[i].className.split(' '); + } + else + { + classes = new Array(); + classes.push(el[i].className); + } + if ( in_array(cls, classes) ) + ret.push(el[i]); + } + } + return ret; +} + +function in_array(needle, haystack) +{ + for( var i in haystack ) + { + if(haystack[i] == needle) + return true; + } + return false; +} + +function array_search(needle, haystack) +{ + for( var i in haystack ) + { + if(haystack[i] == needle) + return i; + } + return false; +} + +document.getElementsByClassName = function(type, cls) { + return getElementsByClassName(document, type, cls); +} + +// Function adapted from MediaWiki/phpBB +function formatBBCode(parent, tagOpen, tagClose, sampleText) +{ + var txtarea = parent.parentNode.nextSibling; + + // IE + if (document.selection && !is_gecko) { + var theSelection = document.selection.createRange().text; + if (!theSelection) + theSelection=sampleText; + txtarea.focus(); + if (theSelection.charAt(theSelection.length - 1) == " ") { // exclude ending space char, if any + theSelection = theSelection.substring(0, theSelection.length - 1); + document.selection.createRange().text = tagOpen + theSelection + tagClose + " "; + } else { + document.selection.createRange().text = tagOpen + theSelection + tagClose; + } + + // Mozilla + } else if(txtarea.selectionStart || txtarea.selectionStart == '0') { + var replaced = false; + var startPos = txtarea.selectionStart; + var endPos = txtarea.selectionEnd; + if (endPos-startPos) + replaced = true; + var scrollTop = txtarea.scrollTop; + var myText = (txtarea.value).substring(startPos, endPos); + if (!myText) + myText=sampleText; + if (myText.charAt(myText.length - 1) == " ") { // exclude ending space char, if any + subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " "; + } else { + subst = tagOpen + myText + tagClose; + } + txtarea.value = txtarea.value.substring(0, startPos) + subst + + txtarea.value.substring(endPos, txtarea.value.length); + txtarea.focus(); + //set new selection + if (replaced) { + var cPos = startPos+(tagOpen.length+myText.length+tagClose.length); + txtarea.selectionStart = cPos; + txtarea.selectionEnd = cPos; + } else { + txtarea.selectionStart = startPos+tagOpen.length; + txtarea.selectionEnd = startPos+tagOpen.length+myText.length; + } + txtarea.scrollTop = scrollTop; + + // All other browsers get no toolbar. + } + // reposition cursor if possible + if (txtarea.createTextRange) + txtarea.caretPos = document.selection.createRange().duplicate(); +} + +addOnloadHook(initBBCodeControls); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/js/colorpick/farbtastic.css Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,33 @@ +.farbtastic { + position: relative; +} +.farbtastic * { + position: absolute; + cursor: crosshair; +} +.farbtastic, .farbtastic .wheel { + width: 195px; + height: 195px; +} +.farbtastic .color, .farbtastic .overlay { + top: 47px; + left: 47px; + width: 101px; + height: 101px; +} +.farbtastic .wheel { + background: url(wheel.png) no-repeat; + width: 195px; + height: 195px; +} +.farbtastic .overlay { + background: url(mask.png) no-repeat; +} +.farbtastic .marker { + width: 17px; + height: 17px; + margin: -8px 0 0 -8px; + overflow: hidden; + background: url(marker.png) no-repeat; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/js/colorpick/farbtastic.js Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,331 @@ +// $Id: farbtastic.js,v 1.2 2007/01/08 22:53:01 unconed Exp $ +// Farbtastic 1.2 + +// This code has been modified to not break Dynano + +jQuery.fn.farbtastic = function (callback) { + $jq.farbtastic(this, callback); + return this; +}; + +jQuery.farbtastic = function (container, callback) { + var container = $jq(container).get(0); + return container.farbtastic || (container.farbtastic = new jQuery._farbtastic(container, callback)); +} + +jQuery._farbtastic = function (container, callback) { + // Store farbtastic object + var fb = this; + + // Insert markup + $jq(container).html('<div class="farbtastic"><div class="color"></div><div class="wheel"></div><div class="overlay"></div><div class="h-marker marker"></div><div class="sl-marker marker"></div></div>'); + var e = $jq('.farbtastic', container); + fb.wheel = $jq('.wheel', container).get(0); + // Dimensions + fb.radius = 84; + fb.square = 100; + fb.width = 194; + + // Fix background PNGs in IE6 + if (navigator.appVersion.match(/MSIE [0-6]\./)) { + $jq('*', e).each(function () { + if (this.currentStyle.backgroundImage != 'none') { + var image = this.currentStyle.backgroundImage; + image = this.currentStyle.backgroundImage.substring(5, image.length - 2); + $jq(this).css({ + 'backgroundImage': 'none', + 'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" + }); + } + }); + } + + /** + * Link to the given element(s) or callback. + */ + fb.linkTo = function (callback) { + // Unbind previous nodes + if (typeof fb.callback == 'object') { + $jq(fb.callback).unbind('keyup', fb.updateValue); + } + + // Reset color + fb.color = null; + + // Bind callback or elements + if (typeof callback == 'function') { + fb.callback = callback; + } + else if (typeof callback == 'object' || typeof callback == 'string') { + fb.callback = $jq(callback); + fb.callback.bind('keyup', fb.updateValue); + if (fb.callback.get(0).value) { + fb.setColor(fb.callback.get(0).value); + } + } + return this; + } + fb.updateValue = function (event) { + if (this.value && this.value != fb.color) { + fb.setColor(this.value); + } + } + + /** + * Change color with HTML syntax #123456 + */ + fb.setColor = function (color) { + var unpack = fb.unpack(color); + if (fb.color != color && unpack) { + fb.color = color; + fb.rgb = unpack; + fb.hsl = fb.RGBToHSL(fb.rgb); + fb.updateDisplay(); + } + return this; + } + + /** + * Change color with HSL triplet [0..1, 0..1, 0..1] + */ + fb.setHSL = function (hsl) { + fb.hsl = hsl; + fb.rgb = fb.HSLToRGB(hsl); + fb.color = fb.pack(fb.rgb); + fb.updateDisplay(); + return this; + } + + ///////////////////////////////////////////////////// + + /** + * Retrieve the coordinates of the given event relative to the center + * of the widget. + */ + fb.widgetCoords = function (event) { + var x, y; + var el = event.target || event.srcElement; + var reference = fb.wheel; + + if (typeof event.offsetX != 'undefined') { + // Use offset coordinates and find common offsetParent + var pos = { x: event.offsetX, y: event.offsetY }; + + // Send the coordinates upwards through the offsetParent chain. + var e = el; + while (e) { + e.mouseX = pos.x; + e.mouseY = pos.y; + pos.x += e.offsetLeft; + pos.y += e.offsetTop; + e = e.offsetParent; + } + + // Look for the coordinates starting from the wheel widget. + var e = reference; + var offset = { x: 0, y: 0 } + while (e) { + if (typeof e.mouseX != 'undefined') { + x = e.mouseX - offset.x; + y = e.mouseY - offset.y; + break; + } + offset.x += e.offsetLeft; + offset.y += e.offsetTop; + e = e.offsetParent; + } + + // Reset stored coordinates + e = el; + while (e) { + e.mouseX = undefined; + e.mouseY = undefined; + e = e.offsetParent; + } + } + else { + // Use absolute coordinates + var pos = fb.absolutePosition(reference); + x = (event.pageX || 0*(event.clientX + $jq('html').get(0).scrollLeft)) - pos.x; + y = (event.pageY || 0*(event.clientY + $jq('html').get(0).scrollTop)) - pos.y; + } + // Subtract distance to middle + return { x: x - fb.width / 2, y: y - fb.width / 2 }; + } + + /** + * Mousedown handler + */ + fb.mousedown = function (event) { + // Capture mouse + if (!document.dragging) { + $jq(document).bind('mousemove', fb.mousemove).bind('mouseup', fb.mouseup); + document.dragging = true; + } + + // Check which area is being dragged + var pos = fb.widgetCoords(event); + fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square; + + // Process + fb.mousemove(event); + return false; + } + + /** + * Mousemove handler + */ + fb.mousemove = function (event) { + // Get coordinates relative to color picker center + var pos = fb.widgetCoords(event); + + // Set new HSL parameters + if (fb.circleDrag) { + var hue = Math.atan2(pos.x, -pos.y) / 6.28; + if (hue < 0) hue += 1; + fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]); + } + else { + var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5)); + var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5)); + fb.setHSL([fb.hsl[0], sat, lum]); + } + return false; + } + + /** + * Mouseup handler + */ + fb.mouseup = function () { + // Uncapture mouse + $jq(document).unbind('mousemove', fb.mousemove); + $jq(document).unbind('mouseup', fb.mouseup); + document.dragging = false; + } + + /** + * Update the markers and styles + */ + fb.updateDisplay = function () { + // Markers + var angle = fb.hsl[0] * 6.28; + $jq('.h-marker', e).css({ + left: Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px', + top: Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px' + }); + + $jq('.sl-marker', e).css({ + left: Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px', + top: Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px' + }); + + // Saturation/Luminance gradient + $jq('.color', e).css('backgroundColor', fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5]))); + + // Linked elements or callback + if (typeof fb.callback == 'object') { + // Set background/foreground color + $jq(fb.callback).css({ + backgroundColor: fb.color, + color: fb.hsl[2] > 0.5 ? '#000' : '#fff' + }); + + // Change linked value + $jq(fb.callback).each(function() { + if (this.value && this.value != fb.color) { + this.value = fb.color; + } + }); + } + else if (typeof fb.callback == 'function') { + fb.callback.call(fb, fb.color); + } + } + + /** + * Get absolute position of element + */ + fb.absolutePosition = function (el) { + var r = { x: el.offsetLeft, y: el.offsetTop }; + // Resolve relative to offsetParent + if (el.offsetParent) { + var tmp = fb.absolutePosition(el.offsetParent); + r.x += tmp.x; + r.y += tmp.y; + } + return r; + }; + + /* Various color utility functions */ + fb.pack = function (rgb) { + var r = Math.round(rgb[0] * 255); + var g = Math.round(rgb[1] * 255); + var b = Math.round(rgb[2] * 255); + return '#' + (r < 16 ? '0' : '') + r.toString(16) + + (g < 16 ? '0' : '') + g.toString(16) + + (b < 16 ? '0' : '') + b.toString(16); + } + + fb.unpack = function (color) { + if (color.length == 7) { + return [parseInt('0x' + color.substring(1, 3)) / 255, + parseInt('0x' + color.substring(3, 5)) / 255, + parseInt('0x' + color.substring(5, 7)) / 255]; + } + else if (color.length == 4) { + return [parseInt('0x' + color.substring(1, 2)) / 15, + parseInt('0x' + color.substring(2, 3)) / 15, + parseInt('0x' + color.substring(3, 4)) / 15]; + } + } + + fb.HSLToRGB = function (hsl) { + var m1, m2, r, g, b; + var h = hsl[0], s = hsl[1], l = hsl[2]; + m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s; + m1 = l * 2 - m2; + return [this.hueToRGB(m1, m2, h+0.33333), + this.hueToRGB(m1, m2, h), + this.hueToRGB(m1, m2, h-0.33333)]; + } + + fb.hueToRGB = function (m1, m2, h) { + h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h); + if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; + if (h * 2 < 1) return m2; + if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6; + return m1; + } + + fb.RGBToHSL = function (rgb) { + var min, max, delta, h, s, l; + var r = rgb[0], g = rgb[1], b = rgb[2]; + min = Math.min(r, Math.min(g, b)); + max = Math.max(r, Math.max(g, b)); + delta = max - min; + l = (min + max) / 2; + s = 0; + if (l > 0 && l < 1) { + s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l)); + } + h = 0; + if (delta > 0) { + if (max == r && max != g) h += (g - b) / delta; + if (max == g && max != b) h += (2 + (b - r) / delta); + if (max == b && max != r) h += (4 + (r - g) / delta); + h /= 6; + } + return [h, s, l]; + } + + // Install mousedown handler (the others are set on the document on-demand) + $jq('*', e).mousedown(fb.mousedown); + + // Init color + fb.setColor('#000000'); + + // Set linked elements/callback + if (callback) { + fb.linkTo(callback); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/js/colorpick/jquery.js Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,2246 @@ +/* prevent execution of jQuery if included more than once */ +if(typeof window.jQuery == "undefined") { +/* + * jQuery 1.1.2 - New Wave Javascript + * + * Copyright (c) 2007 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2007-02-28 12:03:00 -0500 (Wed, 28 Feb 2007) $ + * $Rev: 1465 $ + */ + +// Global undefined variable +window.undefined = window.undefined; +var jQuery = function(a,c) { + // If the context is global, return a new object + if ( window == this ) + return new jQuery(a,c); + + // Make sure that a selection was provided + a = a || document; + + // HANDLE: $(function) + // Shortcut for document ready + if ( jQuery.isFunction(a) ) + return new jQuery(document)[ jQuery.fn.ready ? "ready" : "load" ]( a ); + + // Handle HTML strings + if ( typeof a == "string" ) { + // HANDLE: $(html) -> $(array) + var m = /^[^<]*(<(.|\s)+>)[^>]*$/.exec(a); + if ( m ) + a = jQuery.clean( [ m[1] ] ); + + // HANDLE: $(expr) + else + return new jQuery( c ).find( a ); + } + + return this.setArray( + // HANDLE: $(array) + a.constructor == Array && a || + + // HANDLE: $(arraylike) + // Watch for when an array-like object is passed as the selector + (a.jquery || a.length && a != window && !a.nodeType && a[0] != undefined && a[0].nodeType) && jQuery.makeArray( a ) || + + // HANDLE: $(*) + [ a ] ); +}; + +// Map over the $ in case of overwrite +if ( typeof $ != "undefined" ) + jQuery._$ = $; + +// Map the jQuery namespace to the '$' one +var $jq = jQuery; + +jQuery.fn = jQuery.prototype = { + jquery: "1.1.2", + + size: function() { + return this.length; + }, + + length: 0, + + get: function( num ) { + return num == undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + this[num]; + }, + pushStack: function( a ) { + var ret = jQuery(a); + ret.prevObject = this; + return ret; + }, + setArray: function( a ) { + this.length = 0; + [].push.apply( this, a ); + return this; + }, + each: function( fn, args ) { + return jQuery.each( this, fn, args ); + }, + index: function( obj ) { + var pos = -1; + this.each(function(i){ + if ( this == obj ) pos = i; + }); + return pos; + }, + + attr: function( key, value, type ) { + var obj = key; + + // Look for the case where we're accessing a style value + if ( key.constructor == String ) + if ( value == undefined ) + return this.length && jQuery[ type || "attr" ]( this[0], key ) || undefined; + else { + obj = {}; + obj[ key ] = value; + } + + // Check to see if we're setting style values + return this.each(function(index){ + // Set all the styles + for ( var prop in obj ) + jQuery.attr( + type ? this.style : this, + prop, jQuery.prop(this, obj[prop], type, index, prop) + ); + }); + }, + + css: function( key, value ) { + return this.attr( key, value, "curCSS" ); + }, + + text: function(e) { + if ( typeof e == "string" ) + return this.empty().append( document.createTextNode( e ) ); + + var t = ""; + jQuery.each( e || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + t += this.nodeType != 1 ? + this.nodeValue : jQuery.fn.text([ this ]); + }); + }); + return t; + }, + + wrap: function() { + // The elements to wrap the target around + var a = jQuery.clean(arguments); + + // Wrap each of the matched elements individually + return this.each(function(){ + // Clone the structure that we're using to wrap + var b = a[0].cloneNode(true); + + // Insert it before the element to be wrapped + this.parentNode.insertBefore( b, this ); + + // Find the deepest point in the wrap structure + while ( b.firstChild ) + b = b.firstChild; + + // Move the matched element to within the wrap structure + b.appendChild( this ); + }); + }, + append: function() { + return this.domManip(arguments, true, 1, function(a){ + this.appendChild( a ); + }); + }, + prepend: function() { + return this.domManip(arguments, true, -1, function(a){ + this.insertBefore( a, this.firstChild ); + }); + }, + before: function() { + return this.domManip(arguments, false, 1, function(a){ + this.parentNode.insertBefore( a, this ); + }); + }, + after: function() { + return this.domManip(arguments, false, -1, function(a){ + this.parentNode.insertBefore( a, this.nextSibling ); + }); + }, + end: function() { + return this.prevObject || jQuery([]); + }, + find: function(t) { + return this.pushStack( jQuery.map( this, function(a){ + return jQuery.find(t,a); + }), t ); + }, + clone: function(deep) { + return this.pushStack( jQuery.map( this, function(a){ + var a = a.cloneNode( deep != undefined ? deep : true ); + a.$events = null; // drop $events expando to avoid firing incorrect events + return a; + }) ); + }, + + filter: function(t) { + return this.pushStack( + jQuery.isFunction( t ) && + jQuery.grep(this, function(el, index){ + return t.apply(el, [index]) + }) || + + jQuery.multiFilter(t,this) ); + }, + + not: function(t) { + return this.pushStack( + t.constructor == String && + jQuery.multiFilter(t, this, true) || + + jQuery.grep(this, function(a) { + return ( t.constructor == Array || t.jquery ) + ? jQuery.inArray( a, t ) < 0 + : a != t; + }) + ); + }, + + add: function(t) { + return this.pushStack( jQuery.merge( + this.get(), + t.constructor == String ? + jQuery(t).get() : + t.length != undefined && (!t.nodeName || t.nodeName == "FORM") ? + t : [t] ) + ); + }, + is: function(expr) { + return expr ? jQuery.filter(expr,this).r.length > 0 : false; + }, + + val: function( val ) { + return val == undefined ? + ( this.length ? this[0].value : null ) : + this.attr( "value", val ); + }, + + html: function( val ) { + return val == undefined ? + ( this.length ? this[0].innerHTML : null ) : + this.empty().append( val ); + }, + domManip: function(args, table, dir, fn){ + var clone = this.length > 1; + var a = jQuery.clean(args); + if ( dir < 0 ) + a.reverse(); + + return this.each(function(){ + var obj = this; + + if ( table && jQuery.nodeName(this, "table") && jQuery.nodeName(a[0], "tr") ) + obj = this.getElementsByTagName("tbody")[0] || this.appendChild(document.createElement("tbody")); + + jQuery.each( a, function(){ + fn.apply( obj, [ clone ? this.cloneNode(true) : this ] ); + }); + + }); + } +}; + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0], + a = 1; + + // extend jQuery itself if only one argument is passed + if ( arguments.length == 1 ) { + target = this; + a = 0; + } + var prop; + while (prop = arguments[a++]) + // Extend the base object + for ( var i in prop ) target[i] = prop[i]; + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function() { + if ( jQuery._$ ) + $ = jQuery._$; + return jQuery; + }, + + // This may seem like some crazy code, but trust me when I say that this + // is the only cross-browser way to do this. --John + isFunction: function( fn ) { + return !!fn && typeof fn != "string" && !fn.nodeName && + typeof fn[0] == "undefined" && /function/i.test( fn + "" ); + }, + + // check if an element is in a XML document + isXMLDoc: function(elem) { + return elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + // args is for internal usage only + each: function( obj, fn, args ) { + if ( obj.length == undefined ) + for ( var i in obj ) + fn.apply( obj[i], args || [i, obj[i]] ); + else + for ( var i = 0, ol = obj.length; i < ol; i++ ) + if ( fn.apply( obj[i], args || [i, obj[i]] ) === false ) break; + return obj; + }, + + prop: function(elem, value, type, index, prop){ + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, [index] ); + + // exclude the following css properties to add px + var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; + + // Handle passing in a number to a CSS property + return value && value.constructor == Number && type == "curCSS" && !exclude.test(prop) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, c ){ + jQuery.each( c.split(/\s+/), function(i, cur){ + if ( !jQuery.className.has( elem.className, cur ) ) + elem.className += ( elem.className ? " " : "" ) + cur; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, c ){ + elem.className = c ? + jQuery.grep( elem.className.split(/\s+/), function(cur){ + return !jQuery.className.has( c, cur ); + }).join(" ") : ""; + }, + + // internal only, use is(".class") + has: function( t, c ) { + t = t.className || t; + // escape regex characters + c = c.replace(/([\.\\\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1"); + return t && new RegExp("(^|\\s)" + c + "(\\s|$)").test( t ); + } + }, + swap: function(e,o,f) { + for ( var i in o ) { + e.style["old"+i] = e.style[i]; + e.style[i] = o[i]; + } + f.apply( e, [] ); + for ( var i in o ) + e.style[i] = e.style["old"+i]; + }, + + css: function(e,p) { + if ( p == "height" || p == "width" ) { + var old = {}, oHeight, oWidth, d = ["Top","Bottom","Right","Left"]; + + jQuery.each( d, function(){ + old["padding" + this] = 0; + old["border" + this + "Width"] = 0; + }); + + jQuery.swap( e, old, function() { + if (jQuery.css(e,"display") != "none") { + oHeight = e.offsetHeight; + oWidth = e.offsetWidth; + } else { + e = jQuery(e.cloneNode(true)) + .find(":radio").removeAttr("checked").end() + .css({ + visibility: "hidden", position: "absolute", display: "block", right: "0", left: "0" + }).appendTo(e.parentNode)[0]; + + var parPos = jQuery.css(e.parentNode,"position"); + if ( parPos == "" || parPos == "static" ) + e.parentNode.style.position = "relative"; + + oHeight = e.clientHeight; + oWidth = e.clientWidth; + + if ( parPos == "" || parPos == "static" ) + e.parentNode.style.position = "static"; + + e.parentNode.removeChild(e); + } + }); + + return p == "height" ? oHeight : oWidth; + } + + return jQuery.curCSS( e, p ); + }, + + curCSS: function(elem, prop, force) { + var ret; + + if (prop == "opacity" && jQuery.browser.msie) + return jQuery.attr(elem.style, "opacity"); + + if (prop == "float" || prop == "cssFloat") + prop = jQuery.browser.msie ? "styleFloat" : "cssFloat"; + + if (!force && elem.style[prop]) + ret = elem.style[prop]; + + else if (document.defaultView && document.defaultView.getComputedStyle) { + + if (prop == "cssFloat" || prop == "styleFloat") + prop = "float"; + + prop = prop.replace(/([A-Z])/g,"-$1").toLowerCase(); + var cur = document.defaultView.getComputedStyle(elem, null); + + if ( cur ) + ret = cur.getPropertyValue(prop); + else if ( prop == "display" ) + ret = "none"; + else + jQuery.swap(elem, { display: "block" }, function() { + var c = document.defaultView.getComputedStyle(this, ""); + ret = c && c.getPropertyValue(prop) || ""; + }); + + } else if (elem.currentStyle) { + + var newProp = prop.replace(/\-(\w)/g,function(m,c){return c.toUpperCase();}); + ret = elem.currentStyle[prop] || elem.currentStyle[newProp]; + + } + + return ret; + }, + + clean: function(a) { + var r = []; + + jQuery.each( a, function(i,arg){ + if ( !arg ) return; + + if ( arg.constructor == Number ) + arg = arg.toString(); + + // Convert html string into DOM nodes + if ( typeof arg == "string" ) { + // Trim whitespace, otherwise indexOf won't work as expected + var s = jQuery.trim(arg), div = document.createElement("div"), tb = []; + + var wrap = + // option or optgroup + !s.indexOf("<opt") && + [1, "<select>", "</select>"] || + + (!s.indexOf("<thead") || !s.indexOf("<tbody") || !s.indexOf("<tfoot")) && + [1, "<table>", "</table>"] || + + !s.indexOf("<tr") && + [2, "<table><tbody>", "</tbody></table>"] || + + // <thead> matched above + (!s.indexOf("<td") || !s.indexOf("<th")) && + [3, "<table><tbody><tr>", "</tr></tbody></table>"] || + + [0,"",""]; + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + s + wrap[2]; + + // Move to the right depth + while ( wrap[0]-- ) + div = div.firstChild; + + // Remove IE's autoinserted <tbody> from table fragments + if ( jQuery.browser.msie ) { + + // String was a <table>, *may* have spurious <tbody> + if ( !s.indexOf("<table") && s.indexOf("<tbody") < 0 ) + tb = div.firstChild && div.firstChild.childNodes; + + // String was a bare <thead> or <tfoot> + else if ( wrap[1] == "<table>" && s.indexOf("<tbody") < 0 ) + tb = div.childNodes; + + for ( var n = tb.length-1; n >= 0 ; --n ) + if ( jQuery.nodeName(tb[n], "tbody") && !tb[n].childNodes.length ) + tb[n].parentNode.removeChild(tb[n]); + + } + + arg = []; + for (var i=0, l=div.childNodes.length; i<l; i++) + arg.push(div.childNodes[i]); + } + + if ( arg.length === 0 && !jQuery.nodeName(arg, "form") ) + return; + + if ( arg[0] == undefined || jQuery.nodeName(arg, "form") ) + r.push( arg ); + else + r = jQuery.merge( r, arg ); + + }); + + return r; + }, + + attr: function(elem, name, value){ + var fix = jQuery.isXMLDoc(elem) ? {} : { + "for": "htmlFor", + "class": "className", + "float": jQuery.browser.msie ? "styleFloat" : "cssFloat", + cssFloat: jQuery.browser.msie ? "styleFloat" : "cssFloat", + innerHTML: "innerHTML", + className: "className", + value: "value", + disabled: "disabled", + checked: "checked", + readonly: "readOnly", + selected: "selected" + }; + + // IE actually uses filters for opacity ... elem is actually elem.style + if ( name == "opacity" && jQuery.browser.msie && 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 + return elem.filter = elem.filter.replace(/alpha\([^\)]*\)/gi,"") + + ( value == 1 ? "" : "alpha(opacity=" + value * 100 + ")" ); + + } else if ( name == "opacity" && jQuery.browser.msie ) + return elem.filter ? + parseFloat( elem.filter.match(/alpha\(opacity=(.*)\)/)[1] ) / 100 : 1; + + // Mozilla doesn't play well with opacity 1 + if ( name == "opacity" && jQuery.browser.mozilla && value == 1 ) + value = 0.9999; + + + // 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 ( 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 ) 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 { + name = name.replace(/-([a-z])/ig,function(z,b){return b.toUpperCase();}); + if ( value != undefined ) elem[name] = value; + return elem[name]; + } + }, + trim: function(t){ + return t.replace(/^\s+|\s+$/g, ""); + }, + + makeArray: function( a ) { + var r = []; + + if ( a.constructor != Array ) + for ( var i = 0, al = a.length; i < al; i++ ) + r.push( a[i] ); + else + r = a.slice( 0 ); + + return r; + }, + + inArray: function( b, a ) { + for ( var i = 0, al = a.length; i < al; i++ ) + if ( a[i] == b ) + return i; + return -1; + }, + merge: function(first, second) { + var r = [].slice.call( first, 0 ); + + // Now check for duplicates between the two arrays + // and only add the unique items + for ( var i = 0, sl = second.length; i < sl; i++ ) + // Check for duplicates + if ( jQuery.inArray( second[i], r ) == -1 ) + // The item is unique, add it + first.push( second[i] ); + + return first; + }, + grep: function(elems, fn, inv) { + // If a string is passed in for the function, make a function + // for it (a handy shortcut) + if ( typeof fn == "string" ) + fn = new Function("a","i","return " + fn); + + var result = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, el = elems.length; i < el; i++ ) + if ( !inv && fn(elems[i],i) || inv && !fn(elems[i],i) ) + result.push( elems[i] ); + + return result; + }, + map: function(elems, fn) { + // If a string is passed in for the function, make a function + // for it (a handy shortcut) + if ( typeof fn == "string" ) + fn = new Function("a","return " + fn); + + var result = [], r = []; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, el = elems.length; i < el; i++ ) { + var val = fn(elems[i],i); + + if ( val !== null && val != undefined ) { + if ( val.constructor != Array ) val = [val]; + result = result.concat( val ); + } + } + + var r = result.length ? [ result[0] ] : []; + + check: for ( var i = 1, rl = result.length; i < rl; i++ ) { + for ( var j = 0; j < i; j++ ) + if ( result[i] == r[j] ) + continue check; + + r.push( result[i] ); + } + + return r; + } +}); + +/* + * Whether the W3C compliant box model is being used. + * + * @property + * @name $.boxModel + * @type Boolean + * @cat JavaScript + */ +new function() { + var b = navigator.userAgent.toLowerCase(); + + // Figure out what browser is being used + jQuery.browser = { + safari: /webkit/.test(b), + opera: /opera/.test(b), + msie: /msie/.test(b) && !/opera/.test(b), + mozilla: /mozilla/.test(b) && !/(compatible|webkit)/.test(b) + }; + + // Check to see if the W3C box model is being used + jQuery.boxModel = !jQuery.browser.msie || document.compatMode == "CSS1Compat"; +}; + +jQuery.each({ + parent: "a.parentNode", + parents: "jQuery.parents(a)", + next: "jQuery.nth(a,2,'nextSibling')", + prev: "jQuery.nth(a,2,'previousSibling')", + siblings: "jQuery.sibling(a.parentNode.firstChild,a)", + children: "jQuery.sibling(a.firstChild)" +}, function(i,n){ + jQuery.fn[ i ] = function(a) { + var ret = jQuery.map(this,n); + if ( a && typeof a == "string" ) + ret = jQuery.multiFilter(a,ret); + return this.pushStack( ret ); + }; +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after" +}, function(i,n){ + jQuery.fn[ i ] = function(){ + var a = arguments; + return this.each(function(){ + for ( var j = 0, al = a.length; j < al; j++ ) + jQuery(a[j])[n]( this ); + }); + }; +}); + +jQuery.each( { + removeAttr: function( key ) { + jQuery.attr( this, key, "" ); + this.removeAttribute( key ); + }, + addClass: function(c){ + jQuery.className.add(this,c); + }, + removeClass: function(c){ + jQuery.className.remove(this,c); + }, + toggleClass: function( c ){ + jQuery.className[ jQuery.className.has(this,c) ? "remove" : "add" ](this, c); + }, + remove: function(a){ + if ( !a || jQuery.filter( a, [this] ).r.length ) + this.parentNode.removeChild( this ); + }, + empty: function() { + while ( this.firstChild ) + this.removeChild( this.firstChild ); + } +}, function(i,n){ + jQuery.fn[ i ] = function() { + return this.each( n, arguments ); + }; +}); + +jQuery.each( [ "eq", "lt", "gt", "contains" ], function(i,n){ + jQuery.fn[ n ] = function(num,fn) { + return this.filter( ":" + n + "(" + num + ")", fn ); + }; +}); + +jQuery.each( [ "height", "width" ], function(i,n){ + jQuery.fn[ n ] = function(h) { + return h == undefined ? + ( this.length ? jQuery.css( this[0], n ) : null ) : + this.css( n, h.constructor == String ? h : h + "px" ); + }; +}); +jQuery.extend({ + expr: { + "": "m[2]=='*'||jQuery.nodeName(a,m[2])", + "#": "a.getAttribute('id')==m[2]", + ":": { + // Position Checks + lt: "i<m[3]-0", + gt: "i>m[3]-0", + nth: "m[3]-0==i", + eq: "m[3]-0==i", + first: "i==0", + last: "i==r.length-1", + even: "i%2==0", + odd: "i%2", + + // Child Checks + "nth-child": "jQuery.nth(a.parentNode.firstChild,m[3],'nextSibling',a)==a", + "first-child": "jQuery.nth(a.parentNode.firstChild,1,'nextSibling')==a", + "last-child": "jQuery.nth(a.parentNode.lastChild,1,'previousSibling')==a", + "only-child": "jQuery.sibling(a.parentNode.firstChild).length==1", + + // Parent Checks + parent: "a.firstChild", + empty: "!a.firstChild", + + // Text Check + contains: "jQuery.fn.text.apply([a]).indexOf(m[3])>=0", + + // Visibility + visible: 'a.type!="hidden"&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden"', + hidden: 'a.type=="hidden"||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden"', + + // Form attributes + enabled: "!a.disabled", + disabled: "a.disabled", + checked: "a.checked", + selected: "a.selected||jQuery.attr(a,'selected')", + + // Form elements + text: "a.type=='text'", + radio: "a.type=='radio'", + checkbox: "a.type=='checkbox'", + file: "a.type=='file'", + password: "a.type=='password'", + submit: "a.type=='submit'", + image: "a.type=='image'", + reset: "a.type=='reset'", + button: 'a.type=="button"||jQuery.nodeName(a,"button")', + input: "/input|select|textarea|button/i.test(a.nodeName)" + }, + ".": "jQuery.className.has(a,m[2])", + "@": { + "=": "z==m[4]", + "!=": "z!=m[4]", + "^=": "z&&!z.indexOf(m[4])", + "$=": "z&&z.substr(z.length - m[4].length,m[4].length)==m[4]", + "*=": "z&&z.indexOf(m[4])>=0", + "": "z", + _resort: function(m){ + return ["", m[1], m[3], m[2], m[5]]; + }, + _prefix: "z=a[m[3]];if(!z||/href|src/.test(m[3]))z=jQuery.attr(a,m[3]);" + }, + "[": "jQuery.find(m[2],a).length" + }, + + // The regular expressions that power the parsing engine + parse: [ + // Match: [@value='test'], [@foo] + /^\[ *(@)([a-z0-9_-]*) *([!*$^=]*) *('?"?)(.*?)\4 *\]/i, + + // Match: [div], [div p] + /^(\[)\s*(.*?(\[.*?\])?[^[]*?)\s*\]/, + + // Match: :contains('foo') + /^(:)([a-z0-9_-]*)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/i, + + // Match: :even, :last-chlid + /^([:.#]*)([a-z0-9_*-]*)/i + ], + + token: [ + /^(\/?\.\.)/, "a.parentNode", + /^(>|\/)/, "jQuery.sibling(a.firstChild)", + /^(\+)/, "jQuery.nth(a,2,'nextSibling')", + /^(~)/, function(a){ + var s = jQuery.sibling(a.parentNode.firstChild); + return s.slice(jQuery.inArray(a,s) + 1); + } + ], + + 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 ]; + + // Make sure that the context is a DOM Element + if ( context && !context.nodeType ) + context = null; + + // Set the correct context (if none is provided) + context = context || document; + + // Handle the common XPath // expression + if ( !t.indexOf("//") ) { + context = context.documentElement; + t = t.substr(2,t.length); + + // And the / root expression + } else if ( !t.indexOf("/") ) { + context = context.documentElement; + t = t.substr(1,t.length); + if ( t.indexOf("/") >= 1 ) + t = t.substr(t.indexOf("/"),t.length); + } + + // Initialize the search + var ret = [context], done = [], last = null; + + // 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).replace( /^\/\//i, "" ); + + var foundToken = false; + + // An attempt at speeding up child selectors that + // point to a specific element tag + var re = /^[\/>]\s*([a-z0-9*-]+)/i; + var m = re.exec(t); + + if ( m ) { + // Perform our own iteration and filter + jQuery.each( ret, function(){ + for ( var c = this.firstChild; c; c = c.nextSibling ) + if ( c.nodeType == 1 && ( jQuery.nodeName(c, m[1]) || m[1] == "*" ) ) + r.push( c ); + }); + + ret = r; + t = t.replace( re, "" ); + if ( t.indexOf(" ") == 0 ) continue; + foundToken = true; + } else { + // Look for pre-defined expression tokens + for ( var i = 0; i < jQuery.token.length; i += 2 ) { + // Attempt to match each, individual, token in + // the specified order + var re = jQuery.token[i]; + var m = re.exec(t); + + // If the token match was found + if ( m ) { + // Map it against the token's handler + r = ret = jQuery.map( ret, jQuery.isFunction( jQuery.token[i+1] ) ? + jQuery.token[i+1] : + function(a){ return eval(jQuery.token[i+1]); }); + + // And remove the token + t = jQuery.trim( t.replace( re, "" ) ); + foundToken = true; + break; + } + } + } + + // 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 ( ret[0] == context ) ret.shift(); + + // Merge the result sets + jQuery.merge( done, ret ); + + // Reset the context + r = ret = [context]; + + // Touch up the selector string + t = " " + t.substr(1,t.length); + + } else { + // Optomize for the case nodeName#idName + var re2 = /^([a-z0-9_-]+)(#)([a-z0-9\\*_-]*)/i; + 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 = /^([#.]?)([a-z0-9\\*_-]*)/i; + m = re2.exec(t); + } + + // Try to do a global search by ID, where we can + if ( m[1] == "#" && ret[ret.length-1].getElementById ) { + // Optimization for HTML document case + var oid = ret[ret.length-1].getElementById(m[2]); + + // Do a quick check for the existence of the actual ID attribute + // to avoid selecting by the name attribute in IE + if ( jQuery.browser.msie && oid && oid.id != m[2] ) + oid = jQuery('[@id="'+m[2]+'"]', ret[ret.length-1])[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 { + // Pre-compile a regular expression to handle class searches + if ( m[1] == "." ) + var rec = new RegExp("(^|\\s)" + m[2] + "(\\s|$)"); + + // We need to find all descendant elements, it is more + // efficient to use getAll() when we are already further down + // the tree - we try to recognize that here + jQuery.each( ret, function(){ + // Grab the tag name being searched for + var tag = m[1] != "" || m[0] == "" ? "*" : m[2]; + + // Handle IE7 being really dumb about <object>s + if ( jQuery.nodeName(this, "object") && tag == "*" ) + tag = "param"; + + jQuery.merge( r, + m[1] != "" && ret.length != 1 ? + jQuery.getAll( this, [], m[1], m[2], rec ) : + this.getElementsByTagName( tag ) + ); + }); + + // It's faster to filter by class and be done with it + if ( m[1] == "." && ret.length == 1 ) + r = jQuery.grep( r, function(e) { + return rec.test(e.className); + }); + + // Same with ID filtering + if ( m[1] == "#" && ret.length == 1 ) { + // Remember, then wipe out, the result set + var tmp = r; + r = []; + + // Then try to find the element with the ID + jQuery.each( tmp, function(){ + if ( this.getAttribute("id") == m[2] ) { + r = [ this ]; + return false; + } + }); + } + + 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); + } + } + + // Remove the root context + if ( ret && ret[0] == context ) ret.shift(); + + // And combine the results + jQuery.merge( done, ret ); + + return done; + }, + + filter: function(t,r,not) { + // Look for common filter expressions + while ( t && /^[a-z[({<*:.#]/i.test(t) ) { + + var p = jQuery.parse, m; + + jQuery.each( p, function(i,re){ + + // Look for, and replace, string-like sequences + // and finally build a regexp out of it + m = re.exec( t ); + + if ( m ) { + // Remove what we just matched + t = t.substring( m[0].length ); + + // Re-organize the first match + if ( jQuery.expr[ m[1] ]._resort ) + m = jQuery.expr[ m[1] ]._resort( m ); + + return false; + } + }); + + // :not() is a special case that can be optimized by + // keeping it out of the expression list + if ( m[1] == ":" && m[2] == "not" ) + r = jQuery.filter(m[3], r, true).r; + + // Handle classes as a special case (this will help to + // improve the speed, as the regexp will only be compiled once) + else if ( m[1] == "." ) { + + var re = new RegExp("(^|\\s)" + m[2] + "(\\s|$)"); + r = jQuery.grep( r, function(e){ + return re.test(e.className || ""); + }, not); + + // Otherwise, find the expression to execute + } else { + var f = jQuery.expr[m[1]]; + if ( typeof f != "string" ) + f = jQuery.expr[m[1]][m[2]]; + + // Build a custom macro to enclose it + eval("f = function(a,i){" + + ( jQuery.expr[ m[1] ]._prefix || "" ) + + "return " + f + "}"); + + // Execute it against the current filter + r = jQuery.grep( r, f, not ); + } + } + + // Return an array of filtered elements (r) + // and the modified expression string (t) + return { r: r, t: t }; + }, + + getAll: function( o, r, token, name, re ) { + for ( var s = o.firstChild; s; s = s.nextSibling ) + if ( s.nodeType == 1 ) { + var add = true; + + if ( token == "." ) + add = s.className && re.test(s.className); + else if ( token == "#" ) + add = s.getAttribute("id") == name; + + if ( add ) + r.push( s ); + + if ( token == "#" && r.length ) break; + + if ( s.firstChild ) + jQuery.getAll( s, r, token, name, re ); + } + + return r; + }, + parents: function( elem ){ + var matched = []; + var cur = elem.parentNode; + while ( cur && cur != document ) { + matched.push( cur ); + cur = cur.parentNode; + } + return matched; + }, + nth: function(cur,result,dir,elem){ + result = result || 1; + var num = 0; + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType == 1 ) num++; + if ( num == result || result == "even" && num % 2 == 0 && num > 1 && cur == elem || + result == "odd" && num % 2 == 1 && cur == elem ) 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(element, type, handler, data) { + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( jQuery.browser.msie && element.setInterval != undefined ) + element = window; + + // if data is passed, bind to handler + if( data ) + handler.data = data; + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) + handler.guid = this.guid++; + + // Init the element's event structure + if (!element.$events) + element.$events = {}; + + // Get the current list of functions bound to this event + var handlers = element.$events[type]; + + // If it hasn't been initialized yet + if (!handlers) { + // Init the event handler queue + handlers = element.$events[type] = {}; + + // Remember an existing handler, if it's already there + if (element["on" + type]) + handlers[0] = element["on" + type]; + } + + // Add the function to the element's handler list + handlers[handler.guid] = handler; + + // And bind the global event handler to the element + element["on" + type] = this.handle; + + // Remember the function in a global list (for triggering) + if (!this.global[type]) + this.global[type] = []; + this.global[type].push( element ); + }, + + guid: 1, + global: {}, + + // Detach an event or set of events from an element + remove: function(element, type, handler) { + if (element.$events) { + var i,j,k; + if ( type && type.type ) { // type is actually an event object here + handler = type.handler; + type = type.type; + } + + if (type && element.$events[type]) + // remove the given handler for the given type + if ( handler ) + delete element.$events[type][handler.guid]; + + // remove all handlers for the given type + else + for ( i in element.$events[type] ) + delete element.$events[type][i]; + + // remove all handlers + else + for ( j in element.$events ) + this.remove( element, j ); + + // remove event handler if no more handlers exist + for ( k in element.$events[type] ) + if (k) { + k = true; + break; + } + if (!k) element["on" + type] = null; + } + }, + + trigger: function(type, data, element) { + // Clone the incoming data, if any + data = jQuery.makeArray(data || []); + + // Handle a global trigger + if ( !element ) + jQuery.each( this.global[type] || [], function(){ + jQuery.event.trigger( type, data, this ); + }); + + // Handle triggering a single element + else { + var handler = element["on" + type ], val, + fn = jQuery.isFunction( element[ type ] ); + + if ( handler ) { + // Pass along a fake event + data.unshift( this.fix({ type: type, target: element }) ); + + // Trigger the event + if ( (val = handler.apply( element, data )) !== false ) + this.triggered = true; + } + + if ( fn && val !== false ) + element[ type ](); + + this.triggered = false; + } + }, + + handle: function(event) { + // 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; + + // Empty object is for triggered events with no data + event = jQuery.event.fix( event || window.event || {} ); + + // returned undefined or false + var returnValue; + + var c = this.$events[event.type]; + + var args = [].slice.call( arguments, 1 ); + args.unshift( event ); + + for ( var j in c ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + args[0].handler = c[j]; + args[0].data = c[j].data; + + if ( c[j].apply( this, args ) === false ) { + event.preventDefault(); + event.stopPropagation(); + returnValue = false; + } + } + + // 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 returnValue; + }, + + fix: function(event) { + // Fix target property, if necessary + if ( !event.target && event.srcElement ) + event.target = event.srcElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == undefined && event.clientX != undefined ) { + var e = document.documentElement, b = document.body; + event.pageX = event.clientX + (e.scrollLeft || b.scrollLeft); + event.pageY = event.clientY + (e.scrollTop || b.scrollTop); + } + + // check if target is a textnode (safari) + if (jQuery.browser.safari && event.target.nodeType == 3) { + // store a copy of the original event object + // and clone because target is read only + var originalEvent = event; + event = jQuery.extend({}, originalEvent); + + // get parentnode from textnode + event.target = originalEvent.target.parentNode; + + // add preventDefault and stopPropagation since + // they will not work on the clone + event.preventDefault = function() { + return originalEvent.preventDefault(); + }; + event.stopPropagation = function() { + return originalEvent.stopPropagation(); + }; + } + + // fix preventDefault and stopPropagation + if (!event.preventDefault) + event.preventDefault = function() { + this.returnValue = false; + }; + + if (!event.stopPropagation) + event.stopPropagation = function() { + this.cancelBubble = true; + }; + + return event; + } +}; + +jQuery.fn.extend({ + bind: function( type, data, fn ) { + return this.each(function(){ + jQuery.event.add( this, type, fn || data, 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); + }, data); + }); + }, + unbind: function( type, fn ) { + return this.each(function(){ + jQuery.event.remove( this, type, fn ); + }); + }, + trigger: function( type, data ) { + return this.each(function(){ + jQuery.event.trigger( type, data, this ); + }); + }, + toggle: function() { + // Save reference to arguments for access in closure + var a = arguments; + + return this.click(function(e) { + // Figure out which function to execute + this.lastToggle = this.lastToggle == 0 ? 1 : 0; + + // Make sure that clicks stop + e.preventDefault(); + + // and execute the function + return a[this.lastToggle].apply( this, [e] ) || false; + }); + }, + hover: function(f,g) { + + // A private function for handling mouse 'hovering' + function handleHover(e) { + // Check if mouse(over|out) are still within the same parent element + var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget; + + // Traverse up the tree + while ( p && p != this ) try { p = p.parentNode } catch(e) { p = this; }; + + // If we actually just moused on to a sub-element, ignore it + if ( p == this ) return false; + + // Execute the right function + return (e.type == "mouseover" ? f : g).apply(this, [e]); + } + + // Bind the function to the two event listeners + return this.mouseover(handleHover).mouseout(handleHover); + }, + ready: function(f) { + // If the DOM is already ready + if ( jQuery.isReady ) + // Execute the function immediately + f.apply( document, [jQuery] ); + + // Otherwise, remember the function for later + else { + // Add the function to the wait list + jQuery.readyList.push( function() { return f.apply(this, [jQuery]) } ); + } + + return this; + } +}); + +jQuery.extend({ + /* + * All the code that makes DOM Ready work nicely. + */ + 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; + } + // Remove event lisenter to avoid memory leak + if ( jQuery.browser.mozilla || jQuery.browser.opera ) + document.removeEventListener( "DOMContentLoaded", jQuery.ready, false ); + } + } +}); + +new function(){ + + 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,o){ + + // Handle event binding + jQuery.fn[o] = function(f){ + return f ? this.bind(o, f) : this.trigger(o); + }; + + }); + + // If Mozilla is used + if ( jQuery.browser.mozilla || jQuery.browser.opera ) + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", jQuery.ready, false ); + + // If IE is used, use the excellent hack by Matthias Miller + // http://www.outofhanwell.com/blog/index.php?title=the_window_onload_problem_revisited + else if ( jQuery.browser.msie ) { + + // Only works if you document.write() it + document.write("<scr" + "ipt id=__ie_init defer=true " + + "src=//:><\/script>"); + + // Use the defer script hack + var script = document.getElementById("__ie_init"); + + // script does not exist if jQuery is loaded dynamically + if ( script ) + script.onreadystatechange = function() { + if ( this.readyState != "complete" ) return; + this.parentNode.removeChild( this ); + jQuery.ready(); + }; + + // Clear from memory + script = null; + + // If Safari is used + } else if ( jQuery.browser.safari ) + // Continually check to see if the document.readyState is valid + jQuery.safariTimer = setInterval(function(){ + // loaded and complete are both valid states + if ( document.readyState == "loaded" || + document.readyState == "complete" ) { + + // If either one are found, remove the timer + clearInterval( jQuery.safariTimer ); + jQuery.safariTimer = null; + + // and execute any waiting functions + jQuery.ready(); + } + }, 10); + + // A fallback to window.onload, that will always work + jQuery.event.add( window, "load", jQuery.ready ); + +}; + +// Clean up after IE to avoid memory leaks +if (jQuery.browser.msie) + jQuery(window).one("unload", function() { + var global = jQuery.event.global; + for ( var type in global ) { + var els = global[type], i = els.length; + if ( i && type != 'unload' ) + do + jQuery.event.remove(els[i-1], type); + while (--i); + } + }); +jQuery.fn.extend({ + loadIfModified: function( url, params, callback ) { + this.load( url, params, callback, 1 ); + }, + load: function( url, params, callback, ifModified ) { + if ( jQuery.isFunction( url ) ) + return this.bind("load", url); + + 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, + data: params, + ifModified: ifModified, + complete: function(res, status){ + if ( status == "success" || !ifModified && status == "notmodified" ) + // Inject the HTML into all the matched elements + self.attr("innerHTML", res.responseText) + // Execute all the scripts inside of the newly-injected HTML + .evalScripts() + // Execute callback + .each( callback, [res.responseText, status, res] ); + else + callback.apply( self, [res.responseText, status, res] ); + } + }); + return this; + }, + serialize: function() { + return jQuery.param( this ); + }, + evalScripts: function() { + return this.find("script").each(function(){ + if ( this.src ) + jQuery.getScript( this.src ); + else + jQuery.globalEval( this.text || this.textContent || this.innerHTML || "" ); + }).end(); + } + +}); + +// If IE is used, create a wrapper for the XMLHttpRequest object +if ( !window.XMLHttpRequest ) + XMLHttpRequest = function(){ + return new ActiveXObject("Microsoft.XMLHTTP"); + }; + +// 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); + }; +}); + +jQuery.extend({ + get: function( url, data, callback, type, ifModified ) { + // shift arguments if data argument was ommited + if ( jQuery.isFunction( data ) ) { + callback = data; + data = null; + } + + return jQuery.ajax({ + url: url, + data: data, + success: callback, + dataType: type, + ifModified: ifModified + }); + }, + getIfModified: function( url, data, callback, type ) { + return jQuery.get(url, data, callback, type, 1); + }, + 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 + }); + }, + + // timeout (ms) + //timeout: 0, + ajaxTimeout: function( timeout ) { + jQuery.ajaxSettings.timeout = timeout; + }, + 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 + }, + + // Last-Modified header cache for next request + lastModified: {}, + ajax: function( s ) { + // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout + s = jQuery.extend({}, jQuery.ajaxSettings, s); + + // if data available + if ( s.data ) { + // convert data if not already a string + if (s.processData && typeof s.data != "string") + s.data = jQuery.param(s.data); + // append data to url for get requests + if( s.type.toLowerCase() == "get" ) { + // "?" + data or "&" + data (in case there are already params) + s.url += ((s.url.indexOf("?") > -1) ? "&" : "?") + 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" ); + + var requestDone = false; + + // Create the request object + var xml = new XMLHttpRequest(); + + // Open the socket + xml.open(s.type, s.url, s.async); + + // 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"); + + // Make sure the browser sends the right content length + if ( xml.overrideMimeType ) + xml.setRequestHeader("Connection", "close"); + + // 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 ( xml && (xml.readyState == 4 || isTimeout == "timeout") ) { + requestDone = true; + + // clear poll interval + if (ival) { + clearInterval(ival); + ival = null; + } + + var status; + try { + status = jQuery.httpSuccess( xml ) && isTimeout != "timeout" ? + s.ifModified && jQuery.httpNotModified( xml, s.url ) ? "notmodified" : "success" : "error"; + // Make sure that the request was successful or notmodified + if ( status != "error" ) { + // 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; + + // process the data (runs the xml through httpData regardless of callback) + var data = jQuery.httpData( xml, s.dataType ); + + // 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] ); + } else + jQuery.handleError(s, xml, status); + } catch(e) { + status = "error"; + jQuery.handleError(s, xml, status, e); + } + + // 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" ); + + // Process result + if ( s.complete ) + s.complete(xml, status); + + // Stop memory leaks + if(s.async) + xml = null; + } + }; + + // 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(); + + // 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 { + return !r.status && location.protocol == "file:" || + ( r.status >= 200 && r.status < 300 ) || r.status == 304 || + 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; + }, + + /* Get the data out of an XMLHttpRequest. + * Return parsed XML if content-type header is "xml" and type is "xml" or omitted, + * otherwise return plain text. + * (String) data - The type of data that you're expecting back, + * (e.g. "xml", "html", "script") + */ + httpData: function( r, type ) { + var ct = r.getResponseHeader("content-type"); + var data = !type && ct && ct.indexOf("xml") >= 0; + data = type == "xml" || data ? r.responseXML : r.responseText; + + // 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" ) + eval( "data = " + data ); + + // evaluate scripts within html + if ( type == "html" ) + jQuery("<div>").html(data).evalScripts(); + + 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("&"); + }, + + // evalulates a script in global context + // not reliable for safari + globalEval: function( data ) { + if ( window.execScript ) + window.execScript( data ); + else if ( jQuery.browser.safari ) + // safari doesn't provide a synchronous global eval + window.setTimeout( data, 0 ); + else + eval.call( window, data ); + } + +}); +jQuery.fn.extend({ + + show: function(speed,callback){ + var hidden = this.filter(":hidden"); + speed ? + hidden.animate({ + height: "show", width: "show", opacity: "show" + }, speed, callback) : + + hidden.each(function(){ + this.style.display = this.oldblock ? this.oldblock : ""; + if ( jQuery.css(this,"display") == "none" ) + this.style.display = "block"; + }); + return this; + }, + + hide: function(speed,callback){ + var visible = this.filter(":visible"); + speed ? + visible.animate({ + height: "hide", width: "hide", opacity: "hide" + }, speed, callback) : + + visible.each(function(){ + this.oldblock = this.oldblock || jQuery.css(this,"display"); + if ( this.oldblock == "none" ) + this.oldblock = "block"; + this.style.display = "none"; + }); + return this; + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + toggle: function( fn, fn2 ){ + var args = arguments; + return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ? + this._toggle( fn, fn2 ) : + this.each(function(){ + jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ] + .apply( jQuery(this), args ); + }); + }, + 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.each(function(){ + var state = jQuery(this).is(":hidden") ? "show" : "hide"; + jQuery(this).animate({height: state}, 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 ) { + return this.queue(function(){ + + this.curAnim = jQuery.extend({}, prop); + var opt = jQuery.speed(speed, easing, callback); + + for ( var p in prop ) { + var e = new jQuery.fx( this, opt, p ); + if ( prop[p].constructor == Number ) + e.custom( e.cur(), prop[p] ); + else + e[ prop[p] ]( prop ); + } + + }); + }, + queue: function(type,fn){ + if ( !fn ) { + fn = type; + type = "fx"; + } + + return this.each(function(){ + if ( !this.queue ) + this.queue = {}; + + if ( !this.queue[type] ) + this.queue[type] = []; + + this.queue[type].push( fn ); + + if ( this.queue[type].length == 1 ) + fn.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(){ + jQuery.dequeue(this, "fx"); + if ( jQuery.isFunction( opt.old ) ) + opt.old.apply( this ); + }; + + return opt; + }, + + easing: {}, + + queue: {}, + + dequeue: function(elem,type){ + type = type || "fx"; + + if ( elem.queue && elem.queue[type] ) { + // Remove self + elem.queue[type].shift(); + + // Get next function + var f = elem.queue[type][0]; + + if ( f ) f.apply( elem ); + } + }, + + /* + * I originally wrote fx() as a clone of moo.fx and in the process + * of making it small in size the code became illegible to sane + * people. You've been warned. + */ + + fx: function( elem, options, prop ){ + + var z = this; + + // The styles + var y = elem.style; + + // Store display property + var oldDisplay = jQuery.css(elem, "display"); + + // Make sure that nothing sneaks out + y.overflow = "hidden"; + + // Simple function for setting a style value + z.a = function(){ + if ( options.step ) + options.step.apply( elem, [ z.now ] ); + + if ( prop == "opacity" ) + jQuery.attr(y, "opacity", z.now); // Let attr handle opacity + else if ( parseInt(z.now) ) // My hate for IE will never die + y[prop] = parseInt(z.now) + "px"; + + y.display = "block"; // Set display property to block for animation + }; + + // Figure out the maximum number to run to + z.max = function(){ + return parseFloat( jQuery.css(elem,prop) ); + }; + + // Get the current size + z.cur = function(){ + var r = parseFloat( jQuery.curCSS(elem, prop) ); + return r && r > -10000 ? r : z.max(); + }; + + // Start an animation from one number to another + z.custom = function(from,to){ + z.startTime = (new Date()).getTime(); + z.now = from; + z.a(); + + z.timer = setInterval(function(){ + z.step(from, to); + }, 13); + }; + + // Simple 'show' function + z.show = function(){ + if ( !elem.orig ) elem.orig = {}; + + // Remember where we started, so that we can go back to it later + elem.orig[prop] = this.cur(); + + options.show = true; + + // Begin the animation + z.custom(0, elem.orig[prop]); + + // Stupid IE, look what you made me do + if ( prop != "opacity" ) + y[prop] = "1px"; + }; + + // Simple 'hide' function + z.hide = function(){ + if ( !elem.orig ) elem.orig = {}; + + // Remember where we started, so that we can go back to it later + elem.orig[prop] = this.cur(); + + options.hide = true; + + // Begin the animation + z.custom(elem.orig[prop], 0); + }; + + //Simple 'toggle' function + z.toggle = function() { + if ( !elem.orig ) elem.orig = {}; + + // Remember where we started, so that we can go back to it later + elem.orig[prop] = this.cur(); + + if(oldDisplay == "none") { + options.show = true; + + // Stupid IE, look what you made me do + if ( prop != "opacity" ) + y[prop] = "1px"; + + // Begin the animation + z.custom(0, elem.orig[prop]); + } else { + options.hide = true; + + // Begin the animation + z.custom(elem.orig[prop], 0); + } + }; + + // Each step of an animation + z.step = function(firstNum, lastNum){ + var t = (new Date()).getTime(); + + if (t > options.duration + z.startTime) { + // Stop the timer + clearInterval(z.timer); + z.timer = null; + + z.now = lastNum; + z.a(); + + if (elem.curAnim) elem.curAnim[ prop ] = true; + + var done = true; + for ( var i in elem.curAnim ) + if ( elem.curAnim[i] !== true ) + done = false; + + if ( done ) { + // Reset the overflow + y.overflow = ""; + + // Reset the display + y.display = oldDisplay; + if (jQuery.css(elem, "display") == "none") + y.display = "block"; + + // Hide the element if the "hide" operation was done + if ( options.hide ) + y.display = "none"; + + // Reset the properties, if the item has been hidden or shown + if ( options.hide || options.show ) + for ( var p in elem.curAnim ) + if (p == "opacity") + jQuery.attr(y, p, elem.orig[p]); + else + y[p] = ""; + } + + // If a callback was provided, execute it + if ( done && jQuery.isFunction( options.complete ) ) + // Execute the complete function + options.complete.apply( elem ); + } else { + var n = t - this.startTime; + // Figure out where in the animation we are and set the number + var p = n / options.duration; + + // If the easing function exists, then use it + z.now = options.easing && jQuery.easing[options.easing] ? + jQuery.easing[options.easing](p, n, firstNum, (lastNum-firstNum), options.duration) : + // else use default linear easing + ((-Math.cos(p*Math.PI)/2) + 0.5) * (lastNum-firstNum) + firstNum; + + // Perform the next step of the animation + z.a(); + } + }; + + } +}); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/notes.sql Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,43 @@ +SELECT f.forum_id,f.forum_type,f.forum_name,f.forum_desc, + COUNT(t.topic_id) AS num_topics, COUNT(p.post_id) AS num_posts, + p.post_id,t.topic_id,t.topic_title,u.username,u.user_level,p.timestamp FROM decir_forums AS f + LEFT JOIN decir_topics AS t + ON (t.forum_id=f.forum_id) + LEFT JOIN decir_posts AS p + ON (p.topic_id=t.topic_id) + LEFT JOIN users AS u + ON (u.user_id=f.last_post_user) + WHERE ( t.topic_id=f.last_post_topic AND p.post_id=f.last_post_id ) OR ( f.last_post_topic IS NULL AND f.last_post_id IS NULL ) + GROUP BY f.parent,f.forum_id + ORDER BY f.forum_order; + +SELECT COUNT(t.topic_id) AS num_topics, COUNT(p.post_id) AS num_posts FROM decir_forums AS f + LEFT JOIN decir_topics AS t + ON (t.forum_id=f.forum_id) + LEFT JOIN decir_posts AS p + ON (p.topic_id=t.topic_id) + GROUP BY f.forum_id + ORDER BY f.forum_order; + +INSERT INTO decir_forums(forum_id,forum_type,forum_name,forum_order) VALUES(1,2,'Test category',1); +INSERT INTO decir_forums(forum_id,forum_type,forum_name,forum_desc,parent,forum_order,last_post_id,last_post_topic,last_post_user) VALUES(3,1,'Test forum 1','This is just a test forum.',1,2,3,3,2); +INSERT INTO decir_topics(topic_id,forum_id,topic_title,topic_icon,topic_starter,timestamp) VALUES(1,3,'Test topic 1',1,2,UNIX_TIMESTAMP()); +INSERT INTO decir_posts(post_id,topic_id,poster_id,poster_name,timestamp) VALUES(1,1,2,'Dan',UNIX_TIMESTAMP()); +INSERT INTO decir_posts_text(post_id,post_text,bbcode_uid) VALUES(1,'This post was created manually using SQL queries. +It is nothing more than a [b:0123456789]proof of concept[/b:0123456789]! + +-Dan','0123456789'); +INSERT INTO decir_forums(forum_id,forum_type,forum_name,forum_desc,parent,forum_order,last_post_id,last_post_topic,last_post_user) VALUES(4,1,'Test forum 2','This is just a test forum.',1,3,2,2,2); +INSERT INTO decir_topics(topic_id,forum_id,topic_title,topic_icon,topic_starter,timestamp) VALUES(2,4,'Test topic 2',1,2,UNIX_TIMESTAMP()); +INSERT INTO decir_posts(post_id,topic_id,poster_id,poster_name,timestamp) VALUES(2,2,2,'Dan',UNIX_TIMESTAMP()); +INSERT INTO decir_posts_text(post_id,post_text,bbcode_uid) VALUES(2,'This post was created manually using SQL queries. +It is nothing more than a [b:0123456789]proof of concept[/b:0123456789]! + +-Dan','0123456789'); +INSERT INTO decir_topics(topic_id,forum_id,topic_title,topic_icon,topic_starter,timestamp) VALUES(3,3,'Test topic 3',1,2,UNIX_TIMESTAMP()); +INSERT INTO decir_posts(post_id,topic_id,poster_id,poster_name,timestamp) VALUES(3,3,2,'Dan',UNIX_TIMESTAMP()); +INSERT INTO decir_posts_text(post_id,post_text,bbcode_uid) VALUES(3,'This post was created manually using SQL queries. +It is nothing more than a [b:0123456789]proof of concept[/b:0123456789]! + +-Dan','0123456789'); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/posting.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,239 @@ +<?php +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * posting.php - post topics and replies + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +require('common.php'); +require('bbcode.php'); + +// +// Set mode and parameters +// + +$mode = 'topic'; + +if ( $paths->getParam(1) ) +{ + $n = strtolower($paths->getParam(1)); + if ( $n == 'reply' || $n == 'post' ) + { + $mode = 'reply'; + } + elseif ( $n == 'quote' ) + { + $mode = 'quote'; + } +} + +// Set the parameters for posting, then encrypt it so we don't have to do authorization checks again +// Why? Because it's better than going through some session system for postings where the data is stored on the server +// We already have AES encryption - might as well use it ;-) +$aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE); + +$do_preview = false; + +if ( isset($_GET['act']) && $_GET['act'] == 'post' ) +{ + if ( !is_array($_POST['do']) ) + die('Hacking attempt'); + + if ( isset($_POST['do']['preview']) ) + { + $do_preview = true; + $parms = $_POST['authorization']; + $parms2 = $aes->decrypt($parms, $session->private_key, ENC_HEX); + if ( !$parms2 || substr($parms2, 0, 1) != 'a' ) + { + die('Hacking attempt: ' . $parms2); + } + $parms2 = unserialize($parms2); + $mode = 'already_taken_care_of'; + } + else if ( isset($_POST['do']['post']) ) + { + // Decrypt authorization array + $parms = $aes->decrypt($_POST['authorization'], $session->private_key, ENC_HEX); + $parms = unserialize($parms); + + // Perform a little input validation + $errors = Array(); + if ( empty($_POST['post_text']) ) + $errors[] = 'Please enter a post.'; + if ( empty($_POST['subject']) && $parms['mode'] == 'topic' ) + $errors[] = 'Please enter a topic title.'; + // It's OK to trust this! The auth key is encrypted with the site's private key. + if ( !$parms['authorized'] ) + $errors[] = 'Invalid authorization key'; + + if ( sizeof($errors) > 0 ) + { + // Collect other options + + // Submit post + decir_submit_post(); + return; + } + } +} + +if ( $mode == 'reply' || $mode == 'quote' ) +{ + if ( $mode == 'reply' ) + { + $message = ''; + // Validate topic ID + $topic_id = intval($paths->getParam(2)); + if ( empty($topic_id) ) + die_friendly('Error', '<p>Invalid topic ID</p>'); + $title = 'Reply to topic'; + } + else if ( $mode == 'quote' ) + { + + /** + * @TODO: validate read permissions + */ + + $post_id = intval($paths->getParam(2)); + if ( empty($post_id) ) + die_friendly('Error', '<p>Invalid post ID</p>'); + + // Get post text and topic ID + $q = $db->sql_query('SELECT p.topic_id,t.post_text,t.bbcode_uid,p.poster_name FROM '.table_prefix.'decir_posts AS p + LEFT JOIN '.table_prefix.'decir_posts_text AS t + ON ( p.post_id = t.post_id ) + WHERE p.post_id=' . $post_id . ';'); + + if ( !$q ) + $db->_die(); + + if ( $db->numrows() < 1 ) + die_friendly('Error', '<p>The post you requested does not exist.</p>'); + + $row = $db->fetchrow(); + $db->free_result(); + + $message = '[quote="' . $row['poster_name'] . '"]' . bbcode_strip_uid( $row['post_text'], $row['bbcode_uid'] ) . '[/quote]'; + $quote_poster = $row['poster_name']; + $topic_id = intval($row['topic_id']); + + $title = 'Reply to topic with quote'; + + } + + // Topic ID is good, verify topic status + $q = $db->sql_query('SELECT topic_id,forum_id,topic_type,topic_locked,topic_moved FROM '.table_prefix.'decir_topics WHERE topic_id=' . $topic_id . ';'); + + if ( !$q ) + $db->_die(); + + $row = $db->fetchrow(); + $db->free_result(); + + $forum_perms = $session->fetch_page_acl('DecirForum', $row['forum_id']); + $topic_perms = $session->fetch_page_acl('DecirTopic', $row['topic_id']); + + if ( !$forum_perms->get_permissions('decir_see_forum') ) + die_friendly('Error', '<p>The forum you requested does not exist.</p>'); + + if ( !$topic_perms->get_permissions('decir_reply') ) + die_friendly('Access denied', '<p>You are not allowed to post replies in this topic.</p>'); + + $forum_in = intval($row['forum_id']); + $topic_in = intval($row['topic_id']); + + $parms = Array( + 'mode' => $mode, + 'forum_in' => $forum_in, + 'topic_in' => $topic_in, + 'timestamp' => time(), + 'authorized' => true + ); + + $parms = serialize($parms); + $parms = $aes->encrypt($parms, $session->private_key, ENC_HEX); + +} +else if ( $mode == 'topic' ) +{ + $message = ''; + // Validate topic ID + $forum_id = intval($paths->getParam(2)); + if ( empty($forum_id) ) + die_friendly('Error', '<p>Invalid forum ID</p>'); + $title = 'Post new topic'; + + // Topic ID is good, verify topic status + $q = $db->sql_query('SELECT forum_id FROM '.table_prefix.'decir_forums WHERE forum_id=' . $forum_id . ';'); + + if ( !$q ) + $db->_die(); + + if ( $db->numrows() < 1 ) + die_friendly('Error', '<p>The forum you requested does not exist.</p>'); + + $row = $db->fetchrow(); + $db->free_result(); + + $forum_perms = $session->fetch_page_acl('DecirForum', $row['forum_id']); + + if ( !$forum_perms->get_permissions('decir_see_forum') ) + die_friendly('Error', '<p>The forum you requested does not exist.</p>'); + + $parms = Array( + 'mode' => $mode, + 'forum_in' => $forum_in, + 'timestamp' => time(), + 'authorized' => true + ); + + $parms = serialize($parms); + $parms = $aes->encrypt($parms, $session->private_key, ENC_HEX); + +} +else if ( $mode == 'already_taken_care_of' ) +{ + $mode = $parms2['mode']; + $title = ( $mode == 'topic' ) ? 'Post new topic' : ( $mode == 'reply' ) ? 'Reply to topic' : ( $mode == 'quote' ) ? 'Reply to topic with quote' : 'Duh...'; +} +else +{ + die_friendly('Invalid request', '<p>Invalid action defined</p>'); +} + +$template->tpl_strings['PAGE_NAME'] = $title; +$template->add_header('<!-- DECIR BEGIN --> + <script type="text/javascript" src="' . scriptPath . '/decir/js/bbcedit.js"></script> + <script type="text/javascript" src="' . scriptPath . '/decir/js/colorpick/jquery.js"></script> + <script type="text/javascript" src="' . scriptPath . '/decir/js/colorpick/farbtastic.js"></script> + <link rel="stylesheet" type="text/css" href="' . scriptPath . '/decir/js/bbcedit.css" /> + <link rel="stylesheet" type="text/css" href="' . scriptPath . '/decir/js/colorpick/farbtastic.css" /> + <!-- DECIR END -->'); + +$template->header(); + +if ( $do_preview ) +{ + echo 'Doing preview'; +} + +$url = makeUrlNS('Special', 'Forum/New', 'act=post', true); +echo '<br /> + <form action="' . $url . '" method="post" enctype="multipart/form-data">'; +echo '<textarea name="post_text" class="bbcode" rows="20" cols="80">' . $message . '</textarea>'; +echo '<input type="hidden" name="authorization" value="' . $parms . '" />'; +echo '<div style="text-align: center; margin-top: 10px;"><input type="submit" name="do[post]" value="Submit post" style="font-weight: bold;" /> <input type="submit" name="do[preview]" value="Show preview" /></div>'; +echo '</form>'; + +$template->footer(); + +?>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/viewforum.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,85 @@ +<?php +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * install.php - Database installation wizard + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +require('common.php'); + +$template->header(); + +$fid = ( $n = $paths->getParam(1) ) ? $n : ( ( isset($_GET['fid']) ) ? $_GET['fid'] : 0 ); +$fid = intval($fid); + +if(empty($fid)) +{ + echo '<p>Invalid forum ID</p>'; + $template->footer(); + return; +} + +$perms = $session->fetch_page_acl((string)$fid, 'DecirForum'); +if ( !$perms->get_permissions('decir_view_forum') ) +{ + die_friendly('Access denied', '<p>You are not authorized to view this forum.</p>'); +} + +$sort_column = ( isset($_GET['sort_column']) && in_array($_GET['sort_column'], array('t.timestamp', 't.topic_title')) ) ? $_GET['sort_column'] : 't.timestamp'; +$sort_dir = ( isset($_GET['sort_dir']) && in_array($_GET['sort_dir'], array('ASC', 'DESC')) ) ? $_GET['sort_dir'] : 'DESC'; + +$q = $db->sql_query('SELECT t.topic_id,t.topic_title,t.topic_type,t.topic_icon,COUNT(p.post_id)-1 AS num_replies, + COUNT(h.hit_id) AS num_views,t.topic_starter AS starter_id, u.username AS topic_starter, + p.poster_name AS last_post_name, p.timestamp AS last_post_time + FROM '.table_prefix.'decir_topics AS t + LEFT JOIN '.table_prefix.'decir_posts AS p + ON (t.last_post=p.post_id) + LEFT JOIN '.table_prefix.'decir_hits AS h + ON (t.topic_id=h.topic_id) + LEFT JOIN '.table_prefix.'users AS u + ON (u.user_id=t.topic_starter) + WHERE t.forum_id='.$fid.' + GROUP BY t.topic_id + ORDER BY '.$sort_column.' '.$sort_dir.';'); + +if(!$q) + $db->_die(); + +echo '<div class="tblholder"> + <table border="0" cellspacing="1" cellpadding="4"> + <tr> + <th colspan="3">Topic</th> + <th>Author</th> + <th>Replies</th> + <th>Views</th> + <th>Last post</th> + </th>'; + +if ( $row = $db->fetchrow() ) +{ + do + { + echo '<tr> + <td class="row2"></td> + <td class="row2"></td> + <td class="row2" style="width: 100%;"><b><a href="' . makeUrlNS('DecirTopic', $row['topic_id']) . '">' . $row['topic_title'] . '</a></b></td> + <td class="row3" style="text-align: center; max-width: 100px;">' . $row['topic_starter'] . '</td> + <td class="row1" style="text-align: center; width: 50px;">' . $row['num_replies'] . '</td> + <td class="row1" style="text-align: center; width: 50px;">' . $row['num_views'] . '</td> + <td class="row3" style="text-align: center;"><small style="white-space: nowrap;">' . date('d M Y h:i a', $row['last_post_time']) . '<br />by '.$row['last_post_name'].'</small></td> + </tr>'; + } while ( $row = $db->fetchrow() ); +} + +echo '</table></div>'; + +$template->footer(); + +?>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decir/viewtopic.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,223 @@ +<?php +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * viewtopic.php - Shows individual posts + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +require('common.php'); +require('bbcode.php'); + +global $whos_online; + +$template->header(); + +if ( strtolower($paths->getParam(0)) == 'post' || isset($_GET['pid']) ) +{ + $pid = ( $n = $paths->getParam(1) ) ? $n : ( ( isset($_GET['pid']) ) ? $_GET['pid'] : 0 ); + $pid = intval($pid); + + if(empty($pid)) + { + echo '<p>Invalid topic ID</p>'; + $template->footer(); + return; + } + + $q = $db->sql_query('SELECT topic_id FROM '.table_prefix.'decir_posts WHERE post_id='.$pid.';'); + if ( !$q ) + $db->_die(); + + $row = $db->fetchrow(); +} +else +{ + $tid = ( $n = $paths->getParam(1) ) ? $n : ( ( isset($_GET['tid']) ) ? $_GET['tid'] : 0 ); + $tid = intval($tid); + + if(empty($tid)) + { + echo '<p>Invalid topic ID</p>'; + $template->footer(); + return; + } +} + +$q = $db->sql_query('SELECT forum_id,topic_id FROM '.table_prefix.'decir_topics WHERE topic_id='.$tid.';'); + +if ( !$q ) + $db->_die(); + +$topic_exists = true; + +if ( $db->numrows() > 0 ) +{ + $row = $db->fetchrow(); + $forum_id = $row['forum_id']; + $topic_id = $row['topic_id']; + $topic_exists = true; +} +else +{ + $topic_exists = false; +} + +$post_template = <<<TPLCODE +<a name="{POST_ID}" id="{POST_ID}"></a> +<div class="post tblholder"> + <table border="0" cellspacing="1" cellpadding="4" style="width: 100%;"> + <tr> + <th colspan="2" style="text-align: left;">Posted: {TIMESTAMP}</th> + </tr> + <tr> + <td class="row3" valign="top"> + {POST_TEXT} + </td> + <td class="row1" style="width: 120px;" valign="top"> + <div class="menu"> + {USER_LINK} + <ul> + <li><a>View profile</a></li> + <li><a>Visit homepage</a></li> + <li><a href="{QUOTE_LINK}">Quote this post</a></li> + <li><a>Vote to ban this user</a></li> + <li><a>Send private message</a></li> + <li><a>View all messages posted by {USERNAME}</a></li> + </ul> + </div> + <span class="menuclear"></span> + {USER_TITLE}<br /> + <br /> + Joined: {REG_TIME} + <!-- BEGIN whos_online_support --> + <br /> + <!-- BEGIN user_is_online --> + <span style="color: #007900;"><b>Online</b></span> + <!-- BEGINELSE user_is_online --> + <span style="color: #666666;">Offline</span> + <!-- END user_is_online --> + <!-- END whos_online_support --> + </td> + </tr> + </table> +</div> +TPLCODE; + +$sql = 'SELECT p.post_id,p.poster_name,p.poster_id,u.username,p.timestamp,u.user_level,u.reg_time,t.post_text,t.bbcode_uid FROM '.table_prefix.'decir_posts AS p + LEFT JOIN '.table_prefix.'users AS u + ON u.user_id=poster_id + LEFT JOIN '.table_prefix.'decir_posts_text AS t + ON p.post_id=t.post_id + WHERE p.topic_id='.$tid.' + ORDER BY p.timestamp ASC;'; + +$q = $db->sql_query($sql); +if ( !$q ) + $db->_die(); + +if ( $db->numrows() < 1 ) +{ + die_friendly('Error', '<p>The topic you requested does not exist.</p>'); +} + +$parser = $template->makeParserText($post_template); + +while ( $row = $db->fetchrow() ) +{ + $poster_name = ( $row['poster_id'] == 1 ) ? $row['poster_name'] : $row['username']; + $datetime = date('F d, Y h:i a', $row['timestamp']); + $post_text = render_bbcode($row['post_text'], $row['bbcode_uid']); + $regtime = date('F Y', $row['reg_time']); + + $user_color = '#0000AA'; + switch ( $row['user_level'] ) + { + case USER_LEVEL_ADMIN: $user_color = '#AA0000'; break; + case USER_LEVEL_MOD: $user_color = '#00AA00'; break; + } + if ( $row['poster_id'] > 1 ) + { + $user_link = "<a style='color: $user_color; background-color: transparent; display: inline; padding: 0;' href='".makeUrlNS('User', str_replace(' ', '_', $poster_name))."'><big>$poster_name</big></a>"; + } + else + { + $user_link = '<big>'.$poster_name.'</big>'; + } + $quote_link = makeUrlNS('Special', 'Forum/New/Quote/' . $row['post_id'], false, true); + $user_title = 'Anonymous user'; + switch ( $row['user_level'] ) + { + case USER_LEVEL_ADMIN: $user_title = 'Administrator'; break; + case USER_LEVEL_MOD: $user_title = 'Moderator'; break; + case USER_LEVEL_MEMBER:$user_title = 'Member'; break; + case USER_LEVEL_GUEST: $user_title = 'Guest'; break; + } + $parser->assign_vars(Array( + 'POST_ID' => (string)$row['post_id'], + 'USERNAME' => $poster_name, + 'USER_LINK' => $user_link, + 'REG_TIME' => $regtime, + 'TIMESTAMP' => $datetime, + 'POST_TEXT' => $post_text, + 'USER_TITLE' => $user_title, + 'QUOTE_LINK' => $quote_link + )); + // Decir can integrate with the Who's Online plugin + $who_support = $plugins->loaded('WhosOnline'); + $user_online = false; + if ( $who_support && in_array($row['username'], $whos_online['users']) ) + { + $user_online = true; + } + elseif ( $row['poster_id'] < 2 ) + { + $who_support = false; + } + $parser->assign_bool(Array( + 'whos_online_support' => $who_support, + 'user_is_online' => $user_online + )); + echo $parser->run(); +} + +$db->free_result(); + +if ( $topic_exists ) +{ + $can_post_replies = false; + $can_post_topics = false; + + $forum_perms = $session->fetch_page_acl('DecirForum', $forum_id); + $topic_perms = $session->fetch_page_acl('DecirTopic', $topic_id); + + if ( $forum_perms->get_permissions('decir_post') ) + $can_post_topics = true; + + if ( $topic_perms->get_permissions('decir_reply') ) + $can_post_replies = true; + + echo '<p>'; + if ( $can_post_topics ) + { + echo '<a href="' . makeUrlNS('Special', 'Forum/New/Topic/' . $forum_id) . '">Post new topic</a>'; + } + if ( $can_post_topics && $can_post_replies ) + { + echo ' | '; + } + if ( $can_post_replies ) + { + echo '<a href="' . makeUrlNS('Special', 'Forum/New/Reply/' . $topic_id) . '">Add reply</a>'; + } + echo '</p>'; +} + +$template->footer(); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/Decir.php Wed Jun 13 22:33:54 2007 -0400 @@ -0,0 +1,92 @@ +<?php +/* +Plugin Name: Decir +Plugin URI: javascript: // No URL yet, stay tuned! +Description: Decir is an advanced bulletin board system (forum) for Enano. +Author: Dan Fuhry +Version: 0.1 +Author URI: http://www.enanocms.org/ +*/ + +/* + * Decir + * Version 0.1 + * Copyright (C) 2007 Dan Fuhry + * + * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + */ + +define('ENANO_DECIR_VERSION', '0.1'); +define('DECIR_ROOT', ENANO_ROOT . '/decir'); + +$plugins->attachHook('acl_rule_init', 'decir_early_init($this, $session);'); +$plugins->attachHook('base_classes_initted', ' + $paths->add_page(Array( + \'name\'=>\'Forum\', + \'urlname\'=>\'Forum\', + \'namespace\'=>\'Special\', + \'special\'=>0,\'visible\'=>0,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\', + )); + '); + +function decir_early_init(&$paths, &$session) +{ + $paths->addAdminNode('Decir forum configuration', 'General settings', 'DecirGeneral'); + $paths->nslist['DecirForum'] = $paths->nslist['Special'] . 'Forum/ViewForum/'; + $paths->nslist['DecirPost'] = $paths->nslist['Special'] . 'Forum/Post/'; + $paths->nslist['DecirTopic'] = $paths->nslist['Special'] . 'Forum/Topic/'; + + $session->register_acl_type('decir_see_forum', AUTH_ALLOW, 'See forum in index', Array('read'), 'DecirForum'); + $session->register_acl_type('decir_view_forum', AUTH_ALLOW, 'View forum', Array('decir_see_forum'), 'DecirForum'); + $session->register_acl_type('decir_post', AUTH_ALLOW, 'Post new topics', Array('decir_view_forum'), 'DecirForum'); + $session->register_acl_type('decir_reply', AUTH_ALLOW, 'Reply to topics', Array('decir_post'), 'DecirTopic'); +} + +function page_Special_Forum() +{ + global $db, $session, $paths, $template, $plugins; // Common objects + + if ( getConfig('decir_version') != ENANO_DECIR_VERSION || isset($_POST['do_install_finish']) ) + { + require(DECIR_ROOT . '/install.php'); + } + + $act = strtolower( ( $n = $paths->getParam(0) ) ? $n : 'Index' ); + + $curdir = getcwd(); + chdir(DECIR_ROOT); + + switch($act) + { + case 'index': + default: + require('forum_index.php'); + break; + case 'viewforum': + require('viewforum.php'); + break; + case 'topic': + case 'post': + case 'viewtopic': + require('viewtopic.php'); + break; + case 'new': + require('posting.php'); + break; + } + + chdir($curdir); + +} + +function page_Admin_DecirGeneral() +{ + global $db, $session, $paths, $template, $plugins; if($session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN) { header('Location: '.makeUrl($paths->nslist['Special'].'Administration'.urlSeparator.'noheaders')); die('Hacking attempt'); } + echo 'Hello world!'; +} + +?>