From 4cff3f3317c2393f81d64dd19fb48b7caa0083b8 Mon Sep 17 00:00:00 2001 From: Newcomer1989 Date: Sat, 7 Mar 2015 16:41:47 +0100 Subject: [PATCH] made changes --- install.php | 172 + jquerylib/README.md | 52 + jquerylib/jquery.ajaxQueue.js | 116 + jquerylib/jquery.autocomplete.css | 48 + jquerylib/jquery.autocomplete.js | 808 +++++ jquerylib/jquery.autocomplete.min.js | 13 + jquerylib/jquery.autocomplete.pack.js | 12 + jquerylib/jquery.bgiframe.min.js | 10 + jquerylib/jquery.css | 53 + jquerylib/jquery.js | 3558 ++++++++++++++++++++ jquerylib/localdata.js | 216 ++ jquerylib/thickbox-compressed.js | 10 + jquerylib/thickbox.css | 163 + lang.php | 487 +++ license.txt | 674 ++++ list_rankup.php | 226 ++ other/config.php | 65 + other/dbconfig.php | 6 + other/search.php | 18 + other/style.css.php | 48 + other/webinterface_list.php | 248 ++ other/webinterface_login.php | 13 + ts3_lib/Adapter/Abstract.php | 160 + ts3_lib/Adapter/Blacklist.php | 119 + ts3_lib/Adapter/Blacklist/Exception.php | 32 + ts3_lib/Adapter/Exception.php | 32 + ts3_lib/Adapter/FileTransfer.php | 190 ++ ts3_lib/Adapter/FileTransfer/Exception.php | 32 + ts3_lib/Adapter/ServerQuery.php | 261 ++ ts3_lib/Adapter/ServerQuery/Event.php | 170 + ts3_lib/Adapter/ServerQuery/Exception.php | 32 + ts3_lib/Adapter/ServerQuery/Reply.php | 346 ++ ts3_lib/Adapter/TSDNS.php | 95 + ts3_lib/Adapter/TSDNS/Exception.php | 32 + ts3_lib/Adapter/Update.php | 217 ++ ts3_lib/Adapter/Update/Exception.php | 32 + ts3_lib/Exception.php | 129 + ts3_lib/Helper/Char.php | 269 ++ ts3_lib/Helper/Convert.php | 349 ++ ts3_lib/Helper/Crypt.php | 482 +++ ts3_lib/Helper/Exception.php | 32 + ts3_lib/Helper/Profiler.php | 101 + ts3_lib/Helper/Profiler/Exception.php | 32 + ts3_lib/Helper/Profiler/Timer.php | 154 + ts3_lib/Helper/Signal.php | 213 ++ ts3_lib/Helper/Signal/Exception.php | 32 + ts3_lib/Helper/Signal/Handler.php | 78 + ts3_lib/Helper/Signal/Interface.php | 353 ++ ts3_lib/Helper/String.php | 939 ++++++ ts3_lib/Helper/Uri.php | 717 ++++ ts3_lib/Node/Abstract.php | 625 ++++ ts3_lib/Node/Channel.php | 588 ++++ ts3_lib/Node/Channelgroup.php | 276 ++ ts3_lib/Node/Client.php | 441 +++ ts3_lib/Node/Exception.php | 32 + ts3_lib/Node/Host.php | 1193 +++++++ ts3_lib/Node/Server.php | 2536 ++++++++++++++ ts3_lib/Node/Servergroup.php | 300 ++ ts3_lib/TeamSpeak3.php | 980 ++++++ ts3_lib/Transport/Abstract.php | 268 ++ ts3_lib/Transport/Exception.php | 32 + ts3_lib/Transport/TCP.php | 179 + ts3_lib/Transport/UDP.php | 113 + ts3_lib/Viewer/Html.php | 670 ++++ ts3_lib/Viewer/Interface.php | 42 + ts3_lib/Viewer/Text.php | 107 + ts3_lib/license.txt | 674 ++++ update_0-02.php | 40 + update_0-10.php | 73 + webinterface.php | 339 ++ worker.php | 429 +++ 71 files changed, 22583 insertions(+) create mode 100644 install.php create mode 100644 jquerylib/README.md create mode 100644 jquerylib/jquery.ajaxQueue.js create mode 100644 jquerylib/jquery.autocomplete.css create mode 100644 jquerylib/jquery.autocomplete.js create mode 100644 jquerylib/jquery.autocomplete.min.js create mode 100644 jquerylib/jquery.autocomplete.pack.js create mode 100644 jquerylib/jquery.bgiframe.min.js create mode 100644 jquerylib/jquery.css create mode 100644 jquerylib/jquery.js create mode 100644 jquerylib/localdata.js create mode 100644 jquerylib/thickbox-compressed.js create mode 100644 jquerylib/thickbox.css create mode 100644 lang.php create mode 100644 license.txt create mode 100644 list_rankup.php create mode 100644 other/config.php create mode 100644 other/dbconfig.php create mode 100644 other/search.php create mode 100644 other/style.css.php create mode 100644 other/webinterface_list.php create mode 100644 other/webinterface_login.php create mode 100644 ts3_lib/Adapter/Abstract.php create mode 100644 ts3_lib/Adapter/Blacklist.php create mode 100644 ts3_lib/Adapter/Blacklist/Exception.php create mode 100644 ts3_lib/Adapter/Exception.php create mode 100644 ts3_lib/Adapter/FileTransfer.php create mode 100644 ts3_lib/Adapter/FileTransfer/Exception.php create mode 100644 ts3_lib/Adapter/ServerQuery.php create mode 100644 ts3_lib/Adapter/ServerQuery/Event.php create mode 100644 ts3_lib/Adapter/ServerQuery/Exception.php create mode 100644 ts3_lib/Adapter/ServerQuery/Reply.php create mode 100644 ts3_lib/Adapter/TSDNS.php create mode 100644 ts3_lib/Adapter/TSDNS/Exception.php create mode 100644 ts3_lib/Adapter/Update.php create mode 100644 ts3_lib/Adapter/Update/Exception.php create mode 100644 ts3_lib/Exception.php create mode 100644 ts3_lib/Helper/Char.php create mode 100644 ts3_lib/Helper/Convert.php create mode 100644 ts3_lib/Helper/Crypt.php create mode 100644 ts3_lib/Helper/Exception.php create mode 100644 ts3_lib/Helper/Profiler.php create mode 100644 ts3_lib/Helper/Profiler/Exception.php create mode 100644 ts3_lib/Helper/Profiler/Timer.php create mode 100644 ts3_lib/Helper/Signal.php create mode 100644 ts3_lib/Helper/Signal/Exception.php create mode 100644 ts3_lib/Helper/Signal/Handler.php create mode 100644 ts3_lib/Helper/Signal/Interface.php create mode 100644 ts3_lib/Helper/String.php create mode 100644 ts3_lib/Helper/Uri.php create mode 100644 ts3_lib/Node/Abstract.php create mode 100644 ts3_lib/Node/Channel.php create mode 100644 ts3_lib/Node/Channelgroup.php create mode 100644 ts3_lib/Node/Client.php create mode 100644 ts3_lib/Node/Exception.php create mode 100644 ts3_lib/Node/Host.php create mode 100644 ts3_lib/Node/Server.php create mode 100644 ts3_lib/Node/Servergroup.php create mode 100644 ts3_lib/TeamSpeak3.php create mode 100644 ts3_lib/Transport/Abstract.php create mode 100644 ts3_lib/Transport/Exception.php create mode 100644 ts3_lib/Transport/TCP.php create mode 100644 ts3_lib/Transport/UDP.php create mode 100644 ts3_lib/Viewer/Html.php create mode 100644 ts3_lib/Viewer/Interface.php create mode 100644 ts3_lib/Viewer/Text.php create mode 100644 ts3_lib/license.txt create mode 100644 update_0-02.php create mode 100644 update_0-10.php create mode 100644 webinterface.php create mode 100644 worker.php diff --git a/install.php b/install.php new file mode 100644 index 0000000..acf6cb6 --- /dev/null +++ b/install.php @@ -0,0 +1,172 @@ + + + + TS-N.NET Ranksystem - Installation + + + + +query("INSERT INTO config (webuser,webpass,tshost,tsquery,tsvoice,tsuser,tspass,language,queryname,queryname2,grouptime,resetbydbchange,msgtouser,upcheck,uniqueid,updateinfotime,currvers,exceptuuid,exceptgroup,dateformat,showexgrp,showexcld,showcolcld,showcoluuid,showcoldbid,showcolot,showcolit,showcolat,showcolnx,showcolsg,bgcolor,hdcolor,txcolor,hvcolor,ifcolor,wncolor,sccolor,showgen) VALUES ('$user','$pass','localhost','10011','9987','serveradmin','querypass','en','http://ts-n.net/ranksystem.php','http://www.ts-n.net/ranksystem.php','31536000=>47,31536060=>50','1','1','1','xrTKhT/HDl4ea0WoFDQH2zOpmKg=,9odBYAU7z2E2feUz965sL0/MyBom=','7200','0.10-beta','xrTKhT/HDl4ea0WoFDQH2zOpmKg=','2,6','%a days, %h hours, %i mins, %s secs','1','1','1','1','1','1','1','1','1','1','#101010','#909090','#707070','#FFFFFF','#3366CC','#CC0000','#008000','1')")) + { + echo $lang['error'].''.$mysqlcon->error.'.'; + } + else + { + echo''.$lang['isntwiusr'].'

'; + echo''.sprintf($lang['isntwidel'],"webinterface.php").''; + } +} +elseif($db['host']!='hostname') +{ + echo''.sprintf($lang['isntwidel'],"webinterface.php").''; +} +else +{ + if(isset($_POST['installdb'])) + { + $host=$_POST['host']; + $user=$_POST['user']; + $pass=$_POST['pass']; + $dbname=$_POST['dbname']; + $mysqlcon=mysqli_connect($host, $user, $pass); + + if(empty($host) or empty($user) or empty($pass) or empty($dbname) or mysqli_connect_errno()) + { + echo '
+ + + + '; + if(mysqli_connect_errno()) + { echo ''; } + if(empty($host)) + { echo ''; } else + { echo ''; } + if(empty($user)) + { echo ''; } else + { echo ''; } + if(empty($pass)) + { echo ''; } else + { echo ''; } + if(empty($dbname)) + { echo ''; } else + { echo ''; } + echo ' +

'.$lang['instdb'].'

  
'.$lang['isntwidberr'].'
  
'.$lang['isntwidbmsg'].mysqli_connect_error().'
  
'.$lang['isntwidbhost'].'
'.$lang['isntwidbhost'].'
'.$lang['isntwidbusr'].'
'.$lang['isntwidbusr'].'
'.$lang['isntwidbpass'].'
'.$lang['isntwidbpass'].'
'.$lang['isntwidbname'].'
'.$lang['isntwidbname'].'
 
'; + + } + else + { + $newconfig=''; + $handle=fopen('./other/dbconfig.php','w'); + if(!fwrite($handle,$newconfig)) + { + echo $lang['isntwicfg']; + } + else + { + echo '

'.$lang['instdb'].'
'; + $mysqlcon->query("DROP DATABASE $dbname"); + if(!$mysqlcon->query("CREATE DATABASE $dbname")) + { + echo $lang['instdberr'].''.$mysqlcon->error.''; + } + else + { + echo''.sprintf($lang['instdbsuc'],$dbname).''; + $count++; + } + echo '

'.$lang['insttb'].'
'; + if(!$mysqlcon->query("CREATE TABLE $dbname.user (uuid text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,cldbid int(10) NOT NULL,count int(11) NOT NULL,ip int(10) NOT NULL,name text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,lastseen int(10) NOT NULL,grpid int(10) NOT NULL,nextup int(11) NOT NULL,idle int(11) NOT NULL,cldgroup text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,online int(1) NOT NULL)")) + { + echo $lang['insttberr'].''.$mysqlcon->error.'.
'; + } + else + { + echo ''.sprintf($lang['insttbsuc'],'user').'
'; + $count++; + } + if(!$mysqlcon->query("CREATE TABLE $dbname.upcheck (timestamp int(10) NOT NULL)")) + { + echo $lang['insttberr'].''.$mysqlcon->error.'.'; + $mysqlcon->query("INSERT INTO $dbname.upcheck (timestamp) VALUES ('1')"); + } + else + { + echo ''.sprintf($lang['insttbsuc'],'upcheck').'
'; + $count++; + } + if(!$mysqlcon->query("CREATE TABLE $dbname.groups (sgid int(10) NOT NULL,sgidname text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL)")) + { + echo $lang['insttberr'].''.$mysqlcon->error.'.
'; + } + else + { + echo ''.sprintf($lang['insttbsuc'],'groups').'
'; + $count++; + } + if(!$mysqlcon->query("CREATE TABLE $dbname.config (webuser text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,webpass text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,tshost text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,tsquery int(5) NOT NULL,tsvoice int(5) NOT NULL,tsuser text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,tspass text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,language text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,queryname text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,queryname2 text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,grouptime text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,resetbydbchange int(1) NOT NULL,msgtouser int(1) NOT NULL,upcheck int(1) NOT NULL,uniqueid text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,updateinfotime int(11) NOT NULL,currvers text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,substridle int(1) NOT NULL,exceptuuid text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,exceptgroup text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,dateformat text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,showexgrp int(1) NOT NULL,showexcld int(1) NOT NULL,showcolcld int(1) NOT NULL,showcoluuid int(1) NOT NULL,showcoldbid int(1) NOT NULL,showcolot int(1) NOT NULL,showcolit int(1) NOT NULL,showcolat int(1) NOT NULL,showcolnx int(1) NOT NULL,showcolsg int(1) NOT NULL,bgcolor text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,hdcolor text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,txcolor text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,hvcolor text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,ifcolor text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,wncolor text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,sccolor text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,showgen int(1) NOT NULL)")) + { + echo $lang['insttberr'].''.$mysqlcon->error.'.'; + } + else + { + echo ''.sprintf($lang['insttbsuc'],'config').'
'; + $count++; + } + if($count>1) + { + echo '
+ + + + + + + +

'.$lang['isntwiusrh'].'

  
'.$lang['isntwiusrdesc'].'
'.$lang['user'].'
'.$lang['pass'].'
 
'; + } + } + fclose($handle); + } + } + else + { + echo '
+ + + + + + + + + + +
Language:

'.$lang['insttb'].'

  
'.$lang['isntwidb'].'
'.$lang['isntwidbhost'].'
'.$lang['isntwidbusr'].'
'.$lang['isntwidbpass'].'
'.$lang['isntwidbname'].'
 
'; + } +} +?> + + \ No newline at end of file diff --git a/jquerylib/README.md b/jquerylib/README.md new file mode 100644 index 0000000..84185a0 --- /dev/null +++ b/jquerylib/README.md @@ -0,0 +1,52 @@ +#jQuery Autocomplete Plugin 1.2.3# + +##About this jQuery plugin## +Jörn Zaefferer’s (now deprecated into jQuery UI) [jQuery Autocomplete Plugin](http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/), with a small modification to enable UP/DOWN arrow keys, allowing navigation of input element text. This is particularly useful in cases where autocomplete is used in a textarea element where navigation of text via UP/DOWN arrow keys may be necessary. The tiny fix is documented with comments in the relevant lines on the development file (jquery.autocomplete.js). The .min and .pack files have the fix without specific annotation within the code. + +We use it in Claritty.com for when a user wants to write a twitter username or a previously used hashtag within a new tweet, so “@” and “#” are trigger the script. While not triggered, though, the plugin still hijacks the UP/DOWN arrow keys making navigation of the text a pain in the ass when the user has multiple lines. This modification prevents this behavior, hijacking the arrow keys only when the autocomplete script has been triggered. + +The original plugin is now deprecated, but this mod works well with jQuery 1.3.2 thru 1.4.4. + +##Documentation## +Introduction to the original plugin, and an explanation of original plugin options are included in the docs folder. (Documentation at the jQuery site is expected to shut down eventually.) + +This modification shouldn’t break any of the options discussed at the these documentation files. + +##Abridged Changelog## +####1.2.3 --AGA#### +* Merged disc’s fork: Removed depricated $.browser (jquery 1.9+) + +* Merged miketaylr’s fork: account for retooled key events handling in newer Opera + +* Correct typo introduced in one of the previous merges + +* New minified and packed versions + +####1.2.2 --AGA#### +* Merged agmcleaod’s fork: Added failure to the options hash: `failure` + +* Merged borkor’s fork: New option not to jump to first or last position: `scrollJumpPosition` + +* Deleted silly console.debug() line left over from testing. + +####1.2.1 --AGA#### +* Added option to activate select list with one click instead of two. `clickFire` is false by default. (Requested/recommendation by fgosfacdjtq.) + +* Added option to forgo input focus on item select. `inputFocus` is set to true by default, but setting it to false in the options will cancel this behavior. + +* Circumvent browser JS bug when user clicked on results list scrollbar and then clicked outside of the list that caused the list to not hide. + +* Changed hijacking of PAGEUP/PAGEDOWN keys to prevent default behavior only when selections list is visible. Helps text navigation inside input element. + +####1.2 --AGA#### +* Use recursive merging when extending setOptions. (Request/recommendation by smarques.) + +####1.1.1 --AGA#### +* Changed hijacking of UP/DOWN arrow keys to prevent default behavior only when selections list is visible. Helps text navigation inside input element. + +_See_ changelog.txt _for pre-1.1.1 entries by original author._ + +##Licensing## +As with the original Plugin, this modification is dual licensed under the MIT and GPL licenses: + http://www.opensource.org/licenses/mit-license.php + http://www.gnu.org/licenses/gpl.html \ No newline at end of file diff --git a/jquerylib/jquery.ajaxQueue.js b/jquerylib/jquery.ajaxQueue.js new file mode 100644 index 0000000..ca42082 --- /dev/null +++ b/jquerylib/jquery.ajaxQueue.js @@ -0,0 +1,116 @@ +/** + * Ajax Queue Plugin + * + * Homepage: http://jquery.com/plugins/project/ajaxqueue + * Documentation: http://docs.jquery.com/AjaxQueue + */ + +/** + + + + + */ +/* + * Queued Ajax requests. + * A new Ajax request won't be started until the previous queued + * request has finished. + */ + +/* + * Synced Ajax requests. + * The Ajax request will happen as soon as you call this method, but + * the callbacks (success/error/complete) won't fire until all previous + * synced requests have been completed. + */ + + +(function($) { + + var ajax = $.ajax; + + var pendingRequests = {}; + + var synced = []; + var syncedData = []; + + $.ajax = function(settings) { + // create settings for compatibility with ajaxSetup + settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings)); + + var port = settings.port; + + switch(settings.mode) { + case "abort": + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + return pendingRequests[port] = ajax.apply(this, arguments); + case "queue": + var _old = settings.complete; + settings.complete = function(){ + if ( _old ) + _old.apply( this, arguments ); + jQuery([ajax]).dequeue("ajax" + port );; + }; + + jQuery([ ajax ]).queue("ajax" + port, function(){ + ajax( settings ); + }); + return; + case "sync": + var pos = synced.length; + + synced[ pos ] = { + error: settings.error, + success: settings.success, + complete: settings.complete, + done: false + }; + + syncedData[ pos ] = { + error: [], + success: [], + complete: [] + }; + + settings.error = function(){ syncedData[ pos ].error = arguments; }; + settings.success = function(){ syncedData[ pos ].success = arguments; }; + settings.complete = function(){ + syncedData[ pos ].complete = arguments; + synced[ pos ].done = true; + + if ( pos == 0 || !synced[ pos-1 ] ) + for ( var i = pos; i < synced.length && synced[i].done; i++ ) { + if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error ); + if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success ); + if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete ); + + synced[i] = null; + syncedData[i] = null; + } + }; + } + return ajax.apply(this, arguments); + }; + +})(jQuery); \ No newline at end of file diff --git a/jquerylib/jquery.autocomplete.css b/jquerylib/jquery.autocomplete.css new file mode 100644 index 0000000..91b6228 --- /dev/null +++ b/jquerylib/jquery.autocomplete.css @@ -0,0 +1,48 @@ +.ac_results { + padding: 0px; + border: 1px solid black; + background-color: white; + overflow: hidden; + z-index: 99999; +} + +.ac_results ul { + width: 100%; + list-style-position: outside; + list-style: none; + padding: 0; + margin: 0; +} + +.ac_results li { + margin: 0px; + padding: 2px 5px; + cursor: default; + display: block; + /* + if width will be 100% horizontal scrollbar will apear + when scroll mode will be used + */ + /*width: 100%;*/ + font: menu; + font-size: 12px; + /* + it is very important, if line-height not setted or setted + in relative units scroll will be broken in firefox + */ + line-height: 16px; + overflow: hidden; +} + +.ac_loading { + background: white url('indicator.gif') right center no-repeat; +} + +.ac_odd { + background-color: #eee; +} + +.ac_over { + background-color: #0A246A; + color: white; +} diff --git a/jquerylib/jquery.autocomplete.js b/jquerylib/jquery.autocomplete.js new file mode 100644 index 0000000..9d12a29 --- /dev/null +++ b/jquerylib/jquery.autocomplete.js @@ -0,0 +1,808 @@ +/* + * jQuery Autocomplete plugin 1.1 + * + * Copyright (c) 2009 Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $ + */ + +;(function($) { + +$.fn.extend({ + autocomplete: function(urlOrData, options) { + var isUrl = typeof urlOrData == "string"; + options = $.extend({}, $.Autocompleter.defaults, { + url: isUrl ? urlOrData : null, + data: isUrl ? null : urlOrData, + delay: isUrl ? $.Autocompleter.defaults.delay : 10, + max: options && !options.scroll ? 10 : 150 + }, options); + + // if highlight is set to false, replace it with a do-nothing function + options.highlight = options.highlight || function(value) { return value; }; + + // if the formatMatch option is not specified, then use formatItem for backwards compatibility + options.formatMatch = options.formatMatch || options.formatItem; + + return this.each(function() { + new $.Autocompleter(this, options); + }); + }, + result: function(handler) { + return this.bind("result", handler); + }, + search: function(handler) { + return this.trigger("search", [handler]); + }, + flushCache: function() { + return this.trigger("flushCache"); + }, + setOptions: function(options){ + return this.trigger("setOptions", [options]); + }, + unautocomplete: function() { + return this.trigger("unautocomplete"); + } +}); + +$.Autocompleter = function(input, options) { + + var KEY = { + UP: 38, + DOWN: 40, + DEL: 46, + TAB: 9, + RETURN: 13, + ESC: 27, + COMMA: 188, + PAGEUP: 33, + PAGEDOWN: 34, + BACKSPACE: 8 + }; + + // Create $ object for input element + var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); + + var timeout; + var previousValue = ""; + var cache = $.Autocompleter.Cache(options); + var hasFocus = 0; + var lastKeyPressCode; + var config = { + mouseDownOnSelect: false + }; + var select = $.Autocompleter.Select(options, input, selectCurrent, config); + + var blockSubmit; + + // prevent form submit in opera when selecting with return key + $.browser.opera && $(input.form).bind("submit.autocomplete", function() { + if (blockSubmit) { + blockSubmit = false; + return false; + } + }); + + // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all + $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { + // a keypress means the input has focus + // avoids issue where input had focus before the autocomplete was applied + hasFocus = 1; + // track last key pressed + lastKeyPressCode = event.keyCode; + switch(event.keyCode) { + + case KEY.UP: + event.preventDefault(); + if ( select.visible() ) { + select.prev(); + } else { + onChange(0, true); + } + break; + + case KEY.DOWN: + event.preventDefault(); + if ( select.visible() ) { + select.next(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEUP: + event.preventDefault(); + if ( select.visible() ) { + select.pageUp(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEDOWN: + event.preventDefault(); + if ( select.visible() ) { + select.pageDown(); + } else { + onChange(0, true); + } + break; + + // matches also semicolon + case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: + case KEY.TAB: + case KEY.RETURN: + if( selectCurrent() ) { + // stop default to prevent a form submit, Opera needs special handling + event.preventDefault(); + blockSubmit = true; + return false; + } + break; + + case KEY.ESC: + select.hide(); + break; + + default: + clearTimeout(timeout); + timeout = setTimeout(onChange, options.delay); + break; + } + }).focus(function(){ + // track whether the field has focus, we shouldn't process any + // results if the field no longer has focus + hasFocus++; + }).blur(function() { + hasFocus = 0; + if (!config.mouseDownOnSelect) { + hideResults(); + } + }).click(function() { + // show select when clicking in a focused field + if ( hasFocus++ > 1 && !select.visible() ) { + onChange(0, true); + } + }).bind("search", function() { + // TODO why not just specifying both arguments? + var fn = (arguments.length > 1) ? arguments[1] : null; + function findValueCallback(q, data) { + var result; + if( data && data.length ) { + for (var i=0; i < data.length; i++) { + if( data[i].result.toLowerCase() == q.toLowerCase() ) { + result = data[i]; + break; + } + } + } + if( typeof fn == "function" ) fn(result); + else $input.trigger("result", result && [result.data, result.value]); + } + $.each(trimWords($input.val()), function(i, value) { + request(value, findValueCallback, findValueCallback); + }); + }).bind("flushCache", function() { + cache.flush(); + }).bind("setOptions", function() { + $.extend(options, arguments[1]); + // if we've updated the data, repopulate + if ( "data" in arguments[1] ) + cache.populate(); + }).bind("unautocomplete", function() { + select.unbind(); + $input.unbind(); + $(input.form).unbind(".autocomplete"); + }); + + + function selectCurrent() { + var selected = select.selected(); + if( !selected ) + return false; + + var v = selected.result; + previousValue = v; + + if ( options.multiple ) { + var words = trimWords($input.val()); + if ( words.length > 1 ) { + var seperator = options.multipleSeparator.length; + var cursorAt = $(input).selection().start; + var wordAt, progress = 0; + $.each(words, function(i, word) { + progress += word.length; + if (cursorAt <= progress) { + wordAt = i; + return false; + } + progress += seperator; + }); + words[wordAt] = v; + // TODO this should set the cursor to the right position, but it gets overriden somewhere + //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); + v = words.join( options.multipleSeparator ); + } + v += options.multipleSeparator; + } + + $input.val(v); + hideResultsNow(); + $input.trigger("result", [selected.data, selected.value]); + return true; + } + + function onChange(crap, skipPrevCheck) { + if( lastKeyPressCode == KEY.DEL ) { + select.hide(); + return; + } + + var currentValue = $input.val(); + + if ( !skipPrevCheck && currentValue == previousValue ) + return; + + previousValue = currentValue; + + currentValue = lastWord(currentValue); + if ( currentValue.length >= options.minChars) { + $input.addClass(options.loadingClass); + if (!options.matchCase) + currentValue = currentValue.toLowerCase(); + request(currentValue, receiveData, hideResultsNow); + } else { + stopLoading(); + select.hide(); + } + }; + + function trimWords(value) { + if (!value) + return [""]; + if (!options.multiple) + return [$.trim(value)]; + return $.map(value.split(options.multipleSeparator), function(word) { + return $.trim(value).length ? $.trim(word) : null; + }); + } + + function lastWord(value) { + if ( !options.multiple ) + return value; + var words = trimWords(value); + if (words.length == 1) + return words[0]; + var cursorAt = $(input).selection().start; + if (cursorAt == value.length) { + words = trimWords(value) + } else { + words = trimWords(value.replace(value.substring(cursorAt), "")); + } + return words[words.length - 1]; + } + + // fills in the input box w/the first match (assumed to be the best match) + // q: the term entered + // sValue: the first matching result + function autoFill(q, sValue){ + // autofill in the complete box w/the first match as long as the user hasn't entered in more data + // if the last user key pressed was backspace, don't autofill + if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { + // fill in the value (keep the case the user has typed) + $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); + // select the portion of the value not typed by the user (so the next character will erase) + $(input).selection(previousValue.length, previousValue.length + sValue.length); + } + }; + + function hideResults() { + clearTimeout(timeout); + timeout = setTimeout(hideResultsNow, 200); + }; + + function hideResultsNow() { + var wasVisible = select.visible(); + select.hide(); + clearTimeout(timeout); + stopLoading(); + if (options.mustMatch) { + // call search and run callback + $input.search( + function (result){ + // if no value found, clear the input box + if( !result ) { + if (options.multiple) { + var words = trimWords($input.val()).slice(0, -1); + $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); + } + else { + $input.val( "" ); + $input.trigger("result", null); + } + } + } + ); + } + }; + + function receiveData(q, data) { + if ( data && data.length && hasFocus ) { + stopLoading(); + select.display(data, q); + autoFill(q, data[0].value); + select.show(); + } else { + hideResultsNow(); + } + }; + + function request(term, success, failure) { + if (!options.matchCase) + term = term.toLowerCase(); + var data = cache.load(term); + // recieve the cached data + if (data && data.length) { + success(term, data); + // if an AJAX url has been supplied, try loading the data now + } else if( (typeof options.url == "string") && (options.url.length > 0) ){ + + var extraParams = { + timestamp: +new Date() + }; + $.each(options.extraParams, function(key, param) { + extraParams[key] = typeof param == "function" ? param() : param; + }); + + $.ajax({ + // try to leverage ajaxQueue plugin to abort previous requests + mode: "abort", + // limit abortion to this input + port: "autocomplete" + input.name, + dataType: options.dataType, + url: options.url, + data: $.extend({ + q: lastWord(term), + limit: options.max + }, extraParams), + success: function(data) { + var parsed = options.parse && options.parse(data) || parse(data); + cache.add(term, parsed); + success(term, parsed); + } + }); + } else { + // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match + select.emptyList(); + failure(term); + } + }; + + function parse(data) { + var parsed = []; + var rows = data.split("\n"); + for (var i=0; i < rows.length; i++) { + var row = $.trim(rows[i]); + if (row) { + row = row.split("|"); + parsed[parsed.length] = { + data: row, + value: row[0], + result: options.formatResult && options.formatResult(row, row[0]) || row[0] + }; + } + } + return parsed; + }; + + function stopLoading() { + $input.removeClass(options.loadingClass); + }; + +}; + +$.Autocompleter.defaults = { + inputClass: "ac_input", + resultsClass: "ac_results", + loadingClass: "ac_loading", + minChars: 1, + delay: 400, + matchCase: false, + matchSubset: true, + matchContains: false, + cacheLength: 10, + max: 100, + mustMatch: false, + extraParams: {}, + selectFirst: true, + formatItem: function(row) { return row[0]; }, + formatMatch: null, + autoFill: false, + width: 0, + multiple: false, + multipleSeparator: ", ", + highlight: function(value, term) { + return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + }, + scroll: true, + scrollHeight: 180 +}; + +$.Autocompleter.Cache = function(options) { + + var data = {}; + var length = 0; + + function matchSubset(s, sub) { + if (!options.matchCase) + s = s.toLowerCase(); + var i = s.indexOf(sub); + if (options.matchContains == "word"){ + i = s.toLowerCase().search("\\b" + sub.toLowerCase()); + } + if (i == -1) return false; + return i == 0 || options.matchContains; + }; + + function add(q, value) { + if (length > options.cacheLength){ + flush(); + } + if (!data[q]){ + length++; + } + data[q] = value; + } + + function populate(){ + if( !options.data ) return false; + // track the matches + var stMatchSets = {}, + nullData = 0; + + // no url was specified, we need to adjust the cache length to make sure it fits the local data store + if( !options.url ) options.cacheLength = 1; + + // track all options for minChars = 0 + stMatchSets[""] = []; + + // loop through the array and create a lookup structure + for ( var i = 0, ol = options.data.length; i < ol; i++ ) { + var rawValue = options.data[i]; + // if rawValue is a string, make an array otherwise just reference the array + rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; + + var value = options.formatMatch(rawValue, i+1, options.data.length); + if ( value === false ) + continue; + + var firstChar = value.charAt(0).toLowerCase(); + // if no lookup array for this character exists, look it up now + if( !stMatchSets[firstChar] ) + stMatchSets[firstChar] = []; + + // if the match is a string + var row = { + value: value, + data: rawValue, + result: options.formatResult && options.formatResult(rawValue) || value + }; + + // push the current match into the set list + stMatchSets[firstChar].push(row); + + // keep track of minChars zero items + if ( nullData++ < options.max ) { + stMatchSets[""].push(row); + } + }; + + // add the data items to the cache + $.each(stMatchSets, function(i, value) { + // increase the cache size + options.cacheLength++; + // add to the cache + add(i, value); + }); + } + + // populate any existing data + setTimeout(populate, 25); + + function flush(){ + data = {}; + length = 0; + } + + return { + flush: flush, + add: add, + populate: populate, + load: function(q) { + if (!options.cacheLength || !length) + return null; + /* + * if dealing w/local data and matchContains than we must make sure + * to loop through all the data collections looking for matches + */ + if( !options.url && options.matchContains ){ + // track all matches + var csub = []; + // loop through all the data grids for matches + for( var k in data ){ + // don't search through the stMatchSets[""] (minChars: 0) cache + // this prevents duplicates + if( k.length > 0 ){ + var c = data[k]; + $.each(c, function(i, x) { + // if we've got a match, add it to the array + if (matchSubset(x.value, q)) { + csub.push(x); + } + }); + } + } + return csub; + } else + // if the exact item exists, use it + if (data[q]){ + return data[q]; + } else + if (options.matchSubset) { + for (var i = q.length - 1; i >= options.minChars; i--) { + var c = data[q.substr(0, i)]; + if (c) { + var csub = []; + $.each(c, function(i, x) { + if (matchSubset(x.value, q)) { + csub[csub.length] = x; + } + }); + return csub; + } + } + } + return null; + } + }; +}; + +$.Autocompleter.Select = function (options, input, select, config) { + var CLASSES = { + ACTIVE: "ac_over" + }; + + var listItems, + active = -1, + data, + term = "", + needsInit = true, + element, + list; + + // Create results + function init() { + if (!needsInit) + return; + element = $("
") + .hide() + .addClass(options.resultsClass) + .css("position", "absolute") + .appendTo(document.body); + + list = $("